client/src/components/screens/Characters.vue

280 lines
7.1 KiB
Vue

<template>
<div class="character-select-screen">
<div class="ui-wrapper">
<div class="characters-wrapper">
<div v-for="character in characters" :key="character.id" class="character" :class="{ active: selected_character == character.id }">
<input type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
<label :for="character.id">{{ character.name }}</label>
<!-- @TODO : Add a confirmation dialog -->
<button class="delete" @click="delete_character(character.id)">
<img draggable="false" src="/assets/icons/trashcan.svg">
</button>
<div class="sprite-container">
<img draggable="false" src="/assets/avatar/default/base_right_down.png" />
</div>
<span>Lvl. {{ character.level }}</span>
</div>
<div class="character new-character" v-if="characters.length < 4">
<button @click="isModalOpen = true">
<img draggable="false" src="/assets/icons/plus-icon.svg" />
<span>Create new</span>
</button>
</div>
</div>
<div class="button-wrapper">
<button class="btn-purple" :disabled="!selected_character" @click="select_character()">
Play
<img draggable="false" src="/assets/icons/arrow.svg">
</button>
</div>
</div>
</div>
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false">
<template #modal-header>
<h2 class="modal-title">Create your character</h2>
</template>
<template #modal-body>
<form method="post" @submit.prevent="create" class="modal-form">
<div class="form-fields">
<label for="name">Name</label>
<input v-model="name" name="name" id="name" />
</div>
<div class="submit">
<button class="btn-purple" type="submit">Create</button>
</div>
</form>
<button class="btn-purple" @click="isModalOpen = false">Cancel</button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { useSocketStore } from '@/stores/socket'
import { ref } from 'vue'
import Modal from '@/components/utilities/Modal.vue'
import {type Character as CharacterT} from '@/types'
const characters = ref([])
const socket = useSocketStore()
// Fetch characters
socket.getConnection.on('character:list', (data: any) => {
characters.value = data
})
socket.getConnection.emit('character:list')
// Select character logics
const selected_character = ref(null)
function select_character() {
if (!selected_character.value) return
socket.getConnection.emit('character:connect', { character_id: selected_character.value })
socket.getConnection.on('character:connect', (data: CharacterT) => socket.setCharacter(data))
}
// Delete character logics
function delete_character(character_id: number) {
if (!character_id) return
socket.getConnection.emit('character:delete', { character_id: character_id })
}
// Create character logics
const isModalOpen = ref(false)
const name = ref('')
function create() {
socket.getConnection.on('character:create:success', (data: CharacterT) => {
socket.setCharacter(data)
isModalOpen.value = false
})
socket.getConnection.emit('character:create', { name: name.value })
}
</script>
<style lang="scss">
@import '@/assets/scss/main';
.character-select-screen {
background: #dddddd;
.ui-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 80px;
padding: 0 80px;
&::before {
content: '';
}
.characters-wrapper {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 60px;
width: 100%;
max-height: 650px;
overflow: auto;
.character {
margin: 10px;
width: 170px;
height: 275px;
display: flex;
flex-direction: column;
border-radius: 20px;
position: relative;
background-repeat: no-repeat;
background-image: url('/assets/shapes/character-select-shape-unselected.svg');
&.active {
background-image: url('/assets/shapes/character-select-shape.svg');
}
&.new-character {
background-color: rgba($light-gray, 0.5);
background-image: none;
button {
height: 100%;
width: 100%;
padding: 40px 0;
display: flex;
flex-direction: column;
justify-content: space-between;
&::before {
content: '';
}
img {
width: 100px;
height: 100px;
margin: auto;
}
span {
align-self: center;
font-size: 1rem;
}
}
&::before,
&::after {
display: none;
}
}
&::before {
content: '';
position: absolute;
top: 0;
width: 100%;
height: 40px;
background-image: url('/assets/shapes/character-select-header-shape.svg');
background-repeat: no-repeat;
border-radius: 20px 20px 0 0;
}
&::after {
content: '';
position: absolute;
bottom: 0;
width: 100%;
height: 40px;
background-color: rgba($purple, 0.6);
border-radius: 0 0 20px 20px;
}
input[type='radio'] {
opacity: 0;
height: 100%;
width: 100%;
position: absolute;
margin: 0;
}
label {
font-weight: bold;
position: absolute;
top: 20px;
width: 100%;
transform: translateY(-50%);
text-align: center;
}
button.delete {
background-color: $red;
width: 30px;
height: 30px;
padding: 5px;
border-radius: 100%;
position: absolute;
right: -10px;
top: 5px;
transform: translateY(-50%);
z-index: 1;
&:hover {
background-color: $dark-red;
}
}
span {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
transform: translateY(50%);
z-index: 1;
}
.sprite-container {
display: flex;
flex-direction: column;
align-items: center;
margin: auto;
}
}
}
.button-wrapper {
display: flex;
gap: 30px;
button {
padding: 8px 10px 8px 20px;
min-width: 100px;
position: relative;
border-radius: 5px;
font-size: 1.125rem;
display: flex;
gap: 10px;
align-items: center;
span {
margin: auto;
}
img {
height: 35px;
opacity: 0.3;
}
&:disabled {
background-color: rgba($purple, 0.5);
cursor: not-allowed;
}
}
}
}
.modal-title {
margin: 0;
}
}
</style>