From 5dd9d1e7afe8a3dcbaaf6ba209cecfc273f9ddc2 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Fri, 31 Jan 2025 02:16:19 +0100
Subject: [PATCH] Added temp. offset logic for easier sprite management

---
 .../partials/sprite/SpriteDetails.vue         | 27 +++++++++++++--
 .../sprite/partials/SpriteImagesInput.vue     | 33 +++++++++++++++++--
 .../sprite/partials/SpritePreview.vue         | 15 +++++++--
 3 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
index 1b25bf4..705af93 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
@@ -48,12 +48,24 @@
               <input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
             </div>
             <div class="form-field-full">
-              <SpriteActionsInput v-model="action.sprites" />
+              <SpriteActionsInput 
+                v-model="action.sprites" 
+                @tempOffsetChange="(index, offset) => handleTempOffsetChange(action, index, offset)" 
+              />
             </div>
           </form>
         </template>
       </Accordion>
-      <SpritePreview v-if="selectedAction" :sprites="selectedAction.sprites" :frame-rate="selectedAction.frameRate" :is-modal-open="isModalOpen" @update:frame-rate="updateFrameRate" @update:is-modal-open="isModalOpen = $event" />
+      <SpritePreview 
+        v-if="selectedAction" 
+        :sprites="selectedAction.sprites" 
+        :frame-rate="selectedAction.frameRate" 
+        :is-modal-open="isModalOpen" 
+        :temp-offset-index="tempOffsetData.index" 
+        :temp-offset="tempOffsetData.offset" 
+        @update:frame-rate="updateFrameRate" 
+        @update:is-modal-open="isModalOpen = $event" 
+      />
     </div>
   </div>
 </template>
@@ -187,6 +199,17 @@ function updateFrameRate(value: number) {
   }
 }
 
+const tempOffsetData = ref<{ index: number | undefined; offset: { x: number; y: number } | undefined }>({ 
+  index: undefined, 
+  offset: undefined 
+})
+
+function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x: number; y: number }) {
+  if (selectedAction.value === action) {
+    tempOffsetData.value = { index, offset }
+  }
+}
+
 watch(selectedSprite, (sprite: Sprite | null) => {
   if (!sprite) return
   spriteName.value = sprite.name
diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
index 6e8d7bb..96b6061 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
@@ -1,7 +1,10 @@
 <template>
   <div class="flex flex-wrap gap-3">
     <div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)">
-      <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" />
+      <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" @load="updateImageDimensions($event, index)" />
+      <div v-if="image.dimensions" class="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-1 py-0.5 rounded transition-opacity font-default">
+        {{ image.dimensions.width }}x{{ image.dimensions.height }}
+      </div>
       <div class="absolute top-1 left-1 flex-row space-y-1">
         <button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image">
           <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -50,7 +53,7 @@
 
 <script setup lang="ts">
 import Modal from '@/components/utilities/Modal.vue'
-import { ref } from 'vue'
+import { ref, watch } from 'vue'
 
 interface SpriteImage {
   url: string
@@ -58,6 +61,10 @@ interface SpriteImage {
     x: number
     y: number
   }
+  dimensions?: {
+    width: number
+    height: number
+  }
 }
 
 interface Props {
@@ -71,6 +78,7 @@ const props = withDefaults(defineProps<Props>(), {
 const emit = defineEmits<{
   (e: 'update:modelValue', value: SpriteImage[]): void
   (e: 'close'): void
+  (e: 'tempOffsetChange', index: number, offset: { x: number, y: number }): void
 }>()
 
 const fileInput = ref<HTMLInputElement | null>(null)
@@ -162,4 +170,25 @@ const saveOffset = (index: number) => {
   updateImages(newImages)
   closeOffsetModal()
 }
+
+const onOffsetChange = () => {
+  if (selectedImageIndex.value !== null) {
+    emit('tempOffsetChange', selectedImageIndex.value, tempOffset.value)
+  }
+}
+
+watch(tempOffset, onOffsetChange, { deep: true })
+
+const updateImageDimensions = (event: Event, index: number) => {
+  const img = event.target as HTMLImageElement
+  const newImages = [...props.modelValue]
+  newImages[index] = {
+    ...newImages[index],
+    dimensions: {
+      width: img.naturalWidth,
+      height: img.naturalHeight
+    }
+  }
+  updateImages(newImages)
+}
 </script>
diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
index caac77c..c44e6ea 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
@@ -16,7 +16,7 @@
             }"
           >
             <img
-              v-for="(sprite, index) in sprites"
+              v-for="(sprite, index) in spritesWithTempOffset"
               :key="index"
               :src="sprite.url"
               alt="Sprite"
@@ -62,12 +62,14 @@
 <script setup lang="ts">
 import type { SpriteImage } from '@/application/types'
 import Modal from '@/components/utilities/Modal.vue'
-import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
 
 const props = defineProps<{
   sprites: SpriteImage[]
   frameRate: number
   isModalOpen?: boolean
+  tempOffsetIndex?: number
+  tempOffset?: { x: number, y: number }
 }>()
 
 const emit = defineEmits<{
@@ -82,6 +84,15 @@ const localFrameRate = ref(props.frameRate)
 const zoomLevel = ref(100)
 let animationInterval: number | null = null
 
+const spritesWithTempOffset = computed(() => {
+  return props.sprites.map((sprite, index) => {
+    if (index === props.tempOffsetIndex && props.tempOffset) {
+      return { ...sprite, offset: props.tempOffset }
+    }
+    return sprite
+  })
+})
+
 function updateContainerSize(event: Event) {
   const img = event.target as HTMLImageElement
   maxWidth.value = Math.max(maxWidth.value, img.naturalWidth)