Worked on zone objects, tile tags and searching

This commit is contained in:
Dennis Postma 2024-07-04 02:07:45 +02:00
parent 2cfe80de11
commit 2fe6f8d9c0
22 changed files with 468 additions and 304 deletions

90
package-lock.json generated
View File

@ -2137,30 +2137,30 @@
} }
}, },
"node_modules/@volar/language-core": { "node_modules/@volar/language-core": {
"version": "2.4.0-alpha.12", "version": "2.4.0-alpha.13",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.12.tgz", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.13.tgz",
"integrity": "sha512-Dj9qTifcGGgzFLfMbU5dCo13kHyNuEyvPJhtWDnoVBBmgwW3GMwFmgWnNxBhjf63m5x0gux1okaxX2CLN7qSww==", "integrity": "sha512-tHeJVIRTJ3dlsdNyRjBlqdKHocWkgORM5eXgf6xcGERoXYe6vBpQpxJgpK1pehA8psXNPqkMN1ryBseA0B+m8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/source-map": "2.4.0-alpha.12" "@volar/source-map": "2.4.0-alpha.13"
} }
}, },
"node_modules/@volar/source-map": { "node_modules/@volar/source-map": {
"version": "2.4.0-alpha.12", "version": "2.4.0-alpha.13",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.12.tgz", "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.13.tgz",
"integrity": "sha512-LXATFSj4D7T9sEm7FFj6iBgHjKjrdhAgRPcechVKiNCMQdr3r3GVkkeu8aM+1peaMH3LsCqoDxVZEmh2r7CHiw==", "integrity": "sha512-NABqcuA9QpHsU3FnA5BENP3PI1FOb6hDxqkV1KAHP7gt4fgfQOqSCWpqj3QAS7RV0PtiKTxiDMIJ7doMBhNm7w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@volar/typescript": { "node_modules/@volar/typescript": {
"version": "2.4.0-alpha.12", "version": "2.4.0-alpha.13",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.12.tgz", "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.13.tgz",
"integrity": "sha512-mLg+OQauMTv/+08a7WBWJo1sev/wc8t2is0zhBZIlFU+j5mG89FM4+4089c2p/zoUFZ400Q/VNg2BPfhpZ8wSA==", "integrity": "sha512-zW/MOPA9SwkCuuVPqADDYfAEPAh68aJQG3/EAqDYozSuK2YNYHEAC0BWYZESSNZC6jxwwx7w0U82fBkDZ9hHEw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-core": "2.4.0-alpha.12", "@volar/language-core": "2.4.0-alpha.13",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8" "vscode-uri": "^3.0.8"
} }
@ -2667,9 +2667,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.0", "version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -2959,9 +2959,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001639", "version": "1.0.30001640",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
"integrity": "sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==", "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3474,6 +3474,27 @@
"xmlhttprequest-ssl": "~2.0.0" "xmlhttprequest-ssl": "~2.0.0"
} }
}, },
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": { "node_modules/engine.io-parser": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
@ -5192,9 +5213,9 @@
} }
}, },
"node_modules/npm-run-all2": { "node_modules/npm-run-all2": {
"version": "6.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.0.tgz", "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.1.tgz",
"integrity": "sha512-wA7yVIkthe6qJBfiJ2g6aweaaRlw72itsFGF6HuwCHKwtwAx/4BY1vVpk6bw6lS8RLMsexoasOkd0aYOmsFG7Q==", "integrity": "sha512-eX4MWsUYOSm1FhPh9LPAWbqq2quny3u8gEEWIY4HHECi10qOyi1dNaJFCyOOv2uP05ZuTPETwS2p1GZk9oLJsw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5213,7 +5234,7 @@
"run-s": "bin/run-s/index.js" "run-s": "bin/run-s/index.js"
}, },
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0", "node": "^14.18.0 || ^16.13.0 || >=18.0.0",
"npm": ">= 8" "npm": ">= 8"
} }
}, },
@ -5636,9 +5657,9 @@
} }
}, },
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.2.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz",
"integrity": "sha512-VEGf1he2DR5yowYRl0XJhWJq5ktm9gYIsH+y8sNJpHlxch7JPDaufgrsl4vYjd9hMUY8QVjoNncKbow9I7exyA==", "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -6665,9 +6686,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.16", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
"integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -6723,14 +6744,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.3.2", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz",
"integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==", "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.38", "postcss": "^8.4.39",
"rollup": "^4.13.0" "rollup": "^4.13.0"
}, },
"bin": { "bin": {
@ -7273,9 +7294,10 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.17.1", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"

View File

@ -0,0 +1,2 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path clip-rule="evenodd" d="m6 5c0-1.65685 1.34315-3 3-3 1.6569 0 3 1.34315 3 3v.99943c.9228.01637 1.808.52521 2.2889 1.39653l3.3612 6.08924c.7139 1.2934.3069 2.9672-.9737 3.7405l-7.27713 4.3945c-1.30875.7904-2.96071.3017-3.68814-1.0161l-3.36116-6.0893c-.71395-1.2934-.30694-2.9671.97368-3.7405l2.67635-1.61618zm4 0v1.74258l-2 1.20777v-2.95035c0-.55228.44772-1 1-1s1 .44772 1 1z" fill-rule="evenodd"/><path d="m19 16c-.4577 0-.7691.253-.9239.4148-.1627.1699-.278.3663-.3589.5273-.1652.3287-.2937.7288-.3908 1.094-.1902.7157-.3264 1.5619-.3264 1.9639 0 1.1046.8954 2 2 2s2-.8954 2-2c0-.402-.1362-1.2482-.3264-1.9639-.0971-.3652-.2256-.7653-.3908-1.094-.0809-.161-.1962-.3574-.3589-.5273-.1548-.1618-.4662-.4148-.9239-.4148z"/></g></svg>

After

Width:  |  Height:  |  Size: 847 B

View File

@ -1,44 +1,34 @@
<template> <template>
<div class="chip-container"> <div class="chip-container">
<div v-for="(chip, i) in chips" :key="i" class="chip"> <div v-for="(chip, i) in modelValue" :key="i" class="chip">
<span>{{ chip }}</span> <span>{{ chip }}</span>
<i class="delete-icon" @click="deleteChip(i)">X</i> <i class="delete-icon" @click="deleteChip(i)">X</i>
</div> </div>
<input <input v-model="currentInput" @keyup.enter="saveChip" @keydown.delete="backspaceDelete" />
v-model="currentInput"
@keypress.enter="saveChip"
@keydown.delete="backspaceDelete"
>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, defineProps } from 'vue' import { ref } from 'vue'
const props = defineProps({ const modelValue = defineModel('modelValue', { type: Array, default: () => [] })
set: {
type: Boolean,
default: true
}
})
const chips = ref([])
const currentInput = ref('') const currentInput = ref('')
const saveChip = () => { const saveChip = () => {
if ((props.set && !chips.value.includes(currentInput.value)) || !props.set) { if (currentInput.value.trim() && !modelValue.value.includes(currentInput.value)) {
chips.value.push(currentInput.value) modelValue.value = [...modelValue.value, currentInput.value]
currentInput.value = ''
} }
currentInput.value = ''
} }
const deleteChip = (index) => { const deleteChip = (index) => {
chips.value.splice(index, 1) modelValue.value = modelValue.value.filter((_, i) => i !== index)
} }
const backspaceDelete = (event) => { const backspaceDelete = (event) => {
if (event.key === 'Backspace' && currentInput.value === '') { if (event.key === 'Backspace' && currentInput.value === '' && modelValue.value.length > 0) {
chips.value.pop() modelValue.value = modelValue.value.slice(0, -1)
} }
} }
</script> </script>
@ -78,11 +68,7 @@ const backspaceDelete = (event) => {
outline: none; outline: none;
padding: 4px; padding: 4px;
margin: 4px; margin: 4px;
color:white; color: white;
} }
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :isModalOpen="gmPanelStore.isOpen" :modal-width="1000" :modal-height="650"> <Modal :isModalOpen="gmPanelStore.isOpen" @modal:close="() => gmPanelStore.toggle()" :modal-width="1000" :modal-height="650">
<template #modalHeader> <template #modalHeader>
<h3 class="modal-title">GM Panel</h3> <h3 class="modal-title">GM Panel</h3>
<div class="gm-selector"> <div class="gm-selector">

