forked from noxious/client
160 lines
7.4 KiB
Vue
160 lines
7.4 KiB
Vue
<template>
|
|
<div class="bg-gray-900 relative">
|
|
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full"></div>
|
|
<div class="ui-wrapper h-dvh flex flex-col justify-center items-center gap-20 px-10 sm:px-20">
|
|
<div class="filler"></div>
|
|
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
|
|
<!-- CHARACTER LIST -->
|
|
<div v-for="character in characters" :key="character.id" class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character" :class="{ active: selected_character == character.id }">
|
|
<img src="/assets/ui-elements/ui-box-outer.svg" class="absolute w-full h-full" />
|
|
<img src="/assets/ui-elements/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" />
|
|
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
|
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
|
|
|
<button
|
|
class="delete bg-red w-8 h-8 p-[3px] rounded-full absolute -right-4 top-0 -translate-y-1/2 z-10 border-2 border-solid border-white hover:bg-red-300"
|
|
@click="
|
|
() => {
|
|
deletingCharacter = character
|
|
}
|
|
"
|
|
>
|
|
<img draggable="false" src="/assets/icons/trashcan.svg" />
|
|
</button>
|
|
|
|
<div class="sprite-container flex flex-col items-center m-auto">
|
|
<img class="drop-shadow-20" draggable="false" src="/assets/avatar/default/0.png" />
|
|
</div>
|
|
<span class="absolute bottom-6 w-full text-center translate-y-1/2 z-10">Lvl. {{ character.level }}</span>
|
|
<div class="selected-character group-[.active]:max-w-[170px] absolute max-w-0 w-4/6 h-[3px] bg-gray-500 rounded-[3px] left-1/2 -bottom-4 -translate-x-1/2 transition-all ease-in-out duration-300"></div>
|
|
</div>
|
|
|
|
<div class="character new-character first:ml-auto mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-gray-500/50 bg-no-repeat shadow-character" v-if="characters.length < 4">
|
|
<button class="h-full w-full py-10 flex flex-col justify-between" @click="isModalOpen = true">
|
|
<div class="filler"></div>
|
|
<img class="w-24 h-24 m-auto" draggable="false" src="/assets/icons/plus-icon.svg" />
|
|
<span class="self-center text-base absolute bottom-5 w-full text-center translate-y-1/2 z-10">Create new</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" />
|
|
</div>
|
|
|
|
<div class="button-wrapper flex gap-8" v-if="!isLoading">
|
|
<button
|
|
class="btn-red py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-red/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
|
@click.stop="gameStore.disconnectSocket()"
|
|
>
|
|
<img class="h-8 drop-shadow-20 rotate-180" draggable="false" src="/assets/icons/arrow.svg" alt="Logout icon" />
|
|
</button>
|
|
<button
|
|
class="btn-cyan py-2 px-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan-800 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
|
:disabled="!selected_character"
|
|
@click="select_character()"
|
|
>
|
|
PLAY
|
|
<img class="h-8 drop-shadow-20" draggable="false" src="/assets/icons/arrow.svg" alt="Play icon" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CREATE CHARACTER MODAL -->
|
|
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275">
|
|
<template #modalHeader>
|
|
<h3 class="m-0 font-medium text-white">Create your character</h3>
|
|
</template>
|
|
|
|
<template #modalBody>
|
|
<div class="p-4 h-[calc(100%_-_32px)]">
|
|
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
|
|
<div class="form-field-full">
|
|
<label for="name" class="text-white">Nickname</label>
|
|
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
|
|
</div>
|
|
<div class="grid grid-flow-col justify-stretch gap-4">
|
|
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
|
|
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|
|
</Modal>
|
|
|
|
<!-- DELETE CHARACTER MODAL -->
|
|
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
|
<template #modalHeader>
|
|
<h3 class="m-0 font-medium text-white">Delete character?</h3>
|
|
</template>
|
|
<template #modalBody>
|
|
<p class="mt-0 mb-5 text-white text-lg">
|
|
Do you want to permanently delete <span class="font-extrabold text-white">{{ deletingCharacter.name }}</span
|
|
>?
|
|
</p>
|
|
</template>
|
|
</ConfirmationModal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useGameStore } from '@/stores/gameStore'
|
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|
import Modal from '@/components/utilities/Modal.vue'
|
|
import { type Character as CharacterT } from '@/types'
|
|
import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue'
|
|
|
|
const isLoading = ref(true)
|
|
const characters = ref([])
|
|
const gameStore = useGameStore()
|
|
const deletingCharacter = ref(null)
|
|
|
|
// Fetch characters
|
|
gameStore.connection?.on('character:list', (data: any) => {
|
|
characters.value = data
|
|
})
|
|
|
|
onMounted(async () => {
|
|
const audio = new Audio('/assets/music/login.mp3')
|
|
await audio.play()
|
|
|
|
// wait 0.75 sec
|
|
setTimeout(() => {
|
|
gameStore.connection?.emit('character:list')
|
|
isLoading.value = false
|
|
}, 750)
|
|
})
|
|
|
|
// Select character logics
|
|
const selected_character = ref(null)
|
|
function select_character() {
|
|
if (!selected_character.value) return
|
|
deletingCharacter.value = null
|
|
gameStore.connection?.emit('character:connect', { character_id: selected_character.value })
|
|
gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data))
|
|
}
|
|
|
|
// Delete character logics
|
|
function delete_character(character_id: number) {
|
|
if (!character_id) return
|
|
deletingCharacter.value = null
|
|
gameStore.connection?.emit('character:delete', { character_id: character_id })
|
|
}
|
|
|
|
// Create character logics
|
|
const isModalOpen = ref(false)
|
|
const name = ref('')
|
|
function create() {
|
|
gameStore.connection?.on('character:create:success', (data: CharacterT) => {
|
|
gameStore.setCharacter(data)
|
|
isModalOpen.value = false
|
|
})
|
|
gameStore.connection?.emit('character:create', { name: name.value })
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
gameStore.connection?.off('character:list')
|
|
gameStore.connection?.off('character:connect')
|
|
gameStore.connection?.off('character:create:success')
|
|
})
|
|
</script>
|