Version 1.0.0 - Finalisation du composant Servers

This commit is contained in:
2025-07-25 22:56:25 +02:00
parent e3d7a911f4
commit 4fdd4aad61
15 changed files with 1632 additions and 58 deletions

1450
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "vite --host --port 8080",
"build": "vue-tsc -b && vite build"
"build": "vite build"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
@@ -20,5 +20,8 @@
"vite": "^7.0.5",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"vite-plugin-vue-devtools": "^7.7.7"
}
}

View File

@@ -1,7 +1,16 @@
<template>
<div>
<img :src="`https://cdn.discordapp.com/avatars/${userId}/${avatarUrl}`" alt='User Avatar'>
<Icon v-if="tag === 'admin'" style="color: var(--main);" icon="fa-solid fa-star" class="tag" />
<Icon v-if="tag === 'owner'" style="color: #FFAA32;" icon="fa-solid fa-crown" class="tag" />
<Icon v-if="tag === 'mod'" style="color: #0BFF89;" icon="fa-solid fa-shield-halved" class="tag" />
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
userId: {
type: String,
@@ -10,8 +19,28 @@ const props = defineProps({
avatarUrl: {
type: String,
required: true
},
isMod: {
type: Boolean,
default: false
},
isOwner: {
type: Boolean,
default: false
},
isAdmin: {
type: Boolean,
default: false
}
});
const tag = computed(() => {
if (props.isAdmin) return 'admin';
if (props.isOwner) return 'owner';
if (props.isMod) return 'mod';
return '';
});
</script>
<style scoped>
img {
@@ -19,5 +48,21 @@ img {
width: 50px;
height: 50px;
object-fit: cover;
background-color: var(--main);
}
div {
position: relative;
display: inline-block;
}
.tag {
position: absolute;
bottom: 0;
right: 0;
font-size: 0.8em;
color: white;
border-radius: 50%;
padding: 2px 5px;
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<div><slot></slot></div>
</template>
<style scoped>
div {
border-radius: 100%;
background-color: var(--secondary);
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -1,16 +1,16 @@
<template>
<img v-if="serverIcon" :src="`https://cdn.discordapp.com/icons/${serverId}/${serverIcon}.png?`" />
<img v-if="src" :src="src" />
<img v-else :src="`https://cdn.discordapp.com/embed/avatars/${serverId % 5}.png`" />
</template>
<script setup>
const props = defineProps({
src: {
type: String,
default: null
},
serverId: {
type: String,
required: true
},
serverIcon: {
type: String,
default: ''
default: null
}
});
</script>
@@ -18,7 +18,7 @@
img {
width: 70px;
height: 70px;
border-radius: 10px;
background-color: var(--secondary);
border-radius: 15px;
background-color: var(--main);
}
</style>

View File

@@ -2,14 +2,14 @@
<Box no-shadow level="second" padding="closed">
<div class="container-list">
<div class="container-avatar">
<ServerAvatar :server-id="server.id" :server-icon="server.icon"/>
<ServerAvatar :server-id="server.id" :src="server.icon"/>
<div class="info">
<p class="name">{{ server.name }}</p>
<p class="data"><Icon style="font-size: 10px;" :color="server.connected ? '#0BFF89' : '#FF0A0A'" icon="fa-solid fa-circle"/> {{ server.connected ? 'En ligne' : 'Hors ligne' }} - {{ server.serverMember }} membres</p>
</div>
</div>
<Button @click="access()">Accéder</Button>
<ServerOnlinePicture class="sop" :key="server.id" v-if="server.members.length > 0" :members="server.members"/>
<Button class="btn" @click="access()">Accéder</Button>
</div>
</Box>
</template>
@@ -20,6 +20,7 @@ import Button from '../UI/Button.vue';
import IconAction from '../UI/IconAction.vue';
import { useGlobalStore } from '@/stores/globalStore';
import { useRouter } from 'vue-router';
import ServerOnlinePicture from '@/components/Widget/ServerOnlinePicture.vue';
const router = useRouter();
const globalStore = useGlobalStore();
@@ -57,6 +58,23 @@ function access() {
gap: 10px;
}
@media screen and (max-width: 768px) {
.container-list {
flex-direction: column;
gap: 20px;;
}
.btn {
width: 100%;
}
.sop {
display: none;
}
}
.container-list {
display: flex;
justify-content: space-between;

View File

@@ -0,0 +1,42 @@
<template>
<div>
<Avatar v-for="member in members"
:key="member.id"
:user-id="member.id"
:avatar-url="member.avatar"
:isMod="member.isMod"
:isOwner="member.isOwner"
:isAdmin="member.isAdmin"
:class="`avatar`"/>
<Circle style="z-index: 10;" class="avatar" v-if="length > 4">+ {{ length - 4}}</Circle>
</div>
</template>
<script setup>
import Avatar from '../UI/Avatar.vue';
import Circle from '../UI/Circle.vue';
const props = defineProps({
members: {
type: Array,
default: () => []
},
})
// ne garder que les quatres premiers membres
const length = props.members.length;
const members = props.members.slice(0, 4);
</script>
<style scoped>
div{
display: flex;
align-items: center;
}
.avatar {
margin-left: -10px; /* Ajustez cette valeur pour contrôler le chevauchement */
}
</style>

View File

@@ -2,6 +2,7 @@
import DefaultSplash from '@/components/Layout/DefaultSplash.vue';
const defaultMessage = "On s'accorde et on prépare le concert !";
const connectMsg = "Erreur de connexion au serveur : xhr poll error"
const props = defineProps({
interuptionMessage: {
@@ -15,8 +16,12 @@ const props = defineProps({
<template>
<DefaultSplash>
<h1 v-if="!interuptionMessage" class="separate"><Icon spin-pulse icon="fa-solid fa-spinner"/> Chargement de l'interface</h1>
<h1 v-else-if="interuptionMessage == connectMsg">Echec de connexion</h1>
<h1 v-else><Icon icon="fa-solid fa-warning"/> Connexion interrompue</h1>
<div class="separate-col">
<p v-if="interuptionMessage" class="retry"><Icon spin-pulse icon="fa-solid fa-spinner"/> Tentative de reconnexion en cours</p>
<p v-if="interuptionMessage" class="second">Contactez Raphix (raphixscrap) en cas de problème prolongé.</p>
</div>
<p v-if="interuptionMessage" class="error"><Icon icon="fa-solid fa-circle-xmark"/> {{ interuptionMessage }}</p>
<p v-else>{{ defaultMessage }}</p>
</DefaultSplash>
@@ -37,4 +42,22 @@ const props = defineProps({
justify-content: center;
gap: 10px;
}
.separate-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
}
.separate-col p {
margin: 0;
}
.second {
font-size: 0.8em;
color: var(--text-secondary);
}
</style>

View File

@@ -75,13 +75,16 @@ import { useGlobalStore } from '@/stores/globalStore';
interuptionMessage.value = null;
return;
}
setInterval(() => {
const intervalId = setInterval(() => {
console.log("Tentative de reconnexion au serveur...");
if(!socket.connected || !needReload.value) {
socket.auth.token = loginStore.token;
socket.disconnect()
// Avoid multiple connections attempts at the same time
socket.connect();
} else {
clearInterval(this);
console.log("Already connected, no need to reconnect.");
clearInterval(intervalId);
}
}, 3000); // Essai de reconnexion après 5 secondes
}

View File

@@ -21,8 +21,8 @@ import { watch } from 'vue';
import { useRouter } from 'vue-router';
import { useGlobalStore } from '@/stores/globalStore';
import { useUserStore } from '@/stores/userStore';
import { IOListener } from '@/utils/IORequest';
import { onMounted } from 'vue';
import { IOListener, IORequest } from '@/utils/IORequest';
import { onMounted, ref } from 'vue';
const props = defineProps({
guildId: {
@@ -40,9 +40,10 @@ if (!guildId) {
const globalStore = useGlobalStore();
const userStore = useUserStore();
const router = useRouter();
const alreadyLoaded = ref(false);
watch(() => globalStore.isLoading, (value) => {
if(!value) {
watch(() => globalStore.isLoading, (value, oldValue) => {
if(!value && oldValue != value) {
loadInteface();
}
})
@@ -53,19 +54,42 @@ onMounted(() => {
}
});
watch(() => userStore.userInfo, (newValue, oldValue) => {
if(userStore.userInfo) {
checkGuildAvailability();
}
}, { immediate: true });
function loadInteface() {
if(alreadyLoaded.value) return;
console.log("Loading interface")
console.log("Guild ID:", guildId);
checkGuildAvailability();
IORequest("/USERS/LIST", (data) => {
console.log("User list received:", data);
})
alreadyLoaded.value = true;
}
function checkGuildAvailability() {
console.log("Checking guild availability for ID:", guildId);
if(userStore.userInfo.guilds.filter(guild => guild.id === guildId).length === 0) {
globalStore.setLastGuild(null);
console.warn("Guild not found, redirecting to servers list.");
router.push('/servers');
} else {
globalStore.setLastGuild(guildId);
IORequest("/GUILD/JOIN", (response) => {
if(response === true) {
console.log("Successfully joined guild:", guildId);
} else {
console.error("Failed to join guild:", response);
globalStore.setLastGuild(null);
router.push('/servers');
}
}, guildId);
}
}

View File

@@ -1,5 +1,5 @@
<script setup>
import ReturnHomeButton from '@/components/Actions/ReturnHomeButton.vue';
import ReturnHomeButton from '@/components/Widget/ReturnHomeButton.vue';
import DefaultSplash from '@/components/Layout/DefaultSplash.vue';
import Box from '@/components/UI/Box.vue';
import Button from '@/components/UI/Button.vue';

View File

@@ -7,9 +7,10 @@ import { useRouter } from 'vue-router';
import { useGlobalStore } from '@/stores/globalStore';
import { useUserStore } from '@/stores/userStore';
import Button from '@/components/UI/Button.vue';
import ReturnHomeButton from '@/components/Actions/ReturnHomeButton.vue';
import ReturnHomeButton from '@/components/Widget/ReturnHomeButton.vue';
import { ref } from 'vue';
import ServerListItem from '@/components/Features/ServerListItem.vue';
import ServerListItem from '@/components/Widget/ServerListItem.vue';
import Info from '@/components/UI/Info.vue';
const globalStore = useGlobalStore();
const userStore = useUserStore();
@@ -36,6 +37,8 @@ function inviteSubsonics() {
window.open(botInviteUrl.value, '_blank');
}
// Vérifier RaphX pourquoi ca plante !
</script>
<template>
@@ -47,8 +50,8 @@ function inviteSubsonics() {
<div class="servers-content">
<div class="servers-container">
<div class="servers-list">
<ServerListItem v-for="server in userStore.userInfo.guilds" :key="server.id" :server="server" />
<ServerListItem v-if="userStore.userInfo.guilds.length > 0" v-for="server in userStore.userInfo.guilds" :key="server.id" :server="server" />
<Info class="center" v-else>Aucun de tes serveurs n'est référencé dans le bot. Invite le bot sur un serveur pour le voir ici.</Info>
</div>
</div>
@@ -60,6 +63,8 @@ function inviteSubsonics() {
</Splash>
</SocketEnvironment>
</template>
<style scoped>
.servers-div {
@@ -83,6 +88,12 @@ function inviteSubsonics() {
}
.center {
margin-left: auto;
margin-right: auto;
padding: 10px;;
}
.server-box {
width: 100%;
max-width: 800px;

View File

@@ -1,5 +1,5 @@
<script setup>
import ReturnHomeButton from '@/components/Actions/ReturnHomeButton.vue';
import ReturnHomeButton from '@/components/Widget/ReturnHomeButton.vue';
import DefaultSplash from '@/components/Layout/DefaultSplash.vue';
import Box from '@/components/UI/Box.vue';
import Button from '@/components/UI/Button.vue';

View File

@@ -1,10 +1,11 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import VitePluginVueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [vue(), VitePluginVueDevTools()],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src'),