add cards & tracks
All checks were successful
Deploy App / build (push) Successful in 1m13s
Deploy App / deploy (push) Successful in 15s

This commit is contained in:
valere
2025-10-02 00:38:54 +02:00
parent 8c1290beae
commit 43b1a11027
11 changed files with 474 additions and 43 deletions

View File

@@ -130,7 +130,7 @@ onMounted(() => {
const down = (ev: PointerEvent) => {
ev.preventDefault()
dragging = true
scene.value?.setPointerCapture(ev.pointerId)
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 }
@@ -158,17 +158,17 @@ onMounted(() => {
const end = (ev: PointerEvent) => {
if (!dragging) return
dragging = false
try { scene.value?.releasePointerCapture(ev.pointerId) } catch { }
try { box.value?.releasePointerCapture(ev.pointerId) } catch { }
if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) {
if (!raf) raf = requestAnimationFrame(tickInertia)
}
}
scene.value?.addEventListener('pointerdown', down)
scene.value?.addEventListener('pointermove', move)
scene.value?.addEventListener('pointerup', end)
scene.value?.addEventListener('pointercancel', end)
scene.value?.addEventListener('pointerleave', end)
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!)

View File

@@ -1,14 +1,17 @@
<template>
<article class="relative">
<main
class="absolute top-0 backdrop-blur-sm z-40 -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="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">
<!-- Cover -->
<figure class="flex-1 overflow-hidden rounded-t-xl">
<figure @click="playerStore.playTrack(props.track)" class="flex-1 overflow-hidden rounded-t-xl cursor-pointer">
<img :src="coverUrl" alt="Pochette de l'album" class="w-full h-full object-cover object-center" />
</figure>
<!-- Body -->
<div class="p-3 text-center bg-white rounded-b-xl">
<div class="label">
{{ 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 }}
@@ -17,11 +20,14 @@
</main>
<footer
class="absolute top-0 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="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">
<!-- Back -->
<div class="h-full flex p-16 text-center bg-slate-800 rounded-xl">
<img src="/favicon.svg" />
</div>
<div class="label">
{{ props.track.id }}
</div>
</footer>
</article>
@@ -29,7 +35,19 @@
<script setup lang="ts">
import type { Track } from '~~/types/types'
import { usePlayerStore } from '~/store/player'
const props = defineProps<{ track: Track }>()
const coverUrl = `https://f4.bcbits.com/img/${props.track.artist.coverId}_4.jpg`
</script>
const playerStore = usePlayerStore()
const coverUrl = props.track.coverId.startsWith('http')
? props.track.coverId
: `https://f4.bcbits.com/img/${props.track.coverId}_4.jpg`;
</script>
<style>
.label {
@apply rounded-full size-7 p-2 bg-esyellow leading-3 -mt-6;
font-weight: bold;
text-align: center;
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<audio ref="audioRef" class="fixed z-50 bottom-0 left-1/2 -translate-x-1/2 w-1/2"
:src="playerStore.currentTrack ? playerStore.getCompilationUrlFromTrack(playerStore.currentTrack) : ''" controls
@timeupdate="updatePosition" @ended="onEnded" />
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { usePlayerStore } from '~/store/player'
const playerStore = usePlayerStore()
const audioRef = ref<HTMLAudioElement | null>(null)
onMounted(() => {
if (audioRef.value) playerStore.audio = audioRef.value
})
// Mettre à jour la position
function updatePosition() {
if (audioRef.value) playerStore.position = 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()
}
}
)
</script>

View File

@@ -12,7 +12,6 @@ import type { BoxState } from '~~/types/types'
import { useRouter } from 'vue-router'
const dataStore = useDataStore()
const router = useRouter()
const boxStates = ref<Record<string, BoxState>>({})
function openCompilation(id: string) {
@@ -20,7 +19,6 @@ function openCompilation(id: string) {
for (const key in boxStates.value) {
boxStates.value[key] = (key === id) ? 'selected' : 'hide'
}
window.history.pushState({}, '', '/compilation/' + id)
}
}
@@ -30,7 +28,6 @@ function closeCompilation(e: KeyboardEvent) {
boxStates.value[key] = 'list'
}
}
window.history.pushState({}, '', '/')
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="mt-8 p-8 w-96">
<div class="mt-8 p-8 w-96 flex flex-wrap">
<MoleculeCard v-for="track in dataStore.getTracksByCompilationId(compilation.id)" :key="track.id" :track="track" />
</div>
</template>
@@ -13,5 +13,4 @@ const props = defineProps<{
}>()
const dataStore = useDataStore()
</script>

