Compare commits
2 Commits
43b1a11027
...
96ffb4b10a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96ffb4b10a | ||
|
|
fef1a8c234 |
@@ -31,6 +31,8 @@ const props = withDefaults(
|
|||||||
{ BoxState: 'list' }
|
{ BoxState: 'list' }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isDraggable = computed(() => !['list', 'hidden'].includes(BoxState.value()))
|
||||||
|
|
||||||
// --- Réfs ---
|
// --- Réfs ---
|
||||||
const scene = ref<HTMLElement>()
|
const scene = ref<HTMLElement>()
|
||||||
const box = ref<HTMLElement>()
|
const box = ref<HTMLElement>()
|
||||||
@@ -123,61 +125,81 @@ function tickInertia() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Pointer events ---
|
// --- Pointer events ---
|
||||||
|
let listenersAttached = false
|
||||||
|
|
||||||
|
const down = (ev: PointerEvent) => {
|
||||||
|
ev.preventDefault()
|
||||||
|
dragging = true
|
||||||
|
box.value?.setPointerCapture(ev.pointerId)
|
||||||
|
lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() }
|
||||||
|
velocity = { x: 0, y: 0 }
|
||||||
|
if (raf) { cancelAnimationFrame(raf); raf = null }
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = (ev: PointerEvent) => {
|
||||||
|
if (!dragging) return
|
||||||
|
ev.preventDefault()
|
||||||
|
const now = performance.now()
|
||||||
|
const dx = ev.clientX - lastPointer.x
|
||||||
|
const dy = ev.clientY - lastPointer.y
|
||||||
|
const dt = Math.max(1, now - lastPointer.time)
|
||||||
|
|
||||||
|
rotateY.value += dx * sensitivity
|
||||||
|
rotateX.value -= dy * sensitivity
|
||||||
|
rotateX.value = Math.max(-80, Math.min(80, rotateX.value))
|
||||||
|
|
||||||
|
velocity.x = (dx / dt) * 16 * sensitivity
|
||||||
|
velocity.y = (-dy / dt) * 16 * sensitivity
|
||||||
|
|
||||||
|
lastPointer = { x: ev.clientX, y: ev.clientY, time: now }
|
||||||
|
applyTransform(0) // immédiat pendant drag
|
||||||
|
}
|
||||||
|
|
||||||
|
const end = (ev: PointerEvent) => {
|
||||||
|
if (!dragging) return
|
||||||
|
dragging = false
|
||||||
|
try { box.value?.releasePointerCapture(ev.pointerId) } catch { }
|
||||||
|
if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) {
|
||||||
|
if (!raf) raf = requestAnimationFrame(tickInertia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListeners() {
|
||||||
|
if (!box.value || listenersAttached) return
|
||||||
|
box.value.addEventListener('pointerdown', down)
|
||||||
|
box.value.addEventListener('pointermove', move)
|
||||||
|
box.value.addEventListener('pointerup', end)
|
||||||
|
box.value.addEventListener('pointercancel', end)
|
||||||
|
box.value.addEventListener('pointerleave', end)
|
||||||
|
listenersAttached = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListeners() {
|
||||||
|
if (!box.value || !listenersAttached) return
|
||||||
|
box.value.removeEventListener('pointerdown', down)
|
||||||
|
box.value.removeEventListener('pointermove', move)
|
||||||
|
box.value.removeEventListener('pointerup', end)
|
||||||
|
box.value.removeEventListener('pointercancel', end)
|
||||||
|
box.value.removeEventListener('pointerleave', end)
|
||||||
|
listenersAttached = false
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
applyColor()
|
applyColor()
|
||||||
applyBoxState()
|
applyBoxState()
|
||||||
|
if (isDraggable) addListeners()
|
||||||
|
})
|
||||||
|
|
||||||
const down = (ev: PointerEvent) => {
|
onBeforeUnmount(() => {
|
||||||
ev.preventDefault()
|
cancelAnimationFrame(raf!)
|
||||||
dragging = true
|
removeListeners()
|
||||||
box.value?.setPointerCapture(ev.pointerId)
|
|
||||||
lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() }
|
|
||||||
velocity = { x: 0, y: 0 }
|
|
||||||
if (raf) { cancelAnimationFrame(raf); raf = null }
|
|
||||||
}
|
|
||||||
|
|
||||||
const move = (ev: PointerEvent) => {
|
|
||||||
if (!dragging) return
|
|
||||||
ev.preventDefault()
|
|
||||||
const now = performance.now()
|
|
||||||
const dx = ev.clientX - lastPointer.x
|
|
||||||
const dy = ev.clientY - lastPointer.y
|
|
||||||
const dt = Math.max(1, now - lastPointer.time)
|
|
||||||
|
|
||||||
rotateY.value += dx * sensitivity
|
|
||||||
rotateX.value -= dy * sensitivity
|
|
||||||
rotateX.value = Math.max(-80, Math.min(80, rotateX.value))
|
|
||||||
|
|
||||||
velocity.x = (dx / dt) * 16 * sensitivity
|
|
||||||
velocity.y = (-dy / dt) * 16 * sensitivity
|
|
||||||
|
|
||||||
lastPointer = { x: ev.clientX, y: ev.clientY, time: now }
|
|
||||||
applyTransform(0) // immédiat pendant drag
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = (ev: PointerEvent) => {
|
|
||||||
if (!dragging) return
|
|
||||||
dragging = false
|
|
||||||
try { box.value?.releasePointerCapture(ev.pointerId) } catch { }
|
|
||||||
if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) {
|
|
||||||
if (!raf) raf = requestAnimationFrame(tickInertia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
box.value?.addEventListener('pointerdown', down)
|
|
||||||
box.value?.addEventListener('pointermove', move)
|
|
||||||
box.value?.addEventListener('pointerup', end)
|
|
||||||
box.value?.addEventListener('pointercancel', end)
|
|
||||||
box.value?.addEventListener('pointerleave', end)
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
cancelAnimationFrame(raf!)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- Watchers ---
|
// --- Watchers ---
|
||||||
watch(() => props.BoxState, () => applyBoxState())
|
watch(() => props.BoxState, () => applyBoxState())
|
||||||
watch(() => props.compilation, () => applyColor(), { deep: true })
|
watch(() => props.compilation, () => applyColor(), { deep: true })
|
||||||
|
watch(() => isDraggable, (enabled) => enabled ? addListeners() : removeListeners())
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -200,7 +222,6 @@ watch(() => props.compilation, () => applyColor(), { deep: true })
|
|||||||
|
|
||||||
&-scene {
|
&-scene {
|
||||||
height: calc(var(--size) * 20);
|
height: calc(var(--size) * 20);
|
||||||
width: var(--width);
|
|
||||||
perspective: 1000px;
|
perspective: 1000px;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<article class="relative">
|
<article class="flip-card w-56 h-80">
|
||||||
<main
|
<div class="flip-inner">
|
||||||
class="backdrop-blur-sm border-2 -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-40 hover:bg-opacity-80 hover:shadow-xl transition-all bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden">
|
<main
|
||||||
<!-- Cover -->
|
class="flip-front backdrop-blur-sm border-2 -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-40 hover:bg-opacity-80 hover:shadow-xl transition-all bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden">
|
||||||
<figure @click="playerStore.playTrack(props.track)" class="flex-1 overflow-hidden rounded-t-xl cursor-pointer">
|
<!-- Cover -->
|
||||||
<img :src="coverUrl" alt="Pochette de l'album" class="w-full h-full object-cover object-center" />
|
<figure @click="playerStore.playTrack(props.track)" class="flex-1 overflow-hidden rounded-t-xl cursor-pointer">
|
||||||
</figure>
|
<img :src="coverUrl" alt="Pochette de l'album" class="w-full h-full object-cover object-center" />
|
||||||
|
</figure>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="p-3 text-center bg-white rounded-b-xl">
|
<div class="p-3 text-center bg-white rounded-b-xl">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
{{ props.track.order }}
|
{{ props.track.order }}
|
||||||
|
</div>
|
||||||
|
<h2 class="text-base text-neutral-800 font-bold truncate">{{ props.track.title }}</h2>
|
||||||
|
<p class="text-sm text-neutral-500 truncate">
|
||||||
|
{{ props.track.artist.name }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-base text-neutral-800 font-bold truncate">{{ props.track.title }}</h2>
|
</main>
|
||||||
<p class="text-sm text-neutral-500 truncate">
|
|
||||||
{{ props.track.artist.name }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
class="ml-32 backdrop-blur-sm -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-10 bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden">
|
class="flip-back backdrop-blur-sm -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-10 bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<div class="h-full flex p-16 text-center bg-slate-800 rounded-xl">
|
<div class="h-full flex p-16 text-center bg-slate-800 rounded-xl">
|
||||||
<img src="/favicon.svg" />
|
<img src="/favicon.svg" />
|
||||||
</div>
|
<div class="label label--id">
|
||||||
<div class="label">
|
{{ props.track.order }}
|
||||||
{{ props.track.id }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
@@ -50,4 +52,38 @@ const coverUrl = props.track.coverId.startsWith('http')
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Flip effect */
|
||||||
|
.flip-card {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.6s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flipped .flip-inner {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-front,
|
||||||
|
.flip-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-front {
|
||||||
|
transform: rotateY(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-back {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<audio ref="audioRef" class="fixed z-50 bottom-0 left-1/2 -translate-x-1/2 w-1/2"
|
<div class="fixed left-0 bottom-0 opacity-1 z-50 w-full bg-white transition-all"
|
||||||
:src="playerStore.currentTrack ? playerStore.getCompilationUrlFromTrack(playerStore.currentTrack) : ''" controls
|
:class="{ '-bottom-20 opacity-0': !playerStore.currentTrack }">
|
||||||
@timeupdate="updatePosition" @ended="onEnded" />
|
<!-- <p class="hidden">
|
||||||
|
{{ Math.round(currentTime) }}
|
||||||
|
{{ Math.round(currentProgression) }}%
|
||||||
|
</p> -->
|
||||||
|
<audio ref="audioRef" class="w-full"
|
||||||
|
:src="playerStore.currentTrack ? playerStore.getCompilationUrlFromTrack(playerStore.currentTrack) : ''"
|
||||||
|
controls />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -10,29 +17,36 @@ import { usePlayerStore } from '~/store/player'
|
|||||||
|
|
||||||
const playerStore = usePlayerStore()
|
const playerStore = usePlayerStore()
|
||||||
const audioRef = ref<HTMLAudioElement | null>(null)
|
const audioRef = ref<HTMLAudioElement | null>(null)
|
||||||
|
const currentTime = ref(0)
|
||||||
|
const lastValidProgression = ref(0)
|
||||||
|
|
||||||
onMounted(() => {
|
const currentProgression = computed(() => {
|
||||||
if (audioRef.value) playerStore.audio = audioRef.value
|
if (!audioRef.value) return 0
|
||||||
|
const progression = (currentTime.value / audioRef.value.duration) * 100
|
||||||
|
|
||||||
|
if (!isNaN(progression)) {
|
||||||
|
lastValidProgression.value = progression
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastValidProgression.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mettre à jour la position
|
function updateTime() {
|
||||||
function updatePosition() {
|
if (audioRef.value) {
|
||||||
if (audioRef.value) playerStore.position = audioRef.value.currentTime
|
currentTime.value = audioRef.value.currentTime
|
||||||
}
|
|
||||||
|
|
||||||
function onEnded() {
|
|
||||||
playerStore.isPlaying = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si la track change, mettre à jour le src et le start
|
|
||||||
watch(
|
|
||||||
() => playerStore.currentTrack,
|
|
||||||
(newTrack) => {
|
|
||||||
if (newTrack && audioRef.value) {
|
|
||||||
audioRef.value.src = newTrack.url
|
|
||||||
audioRef.value.currentTime = newTrack.start || 0
|
|
||||||
if (playerStore.isPlaying) audioRef.value.play()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (audioRef.value) {
|
||||||
|
playerStore.audio = audioRef.value
|
||||||
|
audioRef.value.addEventListener("timeupdate", updateTime)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (audioRef.value) {
|
||||||
|
audioRef.value.removeEventListener("timeupdate", updateTime)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDataStore } from '~/store/data'
|
import { useDataStore } from '~/store/data'
|
||||||
import type { BoxState } from '~~/types/types'
|
import type { BoxState } from '~~/types/types'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const boxStates = ref<Record<string, BoxState>>({})
|
const boxStates = ref<Record<string, BoxState>>({})
|
||||||
@@ -30,7 +29,6 @@ function closeCompilation(e: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const dataStore = await useDataStore()
|
const dataStore = await useDataStore()
|
||||||
await dataStore.loadData()
|
await dataStore.loadData()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-8 p-8 w-96 flex flex-wrap">
|
<div class="mt-8 p-8 w-full flex flex-wrap justify-around">
|
||||||
<MoleculeCard v-for="track in dataStore.getTracksByCompilationId(compilation.id)" :key="track.id" :track="track" />
|
<MoleculeCard v-for="track in dataStore.getTracksByCompilationId(compilation.id)" :key="track.id" :track="track" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -54,5 +54,20 @@ export const useDataStore = defineStore('data', {
|
|||||||
getTracksByArtistId: (state) => (artistId: number) => {
|
getTracksByArtistId: (state) => (artistId: number) => {
|
||||||
return state.tracks.filter(track => track.artist.id === artistId)
|
return state.tracks.filter(track => track.artist.id === artistId)
|
||||||
},
|
},
|
||||||
|
getNextTrack: (state) => {
|
||||||
|
return (track: Track) => {
|
||||||
|
// Récupérer toutes les tracks de la même compilation et les trier par ordre
|
||||||
|
const tracksInCompilation = state.tracks
|
||||||
|
.filter(t => t.compilationId === track.compilationId)
|
||||||
|
.sort((a, b) => a.order - b.order)
|
||||||
|
|
||||||
|
// Trouver l’index de la track courante
|
||||||
|
const index = tracksInCompilation.findIndex(t => t.id === track.id)
|
||||||
|
// Retourner la track suivante ou null si c’est la dernière
|
||||||
|
return index >= 0 && index < tracksInCompilation.length - 1
|
||||||
|
? tracksInCompilation[index + 1]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,57 +1,113 @@
|
|||||||
// ~/store/player.ts
|
// ~/store/player.ts
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { Track } from '~/types/types'
|
import type { Track } from '~/../types/types'
|
||||||
|
import { useDataStore } from '~/store/data'
|
||||||
|
|
||||||
export const usePlayerStore = defineStore('player', {
|
export const usePlayerStore = defineStore('player', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
currentTrack: null as Track | null,
|
currentTrack: null as Track | null,
|
||||||
isPlaying: false,
|
|
||||||
position: 0,
|
position: 0,
|
||||||
audio: null as HTMLAudioElement | null,
|
audio: null as HTMLAudioElement | null,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
setTrack(track: Track) {
|
async playTrack(track: Track) {
|
||||||
this.currentTrack = track
|
this.currentTrack = track
|
||||||
if (!this.audio) this.audio = new Audio(this.getCompilationUrlFromTrack(track))
|
|
||||||
else this.audio.src = this.getCompilationUrlFromTrack(track)
|
|
||||||
|
|
||||||
// Commencer à start secondes
|
// toggle si on reclique sur la même
|
||||||
this.audio.currentTime = track.start || 0
|
if (this.isPlayingTrack(track)) {
|
||||||
},
|
this.togglePlay()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.audio) {
|
||||||
|
this.audio = new Audio()
|
||||||
|
}
|
||||||
|
|
||||||
playTrack(track?: Track) {
|
// définir la source (fichier de la compilation entière)
|
||||||
if (track) this.setTrack(track)
|
this.audio.src = this.getCompilationUrlFromTrack(track)
|
||||||
if (!this.currentTrack || !this.audio) return
|
this.audio.load()
|
||||||
|
|
||||||
this.audio.play()
|
// attendre que le player soit prêt avant de lire
|
||||||
this.isPlaying = true
|
await new Promise<void>((resolve, reject) => {
|
||||||
},
|
const onCanPlay = () => {
|
||||||
|
this.audio!.removeEventListener("canplay", onCanPlay)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
const onError = (e: Event) => {
|
||||||
|
this.audio!.removeEventListener("error", onError)
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
this.audio!.addEventListener("canplay", onCanPlay, { once: true })
|
||||||
|
this.audio!.addEventListener("error", onError, { once: true })
|
||||||
|
})
|
||||||
|
|
||||||
pauseTrack() {
|
// positionner le début
|
||||||
if (this.audio) this.audio.pause()
|
this.audio.currentTime = track.start ?? 0
|
||||||
this.isPlaying = false
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePlay(track?: Track) {
|
// lancer la lecture
|
||||||
if (track && (!this.currentTrack || track.id !== this.currentTrack.id)) {
|
try {
|
||||||
this.playTrack(track)
|
await this.audio.play()
|
||||||
} else {
|
} catch (err) {
|
||||||
this.isPlaying ? this.pauseTrack() : this.playTrack()
|
console.error("Impossible de lire la piste :", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setPosition(time: number) {
|
togglePlay() {
|
||||||
if (this.audio) this.audio.currentTime = time
|
if (!this.audio) return
|
||||||
this.position = time
|
if (this.audio.paused) {
|
||||||
|
this.audio.play().catch(err => console.error(err))
|
||||||
|
} else {
|
||||||
|
this.audio.pause()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
isCurrentCompilation: (state) => {
|
||||||
|
return (compilationId: string) =>
|
||||||
|
compilationId === state.currentTrack?.compilationId
|
||||||
|
},
|
||||||
|
|
||||||
|
isPlayingTrack: (state) => {
|
||||||
|
return (track: Track) => {
|
||||||
|
if (!state.audio || !state.currentTrack) return false
|
||||||
|
|
||||||
|
const currentTime = state.audio.currentTime
|
||||||
|
if (!currentTime || isNaN(currentTime)) return false
|
||||||
|
|
||||||
|
const from = track.start ?? 0
|
||||||
|
const to = state.getTrackStop(track)
|
||||||
|
if (!to || isNaN(to)) return false
|
||||||
|
|
||||||
|
return currentTime >= from && currentTime < to
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getCurrentTrack: (state) => state.currentTrack,
|
getCurrentTrack: (state) => state.currentTrack,
|
||||||
getPlaying: (state) => state.isPlaying,
|
|
||||||
getCompilationUrlFromTrack: (state) => {
|
getCompilationUrlFromTrack: () => {
|
||||||
return (track: Track) => `https://files.erudi.fr/evilspins/${track.compilationId}.mp3`
|
return (track: Track) =>
|
||||||
|
`https://files.erudi.fr/evilspins/${track.compilationId}.mp3`
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentCompilation: (state) => {
|
||||||
|
return state.currentTrack
|
||||||
|
? state.getCompilationUrlFromTrack(state.currentTrack)
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
|
||||||
|
getTrackStop: (state) => {
|
||||||
|
return (track: Track) => {
|
||||||
|
if (!state.audio) return 0
|
||||||
|
|
||||||
|
if (track.order === 0) {
|
||||||
|
return Math.round(state.audio.duration)
|
||||||
|
} else {
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
const nextTrack = dataStore.getNextTrack(track)
|
||||||
|
return nextTrack ? nextTrack.start : Math.round(state.audio.duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES00A',
|
compilationId: 'ES00A',
|
||||||
title: 'Bleach',
|
title: 'Bleach',
|
||||||
artist: 1,
|
artist: 1,
|
||||||
start: 393,
|
start: 392,
|
||||||
url: 'https://the-kundalini-genie.bandcamp.com/track/bleach-2',
|
url: 'https://the-kundalini-genie.bandcamp.com/track/bleach-2',
|
||||||
coverId: 'a1714786533',
|
coverId: 'a1714786533',
|
||||||
},
|
},
|
||||||
@@ -28,7 +28,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES00A',
|
compilationId: 'ES00A',
|
||||||
title: 'Televised mind',
|
title: 'Televised mind',
|
||||||
artist: 2,
|
artist: 2,
|
||||||
start: 892,
|
start: 896,
|
||||||
url: 'https://fontainesdc.bandcamp.com/track/televised-mind',
|
url: 'https://fontainesdc.bandcamp.com/track/televised-mind',
|
||||||
coverId: 'a3772806156'
|
coverId: 'a3772806156'
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES00A',
|
compilationId: 'ES00A',
|
||||||
title: 'In it',
|
title: 'In it',
|
||||||
artist: 3,
|
artist: 3,
|
||||||
start: 1138,
|
start: 1139,
|
||||||
url: 'https://howlinbananarecords.bandcamp.com/track/in-it',
|
url: 'https://howlinbananarecords.bandcamp.com/track/in-it',
|
||||||
coverId: 'a1720372066',
|
coverId: 'a1720372066',
|
||||||
},
|
},
|
||||||
@@ -104,11 +104,11 @@ export default eventHandler(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 10,
|
||||||
order: 0,
|
order: 11,
|
||||||
compilationId: 'ES00A',
|
compilationId: 'ES00A',
|
||||||
title: 'Like in the movies',
|
title: 'Like in the movies',
|
||||||
artist: 10,
|
artist: 10,
|
||||||
start: 2559,
|
start: 2560,
|
||||||
url: 'https://bruitblanc.bandcamp.com/track/like-in-the-movies-2',
|
url: 'https://bruitblanc.bandcamp.com/track/like-in-the-movies-2',
|
||||||
coverId: 'a2203158939',
|
coverId: 'a2203158939',
|
||||||
},
|
},
|
||||||
@@ -214,11 +214,11 @@ export default eventHandler(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 21,
|
id: 21,
|
||||||
order: 0,
|
order: 11,
|
||||||
compilationId: 'ES00B',
|
compilationId: 'ES00B',
|
||||||
title: 'Like in the movies',
|
title: 'Like in the movies',
|
||||||
artist: 10,
|
artist: 10,
|
||||||
start: 2185,
|
start: 2186,
|
||||||
url: 'https://bruitblanc.bandcamp.com/track/like-in-the-movies',
|
url: 'https://bruitblanc.bandcamp.com/track/like-in-the-movies',
|
||||||
coverId: 'a3647322740',
|
coverId: 'a3647322740',
|
||||||
},
|
},
|
||||||
@@ -238,7 +238,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'The Third Wave',
|
title: 'The Third Wave',
|
||||||
artist: 12,
|
artist: 12,
|
||||||
start: 854,
|
start: 841,
|
||||||
url: 'https://firefriend.bandcamp.com/track/the-third-wave',
|
url: 'https://firefriend.bandcamp.com/track/the-third-wave',
|
||||||
coverId: 'a2803689859',
|
coverId: 'a2803689859',
|
||||||
},
|
},
|
||||||
@@ -248,7 +248,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Broadcaster',
|
title: 'Broadcaster',
|
||||||
artist: 13,
|
artist: 13,
|
||||||
start: 0,
|
start: 1104.5,
|
||||||
url: 'https://squiduk.bandcamp.com/track/broadcaster',
|
url: 'https://squiduk.bandcamp.com/track/broadcaster',
|
||||||
coverId: 'a3391719769',
|
coverId: 'a3391719769',
|
||||||
},
|
},
|
||||||
@@ -258,7 +258,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Mourn',
|
title: 'Mourn',
|
||||||
artist: 14,
|
artist: 14,
|
||||||
start: 0,
|
start: 1441,
|
||||||
url: 'https://lysistrata.bandcamp.com/track/mourn-2',
|
url: 'https://lysistrata.bandcamp.com/track/mourn-2',
|
||||||
coverId: 'a0872900041',
|
coverId: 'a0872900041',
|
||||||
},
|
},
|
||||||
@@ -268,7 +268,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Let it Blow',
|
title: 'Let it Blow',
|
||||||
artist: 15,
|
artist: 15,
|
||||||
start: 0,
|
start: 1844.8,
|
||||||
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/let-it-blow',
|
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/let-it-blow',
|
||||||
coverId: 'a4000148031',
|
coverId: 'a4000148031',
|
||||||
},
|
},
|
||||||
@@ -278,7 +278,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Sunday Mourning',
|
title: 'Sunday Mourning',
|
||||||
artist: 16,
|
artist: 16,
|
||||||
start: 0,
|
start: 2091.7,
|
||||||
url: 'https://nightbeats.bandcamp.com/track/sunday-mourning',
|
url: 'https://nightbeats.bandcamp.com/track/sunday-mourning',
|
||||||
coverId: 'a0031987121',
|
coverId: 'a0031987121',
|
||||||
},
|
},
|
||||||
@@ -288,7 +288,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: '3030 Instrumental',
|
title: '3030 Instrumental',
|
||||||
artist: 17,
|
artist: 17,
|
||||||
start: 0,
|
start: 2339.3,
|
||||||
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
|
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
|
||||||
coverId: 'a1948146136',
|
coverId: 'a1948146136',
|
||||||
},
|
},
|
||||||
@@ -298,7 +298,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Immortality Break',
|
title: 'Immortality Break',
|
||||||
artist: 18,
|
artist: 18,
|
||||||
start: 0,
|
start: 2530.5,
|
||||||
url: 'https://theaa.bandcamp.com/track/immortality-break',
|
url: 'https://theaa.bandcamp.com/track/immortality-break',
|
||||||
coverId: 'a2749250329',
|
coverId: 'a2749250329',
|
||||||
},
|
},
|
||||||
@@ -308,7 +308,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Lazy Bones',
|
title: 'Lazy Bones',
|
||||||
artist: 19,
|
artist: 19,
|
||||||
start: 0,
|
start: 2718,
|
||||||
url: 'https://woodenshjips.bandcamp.com/track/lazy-bones',
|
url: 'https://woodenshjips.bandcamp.com/track/lazy-bones',
|
||||||
coverId: 'a1884221104',
|
coverId: 'a1884221104',
|
||||||
},
|
},
|
||||||
@@ -318,17 +318,17 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'On the Train of Aches',
|
title: 'On the Train of Aches',
|
||||||
artist: 20,
|
artist: 20,
|
||||||
start: 0,
|
start: 2948,
|
||||||
url: 'https://silasjdirge.bandcamp.com/track/on-the-train-of-aches',
|
url: 'https://silasjdirge.bandcamp.com/track/on-the-train-of-aches',
|
||||||
coverId: 'a1124177379',
|
coverId: 'a1124177379',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 32,
|
id: 32,
|
||||||
order: 0,
|
order: 11,
|
||||||
compilationId: 'ES01A',
|
compilationId: 'ES01A',
|
||||||
title: 'Me',
|
title: 'Me',
|
||||||
artist: 21,
|
artist: 21,
|
||||||
start: 0,
|
start: 3265,
|
||||||
url: 'https://secretcolours.bandcamp.com/track/me',
|
url: 'https://secretcolours.bandcamp.com/track/me',
|
||||||
coverId: 'a1497022499',
|
coverId: 'a1497022499',
|
||||||
},
|
},
|
||||||
@@ -348,7 +348,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Dreamscapes',
|
title: 'Dreamscapes',
|
||||||
artist: 12,
|
artist: 12,
|
||||||
start: 0,
|
start: 235,
|
||||||
url: 'https://littlecloudrecords.bandcamp.com/track/dreamscapes',
|
url: 'https://littlecloudrecords.bandcamp.com/track/dreamscapes',
|
||||||
coverId: 'a3498981203',
|
coverId: 'a3498981203',
|
||||||
},
|
},
|
||||||
@@ -358,7 +358,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Crispy Skin',
|
title: 'Crispy Skin',
|
||||||
artist: 13,
|
artist: 13,
|
||||||
start: 0,
|
start: 644.2,
|
||||||
url: 'https://squiduk.bandcamp.com/track/crispy-skin-2',
|
url: 'https://squiduk.bandcamp.com/track/crispy-skin-2',
|
||||||
coverId: 'a2516727021',
|
coverId: 'a2516727021',
|
||||||
},
|
},
|
||||||
@@ -368,7 +368,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'The Boy Who Stood Above The Earth',
|
title: 'The Boy Who Stood Above The Earth',
|
||||||
artist: 14,
|
artist: 14,
|
||||||
start: 0,
|
start: 1018,
|
||||||
url: 'https://lysistrata.bandcamp.com/track/the-boy-who-stood-above-the-earth-2',
|
url: 'https://lysistrata.bandcamp.com/track/the-boy-who-stood-above-the-earth-2',
|
||||||
coverId: 'a0350933426',
|
coverId: 'a0350933426',
|
||||||
},
|
},
|
||||||
@@ -378,7 +378,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Better Off Alone',
|
title: 'Better Off Alone',
|
||||||
artist: 15,
|
artist: 15,
|
||||||
start: 0,
|
start: 1698,
|
||||||
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/better-off-alone',
|
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/better-off-alone',
|
||||||
coverId: 'a4000148031',
|
coverId: 'a4000148031',
|
||||||
},
|
},
|
||||||
@@ -388,7 +388,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Celebration #1',
|
title: 'Celebration #1',
|
||||||
artist: 16,
|
artist: 16,
|
||||||
start: 0,
|
start: 2235,
|
||||||
url: 'https://nightbeats.bandcamp.com/track/celebration-1',
|
url: 'https://nightbeats.bandcamp.com/track/celebration-1',
|
||||||
coverId: 'a0031987121',
|
coverId: 'a0031987121',
|
||||||
},
|
},
|
||||||
@@ -398,7 +398,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: '3030 Instrumental',
|
title: '3030 Instrumental',
|
||||||
artist: 17,
|
artist: 17,
|
||||||
start: 0,
|
start: 2458.3,
|
||||||
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
|
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
|
||||||
coverId: 'a1948146136',
|
coverId: 'a1948146136',
|
||||||
},
|
},
|
||||||
@@ -408,7 +408,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'The Emptiness Of Nothingness',
|
title: 'The Emptiness Of Nothingness',
|
||||||
artist: 18,
|
artist: 18,
|
||||||
start: 0,
|
start: 2864.5,
|
||||||
url: 'https://theaa.bandcamp.com/track/the-emptiness-of-nothingness',
|
url: 'https://theaa.bandcamp.com/track/the-emptiness-of-nothingness',
|
||||||
coverId: 'a1053923875',
|
coverId: 'a1053923875',
|
||||||
},
|
},
|
||||||
@@ -418,7 +418,7 @@ export default eventHandler(() => {
|
|||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Rising',
|
title: 'Rising',
|
||||||
artist: 19,
|
artist: 19,
|
||||||
start: 0,
|
start: 3145,
|
||||||
url: 'https://woodenshjips.bandcamp.com/track/rising',
|
url: 'https://woodenshjips.bandcamp.com/track/rising',
|
||||||
coverId: 'a1884221104',
|
coverId: 'a1884221104',
|
||||||
},
|
},
|
||||||
@@ -426,19 +426,19 @@ export default eventHandler(() => {
|
|||||||
id: 42,
|
id: 42,
|
||||||
order: 10,
|
order: 10,
|
||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'The Last Time / Jealous Woman',
|
title: 'The Last Time',
|
||||||
artist: 22,
|
artist: 22,
|
||||||
start: 0,
|
start: 3447,
|
||||||
url: 'https://www.discogs.com/release/12110815-Larry-McNeil-And-The-Blue-Knights-Jealous-Woman',
|
url: 'https://www.discogs.com/release/12110815-Larry-McNeil-And-The-Blue-Knights-Jealous-Woman',
|
||||||
coverId: 'https://i.discogs.com/Yr05_neEXwzPwKlDeV7dimmTG34atkAMgpxbMBhHBkI/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTEyMTEw/ODE1LTE1Mjg1NjU1/NzQtMjcyOC5qcGVn.jpeg',
|
coverId: 'https://i.discogs.com/Yr05_neEXwzPwKlDeV7dimmTG34atkAMgpxbMBhHBkI/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTEyMTEw/ODE1LTE1Mjg1NjU1/NzQtMjcyOC5qcGVn.jpeg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 43,
|
id: 43,
|
||||||
order: 0,
|
order: 11,
|
||||||
compilationId: 'ES01B',
|
compilationId: 'ES01B',
|
||||||
title: 'Guajira Con Arpa',
|
title: 'Guajira Con Arpa',
|
||||||
artist: 23,
|
artist: 23,
|
||||||
start: 0,
|
start: 3586,
|
||||||
url: 'https://elpalmasmusic.bandcamp.com/track/guajira-con-arpa',
|
url: 'https://elpalmasmusic.bandcamp.com/track/guajira-con-arpa',
|
||||||
coverId: 'a3463036407',
|
coverId: 'a3463036407',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user