Rebuilt side panel for object & tile lists
Reorganised file structure
This commit is contained in:
@ -1,176 +0,0 @@
|
||||
<template>
|
||||
<div class="absolute border-0 border-l-2 border-solid border-gray-500 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" :style="{ width: width + 'px' }" v-if="isOpen">
|
||||
<div class="absolute left-0 top-0 w-1 h-full cursor-ew-resize hover:bg-cyan transition-colors duration-200 z-20" @mousedown.stop="startDragging"></div>
|
||||
<div class="relative z-10 p-2.5 border-solid border-0 border-b border-gray-500">
|
||||
<h3 class="text-lg text-white">Tiles</h3>
|
||||
</div>
|
||||
<div class="overflow-y-auto grow relative">
|
||||
<div class="h-full w-full">
|
||||
<div class="relative z-10 h-full">
|
||||
<div class="grid auto-rows-max gap-2 justify-items-center p-4" style="grid-template-columns: repeat(auto-fill, minmax(80px, 1fr))">
|
||||
<template v-if="!selectedGroup">
|
||||
<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>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="col-span-full mb-4 flex items-center">
|
||||
<button @click="closeGroup" class="flex items-center text-white hover:text-cyan transition-colors duration-200">
|
||||
<img class="invert w-5 h-5 rotate-90 mr-2" src="/assets/icons/mapEditor/chevron.svg" alt="Back" />
|
||||
Back to groups
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="tile in [selectedGroup.parent, ...selectedGroup.children]" :key="tile.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/${tile.id}.png`"
|
||||
:alt="tile.name"
|
||||
@click="selectTile(tile.id)"
|
||||
@load="() => tileProcessor.processTile(tile)"
|
||||
:class="{
|
||||
'border-cyan shadow-lg': isActiveTile(tile),
|
||||
'border-transparent hover:border-gray-300': !isActiveTile(tile)
|
||||
}"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ getTileCategory(tile) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</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 isOpen = ref(false)
|
||||
const width = ref(320)
|
||||
const isDragging = ref(false)
|
||||
|
||||
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[]>([])
|
||||
|
||||
function startDragging(event: MouseEvent) {
|
||||
isDragging.value = true
|
||||
const startX = event.clientX
|
||||
const startWidth = width.value
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
if (isDragging.value) {
|
||||
const deltaX = startX - e.clientX
|
||||
width.value = Math.max(320, Math.min(800, startWidth + deltaX))
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
isDragging.value = false
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open: () => (isOpen.value = true),
|
||||
close: () => (isOpen.value = false),
|
||||
toggle: () => (isOpen.value = !isOpen.value)
|
||||
})
|
||||
|
||||
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>
|
Reference in New Issue
Block a user