View File

@@ -9,6 +9,7 @@
</header>
<main>
<OrganismCompilationList />
<MoleculePlayer />
</main>
</div>
</template>

View File

@@ -1,9 +1,9 @@
<template>
<div class="flex flex-wrap justify-center">
<div class="flex flex-wrap justify-center items-center h-screen">
<div class="bg-page-dark-bg text-white">
<div class="flex flex-col-reverse bg-gradient-to-r from-primary to-primary-dark">
<div class="mt-8 flex flex-wrap justify-center">
<molecule-box :compilation="compilation" />
<!-- <molecule-box :compilation="compilation" /> -->
<div class="devtool absolute right-4 text-white bg-black rounded-2xl px-4 py-2">
<!-- <button @click="currentPosition = boxPositions.side">side</button>
<button @click="currentPosition = boxPositions.front">front</button>

57
app/store/player.ts Normal file
View File

@@ -0,0 +1,57 @@
// ~/store/player.ts
import { defineStore } from 'pinia'
import type { Track } from '~/types/types'
export const usePlayerStore = defineStore('player', {
state: () => ({
currentTrack: null as Track | null,
isPlaying: false,
position: 0,
audio: null as HTMLAudioElement | null,
}),
actions: {
setTrack(track: Track) {
this.currentTrack = track
if (!this.audio) this.audio = new Audio(this.getCompilationUrlFromTrack(track))
else this.audio.src = this.getCompilationUrlFromTrack(track)
// Commencer à start secondes
this.audio.currentTime = track.start || 0
},
playTrack(track?: Track) {
if (track) this.setTrack(track)
if (!this.currentTrack || !this.audio) return
this.audio.play()
this.isPlaying = true
},
pauseTrack() {
if (this.audio) this.audio.pause()
this.isPlaying = false
},
togglePlay(track?: Track) {
if (track && (!this.currentTrack || track.id !== this.currentTrack.id)) {
this.playTrack(track)
} else {
this.isPlaying ? this.pauseTrack() : this.playTrack()
}
},
setPosition(time: number) {
if (this.audio) this.audio.currentTime = time
this.position = time
},
},
getters: {
getCurrentTrack: (state) => state.currentTrack,
getPlaying: (state) => state.isPlaying,
getCompilationUrlFromTrack: (state) => {
return (track: Track) => `https://files.erudi.fr/evilspins/${track.compilationId}.mp3`
}
},
})

View File

