Version 1.1.0 - Modification et ajout de fonctionnalités
All checks were successful
Frontend Deployment / deploy-frontend (push) Successful in 34s

This commit is contained in:
2025-09-06 15:47:05 +02:00
parent febe1f90a2
commit b2a95c0241
9 changed files with 163 additions and 68 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "chopin-frontend", "name": "chopin-frontend",
"version": "1.0.3", "version": "1.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --host --port 8080", "dev": "vite --host --port 8080",

View File

@@ -54,6 +54,7 @@ import GuildHeaderUsers from '../Widget/Guild/GuildHeaderUsers.vue';
import GuildSettings from '../Widget/Guild/GuildSettings.vue'; import GuildSettings from '../Widget/Guild/GuildSettings.vue';
import { useUserStore } from '@/stores/userStore'; import { useUserStore } from '@/stores/userStore';
import { useGlobalStore } from '@/stores/globalStore'; import { useGlobalStore } from '@/stores/globalStore';
import Events from '@/utils/Events';
const userStore = useUserStore(); const userStore = useUserStore();
@@ -109,8 +110,13 @@ function updateServerInfo() {
console.log("Server info updated"); console.log("Server info updated");
events.emit("GUILD_JOINED", data); events.emit("GUILD_JOINED", data);
}) })
} }
Events.on("GUILD_LIST_UPDATE", (guilds) => {
server.value = guilds.find(g => g.id === globalStore.lastGuild);
});
</script> </script>
<style scoped> <style scoped>

View File