View File

@ -16,7 +16,6 @@ import Modal from '@/components/utilities/Modal.vue'
import { useZoneEditorStore } from '@/stores/zoneEditor' import { useZoneEditorStore } from '@/stores/zoneEditor'
import { useGmPanelStore } from '@/stores/gmPanel' import { useGmPanelStore } from '@/stores/gmPanel'
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()
const gmPanelStore = useGmPanelStore() const gmPanelStore = useGmPanelStore()
</script> </script>

View File

@ -31,16 +31,18 @@
<!-- Asset details --> <!-- Asset details -->
<div class="asset-info"> <div class="asset-info">
<TileDetails :tile="selectedTile" v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" /> <TileDetails :tile="selectedTile" v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
<ObjectDetails :object="selectedTile" v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { ref } from 'vue'
import { useSocketStore } from '@/stores/socket' import { useSocketStore } from '@/stores/socket'
import TileList from '@/components/utilities/assetManager/partials/TileList.vue' import TileList from '@/components/utilities/assetManager/partials/TileList.vue'
import TileDetails from '@/components/utilities/assetManager/partials/TileDetails.vue' import TileDetails from '@/components/utilities/assetManager/partials/TileDetails.vue'
import ObjectList from '@/components/utilities/assetManager/partials/ObjectList.vue' import ObjectList from '@/components/utilities/assetManager/partials/ObjectList.vue'
import ObjectDetails from '@/components/utilities/assetManager/partials/ObjectDetails.vue'
import { useAssetManagerStore } from '@/stores/assetManager' import { useAssetManagerStore } from '@/stores/assetManager'
const socket = useSocketStore() const socket = useSocketStore()

View File

