diff --git a/package-lock.json b/package-lock.json index a66bb42..e0209b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1763,9 +1763,9 @@ } }, "node_modules/@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "version": "20.14.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", + "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1986,9 +1986,9 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", - "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.0.tgz", + "integrity": "sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==", "dev": true, "license": "MIT", "engines": { @@ -2260,14 +2260,14 @@ "license": "MIT" }, "node_modules/@vue/devtools-core": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.3.6.tgz", - "integrity": "sha512-XqFYVkyS3eySHF4bgLt+KF6yL6nYzVY/JTJHnK6KIJXIE4GIAxmn5Gxfsb4cUG9sl0FGiMqRCnM37Q+P08wr8A==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.3.7.tgz", + "integrity": "sha512-IapWbHUqvO6n+p5JFTCE5JyNjpsZ5IS1GYIRX0P7/SqYPgFCOdH0dG+u8PbBHYdnp+VPxHLO+GGZ/WBZFCZnsA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.3.6", - "@vue/devtools-shared": "^7.3.6", + "@vue/devtools-kit": "^7.3.7", + "@vue/devtools-shared": "^7.3.7", "mitt": "^3.0.1", "nanoid": "^3.3.4", "pathe": "^1.1.2", @@ -2278,13 +2278,13 @@ } }, "node_modules/@vue/devtools-kit": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.6.tgz", - "integrity": "sha512-5Ym9V3fkJenEoptqKoo+cgY5RTVwrSssFdzRsuyIgaeiskCT+rRJeQdwoo81tyrQ1mfS7Er1rYZlSzr3Y3L/ew==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.7.tgz", + "integrity": "sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.3.6", + "@vue/devtools-shared": "^7.3.7", "birpc": "^0.2.17", "hookable": "^5.5.3", "mitt": "^3.0.1", @@ -2294,9 +2294,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.6.tgz", - "integrity": "sha512-R/FOmdJV+hhuwcNoxp6e87RRkEeDMVhWH+nOsnHUrwjjsyeXJ2W1475Ozmw+cbZhejWQzftkHVKO28Fuo1yqCw==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.7.tgz", + "integrity": "sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2452,9 +2452,9 @@ } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz", + "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -2544,9 +2544,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz", + "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -2591,9 +2591,9 @@ } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz", + "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3440,9 +3440,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.832", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz", - "integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz", + "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==", "dev": true, "license": "ISC" }, @@ -5669,9 +5669,9 @@ } }, "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz", + "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -6839,9 +6839,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -7063,19 +7063,19 @@ } }, "node_modules/vite-plugin-vue-devtools": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.3.6.tgz", - "integrity": "sha512-j4Cssv6DVBtMZfyVBEm/4MZy7BiL6RedEn+f9jT3zFyGZKG1vNuEpTO86XvPPbHbYdITFyrkWb7VQuWyhfSgqA==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.3.7.tgz", + "integrity": "sha512-pPv6YJYrCIlWP+wwRk9gzDp2rK5M5jQ5oz//Nci3C3FDvORL1btKQqGvgthx3hs6xbx5acToJtkMGgDnZg8smw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-core": "^7.3.6", - "@vue/devtools-kit": "^7.3.6", - "@vue/devtools-shared": "^7.3.6", + "@vue/devtools-core": "^7.3.7", + "@vue/devtools-kit": "^7.3.7", + "@vue/devtools-shared": "^7.3.7", "execa": "^8.0.1", "sirv": "^2.0.4", "vite-plugin-inspect": "^0.8.4", - "vite-plugin-vue-inspector": "^5.1.2" + "vite-plugin-vue-inspector": "^5.1.3" }, "engines": { "node": ">=v14.21.3" @@ -7085,9 +7085,9 @@ } }, "node_modules/vite-plugin-vue-inspector": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.2.tgz", - "integrity": "sha512-M+yH2LlQtVNzJAljQM+61CqDXBvHim8dU5ImGaQuwlo13tMDHue5D7IC20YwDJuWDODiYc/cZBUYspVlyPf2vQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.3.tgz", + "integrity": "sha512-pMrseXIDP1Gb38mOevY+BvtNGNqiqmqa2pKB99lnLsADQww9w9xMbAfT4GB6RUoaOkSPrtlXqpq2Fq+Dj2AgFg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/components/utilities/Accordion.vue b/src/components/utilities/Accordion.vue new file mode 100644 index 0000000..9ac55cb --- /dev/null +++ b/src/components/utilities/Accordion.vue @@ -0,0 +1,26 @@ +<template> + <div class="border border-gray-300 rounded mb-4"> + <button @click="toggle" class="w-full p-3 bg-gray-100 rounded hover:bg-gray-200 text-left cursor-pointer transition-colors duration-200 ease-in-out"> + {{ props.title }} + </button> + <transition enter-active-class="transition-all duration-300 ease-in-out" leave-active-class="transition-all duration-300 ease-in-out" enter-from-class="opacity-0 max-h-0" enter-to-class="opacity-100 max-h-96" leave-from-class="opacity-100 max-h-96" leave-to-class="opacity-0 max-h-0"> + <div v-if="isOpen" class="p-3 overflow-hidden"> + <slot></slot> + </div> + </transition> + </div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue' + +const isOpen = ref(false) + +const toggle = () => { + isOpen.value = !isOpen.value +} + +const props = defineProps({ + title: String +}) +</script> diff --git a/src/components/utilities/assetManager/partials/object/ObjectList.vue b/src/components/utilities/assetManager/partials/object/ObjectList.vue index 03baba3..09ab2ef 100644 --- a/src/components/utilities/assetManager/partials/object/ObjectList.vue +++ b/src/components/utilities/assetManager/partials/object/ObjectList.vue @@ -1,10 +1,12 @@ <template> - <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap"> - <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer"> + <div class="relative p-2.5 flex items-center gap-x-2.5"> + <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" /> + <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-6 h-6"> <input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" /> - Upload object(s) + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> + </svg> </label> - <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" /> <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> </div> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> diff --git a/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue b/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue index 2eab3ac..dbeff23 100644 --- a/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue +++ b/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue @@ -1,56 +1,64 @@ <template> <div class="h-full overflow-auto"> - <div class="relative p-2.5 flex flex-col items-center justify-between h-72"> - <div class="filler"></div> - <img class="max-h-56" :src="`${config.server_endpoint}/assets/sprites/${selectedSprite?.id}.png`" :alt="'Sprite ' + selectedSprite?.id" /> - <button class="btn-bordeaux px-4 py-1.5 min-w-24" type="button" @click.prevent="removeSprite">Remove</button> - <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> + <div class="relative p-2.5 flex flex-col items-center justify-between"> + <div class="w-full flex flex-col sm:flex-row items-center gap-9 mb-5"> + <div class="w-full sm:flex-grow"> + <input v-model="spriteName" class="input-cyan w-full" type="text" name="name" placeholder="New sprite" /> + </div> + <div class="flex gap-2 w-full sm:w-auto"> + <button class="btn-cyan px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button> + <button class="btn-bordeaux px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="removeSprite">Remove</button> + </div> + </div> + <div class="w-full h-px bg-cyan-200"></div> </div> - <div class="m-2.5 p-2.5 block"> - <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite"> - <div class="w-full flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="name">Name</label> - <input v-model="spriteName" class="input-cyan" type="text" name="name" placeholder="Wall #1" /> - </div> - <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="origin-x">Origin X</label> - <input v-model="spriteOriginX" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" /> - </div> - <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="origin-y">Origin Y</label> - <input v-model="spriteOriginY" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" /> - </div> - <div class="w-full flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="frame-speed">Frame speed</label> - <input v-model="spriteFrameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" /> - </div> - <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="frame-width">Frame width</label> - <input v-model="spriteFrameWidth" class="input-cyan" type="number" step="any" name="frame-width" placeholder="Frame width" /> - </div> - <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="frame-height">Frame height</label> - <input v-model="spriteFrameHeight" class="input-cyan" type="number" step="any" name="frame-height" placeholder="Frame height" /> - </div> - <div class="w-full flex flex-col mb-5"> - <label class="mb-1.5 font-titles" for="is-looping">Is looping</label> - <select v-model="spriteIsLooping" class="input-cyan" name="is-looping"> - <option :value="false">No</option> - <option :value="true">Yes</option> - </select> - </div> - <button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button> - </form> + <div class="m-2.5 px-2.5 block"> + <button class="btn-cyan px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24 mb-5" type="button" @click.prevent="addNewImage">New IMG</button> + <Accordion v-for="image in spriteImages" :key="image.id" :title="image.name"> + <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite"> + <div class="w-full flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="name">Name</label> + <input v-model="image.name" class="input-cyan" type="text" name="name" placeholder="Wall #1" /> + </div> + <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="origin-x">Origin X</label> + <input v-model.number="image.origin_x" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" /> + </div> + <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="origin-y">Origin Y</label> + <input v-model.number="image.origin_y" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" /> + </div> + <div class="w-full flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="frame-speed">Frame speed</label> + <input v-model.number="image.frameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" /> + </div> + <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="frame-width">Frame width</label> + <input v-model.number="image.frameWidth" class="input-cyan" type="number" step="any" name="frame-width" placeholder="Frame width" /> + </div> + <div class="w-[calc(50%_-_5px)] flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="frame-height">Frame height</label> + <input v-model.number="image.frameHeight" class="input-cyan" type="number" step="any" name="frame-height" placeholder="Frame height" /> + </div> + <div class="w-full flex flex-col mb-5"> + <label class="mb-1.5 font-titles" for="is-looping">Is looping</label> + <select v-model="image.isLooping" class="input-cyan" name="is-looping"> + <option :value="false">No</option> + <option :value="true">Yes</option> + </select> + </div> + </form> + </Accordion> </div> </div> </template> <script setup lang="ts"> -import type { Sprite } from '@/types' +import type { Sprite, SpriteImage } from '@/types' import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useAssetManagerStore } from '@/stores/assetManager' import { useGameStore } from '@/stores/game' -import config from '@/config' +import Accordion from '@/components/utilities/Accordion.vue' const gameStore = useGameStore() const assetManagerStore = useAssetManagerStore() @@ -58,12 +66,7 @@ const assetManagerStore = useAssetManagerStore() const selectedSprite = computed(() => assetManagerStore.selectedSprite) const spriteName = ref('') -const spriteOriginX = ref(0) -const spriteOriginY = ref(0) -const spriteFrameSpeed = ref(0) -const spriteFrameWidth = ref(0) -const spriteFrameHeight = ref(0) -const spriteIsLooping = ref(false) +const spriteImages = ref<SpriteImage[]>([]) if (!selectedSprite.value) { console.error('No sprite selected') @@ -71,12 +74,7 @@ if (!selectedSprite.value) { if (selectedSprite.value) { spriteName.value = selectedSprite.value.name - spriteOriginX.value = selectedSprite.value.origin_x - spriteOriginY.value = selectedSprite.value.origin_y - spriteFrameSpeed.value = selectedSprite.value.frameSpeed - spriteFrameWidth.value = selectedSprite.value.frameWidth - spriteFrameHeight.value = selectedSprite.value.frameHeight - spriteIsLooping.value = selectedSprite.value.isLooping + spriteImages.value = selectedSprite.value.spriteImages } function removeSprite() { @@ -105,37 +103,51 @@ function saveSprite() { return } - gameStore.connection?.emit( - 'gm:sprite:update', - { - id: selectedSprite.value.id, - name: spriteName.value, - origin_x: spriteOriginX.value, - origin_y: spriteOriginY.value, - frameSpeed: spriteFrameSpeed.value, - frameWidth: spriteFrameWidth.value, - frameHeight: spriteFrameHeight.value, - isLooping: spriteIsLooping.value - }, - (response: boolean) => { - if (!response) { - console.error('Failed to save sprite') - return - } - refreshSpriteList(false) + const updatedSprite = { + id: selectedSprite.value.id, + name: spriteName.value, + spriteImages: selectedSprite.value.spriteImages + } + + gameStore.connection?.emit('gm:sprite:update', updatedSprite, (response: boolean) => { + if (!response) { + console.error('Failed to save sprite') + return } - ) + refreshSpriteList(false) + }) +} + +function addNewImage() { + if (!selectedSprite.value) return + + const newImage: SpriteImage = { + id: Date.now().toString(), // Temporary ID, should be replaced by server-generated ID + name: 'New image', + origin_x: 0, + origin_y: 0, + frameSpeed: 0, + frameWidth: 0, + frameHeight: 0, + isAnimated: false, + spriteId: selectedSprite.value.id, + sprite: selectedSprite.value, + action: '', + isLooping: false + } + + // spriteimages value can be undefined + if (!spriteImages.value) { + spriteImages.value = [] + } + + spriteImages.value.push(newImage) } watch(selectedSprite, (sprite: Sprite | null) => { if (!sprite) return spriteName.value = sprite.name - spriteOriginX.value = sprite.origin_x - spriteOriginY.value = sprite.origin_y - spriteFrameSpeed.value = sprite.frameSpeed - spriteFrameWidth.value = sprite.frameWidth - spriteFrameHeight.value = sprite.frameHeight - spriteIsLooping.value = sprite.isLooping + spriteImages.value = sprite.spriteImages }) onMounted(() => { diff --git a/src/components/utilities/assetManager/partials/sprite/SpriteList.vue b/src/components/utilities/assetManager/partials/sprite/SpriteList.vue index 42840d8..3200b78 100644 --- a/src/components/utilities/assetManager/partials/sprite/SpriteList.vue +++ b/src/components/utilities/assetManager/partials/sprite/SpriteList.vue @@ -1,19 +1,17 @@ <template> - <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap"> - <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer"> - <input class="hidden" id="upload-asset" ref="spriteUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" /> - Upload sprite(s) - </label> - <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" /> + <div class="relative p-2.5 flex items-center gap-x-2.5"> + <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" /> + <button @click.prevent="newButtonClickHandler" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-9 h-9"> + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> + </svg> + </button> <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> </div> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> <div v-bind="wrapperProps" ref="elementToScroll"> <a v-for="{ data: sprite } in list" :key="sprite.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedSprite?.id === sprite.id }" @click="assetManagerStore.setSelectedSprite(sprite as Sprite)"> <div class="flex items-center gap-2.5"> - <div class="h-7 w-16 max-w-16 flex justify-center"> - <img class="h-7" :src="`${config.server_endpoint}/assets/sprites/${sprite.id}.png`" alt="Sprite" /> - </div> <span>{{ sprite.name }}</span> </div> <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> @@ -35,7 +33,6 @@ import { useVirtualList } from '@vueuse/core' import type { Sprite } from '@/types' const gameStore = useGameStore() -const spriteUploadField = ref(null) const assetManagerStore = useAssetManagerStore() const assetStore = useAssetStore() @@ -44,17 +41,13 @@ const searchQuery = ref('') const hasScrolled = ref(false) const elementToScroll = ref() -const handleFileUpload = (e: Event) => { - const files = (e.target as HTMLInputElement).files - if (!files) return - gameStore.connection?.emit('gm:sprite:upload', files, (response: boolean) => { +function newButtonClickHandler() { + gameStore.connection?.emit('gm:sprite:create', {}, (response: boolean) => { if (!response) { - if (config.development) console.error('Failed to upload sprite') + if (config.development) console.error('Failed to create new sprite') return } - assetStore.fetchAssets() - gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => { assetManagerStore.setSpriteList(response) }) diff --git a/src/components/utilities/assetManager/partials/tile/TileList.vue b/src/components/utilities/assetManager/partials/tile/TileList.vue index 4f9c1cb..0fcb043 100644 --- a/src/components/utilities/assetManager/partials/tile/TileList.vue +++ b/src/components/utilities/assetManager/partials/tile/TileList.vue @@ -1,10 +1,12 @@ <template> - <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap"> - <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer"> + <div class="relative p-2.5 flex items-center gap-x-2.5"> + <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" /> + <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-6 h-6"> <input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" /> - Upload tile(s) + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> + </svg> </label> - <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" /> <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> </div> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> diff --git a/src/screens/Login.vue b/src/screens/Login.vue index c02aa68..16a122c 100644 --- a/src/screens/Login.vue +++ b/src/screens/Login.vue @@ -67,6 +67,7 @@ async function loginFunc() { gameStore.setToken(response.token) gameStore.initConnection() + return true // Indicate success } async function registerFunc() { @@ -84,6 +85,9 @@ async function registerFunc() { return } - await loginFunc() + const loginSuccess = await loginFunc() + if (!loginSuccess) { + notifications.addNotification({ message: 'Login after registration failed. Please try logging in manually.' }) + } } </script> diff --git a/src/types.ts b/src/types.ts index 069e77a..88a63ce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,23 +10,10 @@ export type Asset = { type: 'base64' | 'link' } -export type Sprite = { - id: string - name: string - origin_x: number - origin_y: number - frameSpeed: number - frameWidth: number - frameHeight: number - isLooping: boolean - createdAt: Date - updatedAt: Date -} - export type Tile = { id: string name: string - tags?: any + tags: any | null createdAt: Date updatedAt: Date } @@ -34,7 +21,7 @@ export type Tile = { export type Object = { id: string name: string - tags?: any + tags: any | null origin_x: number origin_y: number isAnimated: boolean @@ -49,54 +36,19 @@ export type Object = { export type Item = { id: string name: string - description: string + description: string | null stackable: boolean createdAt: Date updatedAt: Date characters: CharacterItem[] } -export type User = { - id: number - username: string - password: string - characters: Character[] -} - -export type Character = { - id: number - userId: number - user: User - name: string - hitpoints: number - mana: number - level: number - experience: number - role: string - position_x: number - position_y: number - rotation: number - zoneId: number - zone: Zone - chats: Chat[] - items: CharacterItem[] -} - -export type CharacterItem = { - id: number - characterId: number - character: Character - itemId: string - item: Item - quantity: number -} - export type Zone = { id: number name: string width: number height: number - tiles: string[][] + tiles: any | null pvp: boolean zoneEventTiles: ZoneEventTile[] zoneObjects: ZoneObject[] @@ -133,6 +85,92 @@ export type ZoneEventTile = { position_y: number } +export type User = { + id: number + username: string + password: string + characters: Character[] +} + +export enum CharacterGender { + MALE = 'MALE', + FEMALE = 'FEMALE' +} + +export enum CharacterRace { + HUMAN = 'HUMAN', + ELF = 'ELF', + DWARF = 'DWARF', + ORC = 'ORC', + GOBLIN = 'GOBLIN' +} + +export type CharacterType = { + id: number + name: string + gender: CharacterGender + race: CharacterRace + characters: Character[] + spriteId: string + sprite: Sprite + createdAt: Date + updatedAt: Date +} + +export type Character = { + id: number + userId: number + user: User + name: string + hitpoints: number + mana: number + level: number + experience: number + role: string + position_x: number + position_y: number + rotation: number + zoneId: number + zone: Zone + characterTypeId: number | null + characterType: CharacterType | null + chats: Chat[] + items: CharacterItem[] +} + +export type CharacterItem = { + id: number + characterId: number + character: Character + itemId: string + item: Item + quantity: number +} + +export type Sprite = { + id: string + name: string + createdAt: Date + updatedAt: Date + spriteImages: SpriteImage[] + characterTypes: CharacterType[] +} + +export type SpriteImage = { + id: string + spriteId: string + sprite: Sprite + name: string + action: string + origin_x: number + origin_y: number + isAnimated: boolean + isLooping: boolean + frameWidth: number + frameHeight: number + frameSpeed: number +} + export type Chat = { id: number characterId: number