@@ -151,7 +151,6 @@ function onDragEnd(event) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
padding-right: 5px;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
@@ -192,6 +191,7 @@ function onDragEnd(event) {
gap: 10px; gap: 10px;
margin-top: 2.5px; margin-top: 2.5px;
margin-bottom: 2.5px; margin-bottom: 2.5px;
padding-right: 5px;
} }
.allSpace { .allSpace {
flex: 1; flex: 1;

View File

@@ -1,8 +1,8 @@
<template> <template>
<Video :video="video" ref="videoContainer"> <Video :video="video" ref="videoContainer">
<div ref="controls" class="controls"> <div ref="controls" class="controls">
<span v-if="globalStore.currentChannel" title="Ajouter à la liste de lecture" @click="disableAction(); playSong(false)" class="control-icon"><AddList /></span> <span title="Ajouter à la liste de lecture" @click="disableAction(); playSong(false)" :class="{'control-icon': globalStore.currentChannel, 'control-icon-disable': !globalStore.currentChannel}"><AddList /></span>
<span v-if="globalStore.currentChannel" title="Lire maintenant" @click="disableAction(); playSong(true)" class="control-icon"><Icon icon="fa-play" /></span> <span title="Lire maintenant" @click="disableAction(); playSong(true)" :class="{'control-icon': globalStore.currentChannel, 'control-icon-disable': !globalStore.currentChannel}"><Icon icon="fa-play" /></span>
<span v-if="!props.delete && video.type != 'attachment'" title="Enregistrer dans une playlist" @click="disableAction(); saveModal.open()" class="control-icon"><Icon icon="fa-save" /></span> <span v-if="!props.delete && video.type != 'attachment'" title="Enregistrer dans une playlist" @click="disableAction(); saveModal.open()" class="control-icon"><Icon icon="fa-save" /></span>
<span v-if="props.delete" title="Supprimer" @click="disableAction(); Events.emit('video:delete', { video: props.video })" class="control-icon"><Icon icon="fa-trash" /></span> <span v-if="props.delete" title="Supprimer" @click="disableAction(); Events.emit('video:delete', { video: props.video })" class="control-icon"><Icon icon="fa-trash" /></span>
</div> </div>
@@ -11,7 +11,7 @@
<Video :video="video"/> <Video :video="video"/>
<Button v-if="globalStore.currentChannel" @click="playSong(false)"><AddList /> Ajouter à la liste de lecture</Button> <Button v-if="globalStore.currentChannel" @click="playSong(false)"><AddList /> Ajouter à la liste de lecture</Button>
<Button v-if="globalStore.currentChannel" @click="playSong(true)"><Icon icon="fa-solid fa-play"/> Lire maintenant</Button> <Button v-if="globalStore.currentChannel" @click="playSong(true)"><Icon icon="fa-solid fa-play"/> Lire maintenant</Button>
<div v-else> <div v-if="!globalStore.currentChannel" >
<p class="text-secondary">Connectez vous à un salon audio sur le serveur {{ globalStore.actualServer ? globalStore.actualServer.name : '' }}, pour lancer un titre</p> <p class="text-secondary">Connectez vous à un salon audio sur le serveur {{ globalStore.actualServer ? globalStore.actualServer.name : '' }}, pour lancer un titre</p>
<ActualChannel/> <ActualChannel/>
</div> </div>
@@ -64,6 +64,7 @@ const props = defineProps({
let nativeVideo = {} let nativeVideo = {}
function disableAction() { function disableAction() {
if(!globalStore.currentChannel) return;
controls.value.style.display = "none"; controls.value.style.display = "none";
} }
@@ -74,7 +75,7 @@ let activePointerId = null;
const SLIDE_THRESHOLD = 10; // ajuster si besoin const SLIDE_THRESHOLD = 10; // ajuster si besoin
function playSong(now) { function playSong(now) {
if(!globalStore.currentChannel) return;
IORequest("/SEARCH/PLAY", (data) => { IORequest("/SEARCH/PLAY", (data) => {
modal.value.close(); modal.value.close();
}, {song: nativeVideo, now: now}) }, {song: nativeVideo, now: now})
@@ -118,9 +119,12 @@ onMounted(() => {
if (activePointerId != null) videoContainer.value.releasePointerCapture(activePointerId); if (activePointerId != null) videoContainer.value.releasePointerCapture(activePointerId);
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
if(!videoContainer.value) return if(!videoContainer.value) return
try {
videoContainer.value.removeEventListener('pointermove', onPointerMove); videoContainer.value.removeEventListener('pointermove', onPointerMove);
videoContainer.value.removeEventListener('pointerup', onPointerUp); videoContainer.value.removeEventListener('pointerup', onPointerUp);
videoContainer.value.removeEventListener('pointercancel', onPointerCancel); videoContainer.value.removeEventListener('pointercancel', onPointerCancel);
} catch (e) { console.log(videoContainer.value) }
activePointerId = null; activePointerId = null;
}; };
@@ -149,10 +153,16 @@ onMounted(() => {
isSliding = false; isSliding = false;
activePointerId = ev.pointerId; activePointerId = ev.pointerId;
if(!videoContainer.value) return if(!videoContainer.value) return
try {
thumbnailContainer.value = videoContainer.value.getThumbnailContainer();
videoContainer.value = videoContainer.value.getVideoContainer();
} catch (e) { }
try { videoContainer.value.setPointerCapture(activePointerId); } catch (e) { /* ignore */ } try { videoContainer.value.setPointerCapture(activePointerId); } catch (e) { /* ignore */ }
videoContainer.value.addEventListener('pointermove', onPointerMove); try {
videoContainer.value.addEventListener('pointerup', onPointerUp); videoContainer.value.addEventListener('pointermove', onPointerMove);
videoContainer.value.addEventListener('pointercancel', onPointerCancel); videoContainer.value.addEventListener('pointerup', onPointerUp);
videoContainer.value.addEventListener('pointercancel', onPointerCancel);
} catch (e) { console.log(videoContainer.value) }
}; };
videoContainer.value.addEventListener('pointerdown', onPointerDown); videoContainer.value.addEventListener('pointerdown', onPointerDown);
@@ -218,6 +228,20 @@ onMounted(() => {
opacity: 0.8; opacity: 0.8;
} }
.control-icon-disable {
font-size: 1.2em;
background-color: var(--text-inverse);
color: var(--secondary);
border-radius: 100%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.8;
}
.video:hover .controls { .video:hover .controls {
opacity: 1; opacity: 1;

View File

@@ -1,10 +1,6 @@
<template> <template>
<div class="default"> <div class="default">
<!-- <Carousel v-show="!loading" class="child"> -->
<Changelog/> <Changelog/>
<!-- <History/>
<Advice/>
</Carousel> -->
<p v-show="loading" class="loading"> <p v-show="loading" class="loading">
<Icon icon="fa-spinner fa-solid" spin-pulse/> Chargement en cours <Icon icon="fa-spinner fa-solid" spin-pulse/> Chargement en cours
</p> </p>
@@ -47,18 +43,13 @@ p {
} }
.default { .default {
display: grid; display: flex;
grid-template-rows: 1fr;
grid-template-columns: 1fr;
gap: 5px; gap: 5px;
flex: 1; flex: 1;
} }
@media screen and (max-width: 768px), @media screen and (max-width: 768px),
screen and (max-height: 607px) { screen and (max-height: 607px) {
.default {
grid-template-rows: 1fr;
}
} }
.child { .child {

View File

@@ -2,7 +2,7 @@
<section class="changelog"> <section class="changelog">
<Welcome/> <Welcome/>
<h2 class="changelog-title">📝 Changelog</h2> <h2 class="changelog-title">📝 Changelog</h2>
<div class="changelog-overflow" v-if="changelog" > <div v-if="changelog" class="changelog-overflow">
<div class="changelog-container" v-html="changelog" ></div> <div class="changelog-container" v-html="changelog" ></div>
</div> </div>
<div class="textSecond" v-else-if="!error"><Icon icon="fa-spinner fa-solid" spin-pulse/> Chargement en cours</div> <div class="textSecond" v-else-if="!error"><Icon icon="fa-spinner fa-solid" spin-pulse/> Chargement en cours</div>
@@ -39,6 +39,8 @@ function loadChangelog() {
data = data.replaceAll("-*", "</span>"); data = data.replaceAll("-*", "</span>");
data = data.replaceAll("*_", "<span class='underline'>"); data = data.replaceAll("*_", "<span class='underline'>");
data = data.replaceAll("_*", "</span>"); data = data.replaceAll("_*", "</span>");
data = data.replaceAll('/#', "<span class='changelog-tags'>");
data = data.replaceAll('#/', "</span>");
changelog.value = data; changelog.value = data;
} else { } else {
@@ -102,12 +104,13 @@ function loadChangelog() {
} }
.changelog-container { .changelog-container {
display: grid; display: flex;
grid-template-columns: 1fr; flex-direction: column;
gap: 15px;
overflow-y: auto;
position: absolute;
flex: 1;
width: 100%; width: 100%;
grid-template-rows: auto;
gap: 20px;
} }
.changelog { .changelog {
@@ -119,7 +122,10 @@ function loadChangelog() {
.changelog-overflow { .changelog-overflow {
overflow-y: auto; overflow-y: auto;
position: relative;
max-height: 100%; max-height: 100%;
flex: 1;
} }
.tags { .tags {
@@ -225,20 +231,57 @@ function loadChangelog() {
white-space: nowrap; /* Prevent line breaks */ white-space: nowrap; /* Prevent line breaks */
} }
@media screen and (max-width: 768px), .changelog-overflow {
screen and (max-height: 607px) { position: relative;
.changelog-container { }
grid-template-columns: 1fr !important;
}
.changelog-container ul { .changelog-container ul {
padding: 0
display: flex;
flex-direction: column;
gap: 10px;
} }
.changelog-tags {
display: flex;
flex-direction: row;
gap: 5px;
background-color: var(--quaternary);
padding: 5px;
border-radius: 10px;
}
@media screen and (max-width: 768px),
screen and (max-height: 607px) {
.changelog-container ul {
padding: 0;
}
.changelog-version ul li::before { .changelog-version ul li::before {
display: none; display: none;
} }
.welcome-container {
flex-direction: column;
align-items: center;
gap: 10px;
width: 100%;
}
.welcome-actions {
flex-direction: column;
width: 100%;
}
.changelog-tags {
flex-direction: column;
}
} }
</style> </style>

View File

@@ -126,6 +126,7 @@ const props = defineProps({
function openAuthorPage() { function openAuthorPage() {
if (!props.results.authorId) return; if (!props.results.authorId) return;
if(!props.results.author) return;
window.open(props.results.authorId, '_blank'); window.open(props.results.authorId, '_blank');
} }

View File

@@ -4,7 +4,7 @@
<h2><Icon icon="fa-folder"/> Mes fichiers</h2> <h2><Icon icon="fa-folder"/> Mes fichiers</h2>
<Box box-class="area-container" padding="close" level="second"> <Box box-class="area-container" padding="close" level="second">
<div class="upload-area"> <div class="upload-area">
<Button :color-lower="status === 'download'" :disabled="status === 'download'" class="upload-button" @click="uploadFile()"><Icon icon="fa-upload"/> Ajouter un fichier</Button> <Button :color-lower="status === 'download'" :disabled="status === 'download'" class="upload-button" @click="uploadFile()"><Icon icon="fa-upload"/> Ajouter des fichiers</Button>
<p v-if="status === 'download'" class="upload-file-name" ref="uploadStatus"><Icon icon='fa-spinner' spin-pulse/> Téléchargement en cours...</p> <p v-if="status === 'download'" class="upload-file-name" ref="uploadStatus"><Icon icon='fa-spinner' spin-pulse/> Téléchargement en cours...</p>
<p v-else-if="status === 'error'" class="upload-status" ref="uploadStatus"><Error>Erreur lors du téléchargement</Error></p> <p v-else-if="status === 'error'" class="upload-status" ref="uploadStatus"><Error>Erreur lors du téléchargement</Error></p>
<p v-else-if="status === 'toohigh'" class="upload-status" ref="uploadStatus"><Error>Le fichier est trop volumineux</Error></p> <p v-else-if="status === 'toohigh'" class="upload-status" ref="uploadStatus"><Error>Le fichier est trop volumineux</Error></p>
@@ -14,15 +14,17 @@
<p class="text-secondary infosup"><Icon icon="fa-circle-info"/> Ce système n'est pas un stockage permanent de données car il dépend du CDN de Discord. Vos fichiers peuvent exprirer à tout moment.</p> <p class="text-secondary infosup"><Icon icon="fa-circle-info"/> Ce système n'est pas un stockage permanent de données car il dépend du CDN de Discord. Vos fichiers peuvent exprirer à tout moment.</p>
</Box> </Box>
</div> </div>
<div v-if="myFiles && myFiles.length > 0" class="uploaded-files"> <div v-if="myFiles && myFiles.length > 0" class="uploaded-files-container">
<div class="uploaded-files">
<span v-for="file in myFiles" :key="file.id"><VideoComposable :video="file" delete/></span> <span v-for="file in myFiles" :key="file.id"><VideoComposable :video="file" delete/></span>
</div> </div>
</div>
<p v-else-if="isLoading" class="none"><Icon icon="fa-spinner" spin-pulse/> Chargement des fichiers...</p> <p v-else-if="isLoading" class="none"><Icon icon="fa-spinner" spin-pulse/> Chargement des fichiers...</p>
<p v-else class="none"><Icon icon="fa-circle-xmark"/> Aucun fichier enregistré</p> <p v-else class="none"><Icon icon="fa-circle-xmark"/> Aucun fichier enregistré</p>
<Modal icon="fa-upload" title="Uploader un fichier" ref="uploadModal"> <Modal icon="fa-upload" title="Uploader un fichier" ref="uploadModal">
<p>Etes-vous sûr de vouloir uploader ce fichier ?</p> <p>Etes-vous sûr de vouloir uploader ces fichiers ?</p>
<p class="text-secondary">Ce fichier sera stocké sur le CDN de Discord et sera à jamais accessible. Ne diffusez rien de sensible.</p> <p class="text-secondary">Ces fichiers seront stockés sur le CDN de Discord et seront à jamais accessibles. Ne diffusez rien de sensible.</p>
<p v-if="fileSelected" class="upload-modal-name"><Icon icon="fa-file"/> {{ fileSelected.name }}</p> <p v-if="fileSelected.length > 0" v-for="value in fileSelected" class="upload-modal-name"><Icon icon="fa-file"/> {{ value.name }}</p>
<div class="upload-actions"> <div class="upload-actions">
<Button @click="closeModal()">Annuler</Button> <Button @click="closeModal()">Annuler</Button>
<Button @click="confirmUpload()">Confirmer</Button> <Button @click="confirmUpload()">Confirmer</Button>
@@ -41,7 +43,7 @@ import { IORequest } from '@/utils/IORequest';
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import Events from '@/utils/Events'; import Events from '@/utils/Events';
const fileSelected = ref(null); const fileSelected = ref([]);
const status = ref(false); const status = ref(false);
const uploadModal = ref(null); const uploadModal = ref(null);
const myFiles = ref([]); const myFiles = ref([]);
@@ -66,53 +68,68 @@ function uploadFile() {
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.accept = '.mp3,.wav,.ogg'; // Accept audio files input.accept = '.mp3,.wav,.ogg'; // Accept audio files
input.multiple = true;
input.onchange = (event) => { input.onchange = (event) => {
const file = event.target.files[0]; const files = Array.from(event.target.files);
if (file) { if (files.length > 0) {
fileSelected.value = file; fileSelected.value = files;
// Here you would typically handle the file upload to the server console.log(`Files selected:`, files.map(f => f.name));
console.log(`File selected: ${file.name}`);
// Reset the input for future uploads
input.value = ''; input.value = '';
// destroy input
input.remove(); input.remove();
uploadModal.value.open(); uploadModal.value.open();
} else { } else {
fileSelected.value = null; fileSelected.value = [];
} }
}; };
input.click(); input.click();
} }
function confirmUpload() { async function confirmUpload() {
console.log(`Uploading file: ${fileSelected.value.name}`); if (fileSelected.value.length === 0) return;
status.value = 'download'; status.value = 'download';
uploadModal.value.close(); uploadModal.value.close();
if(fileSelected.value) {
// Send the file to the server let errorOccurred = false;
const reader = new FileReader();
reader.onload = () => { for (const file of fileSelected.value) {
const fileBuffer = reader.result; try {
// If it's higher than 300mb const fileBuffer = await readFileAsArrayBuffer(file);
if (fileBuffer.byteLength > 300 * 1024 * 1024) { if (fileBuffer.byteLength > 300 * 1024 * 1024) {
status.value = 'toohigh'; status.value = 'toohigh';
console.error('File is too large'); errorOccurred = true;
return; continue;
} }
IORequest('/UPLOAD/FILE', (response) => {
if(!response) { await new Promise(resolve => {
status.value = 'error'; IORequest('/UPLOAD/FILE', (response) => {
} else if(response === "TOOHIGH") { if (!response || response === "TOOHIGH") {
status.value = 'toohigh'; errorOccurred = true;
} else { status.value = (response === "TOOHIGH") ? 'toohigh' : 'error';
status.value = 'success'; }
} resolve();
refreshUploadedFiles(); }, { name: file.name, file: fileBuffer });
}, {name: fileSelected.value.name, file: fileBuffer}) });
fileSelected.value = null;
}; } catch (err) {
reader.readAsArrayBuffer(fileSelected.value); console.error("Erreur upload fichier:", file.name, err);
errorOccurred = true;
}
} }
status.value = errorOccurred ? status.value : 'success';
refreshUploadedFiles();
fileSelected.value = [];
}
// Petite fonction utilitaire pour transformer FileReader en promesse
function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
} }
@@ -149,6 +166,17 @@ function refreshUploadedFiles() {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px; gap: 10px;
position: absolute;
width: 100%;
}
.uploaded-files-container {
position: relative;
flex: 1;
display: flex;
width: 100%;
height: 100%;
overflow-y: auto;
} }
.none { .none {

View File

@@ -12,6 +12,7 @@ import { IOListener, IORequest } from '@/utils/IORequest';
import { useUserStore } from '@/stores/userStore'; import { useUserStore } from '@/stores/userStore';
import { useGlobalStore } from '@/stores/globalStore'; import { useGlobalStore } from '@/stores/globalStore';
import events from '@/utils/Events.js'; import events from '@/utils/Events.js';
import Events from '@/utils/Events.js';
const router = useRouter(); const router = useRouter();
const interuptionMessage = ref(null); const interuptionMessage = ref(null);
@@ -43,6 +44,7 @@ import events from '@/utils/Events.js';
IORequest("/GUILD/LIST", (response) => { IORequest("/GUILD/LIST", (response) => {
if(response) { if(response) {
userStore.userInfo.guilds = response; userStore.userInfo.guilds = response;
Events.emit("GUILD_LIST_UPDATE", response);
} }
}) })
}) })