Files
chopin-frontend/src/components/Widget/View/Search/PlaylistHeader.vue
2025-08-29 00:22:08 +02:00

448 lines
12 KiB
Vue

<template>
<div class="search-playlist-container">
<div class="search-playlist-header">
<img v-if="results.thumbnail" class="search-playlist-thumbnail" :src="results.thumbnail" alt="Playlist Thumbnail" />
<div v-else class="search-playlist-thumbnail"><div class="defaultIcon"><Icon icon="fa-music" /></div></div>
<div class="search-playlist-info">
<p class="search-playlist-title">{{ results.title }} <Icon @click="openPlaylistPage()" class="link-icon" icon="fa-solid fa-link" /></p>
<p class="search-playlist-stats"><Tag color="var(--text-warning)">{{ results.songs.length }} titres</Tag><Tag v-if="results.views" color="var(--text-success)">{{ results.views }} vues</Tag ><Tag v-if="results.songs.length > 0">Durée : {{ results.readduration }}</Tag></p>
<div @click="openAuthorPage()" class="search-playlist-author-info">
<img v-if="results.authorAvatar" :src="results.authorAvatar" alt="Author Thumbnail" class="search-playlist-author" />
<div v-else class="search-playlist-author-placeholder"></div>
<p v-if="results.author" class="search-playlist-author-name">{{ results.author }}</p>
<p v-else class="search-playlist-author-name">Aucun auteur trouvé</p>
</div>
</div>
</div>
<section :class="{'search-playlist-act-container': true, 'search-playlist-act-container-box': messagePlaylist}">
<div class="search-playlist-actions">
<AddList
icon-action
title="Ajouter à la liste de lecture"
@click="playPlaylist()"
v-if="globalStore.currentChannel"
/>
<IconAction
icon="fa-solid fa-play"
title="Lire maintenant la playlist"
@click="playPlaylist(true)"
v-if="globalStore.currentChannel"
/>
<IconAction v-if="!isPlaylist"
icon="fa-solid fa-save"
title="Sauvegarder la playlist"
@click="savePlaylist()"
/>
<IconAction v-if="isPlaylist"
icon="fa-solid fa-sliders"
title="Configurer la playlist"
@click="settingsModal.open()"/>
</div>
<Success class="playlist-message" v-if="messagePlaylist">{{ messagePlaylist }}</Success>
</section>
</div>
<Modal ref="settingsModal" title="Configuration de la playlist" icon="fa-solid fa-sliders">
<ModalTree title="Actions">
<div class="modal-actions">
<Button v-if="isPlaylist && results.type != 'youtube'"
icon="fa-solid fa-pen-to-square"
@click="renameModal.open(); renameInput = results.title"
>Renommer la playlist</Button>
<Button v-if="isPlaylist && results.type == 'youtube'"
@click="Events.emit('playlist:refresh', results); isRefreshing = true"
><YoutubeRefresh /> Mettre à jour la playlist</Button>
<Button
icon="fa-solid fa-trash"
@click="deleteModal.open()"
>Supprimer la playlist</Button>
</div>
<p class="info-loading" v-if="isRefreshing"><Icon icon="fa-solid fa-spinner" spin-pulse /> Mise à jour en cours ...</p>
</ModalTree>
<ModalTree icon="fa-solid fa-circle-info" title="Informations">
<div class="playlist-conf-info">
<p><Icon :icon="results.type == 'youtube' ? 'fa-brands fa-youtube' : 'fa-solid fa-music'" /> <span class="pconf-key">Titre:</span> <span class="pconf-value">{{ results.title }}</span></p>
<p><Icon icon="fa-solid fa-user" /> <span class="pconf-key">Auteur:</span> <span class="pconf-value">{{ results.author }}</span></p>
<p><Icon icon="fa-solid fa-clock" /> <span class="pconf-key">Durée:</span> <span class="pconf-value">{{ results.readduration ? results.readduration : 'Inconnue' }}</span></p>
<p><Icon icon="fa-solid fa-list" /> <span class="pconf-key">Nombre de titres:</span> <span class="pconf-value">{{ results.songs.length }}</span></p>
<p v-if="results.views"><Icon icon="fa-solid fa-eye" /> <span class="pconf-key">Vues:</span> <span class="pconf-value">{{ results.views }}</span></p>
<p v-if="results.url"><Icon icon="fa-solid fa-link" /> <span class="pconf-key">Lien:</span> <a :href="results.url" target="_blank">{{ results.url }}</a></p>
</div>
</ModalTree>
</Modal>
<Modal ref="renameModal" title="Renommer la playlist" icon="fa-solid fa-pen-to-square">
<div class="modal-rename">
<input type="text" v-model="renameInput" placeholder="Nouveau nom de la playlist" />
<Button :disabled="!renameInput.trim('')" @click="Events.emit('playlist:rename', { id: results.playlistId, newName: renameInput })">Renommer</Button>
</div>
</Modal>
<Modal ref="deleteModal" title="Supprimer une playlist" icon="fa-solid fa-trash">
<p>Êtes-vous sûr de vouloir supprimer cette playlist ?</p>
<p class="info-no">Cette action est irréversible et la playlist ne pourra pas être récupéré.</p>
<p class="info-name"><Icon :icon="results.type == 'youtube' ? 'fa-brands fa-youtube' : 'fa-solid fa-music'" /> {{ results.title }}</p>
<div class="info-actions">
<Button @click="deleteModal.close()">Annuler</Button>
<Button @click="Events.emit('playlist:delete', results); deleteModal.close()">Supprimer</Button>
</div>
</Modal>
</template>
<script setup>
import Tag from '@/components/UI/Tag.vue';
import IconAction from '@/components/UI/IconAction.vue';
import Events from '@/utils/Events';
import Modal from '@/components/UI/Modal.vue';
import Button from '@/components/UI/Button.vue';
import { onMounted, ref } from 'vue';
import ModalTree from '@/components/UI/ModalTree.vue';
import { IORequest } from '@/utils/IORequest';
import { useGlobalStore } from '@/stores/globalStore';
import AddList from '@/assets/Icons/AddList.vue';
import YoutubeRefresh from '@/assets/Icons/YoutubeRefresh.vue';
import Success from '@/components/UI/Success.vue';
const deleteModal = ref(null);
const renameModal = ref(null);
const renameInput = ref('');
const settingsModal = ref(null);
const isRefreshing = ref(false);
const globalStore = useGlobalStore();
const messagePlaylist = ref(null);
const props = defineProps({
results: {
type: Object,
required: true
},
isPlaylist: {
type: Boolean,
required: false
}
});
function openAuthorPage() {
if (!props.results.authorId) return;
window.open(props.results.authorId, '_blank');
}
function openPlaylistPage() {
if (!props.results.url) return;
window.open(props.results.url, '_blank');
}
function savePlaylist() {
if (!props.results.url) return;
IORequest("/PLAYLISTS/CREATE", (response) => {
if(response) {
Events.emit("playlist:open", response);
Events.emit("logic:init")
}
}, { name: 'default', url: props.results.url });
}
Events.on("playlist:hasRefresh", (playlist) => {
isRefreshing.value = false;
if(settingsModal.value) settingsModal.value.close();
});
function playPlaylist(now) {
IORequest("/PLAYLISTS/PLAY", (data) => {
}, { name: props.results.playlistId, now: now });
if(now) {
setMessage("Lecture de la Playlist");
} else {
setMessage("Playlist ajoutée");
}
}
function setMessage(message) {
messagePlaylist.value = message;
setTimeout(() => {
messagePlaylist.value = null;
}, 5000);
}
onMounted(() => {
isRefreshing.value = false;
})
</script>
<style scoped>
.info-loading {
color: var(--text-secondary);
display: flex;
gap: 5px;
font-size: 0.8em;
justify-content: center;
width: 100%;
}
.playlist-conf-info {
padding: 10px;
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
background-color: var(--tertiary);
border-radius: 10px;
}
.search-playlist-act-container {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
justify-content: center;
position: relative;
}
.playlist-message {
bottom: -25px;
font-size: 0.8em;
position: absolute;
}
.playlist-conf-info p {
display: flex;
align-items: center;
gap: 5px;
}
.playlist-conf-info svg {
width: 1em;
height: 1em;
}
.pconf-key {
font-weight: bold;
}
.pconf-value {
color: var(--text-secondary);
font-size: 0.9em;
}
.playlist-conf-info a {
color: var(--text-secondary);
font-size: 0.7em;
word-break: break-all;
}
.modal-actions {
display: flex;
align-items: center;
margin: 10px 0;
gap: 10px;
}
.modal-rename {
display: flex;
flex-direction: column;
gap: 10px;
}
@media screen and (max-width: 768px), screen and (max-height: 607px) {
.modal-actions {
flex-direction: column;
align-items: stretch;
}
}
.modal-actions Button {
flex: 1;
}
.info-no {
color: var(--text-secondary);
font-size: 0.8em
}
.info-name {
background-color: var(--tertiary);
padding: 5px 10px;
border-radius: 5px;
}
.info-actions {
display: flex;
justify-content: space-between;
flex: 1;
gap: 10px;
}
.info-actions Button {
width: 100%;
}
@media screen and (max-width: 768px),
screen and (max-height: 607px) {
.info-actions {
flex-direction: column;
align-items: flex-start;
}
}
.link-icon {
display: none;
font-size: 0.8em;
color: var(--text-secondary);
animation: fadeIn 0.2s ease;
cursor: pointer;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.search-playlist-title:hover .link-icon {
display: flex;
}
.defaultIcon {
border: 2px solid var(--text);
display: flex;
align-items: center;
justify-content: center;
flex: 1;
font-size: 4em;
border-radius: 10px;
}
.search-playlist-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.search-playlist-act-container-box {
margin-bottom: 25px;
}
.search-playlist-actions {
display: flex;
align-items: center;
gap: 15px;
font-size: 1.2em;
gap: 30px;
background-color: var(--tertiary);
padding: 8px 15px;;
border-radius: 30px;
}
.search-playlist-author {
width: 30px;
border-radius: 100%;
aspect-ratio: 1/1;
object-fit: cover;
}
.search-playlist-author-info {
display: flex;
align-items: center;
color: var(--text-secondary);
gap: 5px;
cursor: pointer;
}
.search-playlist-author-name {
font-size: 0.8em;
}
.search-playlist-thumbnail {
aspect-ratio: 1/1;
width: 150px;
border-radius: 10px;
object-fit: cover;
display: flex;
}
.search-playlist-header {
display: flex;
align-items: center;
width: 100%;
gap: 10px;
}
.search-playlist-title {
display: flex;
align-items: center;
gap: 5px;
font-weight: bold;
font-size: 1.2em;
}
@media screen and (max-width: 1020px), screen and (max-height: 607px) {
.search-playlist-title {
font-size: 1em;
}
.search-playlist-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.search-playlist-actions {
display: flex;
justify-content: center;
align-items: center;
}
.search-playlist-title:hover .link-icon {
display: none;
}
}
.search-playlist-info {
display: flex;
flex-direction: column;
gap: 10px;
}
.search-playlist-stats {
display: flex;
gap: 5px;
}
.search-playlist-duration {
display: flex;
align-items: center;
}
.search-playlist-author-placeholder {
width: 30px;
border-radius: 100%;
aspect-ratio: 1/1;
background-color: var(--primary);
object-fit: cover;
}
p {
margin: 0;
}
@media screen and (max-width: 768px), screen and (max-height: 607px) {
.search-playlist-stats {
flex-direction: column;
align-self: start;
}
}
</style>