@@ -36,7 +36,7 @@ export default eventHandler(() => {
id: 5,
name: "New candys",
url: "https://newcandys.bandcamp.com",
coverId: "0033518637",
coverId: "0039963261",
},
{
id: 6,
@@ -67,6 +67,84 @@ export default eventHandler(() => {
name: "I love UFO",
url: "https://bruitblanc.bandcamp.com",
coverId: "a2203158939",
}
},
{
id: 11,
name: "Kid Congo & The Pink Monkey Birds",
url: "https://kidcongothepinkmonkeybirds.bandcamp.com/",
coverId: "0017196290",
},
{
id: 12,
name: "Firefriend",
url: "https://firefriend.bandcamp.com/",
coverId: "0031072203",
},
{
id: 13,
name: "Squid",
url: "https://squiduk.bandcamp.com/",
coverId: "0037649385",
},
{
id: 14,
name: "Lysistrata",
url: "https://lysistrata.bandcamp.com/",
coverId: "0033900158",
},
{
id: 15,
name: "Pablo X Broadcasting Services",
url: "https://pabloxbroadcastingservices.bandcamp.com/",
coverId: "0036956486",
},
{
id: 16,
name: "Night Beats",
url: "https://nightbeats.bandcamp.com/",
coverId: "0036987720",
},
{
id: 17,
name: "Deltron 3030",
url: "https://delthefunkyhomosapien.bandcamp.com/",
coverId: "0005254781",
},
{
id: 18,
name: "The Amorphous Androgynous",
url: "https://theaa.bandcamp.com/",
coverId: "0022226700",
},
{
id: 19,
name: "Wooden Shjips",
url: "https://woodenshjips.bandcamp.com/",
coverId: "0012406678",
},
{
id: 20,
name: "Silas J. Dirge",
url: "https://silasjdirge.bandcamp.com/",
coverId: "0035751570",
},
{
id: 21,
name: "Secret Colours",
url: "https://secretcolours.bandcamp.com/",
coverId: "0010661379",
},
{
id: 22,
name: "Larry McNeil And The Blue Knights",
url: "https://www.discogs.com/artist/6528940-Larry-McNeil-And-The-Blue-Knights",
coverId: "https://i.discogs.com/Yr05_neEXwzPwKlDeV7dimmTG34atkAMgpxbMBhHBkI/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTEyMTEw/ODE1LTE1Mjg1NjU1/NzQtMjcyOC5qcGVn.jpeg",
},
{
id: 23,
name: "Hugo Blanco",
url: "https://elpalmasmusic.bandcamp.com/album/color-de-tr-pico-compiled-by-el-dr-gon-criollo-el-palmas",
coverId: "0016886708",
},
]
})

View File

