From 93e54b21641ec53db43ac7ab1c0da97efad261a5 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Sat, 6 Jul 2024 21:15:22 +0200
Subject: [PATCH] Added search functionality for tiles and objects, finished
 object management and added its logic to the zone editor

---
 .../assetManager/partials/ObjectDetails.vue   | 39 +++++++++++++++++--
 .../assetManager/partials/ObjectList.vue      | 22 +++++++++--
 .../assetManager/partials/TileList.vue        | 22 +++++++++--
 .../utilities/zoneEditor/Objects.vue          |  6 +--
 .../utilities/zoneEditor/ZoneEditor.vue       | 30 +++-----------
 src/stores/zoneEditor.ts                      | 12 +++---
 6 files changed, 88 insertions(+), 43 deletions(-)

diff --git a/src/components/utilities/assetManager/partials/ObjectDetails.vue b/src/components/utilities/assetManager/partials/ObjectDetails.vue
index 7e6c243..17e1a25 100644
--- a/src/components/utilities/assetManager/partials/ObjectDetails.vue
+++ b/src/components/utilities/assetManager/partials/ObjectDetails.vue
@@ -5,7 +5,7 @@
       <div class="absolute left-0 bottom-0 w-full h-[1px] bg-cyan-200"></div>
     </div>
     <div class="modal-form asset-manager m-2.5 p-2.5 block">
-      <form class="flex gap-2.5 flex-wrap" @submit.prevent>
+      <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
         <div class="w-full flex flex-col mb-5">
           <label class="mb-1.5 font-titles" for="name">Name</label>
           <input v-model="objectName" class="input-cyan" type="text" name="name" placeholder="Wall #1" />
@@ -18,8 +18,8 @@
           <label class="mb-1.5 font-titles" for="origin-y">Origin Y</label>
           <input v-model="objectOriginY" class="input-cyan" type="number" name="origin-y" placeholder="Origin Y" />
         </div>
-        <button class="btn-cyan px-[15px] py-1.5 min-w-[100px]" type="button" @click="removeObject">Save</button>
-        <button class="btn-bordeaux px-[15px] py-1.5 min-w-[100px]" type="button" @click="removeObject">Remove</button>
+        <button class="btn-cyan px-[15px] py-1.5 min-w-[100px]" type="submit">Save</button>
+        <button class="btn-bordeaux px-[15px] py-1.5 min-w-[100px]" type="button" @click.prevent="removeObject">Remove</button>
       </form>
     </div>
   </div>
@@ -27,7 +27,7 @@
 
 <script setup lang="ts">
 import type { Object } from '@/types'
-import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 import { useSocketStore } from '@/stores/socket'
 import config from '@/config'
@@ -50,6 +50,13 @@ if (selectedObject.value) {
   objectOriginY.value = selectedObject.value.origin_y
 }
 
