evilSpins v1
This commit is contained in:
102
app/store/card.ts
Normal file
102
app/store/card.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Track } from '~/../types/types'
|
||||
|
||||
interface CardPosition {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
type CardPositions = Record<string, Record<number, CardPosition>>
|
||||
|
||||
export const useCardStore = defineStore('card', {
|
||||
state: () => ({
|
||||
// Stocke les IDs des cartes déjà révélées
|
||||
revealedCards: new Set<number>(),
|
||||
// Stocke les positions personnalisées des cartes par box
|
||||
// Format: { [boxId]: { [trackId]: { x: number, y: number } } }
|
||||
cardPositions: {} as CardPositions
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// Marquer une carte comme révélée
|
||||
revealCard(trackId: number) {
|
||||
this.revealedCards.add(trackId)
|
||||
this.saveToLocalStorage()
|
||||
},
|
||||
|
||||
// Vérifier si une carte est révélée
|
||||
isCardRevealed(trackId: number): boolean {
|
||||
return this.revealedCards.has(trackId)
|
||||
},
|
||||
|
||||
// Définir la position d'une carte dans une box
|
||||
setCardPosition(boxId: string, trackId: number, position: { x: number; y: number }) {
|
||||
if (!this.cardPositions[boxId]) {
|
||||
this.cardPositions[boxId] = {}
|
||||
}
|
||||
this.cardPositions[boxId][trackId] = position
|
||||
this.saveToLocalStorage()
|
||||
},
|
||||
|
||||
// Obtenir la position d'une carte dans une box
|
||||
getCardPosition(boxId: string, trackId: number): { x: number; y: number } | undefined {
|
||||
return this.cardPositions[boxId]?.[trackId]
|
||||
},
|
||||
|
||||
// Sauvegarder l'état dans le localStorage
|
||||
saveToLocalStorage() {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
'cardStore',
|
||||
JSON.stringify({
|
||||
revealedCards: Array.from(this.revealedCards),
|
||||
cardPositions: this.cardPositions
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('Failed to save card store to localStorage', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Charger l'état depuis le localStorage
|
||||
loadFromLocalStorage() {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const saved = localStorage.getItem('cardStore')
|
||||
if (saved) {
|
||||
const { revealedCards, cardPositions } = JSON.parse(saved)
|
||||
if (Array.isArray(revealedCards)) {
|
||||
this.revealedCards = new Set(
|
||||
revealedCards.filter((id): id is number => typeof id === 'number')
|
||||
)
|
||||
}
|
||||
if (cardPositions && typeof cardPositions === 'object') {
|
||||
this.cardPositions = cardPositions
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load card store from localStorage', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Initialiser le store
|
||||
initialize() {
|
||||
this.loadFromLocalStorage()
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
// Getter pour la réactivité dans les templates
|
||||
isRevealed: (state) => (trackId: number) => {
|
||||
return state.revealedCards.has(trackId)
|
||||
},
|
||||
|
||||
// Obtenir toutes les positions des cartes d'une box
|
||||
getBoxCardPositions: (state) => (boxId: string) => {
|
||||
return state.cardPositions[boxId] || {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -37,7 +37,7 @@ export const useDataStore = defineStore('data', {
|
||||
artistObj = { id: 0, name: a, url: '', coverId: '' }
|
||||
} else if (a && typeof a === 'object' && 'id' in (a as any)) {
|
||||
const idVal = (a as any).id as number | undefined
|
||||
artistObj = idVal != null ? artistMap.get(idVal) ?? (a as Artist) : (a as Artist)
|
||||
artistObj = idVal != null ? (artistMap.get(idVal) ?? (a as Artist)) : (a as Artist)
|
||||
} else {
|
||||
artistObj = { id: 0, name: '', url: '', coverId: '' }
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export const useDataStore = defineStore('data', {
|
||||
state: 'box-list'
|
||||
}
|
||||
if (!this.boxes.find((b) => b.id === FAVORITES_BOX_ID)) {
|
||||
this.boxes = [favBox, ...this.boxes]
|
||||
this.boxes = [...this.boxes, favBox]
|
||||
}
|
||||
this.isLoaded = true
|
||||
} finally {
|
||||
|
||||
@@ -1,52 +1,144 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Track } from '~/../types/types'
|
||||
import type { Track, Box, BoxType, Artist } from '~/../types/types'
|
||||
|
||||
export const FAVORITES_BOX_ID = 'FAV'
|
||||
const STORAGE_KEY = 'evilspins:favorites:v1'
|
||||
const STORAGE_KEY = 'evilspins:favorites:v2' // Version changée pour forcer la mise à jour
|
||||
|
||||
export const useFavoritesStore = defineStore('favorites', {
|
||||
state: () => ({
|
||||
trackIds: [] as number[]
|
||||
favoritesPlaylist: {
|
||||
id: FAVORITES_BOX_ID,
|
||||
type: 'userPlaylist' as BoxType,
|
||||
name: 'Favoris',
|
||||
description: 'Vos titres favoris',
|
||||
color1: '#FFD700',
|
||||
color2: '#FFA500',
|
||||
color3: '#FF8C00',
|
||||
duration: 0,
|
||||
state: 'box-hidden',
|
||||
tracks: [] as Track[],
|
||||
isPublic: false
|
||||
} as Box,
|
||||
version: 1
|
||||
}),
|
||||
getters: {
|
||||
trackIds: (state) => state.favoritesPlaylist.tracks?.map((t) => t.id) || [],
|
||||
tracks: (state) => state.favoritesPlaylist.tracks || [],
|
||||
isFavorite: (state) => (trackId: number) =>
|
||||
state.favoritesPlaylist.tracks?.some((t) => t.id === trackId) || false
|
||||
},
|
||||
|
||||
actions: {
|
||||
load() {
|
||||
if (!process.client) return
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
const arr = JSON.parse(raw)
|
||||
if (Array.isArray(arr)) this.trackIds = arr.filter((x) => typeof x === 'number')
|
||||
const data = JSON.parse(raw)
|
||||
if (data.version === this.version && data.playlist) {
|
||||
this.favoritesPlaylist = { ...this.favoritesPlaylist, ...data.playlist }
|
||||
} else if (Array.isArray(data)) {
|
||||
// Migration depuis l'ancienne version
|
||||
this.favoritesPlaylist.tracks = data
|
||||
.filter((x) => typeof x === 'number')
|
||||
.map((id) => ({ id }) as unknown as Track) // On ne stocke que l'ID, les infos seront complétées par le dataStore
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch (error) {
|
||||
console.error('Error loading favorites:', error)
|
||||
}
|
||||
},
|
||||
|
||||
save() {
|
||||
if (!process.client) return
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.trackIds))
|
||||
} catch {}
|
||||
localStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
version: this.version,
|
||||
playlist: this.favoritesPlaylist
|
||||
})
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error saving favorites:', error)
|
||||
}
|
||||
},
|
||||
toggle(track: Track) {
|
||||
const id = track.id
|
||||
const idx = this.trackIds.indexOf(id)
|
||||
if (idx >= 0) this.trackIds.splice(idx, 1)
|
||||
else this.trackIds.unshift(id)
|
||||
this.save()
|
||||
|
||||
// Crée une copie virtuelle d'une piste pour les favoris
|
||||
createVirtualTrack(originalTrack: Track): Track {
|
||||
// Crée une copie profonde de la piste
|
||||
const virtualTrack = JSON.parse(JSON.stringify(originalTrack)) as Track
|
||||
|
||||
// Marque la piste comme provenant d'une playlist utilisateur
|
||||
virtualTrack.source = 'userPlaylist'
|
||||
virtualTrack.originalTrackId = originalTrack.id
|
||||
virtualTrack.boxId = FAVORITES_BOX_ID
|
||||
|
||||
// Si la piste a un artiste sous forme d'objet, on le convertit en chaîne pour éviter les problèmes de référence
|
||||
if (virtualTrack.artist && typeof virtualTrack.artist === 'object') {
|
||||
virtualTrack.artist = (virtualTrack.artist as Artist).name
|
||||
}
|
||||
|
||||
return virtualTrack
|
||||
},
|
||||
|
||||
// Ajoute une piste aux favoris si elle n'y est pas déjà
|
||||
add(track: Track) {
|
||||
if (!this.trackIds.includes(track.id)) {
|
||||
this.trackIds.unshift(track.id)
|
||||
if (!this.isFavorite(track.id)) {
|
||||
// Crée une copie virtuelle de la piste
|
||||
const virtualTrack = this.createVirtualTrack(track)
|
||||
|
||||
if (!this.favoritesPlaylist.tracks) {
|
||||
this.favoritesPlaylist.tracks = []
|
||||
}
|
||||
|
||||
// Ajoute la piste virtuelle au début de la liste
|
||||
this.favoritesPlaylist.tracks.unshift(virtualTrack)
|
||||
this.updateDuration()
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
|
||||
// Supprime une piste des favoris
|
||||
remove(trackId: number) {
|
||||
const idx = this.trackIds.indexOf(trackId)
|
||||
if (idx >= 0) {
|
||||
this.trackIds.splice(idx, 1)
|
||||
this.save()
|
||||
if (this.favoritesPlaylist.tracks) {
|
||||
const index = this.favoritesPlaylist.tracks.findIndex((t) => t.id === trackId)
|
||||
if (index >= 0) {
|
||||
this.favoritesPlaylist.tracks.splice(index, 1)
|
||||
this.updateDuration()
|
||||
this.save()
|
||||
}
|
||||
}
|
||||
},
|
||||
isFavorite(trackId: number) {
|
||||
return this.trackIds.includes(trackId)
|
||||
|
||||
// Bascule l'état favori d'une piste
|
||||
toggle(track: Track) {
|
||||
if (this.isFavorite(track.id)) {
|
||||
this.remove(track.id)
|
||||
} else {
|
||||
this.add(track)
|
||||
}
|
||||
},
|
||||
|
||||
// Met à jour la durée totale de la playlist
|
||||
updateDuration() {
|
||||
if (this.favoritesPlaylist.tracks) {
|
||||
this.favoritesPlaylist.duration = this.favoritesPlaylist.tracks.reduce(
|
||||
(total, track) => total + (track.duration || 0),
|
||||
0
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
// Récupère la piste suivante dans les favoris
|
||||
getNextTrack(currentTrackId: number): Track | null {
|
||||
if (!this.favoritesPlaylist.tracks) return null
|
||||
const currentIndex = this.favoritesPlaylist.tracks.findIndex((t) => t.id === currentTrackId)
|
||||
if (currentIndex >= 0 && currentIndex < this.favoritesPlaylist.tracks.length - 1) {
|
||||
const nextTrack = this.favoritesPlaylist.tracks[currentIndex + 1]
|
||||
return nextTrack ? { ...nextTrack } : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Track, Box } from '~/../types/types'
|
||||
import { useDataStore } from '~/store/data'
|
||||
import { useCardStore } from '~/store/card'
|
||||
import { useFavoritesStore, FAVORITES_BOX_ID } from '~/store/favorites'
|
||||
|
||||
export const usePlayerStore = defineStore('player', {
|
||||
state: () => ({
|
||||
currentTrack: null as Track | null,
|
||||
position: 0,
|
||||
audio: null as HTMLAudioElement | null,
|
||||
isPaused: true,
|
||||
progressionLast: 0
|
||||
progressionLast: 0,
|
||||
isPlaying: false,
|
||||
isLoading: false
|
||||
}),
|
||||
|
||||
actions: {
|
||||
@@ -17,109 +20,209 @@ export const usePlayerStore = defineStore('player', {
|
||||
this.audio = el
|
||||
// attach listeners if not already attached (idempotent enough for our use)
|
||||
this.audio.addEventListener('play', () => {
|
||||
this.isPaused = false
|
||||
})
|
||||
this.audio.addEventListener('playing', () => {
|
||||
this.isPaused = false
|
||||
this.isPlaying = true
|
||||
// Révéler la carte quand la lecture commence
|
||||
if (this.currentTrack) {
|
||||
const cardStore = useCardStore()
|
||||
if (!cardStore.isCardRevealed(this.currentTrack.id)) {
|
||||
requestAnimationFrame(() => {
|
||||
cardStore.revealCard(this.currentTrack!.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.audio.addEventListener('playing', () => {})
|
||||
this.audio.addEventListener('pause', () => {
|
||||
this.isPaused = true
|
||||
this.isPlaying = false
|
||||
})
|
||||
this.audio.addEventListener('ended', () => {
|
||||
this.isPaused = true
|
||||
this.audio.addEventListener('ended', async () => {
|
||||
const track = this.currentTrack
|
||||
if (!track) return
|
||||
|
||||
const dataStore = useDataStore()
|
||||
if (track.type === 'playlist') {
|
||||
const favoritesStore = useFavoritesStore()
|
||||
|
||||
// Vérifier si on est dans la playlist des favoris
|
||||
if (track.boxId === FAVORITES_BOX_ID) {
|
||||
const nextTrack = favoritesStore.getNextTrack(track.id)
|
||||
if (nextTrack) {
|
||||
await this.playTrack(nextTrack)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Comportement par défaut pour les playlists standards
|
||||
else if (track.type === 'playlist') {
|
||||
const next = dataStore.getNextPlaylistTrack(track)
|
||||
if (next && next.boxId === track.boxId) {
|
||||
this.playTrack(next)
|
||||
await this.playTrack(next)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
console.log('ended')
|
||||
this.currentTrack = null
|
||||
}
|
||||
|
||||
// Si on arrive ici, c'est qu'il n'y a pas de piste suivante
|
||||
this.currentTrack = null
|
||||
this.isPlaying = false
|
||||
})
|
||||
},
|
||||
|
||||
async playBox(box: Box) {
|
||||
// Si c'est la même box, on toggle simplement la lecture
|
||||
if (this.currentTrack?.boxId === box.id) {
|
||||
this.togglePlay()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
// Sinon, on charge la première piste de la box
|
||||
try {
|
||||
const dataStore = useDataStore()
|
||||
const first = dataStore.getFirstTrackOfBox(box)
|
||||
if (first) {
|
||||
await this.playTrack(first)
|
||||
const firstTrack = dataStore.getFirstTrackOfBox(box)
|
||||
if (firstTrack) {
|
||||
await this.playTrack(firstTrack)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error playing box:', error)
|
||||
}
|
||||
},
|
||||
|
||||
async playTrack(track: Track) {
|
||||
// mettre à jour la piste courante uniquement après avoir géré le toggle
|
||||
if (this.currentTrack && this.currentTrack?.id === track.id) {
|
||||
this.togglePlay()
|
||||
} else {
|
||||
// Si c'est une piste de la playlist utilisateur, on utilise directement cette piste
|
||||
if (track.boxId === FAVORITES_BOX_ID) {
|
||||
this.currentTrack = track
|
||||
if (!this.audio) {
|
||||
// fallback: create an audio element and attach listeners
|
||||
this.attachAudio(new Audio())
|
||||
}
|
||||
|
||||
// Interrompre toute lecture en cours avant de charger une nouvelle source
|
||||
const audio = this.audio as HTMLAudioElement
|
||||
try {
|
||||
audio.pause()
|
||||
} catch (_) {}
|
||||
|
||||
// on entre en phase de chargement
|
||||
this.isPaused = true
|
||||
audio.src = track.url
|
||||
audio.load()
|
||||
|
||||
// lancer la lecture (seek si nécessaire une fois les metadata chargées)
|
||||
try {
|
||||
const wantedStart = track.start ?? 0
|
||||
|
||||
// Attendre que les metadata soient prêtes pour pouvoir positionner currentTime
|
||||
await new Promise<void>((resolve) => {
|
||||
if (audio.readyState >= 1) return resolve()
|
||||
const onLoaded = () => resolve()
|
||||
audio.addEventListener('loadedmetadata', onLoaded, { once: true })
|
||||
})
|
||||
// Appliquer le temps de départ
|
||||
audio.currentTime = wantedStart > 0 ? wantedStart : 0
|
||||
await new Promise<void>((resolve) => {
|
||||
const onCanPlay = () => {
|
||||
if (wantedStart > 0 && audio.currentTime < wantedStart - 0.05) {
|
||||
audio.currentTime = wantedStart
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
if (audio.readyState >= 3) return resolve()
|
||||
audio.addEventListener('canplay', onCanPlay, { once: true })
|
||||
})
|
||||
this.isPaused = false
|
||||
await audio.play()
|
||||
} catch (err: any) {
|
||||
// Ignorer les AbortError (arrivent lorsqu'une nouvelle source est chargée rapidement)
|
||||
if (err && err.name === 'AbortError') return
|
||||
this.isPaused = true
|
||||
console.error('Impossible de lire la piste :', err)
|
||||
}
|
||||
await this.loadAndPlayTrack(track)
|
||||
} else {
|
||||
// Pour les autres types de pistes, on utilise la logique existante
|
||||
this.isCompilationTrack(track)
|
||||
? await this.playCompilationTrack(track)
|
||||
: await this.playPlaylistTrack(track)
|
||||
}
|
||||
},
|
||||
|
||||
togglePlay() {
|
||||
async playCompilationTrack(track: Track) {
|
||||
// Si c'est la même piste, on toggle simplement la lecture
|
||||
if (this.currentTrack?.id === track.id) {
|
||||
// Si la lecture est en cours, on met en pause
|
||||
if (this.isPlaying) {
|
||||
this.togglePlay()
|
||||
return
|
||||
}
|
||||
|
||||
// Si c'est une compilation, on vérifie si on est dans la plage de la piste
|
||||
if (track.type === 'compilation' && track.start !== undefined) {
|
||||
const dataStore = useDataStore()
|
||||
const nextTrack = dataStore.getNextTrack(track)
|
||||
|
||||
// Si on a une piste suivante et qu'on est dans la plage de la piste courante
|
||||
if (nextTrack?.start && this.position >= track.start && this.position < nextTrack.start) {
|
||||
this.togglePlay()
|
||||
return
|
||||
}
|
||||
// Si c'est la dernière piste de la compilation
|
||||
else if (!nextTrack && this.position >= track.start) {
|
||||
this.togglePlay()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sinon, on charge et on lit la piste
|
||||
this.currentTrack = track
|
||||
await this.loadAndPlayTrack(track)
|
||||
},
|
||||
|
||||
async playPlaylistTrack(track: Track) {
|
||||
// Toggle simple si c'est la même piste
|
||||
if (this.currentTrack?.id === track.id) {
|
||||
this.togglePlay()
|
||||
return
|
||||
}
|
||||
|
||||
// Sinon, on charge et on lit la piste
|
||||
this.currentTrack = track
|
||||
await this.loadAndPlayTrack(track)
|
||||
},
|
||||
|
||||
async loadTrack(track: Track) {
|
||||
if (!this.audio) return
|
||||
if (this.audio.paused) {
|
||||
this.isPaused = false
|
||||
this.audio
|
||||
.play()
|
||||
.then(() => {
|
||||
this.isPaused = false
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
} else {
|
||||
this.audio.pause()
|
||||
this.isPaused = true
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
this.currentTrack = track
|
||||
const audio = this.audio as HTMLAudioElement
|
||||
|
||||
// Si c'est la même source, on ne fait rien
|
||||
if (audio.src === track.url) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
// Nouvelle source
|
||||
audio.src = track.url
|
||||
|
||||
// Une fois que suffisamment de données sont chargées
|
||||
const onCanPlay = () => {
|
||||
audio.removeEventListener('canplay', onCanPlay)
|
||||
resolve()
|
||||
}
|
||||
audio.addEventListener('canplay', onCanPlay)
|
||||
|
||||
// Timeout de sécurité
|
||||
setTimeout(resolve, 1000)
|
||||
})
|
||||
},
|
||||
|
||||
async loadAndPlayTrack(track: Track) {
|
||||
if (!this.audio) {
|
||||
const newAudio = new Audio()
|
||||
this.attachAudio(newAudio)
|
||||
}
|
||||
|
||||
const audio = this.audio as HTMLAudioElement
|
||||
|
||||
try {
|
||||
this.isLoading = true
|
||||
// Mettre en pause
|
||||
audio.pause()
|
||||
|
||||
// Pour les compilations, on utilise l'URL de la piste avec le point de départ
|
||||
if (track.type === 'compilation' && track.start !== undefined) {
|
||||
audio.src = track.url
|
||||
audio.currentTime = track.start
|
||||
} else {
|
||||
// Pour les playlists, on charge la piste individuelle
|
||||
audio.currentTime = 0
|
||||
await this.loadTrack(track)
|
||||
}
|
||||
|
||||
// Attendre que les métadonnées soient chargées
|
||||
await new Promise<void>((resolve) => {
|
||||
const onCanPlay = () => {
|
||||
audio.removeEventListener('canplay', onCanPlay)
|
||||
resolve()
|
||||
}
|
||||
audio.addEventListener('canplay', onCanPlay)
|
||||
// Timeout de sécurité
|
||||
setTimeout(resolve, 1000)
|
||||
})
|
||||
|
||||
// Lancer la lecture
|
||||
await audio.play()
|
||||
this.isLoading = false
|
||||
} catch (error) {
|
||||
console.error('Error loading/playing track:', error)
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
async togglePlay() {
|
||||
if (!this.audio) return
|
||||
|
||||
try {
|
||||
if (this.audio.paused) {
|
||||
await this.audio.play()
|
||||
} else {
|
||||
this.audio.pause()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error toggling play state:', error)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -149,18 +252,27 @@ export const usePlayerStore = defineStore('player', {
|
||||
if (tracks.length > 0) {
|
||||
const now = audio.currentTime
|
||||
// find the last track whose start <= now (fallback to first track)
|
||||
let found = tracks[0]
|
||||
let nextTrack = tracks[0]
|
||||
for (const t of tracks) {
|
||||
const s = t.start ?? 0
|
||||
if (s <= now) {
|
||||
found = t
|
||||
nextTrack = t
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (found && found.id !== cur.id) {
|
||||
if (nextTrack && nextTrack.id !== cur.id) {
|
||||
// only update metadata reference; do not reload audio
|
||||
this.currentTrack = found
|
||||
this.currentTrack = nextTrack
|
||||
|
||||
// Révéler la carte avec une animation fluide
|
||||
const cardStore = useCardStore()
|
||||
if (nextTrack.id && !cardStore.isCardRevealed(nextTrack.id)) {
|
||||
// Utiliser requestAnimationFrame pour une meilleure synchronisation avec le rendu
|
||||
requestAnimationFrame(() => {
|
||||
cardStore.revealCard(nextTrack.id!)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +290,16 @@ export const usePlayerStore = defineStore('player', {
|
||||
}
|
||||
},
|
||||
|
||||
isCompilationTrack: () => {
|
||||
return (track: Track) => {
|
||||
return track.type === 'compilation'
|
||||
}
|
||||
},
|
||||
|
||||
isPaused: (state) => {
|
||||
return state.audio?.paused ?? true
|
||||
},
|
||||
|
||||
getCurrentTrack: (state) => state.currentTrack,
|
||||
|
||||
getCurrentBox: (state) => {
|
||||
|
||||
@@ -62,6 +62,10 @@ export const useUiStore = defineStore('ui', {
|
||||
},
|
||||
|
||||
getters: {
|
||||
isBoxSelected: () => {
|
||||
const dataStore = useDataStore()
|
||||
return dataStore.boxes.some((box) => box.state === 'box-selected')
|
||||
},
|
||||
getSelectedBox: () => {
|
||||
const dataStore = useDataStore()
|
||||
return (dataStore.boxes as Box[]).find((box) => box.state === 'box-selected') || null
|
||||
|
||||
Reference in New Issue
Block a user