2025-02-10 02:12:24 +01:00

169 lines
7.1 KiB
Vue

<template>
<div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" v-if="(mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'tile') || mapEditor.tool.value === 'paint'">
<div class="flex flex-col gap-2.5 p-2.5">
<div class="relative flex">
<img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
<label class="mb-1.5 font-titles hidden" for="search">Search</label>
<input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
</div>
<div class="flex">
<select class="input-field w-full" name="lists" v-model="mapEditor.drawMode.value" @change="(event: any) => mapEditor.setDrawMode(event.target.value)">
<option value="tile">Tiles</option>
<option value="map_object">Objects</option>
</select>
</div>
</div>
<div class="h-full overflow-auto relative border-0 border-t border-solid border-gray-500 p-2.5">
<div class="h-full" v-if="!selectedGroup">
<div class="grid grid-cols-4 gap-2 justify-items-center">
<div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
<img
class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
:src="`${config.server_endpoint}/textures/tiles/${group.parent.id}.png`"
:alt="group.parent.name"
@click="openGroup(group)"
@load="() => tileProcessor.processTile(group.parent)"
:class="{
'border-cyan shadow-lg': isActiveTile(group.parent),
'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
}"
/>
<span class="text-xs mt-1">{{ getTileCategory(group.parent) }}</span>
<span v-if="group.children.length > 0" class="absolute top-0 right-0 bg-cyan text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">
{{ group.children.length + 1 }}
</span>
</div>
</div>
</div>
<div v-else class="h-full overflow-auto">
<div class="p-4">
<div class="text-center mb-8">
<button @click="closeGroup" class="hover:text-white">Back to all tiles</button>
</div>
<div class="grid grid-cols-4 gap-2 justify-items-center">
<div class="flex flex-col items-center justify-center">
<img
class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
:src="`${config.server_endpoint}/textures/tiles/${selectedGroup.parent.id}.png`"
:alt="selectedGroup.parent.name"
@click="selectTile(selectedGroup.parent.id)"
:class="{
'border-cyan shadow-lg': isActiveTile(selectedGroup.parent),
'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
}"
/>
<span class="text-xs mt-1">{{ getTileCategory(selectedGroup.parent) }}</span>
</div>
<div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
<img
class="max-w-full max-h-full border-2 border-solid rounded cursor-pointer transition-all duration-300"
:src="`${config.server_endpoint}/textures/tiles/${childTile.id}.png`"
:alt="childTile.name"
@click="selectTile(childTile.id)"
:class="{
'border-cyan shadow-lg': isActiveTile(childTile),
'border-transparent hover:border-gray-300': !isActiveTile(childTile)
}"
/>
<span class="text-xs mt-1">{{ getTileCategory(childTile) }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-col h-40 gap-2.5 p-3.5 border-t border-0 border-solid border-gray-500">
<span>Tags:</span>
<div class="flex grow items-center flex-wrap gap-1.5 overflow-auto">
<span class="m-auto">No tags selected</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import config from '@/application/config'
import type { Tile } from '@/application/types'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useTileProcessingComposable } from '@/composables/useTileProcessingComposable'
import { TileStorage } from '@/storage/storages'
import { computed, onMounted, onUnmounted, ref } from 'vue'
const tileStorage = new TileStorage()
const mapEditor = useMapEditorComposable()
const tileProcessor = useTileProcessingComposable()
const searchQuery = ref('')
const selectedTags = ref<string[]>([])
const tileCategories = ref<Map<string, string>>(new Map())
const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null)
const tiles = ref<Tile[]>([])
const uniqueTags = computed(() => {
const allTags = tiles.value.flatMap((tile) => tile.tags || [])
return Array.from(new Set(allTags))
})
const groupedTiles = computed(() => {
const groups: { parent: Tile; children: Tile[] }[] = []
const filteredTiles = tiles.value.filter((tile) => {
const matchesSearch = !searchQuery.value || tile.name.toLowerCase().includes(searchQuery.value.toLowerCase())
const matchesTags = selectedTags.value.length === 0 || (tile.tags && selectedTags.value.some((tag) => tile.tags.includes(tag)))
return matchesSearch && matchesTags
})
filteredTiles.forEach((tile) => {
const parentGroup = groups.find((group) => tileProcessor.areTilesRelated(group.parent, tile))
if (parentGroup && parentGroup.parent.id !== tile.id) {
parentGroup.children.push(tile)
} else {
groups.push({ parent: tile, children: [] })
}
})
return groups
})
const toggleTag = (tag: string) => {
if (selectedTags.value.includes(tag)) {
selectedTags.value = selectedTags.value.filter((t) => t !== tag)
} else {
selectedTags.value.push(tag)
}
}
function getTileCategory(tile: Tile): string {
return tileCategories.value.get(tile.id) || ''
}
function openGroup(group: { parent: Tile; children: Tile[] }) {
selectedGroup.value = group
}
function closeGroup() {
selectedGroup.value = null
}
function selectTile(tile: string) {
mapEditor.setSelectedTile(tile)
}
function isActiveTile(tile: Tile): boolean {
return mapEditor.selectedTile.value === tile.id
}
onMounted(async () => {
tiles.value = await tileStorage.getAll()
const initialBatchSize = 20
const initialTiles = tiles.value.slice(0, initialBatchSize)
initialTiles.forEach((tile) => tileProcessor.processTile(tile))
// Process remaining tiles in background
setTimeout(() => {
tiles.value.slice(initialBatchSize).forEach((tile) => tileProcessor.processTile(tile))
}, 1000)
})
onUnmounted(() => {
tileProcessor.cleanup()
})
</script>