<template> <div class="flex justify-center p-5"> <div class="toolbar fixed bottom-0 left-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10"> <div ref="toolbar" class="tools flex gap-2.5" v-if="mapEditor.currentMap.value"> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'move' }" @click="handleClick('move')"> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/move.svg" alt="Move camera" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'move' }">(M)</span> </button> <div class="w-px bg-cyan"></div> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'pencil' }" @click="handleClick('pencil')"> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/pencil.svg" alt="Pencil" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'pencil' }">(P)</span> <div class="select" v-if="mapEditor.tool.value === 'pencil'"> <div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }"> {{ mapEditor.drawMode.value.replace('_', ' ') }} <img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" alt="" /> </div> <div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectPencilOpen && mapEditor.tool.value === 'pencil'"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('tile', 'pencil')"> Tile <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('map_object', 'pencil')"> Map object <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('teleport', 'pencil')"> Teleport <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('blocking tile', 'pencil')">Blocking tile</span> </div> </div> </button> <div class="w-px bg-cyan"></div> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'eraser' }" @click="handleClick('eraser')"> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/eraser.svg" alt="Eraser" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'eraser' }">(E)</span> <div class="select" v-if="mapEditor.tool.value === 'eraser'"> <div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }"> {{ mapEditor.drawMode.value.replace('_', ' ') }} <img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" /> </div> <div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectEraserOpen"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('tile', 'eraser')"> Tile <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('map_object', 'eraser')"> Map object <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('teleport', 'eraser')"> Teleport <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> </span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('blocking tile', 'eraser')">Blocking tile</span> </div> </div> </button> <div class="w-px bg-cyan"></div> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'paint' }" @click="handleClick('paint')"> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/paint.svg" alt="Paint bucket" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'paint' }">(B)</span> </button> <div class="w-px bg-cyan"></div> <button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')"><img class="invert w-5 h-5" src="/assets/icons/mapEditor/gear.svg" alt="Map settings" /> <span class="h-5 ml-2.5">(Z)</span></button> <div class="w-px bg-cyan"></div> <label class="my-auto gap-0" for="checkbox">Continuous Drawing</label> <input type="checkbox" /> </div> <div class="toolbar fixed bottom-0 right-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10 space-x-2"> <button class="btn-cyan px-3.5" @click="() => emit('open-maps')">Load</button> <button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="mapEditor.currentMap.value">Save</button> <button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="mapEditor.currentMap.value">Clear</button> <button class="btn-cyan px-3.5" @click="() => emit('close-editor')">Exit</button> </div> </div> </div> </template> <script setup lang="ts"> import { useMapEditorComposable } from '@/composables/useMapEditorComposable' import { onClickOutside } from '@vueuse/core' import { onBeforeUnmount, onMounted, ref } from 'vue' const mapEditor = useMapEditorComposable() const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor', 'open-tile-list', 'open-map-object-list', 'close-lists']) // track when clicked outside of toolbar items const toolbar = ref(null) // track select state let selectPencilOpen = ref(false) let selectEraserOpen = ref(false) let tileListShown = ref(false) let mapObjectListShown = ref(false) defineExpose({ tileListShown, mapObjectListShown }) // drawMode function setDrawMode(value: string) { if (mapEditor.tool.value === 'paint' || mapEditor.tool.value === 'pencil') { emit('close-lists') if (value === 'tile') emit('open-tile-list') if (value === 'map_object') emit('open-map-object-list') } mapEditor.setDrawMode(value) selectPencilOpen.value = false selectEraserOpen.value = false } function setPencilMode() { mapEditor.setTool('pencil') selectPencilOpen.value = false } // drawMode function setEraserMode() { mapEditor.setTool('eraser') selectEraserOpen.value = false } function handleModeClick(mode: string, type: 'pencil' | 'eraser') { setDrawMode(mode) type === 'pencil' ? setPencilMode() : setEraserMode() } function handleClick(tool: string) { if (tool === 'settings') { emit('open-settings') } else { mapEditor.setTool(tool) } selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false } function cycleToolMode(tool: 'pencil' | 'eraser') { const modes = ['tile', 'map_object', 'teleport', 'blocking tile'] const currentIndex = modes.indexOf(mapEditor.drawMode.value) const nextIndex = (currentIndex + 1) % modes.length const nextMode = modes[nextIndex] setDrawMode(nextMode) } function initKeyShortcuts(event: KeyboardEvent) { // Check if map is set if (!mapEditor.currentMap.value) return // prevent if focused on composables if (document.activeElement?.tagName === 'INPUT') return const keyActions: { [key: string]: string } = { m: 'move', p: 'pencil', e: 'eraser', b: 'paint', z: 'settings' } if (keyActions.hasOwnProperty(event.key)) { const tool = keyActions[event.key] if ((tool === 'pencil' || tool === 'eraser') && mapEditor.tool.value === tool) { cycleToolMode(tool) } else { handleClick(tool) } } } function handleClickOutside() { selectPencilOpen.value = false selectEraserOpen.value = false } onClickOutside(toolbar, handleClickOutside) onMounted(() => { addEventListener('keydown', initKeyShortcuts) }) onBeforeUnmount(() => { removeEventListener('keydown', initKeyShortcuts) }) </script>