@ -0,0 +1,81 @@
<template>
<div class="object-manager">
<div class="image-container">
<img :src="objectImageUrl" :alt="'Object ' + selectedObject" />
</div>
<div class="modal-form asset-manager">
<form class="form-fields" @submit.prevent>
<div class="form-field name">
<label for="name">Name</label>
<input class="input-cyan" type="text" name="name" placeholder="Wall #1" />
</div>
<div class="form-field name">
<label for="name">Origin X</label>
<!-- @TODO only allow numbers here -->
<input class="input-cyan" type="text" name="name" placeholder="Origin X" />
</div>
<div class="form-field name">
<label for="name">Origin Y</label>
<!-- @TODO only allow numbers here -->
<input class="input-cyan" type="text" name="name" placeholder="Origin Y" />
</div>
<div class="submit">
<button class="btn-cyan" type="button" @click="removeObject">Save</button>
<button class="btn-bordeaux" type="button" @click="removeObject">Remove</button>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { useAssetManagerStore } from '@/stores/assetManager'
import { useSocketStore } from '@/stores/socket'
import config from '@/config'
const socket = useSocketStore()
const assetManagerStore = useAssetManagerStore()
const selectedObject = computed(() => assetManagerStore.selectedObject)
const objectImageUrl = computed(() => `${config.server_endpoint}/assets/objects/${selectedObject.value}.png`)
const objectDetails = computed(() => assetManagerStore.objectDetails)
function removeObject() {
socket.connection.emit('gm:object:remove', { object: selectedObject.value }, (response: boolean) => {
if (!response) {
console.error('Failed to remove object')
return
}
refreshObjectList()
})
}
function refreshObjectList() {
socket.connection.emit('gm:object:list', {}, (response: string[]) => {
assetManagerStore.setObjectList(response)
assetManagerStore.setSelectedObject('')
})
}
onMounted(() => {
if (!selectedObject.value) {
return
}
socket.connection.emit('gm:object:details', { object: selectedObject.value }, (response: any) => {
assetManagerStore.setObjectDetails(response)
})
})
onBeforeUnmount(() => {
assetManagerStore.setSelectedObject('')
})
</script>
<style lang="scss">
/**
* @TODO when scrolling here, the vertical line is cut off
*/
.modal-form {
padding: 10px; // @TODO why dis no work? fixme
}
</style>

View File

