448 lines
12 KiB
Vue
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> |