forked from noxious/client
GM panel UI improvements, added accordion component, worked on sprite logics, updated types, npm update
This commit is contained in:
parent
a788cdef99
commit
026165cff3
92
package-lock.json
generated
92
package-lock.json
generated
@ -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": {
|
||||
|
26
src/components/utilities/Accordion.vue
Normal file
26
src/components/utilities/Accordion.vue
Normal file
@ -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>
|
@ -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">
|
||||
|
@ -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(() => {
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
142
src/types.ts
142
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user