@ -1,45 +1,47 @@
<template> <template>
<div class="asset"> <div class="asset add-new">
<label for="upload-asset" class="file-upload">
<input id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
Upload object(s)
</label>
<input class="input-cyan search-field" placeholder="Search..." /> <input class="input-cyan search-field" placeholder="Search..." />
</div> </div>
<a class="asset" :class="{ active: assetManagerStore.selectedObject === object }" v-for="(object, index) in assetManagerStore.objectList" :key="index" @click="assetManagerStore.setSelectedObject(object)">
<!-- TODO: use the passed :name in props to switch out assets-->
<a class="asset" :class="{ active: name === 'tiles' }" v-for="(tile, index) in tiles" :key="index">
<div class="asset-details"> <div class="asset-details">
<img :src="`${config.server_endpoint}/assets/tiles/${tile}`" /> <img :src="`${config.server_endpoint}/assets/objects/${object}.png`" alt="Object" />
<span class="asset-name">{{ tile }}</span> <span class="asset-name">{{ object }}</span>
</div> </div>
</a> </a>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useSocketStore } from '@/stores/socket'
import config from '@/config' import config from '@/config'
import { onMounted, ref, defineProps } from 'vue' import { useSocketStore } from '@/stores/socket'
import { onMounted, ref } from 'vue'
const props = defineProps<{ name: string }>() import { useAssetManagerStore } from '@/stores/assetManager'
const socket = useSocketStore() const socket = useSocketStore()
const tileUploadField = ref(null) const objectUploadField = ref(null)
const tiles = ref() const assetManagerStore = useAssetManagerStore()
const handleFileUpload = (e: Event) => { const handleFileUpload = (e: Event) => {
const files = (e.target as HTMLInputElement).files const files = (e.target as HTMLInputElement).files
if (!files) return if (!files) return
socket.connection.emit('gm:tile:upload', files, (response: boolean) => { socket.connection.emit('gm:object:upload', files, (response: boolean) => {
if (!response) { if (!response) {
console.error('Failed to upload tile') if (config.development) console.error('Failed to upload object')
return return
} }
socket.connection.emit('gm:tile:list', {}, (response: string[]) => { socket.connection.emit('gm:object:list', {}, (response: string[]) => {
tiles.value = response assetManagerStore.setObjectList(response)
}) })
}) })
} }
onMounted(() => { onMounted(() => {
socket.connection.emit('gm:tile:list', {}, (response: string[]) => { socket.connection.emit('gm:object:list', {}, (response: string[]) => {
tiles.value = response if (config.development) console.log(response)
assetManagerStore.setObjectList(response)
}) })
}) })
</script> </script>
@ -48,9 +50,10 @@ onMounted(() => {
@import '@/assets/scss/main'; @import '@/assets/scss/main';
.asset { .asset {
cursor: pointer;
&.add-new { &.add-new {
display: flex; display: flex;
align-items: center;
gap: 10px 20px; gap: 10px 20px;
flex-wrap: wrap; flex-wrap: wrap;
.asset-name { .asset-name {

View File

@ -1,45 +1,94 @@
<template> <template>
<div class="image-container"> <div class="tile-manager">
<img :src="`${config.server_endpoint}/assets/tiles/${assetManagerStore.selectedTile}.png`" alt="Tile" /> <div class="image-container">
</div> <img :src="tileImageUrl" :alt="'Tile ' + selectedTile" />
<div class="modal-form asset-manager"> </div>
<form class="form-fields" @submit.prevent> <div class="modal-form asset-manager">
<div class="form-field name"> <form class="form-fields" @submit.prevent>
<label for="name">Name</label> <div class="form-field tags">
<input class="input-cyan" type="text" name="name" placeholder="E.g. grass" /> <label for="tags">Tags</label>
</div> <ChipsInput v-model="tags" @update:modelValue="handleTagsUpdate" />
<div class="form-field tags"> </div>
<label for="tags">Tags</label> <div class="submit">
<ChipsInput /> <button class="btn-bordeaux" type="button" @click="removeTile">Remove</button>
</div> </div>
<div class="submit"> </form>
<button class="btn-cyan" type="submit">Save</button> </div>
<button class="btn-bordeaux" type="button" @click="removeTile">Remove</button>
</div>
</form>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import config from '@/config' import { ref, computed, watch, onBeforeUnmount, onMounted } from 'vue'
import { useAssetManagerStore } from '@/stores/assetManager' import { useAssetManagerStore } from '@/stores/assetManager'
import { useSocketStore } from '@/stores/socket' import { useSocketStore } from '@/stores/socket'
import ChipsInput from '@/components/forms/ChipsInput.vue' import ChipsInput from '@/components/forms/ChipsInput.vue'
import config from '@/config'
const socket = useSocketStore() const socket = useSocketStore()
const assetManagerStore = useAssetManagerStore() const assetManagerStore = useAssetManagerStore()
function removeTile() { const tags = ref<string[]>([])
socket.connection.emit('gm:tile:remove', { tile: assetManagerStore.selectedTile }, (response: boolean) => {
if (!response) { const selectedTile = computed(() => assetManagerStore.selectedTile)
return
} const tileImageUrl = computed(() => `${config.server_endpoint}/assets/tiles/${selectedTile.value}.png`)
socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
assetManagerStore.setTileList(response) watch(selectedTile, fetchTileTags)
assetManagerStore.setSelectedTile('')
}) function fetchTileTags(tile: string) {
if (config.development) console.log('P241 selectedTile', tile)
socket.connection.emit('gm:tile:tags', { tile }, (response: string[]) => {
tags.value = response
}) })
} }
function handleTagsUpdate(newTags: string[]) {
if (config.development) console.log(newTags)
saveTags(newTags)
}
function saveTags(tagsToSave: string[]) {
socket.connection.emit(
'gm:tile:tags:update',
{
tile: selectedTile.value,
tags: tagsToSave
},
(response: boolean) => {
if (!response) console.error('Failed to save tags')
}
)
}
function removeTile() {
socket.connection.emit('gm:tile:remove', { tile: selectedTile.value }, (response: boolean) => {
if (!response) {
console.error('Failed to remove tile')
return
}
refreshTileList()
})
}
function refreshTileList() {
socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
assetManagerStore.setTileList(response)
assetManagerStore.setSelectedTile('')
})
}
onMounted(() => {
if (!selectedTile.value) return
fetchTileTags(selectedTile.value)
})
onBeforeUnmount(() => {
assetManagerStore.setSelectedTile('')
})
</script> </script>
<style lang="scss"></style> <style lang="scss">
.modal-form {
padding: 10px; // @TODO why dis no work? fixme
}
</style>

View File

@ -29,7 +29,7 @@ const handleFileUpload = (e: Event) => {
if (!files) return if (!files) return
socket.connection.emit('gm:tile:upload', files, (response: boolean) => { socket.connection.emit('gm:tile:upload', files, (response: boolean) => {
if (!response) { if (!response) {
console.error('Failed to upload tile') if (config.development) console.error('Failed to upload tile')
return return
} }
socket.connection.emit('gm:tile:list', {}, (response: string[]) => { socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
@ -40,6 +40,7 @@ const handleFileUpload = (e: Event) => {
onMounted(() => { onMounted(() => {
socket.connection.emit('gm:tile:list', {}, (response: string[]) => { socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
if (config.development) console.log(response)
assetManagerStore.setTileList(response) assetManagerStore.setTileList(response)
}) })
}) })

View File

@ -1,120 +0,0 @@
<template>
<Teleport to="body">
<Modal v-if="isModalOpen" :isModalOpen="true" :closable="false" :modal-width="745" :modal-height="460">
<template #modalHeader>
<h3 class="modal-title">Decorations</h3>
</template>
<template #modalBody>
<div class="container decorations">
<div class="buttons">
<button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">Walls</button>
<button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">Decorations</button>
<button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">NPC</button>
</div>
<canvas ref="canvas" :width="decorationWidth" :height="decorationHeight" style="display: none"></canvas>
<div class="decorations">
<img v-for="(decoration, index) in decorations" :key="index" :src="decoration" alt="Decoration" @click="zoneEditorStore.setSelectedDecoration(index)" :class="{ selected: zoneEditorStore.selectedDecoration && zoneEditorStore.selectedDecoration === index }" />
</div>
</div>
</template>
</Modal>
</Teleport>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import config from '@/config'
import Modal from '@/components/utilities/Modal.vue'
import { useZoneEditorStore } from '@/stores/zoneEditor'
const decorationWidth = config.wall_size.x
const decorationHeight = config.wall_size.y
const decorations = ref<number[][]>([])
const selectedDecoration = ref<number | null>(null)
const canvas = ref<HTMLCanvasElement | null>(null)
const isModalOpen = ref(false)
const zoneEditorStore = useZoneEditorStore()
// Hardcoded image path
const imagePath = '/assets/zone/walls.png'
const loadImage = (src: string): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => resolve(img)
img.src = src
})
}
const splitDecorations = (img: HTMLImageElement) => {
if (!canvas.value) {
console.error('Canvas not found')
return
}
const ctx = canvas.value.getContext('2d')
if (!ctx) {
console.error('Failed to get canvas context')
return
}
const decorationsetWidth = img.width
const decorationsetHeight = img.height
const columns = Math.floor(decorationsetWidth / decorationWidth)
const rows = Math.floor(decorationsetHeight / decorationHeight)
decorations.value = []
selectedDecoration.value = null
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
const x = col * decorationWidth
const y = row * decorationHeight
ctx.clearRect(0, 0, decorationWidth, decorationHeight)
ctx.drawImage(img, x, y, decorationWidth, decorationHeight, 0, 0, decorationWidth, decorationHeight)
const decorationDataURL = canvas.value.toDataURL()
decorations.value.push(decorationDataURL)
}
}
}
const selectDecoration = (index: number) => {
selectedDecoration.value = index
}
onMounted(async () => {
isModalOpen.value = true
const img = await loadImage(imagePath)
await nextTick()
splitDecorations(img)
})
</script>
<style lang="scss">
@import '@/assets/scss/main';
.decorations {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.decorations img {
width: 30px;
height: 130px;
cursor: pointer;
border: 2px solid transparent;
transition: border 0.3s ease;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.decorations img.selected {
border: 2px solid $red;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<Teleport to="body">
<Modal v-if="isModalOpen" :isModalOpen="true" :closable="false" :modal-width="645" :modal-height="260">
<template #modalHeader>
<h3 class="modal-title">Objects</h3>
</template>
<template #modalBody>
<div class="container objects">
<div class="objects">
<img v-for="(object, index) in objects" :key="index" :src="`${config.server_endpoint}/assets/objects/${object}.png`" alt="Object" @click="zoneEditorStore.setSelectedObject(object)" :class="{ selected: zoneEditorStore.selectedObject && zoneEditorStore.selectedObject === object }" />
</div>
</div>
</template>
</Modal>
</Teleport>
</template>
<script setup lang="ts">
import config from '@/config'
import { ref, onMounted } from 'vue'
import { useZoneEditorStore } from '@/stores/zoneEditor'
import { useSocketStore } from '@/stores/socket'
import Modal from '@/components/utilities/Modal.vue'
const socket = useSocketStore()
const objects = ref<string[]>([])
const isModalOpen = ref(false)
const zoneEditorStore = useZoneEditorStore()
onMounted(async () => {
isModalOpen.value = true
socket.connection.emit('gm:object:list', {}, (response: string[]) => {
objects.value = response
})
})
</script>
<style lang="scss">
@import '@/assets/scss/main.scss';
/**
@TODO add masonry layout
https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/
*/
.objects {
display: grid;
width: 100%;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
grid-auto-rows: auto;
gap: 10px;
}
.objects img {
width: 100%;
height: auto;
cursor: pointer;
border: 2px solid transparent;
transition: border 0.3s ease;
}
.objects img.selected {
border: 2px solid $red;
}
</style>

View File

@ -31,7 +31,7 @@ const zoneEditorStore = useZoneEditorStore()
onMounted(async () => { onMounted(async () => {
isModalOpen.value = true isModalOpen.value = true
socket.connection.emit('gm:tile:list', {}, (response: string[]) => { socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
tiles.value = response; tiles.value = response
}) })
}) })
</script> </script>

View File

@ -17,7 +17,7 @@
</div> </div>
<div class="options" v-show="selectPencilOpen && zoneEditorStore.tool === 'pencil'"> <div class="options" v-show="selectPencilOpen && zoneEditorStore.tool === 'pencil'">
<span class="option" @click="setDrawMode('tile')">Tile</span> <span class="option" @click="setDrawMode('tile')">Tile</span>
<span class="option" @click="setDrawMode('decoration')">Decoration</span> <span class="option" @click="setDrawMode('object')">Object</span>
<span class="option" @click="setDrawMode('teleport')">Teleport</span> <span class="option" @click="setDrawMode('teleport')">Teleport</span>
<span class="option" @click="setDrawMode('blocking tile')">Blocking tile</span> <span class="option" @click="setDrawMode('blocking tile')">Blocking tile</span>
</div> </div>
@ -26,6 +26,12 @@
<div class="divider"></div> <div class="divider"></div>
<button class="tool paint" :class="{ active: zoneEditorStore.tool === 'paint' }" @click="zoneEditorStore.setTool('paint')">
<img src="/assets/icons/zoneEditor/paint.svg" alt="Paint bucket" />
</button>
<div class="divider"></div>
<button class="tool eraser" :class="{ active: zoneEditorStore.tool === 'eraser' }" @click="zoneEditorStore.setTool('eraser')"> <button class="tool eraser" :class="{ active: zoneEditorStore.tool === 'eraser' }" @click="zoneEditorStore.setTool('eraser')">
<img src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser" /> <img src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser" />
<div class="select" v-if="zoneEditorStore.tool === 'eraser'"> <div class="select" v-if="zoneEditorStore.tool === 'eraser'">
@ -43,6 +49,7 @@
</button> </button>
<div class="divider"></div> <div class="divider"></div>
<button class="tool settings" @click="() => zoneEditorStore.toggleSettingsModal()"> <button class="tool settings" @click="() => zoneEditorStore.toggleSettingsModal()">
<img src="/assets/icons/zoneEditor/gear.svg" alt="Zone settings" /> <img src="/assets/icons/zoneEditor/gear.svg" alt="Zone settings" />
</button> </button>
@ -61,9 +68,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, ref, watch } from 'vue' import { onBeforeUnmount, ref, watch } from 'vue'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { getTile, tileToWorldXY } from '@/services/zone' import { getTile } from '@/services/zone'
import config from '@/config'
import { useZoneStore } from '@/stores/zone'
import { useZoneEditorStore } from '@/stores/zoneEditor' import { useZoneEditorStore } from '@/stores/zoneEditor'
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()

View File

@ -4,14 +4,13 @@
<Controls :layer="tiles" /> <Controls :layer="tiles" />
<!-- @TODO: inside asset manager we need to be able to set the originX and originY per individial asset --> <!-- @TODO: inside asset manager we need to be able to set the originX and originY per individial asset -->
<Container> <Container>
<!-- <Image :texture="'wall1'" :x="pos.position_x" :y="pos.position_y" :originY="1.13" :originX="1" />--> <!-- <Image :texture="'wall1'" :x="pos.position_x" :y="pos.position_y" :originY="1.13" :originX="1" />-->
<!-- <Image :texture="'wall1'" :x="pos2.position_x" :y="pos2.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 :texture="'wall2'" :x="pos3.position_x" :y="pos3.position_y" :originY="1.255" :originX="1" />-->
</Container> </Container>
<Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @save="save" /> <Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @save="save" />
<Tiles v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'tile'" /> <Tiles v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'tile'" />
<Decorations v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'decoration'" /> <Objects v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'object'" />
<ZoneSettings v-if="zoneEditorStore.isSettingsModalShown" /> <ZoneSettings v-if="zoneEditorStore.isSettingsModalShown" />
</template> </template>
@ -20,17 +19,17 @@ import config from '@/config'
import Tileset = Phaser.Tilemaps.Tileset import Tileset = Phaser.Tilemaps.Tileset
import TilemapLayer = Phaser.Tilemaps.TilemapLayer import TilemapLayer = Phaser.Tilemaps.TilemapLayer
import { Container, TilemapLayer as TilemapLayerC, useScene, Image } from 'phavuer' import { Container, TilemapLayer as TilemapLayerC, useScene, Image } from 'phavuer'
import { onBeforeMount, onBeforeUnmount, onMounted, toRaw } from 'vue' import { onBeforeMount, onBeforeUnmount, onMounted, ref, toRaw } from 'vue'
import Controls from '@/components/utilities/Controls.vue' import Controls from '@/components/utilities/Controls.vue'
import { useSocketStore } from '@/stores/socket' import { useSocketStore } from '@/stores/socket'
import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue' import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
import Tiles from '@/components/utilities/zoneEditor/Tiles.vue' import Tiles from '@/components/utilities/zoneEditor/Tiles.vue'
import { useZoneEditorStore } from '@/stores/zoneEditor' import { useZoneEditorStore } from '@/stores/zoneEditor'
import ZoneSettings from '@/components/utilities/zoneEditor/ZoneSettings.vue' import ZoneSettings from '@/components/utilities/zoneEditor/ZoneSettings.vue'
import Decorations from '@/components/utilities/zoneEditor/Decorations.vue'
import { placeTile, tileToWorldXY } from '@/services/zone' import { placeTile, tileToWorldXY } from '@/services/zone'
import GmPanel from '@/components/utilities/GmPanel.vue'
import { useAssetStore } from '@/stores/assets' import { useAssetStore } from '@/stores/assets'
import Objects from '@/components/utilities/zoneEditor/Objects.vue'
import { randomUUID } from 'crypto'
const scene = useScene() const scene = useScene()
const socket = useSocketStore() const socket = useSocketStore()
@ -48,12 +47,20 @@ const zoneData = new Phaser.Tilemaps.MapData({
}) })
const zone = new Phaser.Tilemaps.Tilemap(scene, zoneData) const zone = new Phaser.Tilemaps.Tilemap(scene, zoneData)
const tilesetImages: Tileset[] = []; const tilesetImages: Tileset[] = []
type ZoneObject = {
id: string
object: string
position_x: number
position_y: number
}
const zoneObjects = ref<ZoneObject[]>([])
/** /**
* Walk through tiles and add them to the zone as tilesetImages * Walk through tiles and add them to the zone as tilesetImages
*/ */
let tileCount = 1; let tileCount = 1
toRaw(assetStore.assets).forEach((asset) => { toRaw(assetStore.assets).forEach((asset) => {
if (asset.group !== 'tiles') return if (asset.group !== 'tiles') return
tilesetImages.push(zone.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 0, 0, tileCount++) as Tileset) tilesetImages.push(zone.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 0, 0, tileCount++) as Tileset)
@ -65,7 +72,6 @@ const exampleTilesArray = Array.from({ length: zoneEditorStore.width }, () => Ar
placeTile(zone, tiles, 0, 0, 'blank_tile') placeTile(zone, tiles, 0, 0, 'blank_tile')
const pos = tileToWorldXY(tiles, 1, 1) const pos = tileToWorldXY(tiles, 1, 1)
const pos2 = tileToWorldXY(tiles, 1, 2) const pos2 = tileToWorldXY(tiles, 1, 2)
const pos3 = tileToWorldXY(tiles, 2, 1) const pos3 = tileToWorldXY(tiles, 2, 1)
@ -108,11 +114,16 @@ function pencil(tile: Phaser.Tilemaps.Tile) {
// zoneEditorStore.setTiles(tile.x, tile.y, zoneEditorStore.selectedTile) // zoneEditorStore.setTiles(tile.x, tile.y, zoneEditorStore.selectedTile)
} }
if (zoneEditorStore.drawMode === 'wall') { if (zoneEditorStore.drawMode === 'object') {
// @TODO fix position // @TODO fix position
if (zoneEditorStore.selectedWall === null) return if (zoneEditorStore.selectedObject === null) return
walls.putTileAt(zoneEditorStore.selectedWall, tile.x, tile.y) if (config.development) console.log('placing object', tile.x, tile.y, zoneEditorStore.selectedObject)
zoneEditorStore.updateWall(tile.x, tile.y, zoneEditorStore.selectedWall) zoneObjects.value.push({
id: Math.random().toString(10),
object: zoneEditorStore.selectedObject,
position_x: tileToWorldXY(tiles, tile.x, tile.y).position_x,
position_y: tileToWorldXY(tiles, tile.x, tile.y).position_y
})
} }
} }
@ -123,7 +134,7 @@ function save() {
width: zoneEditorStore.width, width: zoneEditorStore.width,
height: zoneEditorStore.height, height: zoneEditorStore.height,
tiles: zoneEditorStore.tiles, tiles: zoneEditorStore.tiles,
walls: zoneEditorStore.walls objects: zoneEditorStore.objects
}) })
} }

