Replaced modal resize icon, started writing components for character type management, spride field made optional
This commit is contained in:
@ -22,8 +22,16 @@
|
||||
<span>NPC's</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Characters</span>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'shops' }" @click="() => (selectedCategory = 'shops')">
|
||||
<span>Shops</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterTypes' }" @click="() => (selectedCategory = 'characterTypes')">
|
||||
<span>Character types</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterHair' }" @click="() => (selectedCategory = 'characterHair')">
|
||||
<span>Character hair</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
@ -34,6 +42,10 @@
|
||||
<span>Pets</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Emoticons</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/6"></div>
|
||||
|
||||
@ -42,6 +54,7 @@
|
||||
<TileList v-if="selectedCategory === 'tiles'" />
|
||||
<ObjectList v-if="selectedCategory === 'objects'" />
|
||||
<SpriteList v-if="selectedCategory === 'sprites'" />
|
||||
<CharacterTypeList v-if="selectedCategory === 'characterTypes'" />
|
||||
</div>
|
||||
|
||||
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/2"></div>
|
||||
@ -51,6 +64,7 @@
|
||||
<TileDetails v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
|
||||
<ObjectDetails v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
|
||||
<SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" />
|
||||
<CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -64,6 +78,8 @@ import ObjectList from '@/components/gameMaster/assetManager/partials/object/Obj
|
||||
import ObjectDetails from '@/components/gameMaster/assetManager/partials/object/ObjectDetails.vue'
|
||||
import SpriteList from '@/components/gameMaster/assetManager/partials/sprite/SpriteList.vue'
|
||||
import SpriteDetails from '@/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue'
|
||||
import CharacterTypeList from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeList.vue'
|
||||
import CharacterTypeDetails from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeDetails.vue'
|
||||
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
const selectedCategory = ref('tiles')
|
||||
|
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="m-2.5 p-2.5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveCharacterType">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
<input v-model="characterName" class="input-field" type="text" name="name" placeholder="Character Type Name" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="gender">Gender</label>
|
||||
<select v-model="characterGender" class="input-field" name="gender">
|
||||
<option v-for="gender in genderOptions" :key="gender" :value="gender">{{ gender }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="race">Race</label>
|
||||
<select v-model="characterRace" class="input-field" name="race">
|
||||
<option v-for="race in raceOptions" :key="race" :value="race">{{ race }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite ID</label>
|
||||
<input v-model="characterSpriteId" class="input-field" type="text" name="spriteId" placeholder="Sprite ID" />
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterType">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CharacterType, CharacterGender, CharacterRace } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedCharacterType = computed(() => assetManagerStore.selectedCharacterType)
|
||||
|
||||
const characterName = ref('')
|
||||
const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
|
||||
const characterRace = ref<CharacterRace>('HUMAN' as CharacterRace.HUMAN)
|
||||
const characterSpriteId = ref('')
|
||||
|
||||
const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
|
||||
const raceOptions: CharacterRace[] = ['HUMAN' as CharacterRace.HUMAN, 'ELF' as CharacterRace.ELF, 'DWARF' as CharacterRace.DWARF, 'ORC' as CharacterRace.ORC, 'GOBLIN' as CharacterRace.GOBLIN]
|
||||
|
||||
if (!selectedCharacterType.value) {
|
||||
console.error('No character type selected')
|
||||
}
|
||||
|
||||
if (selectedCharacterType.value) {
|
||||
characterName.value = selectedCharacterType.value.name
|
||||
characterGender.value = selectedCharacterType.value.gender
|
||||
characterRace.value = selectedCharacterType.value.race
|
||||
characterSpriteId.value = selectedCharacterType.value.spriteId
|
||||
}
|
||||
|
||||
function removeCharacterType() {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:remove', { id: selectedCharacterType.value.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
|
||||
if (unsetSelectedCharacterType) {
|
||||
assetManagerStore.setSelectedCharacterType(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function saveCharacterType() {
|
||||
const characterTypeData = {
|
||||
id: selectedCharacterType.value?.id,
|
||||
name: characterName.value,
|
||||
gender: characterGender.value,
|
||||
race: characterRace.value,
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:update', characterTypeData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList(false)
|
||||
})
|
||||
}
|
||||
|
||||
watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
||||
if (!characterType) return
|
||||
characterName.value = characterType.name
|
||||
characterGender.value = characterType.gender
|
||||
characterRace.value = characterType.race
|
||||
characterSpriteId.value = characterType.spriteId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterType.value) return
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
assetManagerStore.setSelectedCharacterType(null)
|
||||
})
|
||||
</script>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="create-character" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer">
|
||||
<button id="create-character" @click="createNewCharacterType">
|
||||
<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>
|
||||
</label>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: characterType } in list" :key="characterType.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedCharacterType?.id === characterType.id }" @click="assetManagerStore.setSelectedCharacterType(characterType as CharacterType)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span>{{ characterType.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import type { CharacterType } from '@/types'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
const handleSearch = () => {
|
||||
// Trigger a re-render of the virtual list
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
const createNewCharacterType = () => {
|
||||
gameStore.connection?.emit('gm:characterType:create', {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const filteredCharacterTypes = computed(() => {
|
||||
if (!searchQuery.value) {
|
||||
return assetManagerStore.characterTypeList
|
||||
}
|
||||
return assetManagerStore.characterTypeList.filter((character) => character.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(filteredCharacterTypes, {
|
||||
itemHeight: 48
|
||||
})
|
||||
|
||||
const virtualList = ref({ scrollTo })
|
||||
|
||||
const onScroll = () => {
|
||||
let scrollTop = elementToScroll.value.style.marginTop.replace('px', '')
|
||||
|
||||
if (scrollTop > 80) {
|
||||
hasScrolled.value = true
|
||||
} else if (scrollTop <= 80) {
|
||||
hasScrolled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toTop() {
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
</script>
|
Reference in New Issue
Block a user