@@ -3,7 +3,8 @@ import { eventHandler } from 'h3'
export default eventHandler(() => {
return [
{
id: 1,
id: 0,
order: 1,
compilationId: 'ES00A',
title: 'The grinding wheel',
artist: 0,
@@ -12,7 +13,8 @@ export default eventHandler(() => {
coverId: 'a3236746052',
},
{
id: 2,
id: 1,
order: 2,
compilationId: 'ES00A',
title: 'Bleach',
artist: 1,
@@ -21,7 +23,8 @@ export default eventHandler(() => {
coverId: 'a1714786533',
},
{
id: 3,
id: 2,
order: 3,
compilationId: 'ES00A',
title: 'Televised mind',
artist: 2,
@@ -30,7 +33,8 @@ export default eventHandler(() => {
coverId: 'a3772806156'
},
{
id: 4,
id: 3,
order: 4,
compilationId: 'ES00A',
title: 'In it',
artist: 3,
@@ -39,7 +43,8 @@ export default eventHandler(() => {
coverId: 'a1720372066',
},
{
id: 5,
id: 4,
order: 5,
compilationId: 'ES00A',
title: 'Bad michel',
artist: 4,
@@ -48,7 +53,8 @@ export default eventHandler(() => {
coverId: 'a0984622869',
},
{
id: 6,
id: 5,
order: 6,
compilationId: 'ES00A',
title: 'Overall',
artist: 5,
@@ -57,7 +63,8 @@ export default eventHandler(() => {
coverId: 'a0559661270',
},
{
id: 7,
id: 6,
order: 7,
compilationId: 'ES00A',
title: 'Blowup',
artist: 6,
@@ -66,7 +73,8 @@ export default eventHandler(() => {
coverId: 'a1444895293',
},
{
id: 8,
id: 7,
order: 8,
compilationId: 'ES00A',
title: 'Guitar jet',
artist: 7,
@@ -75,7 +83,8 @@ export default eventHandler(() => {
coverId: 'a1494681687',
},
{
id: 9,
id: 8,
order: 9,
compilationId: 'ES00A',
title: 'Intercontinental radio waves',
artist: 8,
@@ -84,7 +93,8 @@ export default eventHandler(() => {
coverId: 'a0046738552',
},
{
id: 10,
id: 9,
order: 10,
compilationId: 'ES00A',
title: 'Here comes the sun',
artist: 9,
@@ -93,7 +103,8 @@ export default eventHandler(() => {
coverId: 'a4102567047',
},
{
id: 11,
id: 10,
order: 0,
compilationId: 'ES00A',
title: 'Like in the movies',
artist: 10,
@@ -102,7 +113,8 @@ export default eventHandler(() => {
coverId: 'a2203158939',
},
{
id: 21,
id: 11,
order: 1,
compilationId: 'ES00B',
title: 'Ce que révèle l\'éclipse',
artist: 0,
@@ -111,7 +123,8 @@ export default eventHandler(() => {
coverId: 'a3236746052',
},
{
id: 22,
id: 12,
order: 2,
compilationId: 'ES00B',
title: 'Bleedin\' Gums Mushrool',
artist: 1,
@@ -120,7 +133,8 @@ export default eventHandler(() => {
coverId: 'a1714786533',
},
{
id: 23,
id: 13,
order: 3,
compilationId: 'ES00B',
title: 'A lucid dream',
artist: 2,
@@ -129,7 +143,8 @@ export default eventHandler(() => {
coverId: 'a3772806156',
},
{
id: 24,
id: 14,
order: 4,
compilationId: 'ES00B',
title: 'Lights off',
artist: 3,
@@ -138,7 +153,8 @@ export default eventHandler(() => {
coverId: 'a1720372066',
},
{
id: 25,
id: 15,
order: 5,
compilationId: 'ES00B',
title: 'I\'m sentimental',
artist: 4,
@@ -147,7 +163,8 @@ export default eventHandler(() => {
coverId: 'a2333676849',
},
{
id: 26,
id: 16,
order: 6,
compilationId: 'ES00B',
title: 'Thrill or trip',
artist: 5,
@@ -156,7 +173,8 @@ export default eventHandler(() => {
coverId: 'a0559661270',
},
{
id: 27,
id: 17,
order: 7,
compilationId: 'ES00B',
title: 'Redhead',
artist: 6,
@@ -165,7 +183,8 @@ export default eventHandler(() => {
coverId: 'a0594426943',
},
{
id: 28,
id: 18,
order: 8,
compilationId: 'ES00B',
title: 'Supersonic twist',
artist: 7,
@@ -174,7 +193,8 @@ export default eventHandler(() => {
coverId: 'a1494681687',
},
{
id: 29,
id: 19,
order: 9,
compilationId: 'ES00B',
title: 'Flowers',
artist: 8,
@@ -183,7 +203,8 @@ export default eventHandler(() => {
coverId: 'a3644668199',
},
{
id: 30,
id: 20,
order: 10,
compilationId: 'ES00B',
title: 'The shade',
artist: 9,
@@ -192,7 +213,8 @@ export default eventHandler(() => {
coverId: 'a0804204790',
},
{
id: 31,
id: 21,
order: 0,
compilationId: 'ES00B',
title: 'Like in the movies',
artist: 10,
@@ -200,5 +222,225 @@ export default eventHandler(() => {
url: 'https://bruitblanc.bandcamp.com/track/like-in-the-movies',
coverId: 'a3647322740',
},
{
id: 22,
order: 1,
compilationId: 'ES01A',
title: 'He Walked In',
artist: 11,
start: 0,
url: 'https://kidcongothepinkmonkeybirds.bandcamp.com/track/he-walked-in',
coverId: 'a0336300523',
},
{
id: 23,
order: 2,
compilationId: 'ES01A',
title: 'The Third Wave',
artist: 12,
start: 854,
url: 'https://firefriend.bandcamp.com/track/the-third-wave',
coverId: 'a2803689859',
},
{
id: 24,
order: 3,
compilationId: 'ES01A',
title: 'Broadcaster',
artist: 13,
start: 0,
url: 'https://squiduk.bandcamp.com/track/broadcaster',
coverId: 'a3391719769',
},
{
id: 25,
order: 4,
compilationId: 'ES01A',
title: 'Mourn',
artist: 14,
start: 0,
url: 'https://lysistrata.bandcamp.com/track/mourn-2',
coverId: 'a0872900041',
},
{
id: 26,
order: 5,
compilationId: 'ES01A',
title: 'Let it Blow',
artist: 15,
start: 0,
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/let-it-blow',
coverId: 'a4000148031',
},
{
id: 27,
order: 6,
compilationId: 'ES01A',
title: 'Sunday Mourning',
artist: 16,
start: 0,
url: 'https://nightbeats.bandcamp.com/track/sunday-mourning',
coverId: 'a0031987121',
},
{
id: 28,
order: 7,
compilationId: 'ES01A',
title: '3030 Instrumental',
artist: 17,
start: 0,
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
coverId: 'a1948146136',
},
{
id: 29,
order: 8,
compilationId: 'ES01A',
title: 'Immortality Break',
artist: 18,
start: 0,
url: 'https://theaa.bandcamp.com/track/immortality-break',
coverId: 'a2749250329',
},
{
id: 30,
order: 9,
compilationId: 'ES01A',
title: 'Lazy Bones',
artist: 19,
start: 0,
url: 'https://woodenshjips.bandcamp.com/track/lazy-bones',
coverId: 'a1884221104',
},
{
id: 31,
order: 10,
compilationId: 'ES01A',
title: 'On the Train of Aches',
artist: 20,
start: 0,
url: 'https://silasjdirge.bandcamp.com/track/on-the-train-of-aches',
coverId: 'a1124177379',
},
{
id: 32,
order: 0,
compilationId: 'ES01A',
title: 'Me',
artist: 21,
start: 0,
url: 'https://secretcolours.bandcamp.com/track/me',
coverId: 'a1497022499',
},
{
id: 33,
order: 1,
compilationId: 'ES01B',
title: 'Lady Hawke Blues',
artist: 11,
start: 0,
url: 'https://kidcongothepinkmonkeybirds.bandcamp.com/track/lady-hawke-blues',
coverId: 'a2532623230',
},
{
id: 34,
order: 2,
compilationId: 'ES01B',
title: 'Dreamscapes',
artist: 12,
start: 0,
url: 'https://littlecloudrecords.bandcamp.com/track/dreamscapes',
coverId: 'a3498981203',
},
{
id: 35,
order: 3,
compilationId: 'ES01B',
title: 'Crispy Skin',
artist: 13,
start: 0,
url: 'https://squiduk.bandcamp.com/track/crispy-skin-2',
coverId: 'a2516727021',
},
{
id: 36,
order: 4,
compilationId: 'ES01B',
title: 'The Boy Who Stood Above The Earth',
artist: 14,
start: 0,
url: 'https://lysistrata.bandcamp.com/track/the-boy-who-stood-above-the-earth-2',
coverId: 'a0350933426',
},
{
id: 37,
order: 5,
compilationId: 'ES01B',
title: 'Better Off Alone',
artist: 15,
start: 0,
url: 'https://pabloxbroadcastingservices.bandcamp.com/track/better-off-alone',
coverId: 'a4000148031',
},
{
id: 38,
order: 6,
compilationId: 'ES01B',
title: 'Celebration #1',
artist: 16,
start: 0,
url: 'https://nightbeats.bandcamp.com/track/celebration-1',
coverId: 'a0031987121',
},
{
id: 39,
order: 7,
compilationId: 'ES01B',
title: '3030 Instrumental',
artist: 17,
start: 0,
url: 'https://delthefunkyhomosapien.bandcamp.com/track/3030',
coverId: 'a1948146136',
},
{
id: 40,
order: 8,
compilationId: 'ES01B',
title: 'The Emptiness Of Nothingness',
artist: 18,
start: 0,
url: 'https://theaa.bandcamp.com/track/the-emptiness-of-nothingness',
coverId: 'a1053923875',
},
{
id: 41,
order: 9,
compilationId: 'ES01B',
title: 'Rising',
artist: 19,
start: 0,
url: 'https://woodenshjips.bandcamp.com/track/rising',
coverId: 'a1884221104',
},
{
id: 42,
order: 10,
compilationId: 'ES01B',
title: 'The Last Time / Jealous Woman',
artist: 22,
start: 0,
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',
},
{
id: 43,
order: 0,
compilationId: 'ES01B',
title: 'Guajira Con Arpa',
artist: 23,
start: 0,
url: 'https://elpalmasmusic.bandcamp.com/track/guajira-con-arpa',
coverId: 'a3463036407',
},
]
})

View File

@@ -19,6 +19,7 @@ export interface Artist {
export interface Track {
id: number
order: number
compilationId: string
title: string
artist: Artist