View File

@ -85,6 +85,7 @@ const preloadScene = (scene: Phaser.Scene) => {
}) })
scene.load.image('blank_tile', '/assets/zone/blank_tile.png') scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
scene.load.image('blank_object', '/assets/zone/blank_tile.png')
scene.load.image('waypoint', '/assets/waypoint.png') scene.load.image('waypoint', '/assets/waypoint.png')
scene.textures.addBase64( scene.textures.addBase64(
'character', 'character',

View File

@ -31,7 +31,7 @@ import { onMounted, ref } from 'vue'
import { login, register } from '@/services/authentication' import { login, register } from '@/services/authentication'
import { useNotificationStore } from '@/stores/notifications' import { useNotificationStore } from '@/stores/notifications'
import { useSocketStore } from '@/stores/socket' import { useSocketStore } from '@/stores/socket'
import {useAssetStore} from '@/stores/assets' import { useAssetStore } from '@/stores/assets'
const bgm = ref('bgm') const bgm = ref('bgm')
if (bgm.value.paused) { if (bgm.value.paused) {
@ -50,7 +50,7 @@ onMounted(async () => {
/** /**
* Fetch assets from the server * Fetch assets from the server
*/ */
assetStore.fetchAssets(); assetStore.fetchAssets()
const response = await login('ethereal', 'kanker123') const response = await login('ethereal', 'kanker123')

View File

@ -27,9 +27,9 @@ export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number
} }
export function placeTile(zone: Tilemap, layer: TilemapLayer, x: number, y: number, tileName: string) { export function placeTile(zone: Tilemap, layer: TilemapLayer, x: number, y: number, tileName: string) {
const tileImg = zone.getTileset(tileName) as Tileset; const tileImg = zone.getTileset(tileName) as Tileset
if (!tileImg) return if (!tileImg) return
layer.putTileAt(tileImg.firstgid, x, y); layer.putTileAt(tileImg.firstgid, x, y)
} }
export function generateTilemap(scene: Phaser.Scene, width: number, height: number) {} export function generateTilemap(scene: Phaser.Scene, width: number, height: number) {}

View File

@ -1,20 +1,50 @@
import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
export const useAssetManagerStore = defineStore('assetManager', { export const useAssetManagerStore = defineStore('assetManager', () => {
state: () => ({ const tileList = ref<string[]>([])
tileList: [] as string[], const objectList = ref<string[]>([])
selectedTile: '' const selectedTile = ref<string | null>(null)
}), const selectedObject = ref<string | null>(null)
actions: { const objectDetails = ref<Record<string, any>>({})
setTileList(tiles: string[]) {
this.tileList = tiles function setTileList(tiles: string[]) {
}, tileList.value = tiles
setSelectedTile(tile: string) { }
this.selectedTile = tile
}, function setObjectList(objects: string[]) {
reset() { objectList.value = objects
this.tileList = [] }
this.selectedTile = ''
} function setSelectedTile(tile: string) {
selectedTile.value = tile
}
function setSelectedObject(object: string) {
selectedObject.value = object
}
function setObjectDetails(object: Record<string, any>) {
objectDetails.value = object
}
function reset() {
tileList.value = []
selectedTile.value = null
selectedObject.value = null
objectDetails.value = {}
}
return {
tileList,
objectList,
setTileList,
setObjectList,
selectedTile,
selectedObject,
setSelectedTile,
setSelectedObject,
objectDetails,
setObjectDetails
} }
}) })

View File

@ -4,7 +4,7 @@ import config from '@/config'
export const useGmPanelStore = defineStore('gmPanel', { export const useGmPanelStore = defineStore('gmPanel', {
state: () => ({ state: () => ({
isOpen: false, isOpen: false
}), }),
actions: { actions: {
toggle() { toggle() {
@ -15,6 +15,6 @@ export const useGmPanelStore = defineStore('gmPanel', {
}, },
close() { close() {
this.isOpen = false this.isOpen = false
}, }
} }
}) })

View File

@ -7,12 +7,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
width: 10, width: 10,
height: 10, height: 10,
tiles: [] as number[][], tiles: [] as number[][],
decorations: [] as number[][], objects: [] as number[][],
tool: 'move', tool: 'move',
drawMode: 'tile', drawMode: 'tile',
selectedTile: '', selectedTile: '',
selectedWall: null, selectedObject: null,
selectedDecoration: null,
isSettingsModalShown: false isSettingsModalShown: false
}), }),
actions: { actions: {
@ -34,11 +33,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
updateTile(x: number, y: number, tile: number) { updateTile(x: number, y: number, tile: number) {
this.tiles[y][x] = tile this.tiles[y][x] = tile
}, },
setDecorations(decorations: number[][]) { setObjects(objects: number[][]) {
this.decorations = decorations this.objects = objects
}, },
updateDecoration(x: number, y: number, decoration: number) { updateObject(x: number, y: number, object: number) {
this.decorations[y][x] = decoration this.objects[y][x] = object
}, },
setTool(tool: string) { setTool(tool: string) {
this.tool = tool this.tool = tool
@ -49,11 +48,8 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
setSelectedTile(tile: string) { setSelectedTile(tile: string) {
this.selectedTile = tile this.selectedTile = tile
}, },
setSelectedWall(wall: any) { setSelectedObject(object: any) {
this.selectedWall = wall this.selectedObject = object
},
setSelectedDecoration(decoration: any) {
this.selectedDecoration = decoration
}, },
toggleSettingsModal() { toggleSettingsModal() {
this.isSettingsModalShown = !this.isSettingsModalShown this.isSettingsModalShown = !this.isSettingsModalShown
@ -66,8 +62,7 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
this.tool = 'move' this.tool = 'move'
this.drawMode = 'tile' this.drawMode = 'tile'
this.selectedTile = '' this.selectedTile = ''
this.selectedWall = null this.selectedObject = null
this.selectedDecoration = null
this.isSettingsModalShown = false this.isSettingsModalShown = false
} }
} }

View File

@ -3,7 +3,33 @@ export type Notification = {
message: string message: string
} }
// User model export type Asset = {
key: string
value: string
group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
type: 'base64' | 'link'
}
export type Object = {
id: string
name: string
origin_x: number
origin_y: number
createdAt: Date
updatedAt: Date
ZoneObject: ZoneObject[]
}
export type Item = {
id: string
name: string
description: string
stackable: boolean
createdAt: Date
updatedAt: Date
characters: CharacterItem[]
}
export type User = { export type User = {
id: number id: number
username: string username: string
@ -11,7 +37,6 @@ export type User = {
characters: Character[] characters: Character[]
} }
// Character model
export type Character = { export type Character = {
id: number id: number
userId: number userId: number
@ -28,33 +53,47 @@ export type Character = {
zoneId: number zoneId: number
zone: Zone zone: Zone
chats: Chat[] chats: Chat[]
items: CharacterItem[]
}
export type CharacterItem = {
id: number
characterId: number
character: Character
itemId: string
item: Item
quantity: number
}
export type TileTag = {
tile: string
tags: any // Using 'any' for Json type, consider using a more specific type if possible
} }
// Zone model
export type Zone = { export type Zone = {
id: number id: number
name: string name: string
width: number width: number
height: number height: number
tiles: number[][] tiles: any // Using 'any' for Json type, consider using a more specific type if possible
walls: number[][] walls: any // Using 'any' for Json type, consider using a more specific type if possible
decorations: ZoneDecoration[] zoneObjects: ZoneObject[]
characters: Character[] characters: Character[]
chats: Chat[] chats: Chat[]
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }
export type ZoneDecoration = { export type ZoneObject = {
id: number id: number
zoneId: number zoneId: number
zone: Zone zone: Zone
type: number objectId: string
object: Object
position_x: number position_x: number
position_y: number position_y: number
} }
// Chat model
export type Chat = { export type Chat = {
id: number id: number
characterId: number characterId: number
@ -64,10 +103,3 @@ export type Chat = {
message: string message: string
createdAt: Date createdAt: Date
} }
export type Asset = {
key: string
value: string
group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
type: 'base64' | 'link'
}