Added search functionality for tiles and objects, finished object management and added its logic to the zone editor

This commit is contained in:
Dennis Postma 2024-07-06 21:15:22 +02:00
parent d5a32de32e
commit 93e54b2164
6 changed files with 88 additions and 43 deletions

View File

@ -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
})

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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)))
}

View File

@ -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 = ''