+watch(selectedObject, (object: Object | null) => {
+  if (!object) return
+  objectName.value = object.name
+  objectOriginX.value = object.origin_x
+  objectOriginY.value = object.origin_y
+})
+
 function removeObject() {
   socket.connection.emit('gm:object:remove', { object: selectedObject.value }, (response: boolean) => {
     if (!response) {
@@ -67,6 +74,30 @@ function refreshObjectList() {
   })
 }
 
+function saveObject() {
+  if (!selectedObject.value) {
+    console.error('No object selected')
+    return
+  }
+
+  socket.connection.emit(
+    'gm:object:update',
+    {
+      id: selectedObject.value.id,
+      name: objectName.value,
+      origin_x: objectOriginX.value,
+      origin_y: objectOriginY.value
+    },
+    (response: boolean) => {
+      if (!response) {
+        console.error('Failed to save object')
+        return
+      }
+      refreshObjectList()
+    }
+  )
+}
+
 onMounted(() => {
   if (!selectedObject.value) return
 })
diff --git a/src/components/utilities/assetManager/partials/ObjectList.vue b/src/components/utilities/assetManager/partials/ObjectList.vue
index 38b295d..f38a36e 100644
--- a/src/components/utilities/assetManager/partials/ObjectList.vue
+++ b/src/components/utilities/assetManager/partials/ObjectList.vue
@@ -4,10 +4,10 @@
       <input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
       Upload object(s)
     </label>
-    <input class="input-cyan search-field w-full" placeholder="Search..." />
+    <input v-model="searchQuery" class="input-cyan search-field w-full" placeholder="Search..." @input="handleSearch" />
     <div class="absolute left-0 bottom-0 w-full h-[1px] bg-cyan-200"></div>
   </div>
-  <a class="asset relative p-2.5 cursor-pointer" :class="{ active: assetManagerStore.selectedObject?.id === object.id }" v-for="(object, index) in assetManagerStore.objectList" :key="index" @click="assetManagerStore.setSelectedObject(object as Object)">
+  <a class="asset relative p-2.5 cursor-pointer" :class="{ active: assetManagerStore.selectedObject?.id === object.id }" v-for="(object, index) in filteredObjects" :key="index" @click="assetManagerStore.setSelectedObject(object as Object)">
     <div class="asset-details flex items-center gap-2.5">
       <div class="h-[28px] w-[75px] max-w-[75px] flex justify-center">
         <img class="h-[28px]" :src="`${config.server_endpoint}/assets/objects/${object.id}.png`" alt="Object" />
@@ -21,7 +21,7 @@
 <script setup lang="ts">
 import config from '@/config'
 import { useSocketStore } from '@/stores/socket'
-import { onMounted, ref } from 'vue'
+import { onMounted, ref, computed } from 'vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 import { useAssetStore } from '@/stores/assets'
 import type { Object } from '@/types'
@@ -31,6 +31,8 @@ const objectUploadField = ref(null)
 const assetManagerStore = useAssetManagerStore()
 const assetStore = useAssetStore()
 
+const searchQuery = ref('')
+
 const handleFileUpload = (e: Event) => {
   const files = (e.target as HTMLInputElement).files
   if (!files) return
@@ -48,6 +50,20 @@ const handleFileUpload = (e: Event) => {
   })
 }
 
+const handleSearch = () => {
+  // The filtering is handled by the computed property, so we don't need to do anything here
+  // This function is kept in case you want to add debounce or other functionality later
+}
+
+const filteredObjects = computed(() => {
+  if (!searchQuery.value) {
+    return assetManagerStore.objectList
+  }
+  return assetManagerStore.objectList.filter(object =>
+    object.name.toLowerCase().includes(searchQuery.value.toLowerCase())
+  )
+})
+
 onMounted(() => {
   socket.connection.emit('gm:object:list', {}, (response: Object[]) => {
     if (config.development) console.log(response)
diff --git a/src/components/utilities/assetManager/partials/TileList.vue b/src/components/utilities/assetManager/partials/TileList.vue
index c29aaf6..85f7fd7 100644
--- a/src/components/utilities/assetManager/partials/TileList.vue
+++ b/src/components/utilities/assetManager/partials/TileList.vue
@@ -4,10 +4,10 @@
       <input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
       Upload tile(s)
     </label>
-    <input class="input-cyan search-field w-full" placeholder="Search..." />
+    <input v-model="searchQuery" class="input-cyan search-field w-full" placeholder="Search..." @input="handleSearch" />
     <div class="absolute left-0 bottom-0 w-full height-[1px] bg-cyan-200"></div>
   </div>
-  <a class="asset relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" :class="{ active: assetManagerStore.selectedTile === tile }" v-for="(tile, index) in assetManagerStore.tileList" :key="index" @click="assetManagerStore.setSelectedTile(tile)">
+  <a class="asset relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" :class="{ active: assetManagerStore.selectedTile === tile }" v-for="(tile, index) in filteredTiles" :key="index" @click="assetManagerStore.setSelectedTile(tile)">
     <div class="asset-details flex items-center gap-2.5">
       <!--      TODO make all img have same width so text aligns nicely -->
       <img class="h-[28px]" :src="`${config.server_endpoint}/assets/tiles/${tile}.png`" alt="Tile" />
@@ -20,7 +20,7 @@
 <script setup lang="ts">
 import config from '@/config'
 import { useSocketStore } from '@/stores/socket'
-import { onMounted, ref } from 'vue'
+import { onMounted, ref, computed } from 'vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 import { useAssetStore } from '@/stores/assets'
 
@@ -29,6 +29,8 @@ const tileUploadField = ref(null)
 const assetManagerStore = useAssetManagerStore()
 const assetStore = useAssetStore()
 
+const searchQuery = ref('')
+
 const handleFileUpload = (e: Event) => {
   const files = (e.target as HTMLInputElement).files
   if (!files) return
@@ -46,6 +48,20 @@ const handleFileUpload = (e: Event) => {
   })
 }
 
+const handleSearch = () => {
+  // The filtering is handled by the computed property, so we don't need to do anything here
+  // This function is kept in case you want to add debounce or other functionality later
+}
+
+const filteredTiles = computed(() => {
+  if (!searchQuery.value) {
+    return assetManagerStore.tileList
+  }
+  return assetManagerStore.tileList.filter(tile =>
+    tile.toLowerCase().includes(searchQuery.value.toLowerCase())
+  )
+})
+
 onMounted(() => {
   socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
     if (config.development) console.log(response)
diff --git a/src/components/utilities/zoneEditor/Objects.vue b/src/components/utilities/zoneEditor/Objects.vue
index 2d2d824..d54b971 100644
--- a/src/components/utilities/zoneEditor/Objects.vue
+++ b/src/components/utilities/zoneEditor/Objects.vue
@@ -17,11 +17,11 @@
               <img
                 :src="`${config.server_endpoint}/assets/objects/${object.id}.png`"
                 alt="Object"
-                @click="zoneEditorStore.setSelectedObject(object.id)"
+                @click="zoneEditorStore.setSelectedObject(object)"
                 :class="{
                   'w-full h-auto cursor-pointer border-2 transition-all duration-300': true,
-                  'border-2 border-solid border-cyan shadow-lg scale-105': zoneEditorStore.selectedObject === object.id,
-                  'border-transparent hover:border-gray-300': zoneEditorStore.selectedObject !== object.id
+                  'border-2 border-solid border-cyan shadow-lg scale-105': zoneEditorStore.selectedObject?.id === object.id,
+                  'border-transparent hover:border-gray-300': zoneEditorStore.selectedObject?.id !== object.id
                 }"
               />
             </div>
diff --git a/src/components/utilities/zoneEditor/ZoneEditor.vue b/src/components/utilities/zoneEditor/ZoneEditor.vue
index a92fb9f..8bf31c0 100644
--- a/src/components/utilities/zoneEditor/ZoneEditor.vue
+++ b/src/components/utilities/zoneEditor/ZoneEditor.vue
@@ -2,10 +2,8 @@
   <TilemapLayerC :tilemap="zone" :tileset="exampleTilesArray" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
 
   <Controls :layer="tiles" />
-  <!--  @TODO: inside asset manager we need to be able to set the originX and originY per individial asset -->
   <Container>
-    <!--      <Image :texture="'wall1'" :x="pos.position_x" :y="pos.position_y" :originY="1.13" :originX="1" />-->
-    <Image v-for="object in zoneObjects" :key="object.id" :texture="object.object" :x="object.position_x" :y="object.position_y" />
+    <Image v-for="object in zoneObjects" :key="object.object.id" :x="object.position_x" :y="object.position_y" :texture="object.object.id" :originY="object.object.origin_x" :originX="object.object.origin_y" />
   </Container>
 
   <Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @paint="paint" @save="save" />
@@ -19,7 +17,7 @@ import config from '@/config'
 import Tileset = Phaser.Tilemaps.Tileset
 import TilemapLayer = Phaser.Tilemaps.TilemapLayer
 import { Container, TilemapLayer as TilemapLayerC, useScene, Image } from 'phavuer'
-import { onBeforeMount, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
+import { onBeforeMount, onBeforeUnmount, ref, toRaw } from 'vue'
 import Controls from '@/components/utilities/Controls.vue'
 import { useSocketStore } from '@/stores/socket'
 import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
@@ -29,6 +27,7 @@ import ZoneSettings from '@/components/utilities/zoneEditor/ZoneSettings.vue'
 import { placeTile, tileToWorldXY } from '@/services/zone'
 import { useAssetStore } from '@/stores/assets'
 import Objects from '@/components/utilities/zoneEditor/Objects.vue'
+import type { Object } from '@/types'
 
 const scene = useScene()
 const socket = useSocketStore()
@@ -50,7 +49,7 @@ const tilesetImages: Tileset[] = []
 
 type ZoneObject = {
   id: string
-  object: string
+  object: Object
   position_x: number
   position_y: number
 }
@@ -69,16 +68,6 @@ tilesetImages.push(zone.addTilesetImage('blank_tile', 'blank_tile', config.tile_
 const tiles = zone.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.y) as TilemapLayer
 const exampleTilesArray = Array.from({ length: zoneEditorStore.width }, () => Array.from({ length: zoneEditorStore.height }, () => 'blank_tile'))
 
-// Watch for changes in the zoneStore and update the layer
-// watch(
-//   () => zoneEditorStore.object,
-//   () => {
-//     // @TODO : change to zone for when loading other maps
-//     zoneEditorStore.object.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
-//   },
-//   { deep: true }
-// )
-
 // socket.connection.on('gm:zone_editor:zone:load', (data: Zone) => {
 //   tileTilemap = generateTilemap(scene, data.width, data.height);
 //   zoneEditorStore.setName(data.name)
@@ -91,25 +80,18 @@ function eraser(tile: Phaser.Tilemaps.Tile) {
     placeTile(zone, tiles, tile.x, tile.y, 'blank_tile')
     zoneEditorStore.updateTile(tile.x, tile.y, 'blank_tile')
   }
-
-  // if (zoneEditorStore.drawMode === 'wall') {
-  //   walls.putTileAt(0, tile.x, tile.y)
-  //   zoneEditorStore.updateWall(tile.x, tile.y, 0)
-  // }
 }
 
 function pencil(tile: Phaser.Tilemaps.Tile) {
   if (zoneEditorStore.drawMode === 'tile') {
     if (!zoneEditorStore.selectedTile) return
-    console.log('placing tile', tile.x, tile.y, zoneEditorStore.selectedTile)
     placeTile(zone, tiles, tile.x, tile.y, zoneEditorStore.selectedTile)
     // zoneEditorStore.setTiles(tile.x, tile.y, zoneEditorStore.selectedTile)
   }
 
   if (zoneEditorStore.drawMode === 'object') {
-    // @TODO fix position
     if (zoneEditorStore.selectedObject === null) return
-    if (config.development) console.log('placing object', tile.x, tile.y, zoneEditorStore.selectedObject)
+    console.log('zoneObjects.value', zoneObjects.value)
     zoneObjects.value.push({
       id: Math.random().toString(10),
       object: zoneEditorStore.selectedObject,
@@ -120,9 +102,7 @@ function pencil(tile: Phaser.Tilemaps.Tile) {
 }
 
 function paint(tile: Phaser.Tilemaps.Tile) {
-  console.log('yoo')
   if (!zoneEditorStore.selectedTile) return
-  console.log('placing tile', tile.x, tile.y, zoneEditorStore.selectedTile)
   exampleTilesArray.forEach((row, y) => row.forEach((tile, x) => placeTile(zone, tiles, x, y, zoneEditorStore.selectedTile)))
 }
 
diff --git a/src/stores/zoneEditor.ts b/src/stores/zoneEditor.ts
index 95288a7..6eac1b1 100644
--- a/src/stores/zoneEditor.ts
+++ b/src/stores/zoneEditor.ts
@@ -1,4 +1,5 @@
 import { defineStore } from 'pinia'
+import type { Object } from '@/types'
 
 export const useZoneEditorStore = defineStore('zoneEditor', {
   state: () => ({
@@ -7,11 +8,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
     width: 10,
     height: 10,
     tiles: [] as string[][],
-    objects: [] as number[][],
+    objects: [] as Object[],
     tool: 'move',
     drawMode: 'tile',
     selectedTile: '',
-    selectedObject: null,
+    selectedObject: null as Object | null,
     isSettingsModalShown: false
   }),
   actions: {
@@ -33,11 +34,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
     updateTile(x: number, y: number, tile: string) {
       this.tiles[y][x] = tile
     },
-    setObjects(objects: number[][]) {
+    setObjects(objects: Object[]) {
       this.objects = objects
     },
-    updateObject(x: number, y: number, object: number) {
-      this.objects[y][x] = object
+    updateObject(index: number, object: Object) {
+      this.objects[index] = object
     },
     setTool(tool: string) {
       this.tool = tool
@@ -59,6 +60,7 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
       this.width = 10
       this.height = 10
       this.tiles = []
+      this.objects = []
       this.tool = 'move'
       this.drawMode = 'tile'
       this.selectedTile = ''