Merge pull request 'feature/spotify-export' (#8) from feature/spotify-export into dev
Reviewed-on: #8
This commit is contained in:
commit
604c368184
4 changed files with 91 additions and 40 deletions
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dicebear/core": "^10",
|
"@dicebear/core": "^10",
|
||||||
"@dicebear/styles": "^10",
|
"@dicebear/styles": "^10",
|
||||||
|
"@icons-pack/react-simple-icons": "^13",
|
||||||
"@nerdware/shared": "workspace:*",
|
"@nerdware/shared": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.101.0",
|
"@tanstack/react-query": "^5.101.0",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
|
import { useState } from "react"
|
||||||
import { Link } from "wouter"
|
import { Link } from "wouter"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { Crown, ExternalLink, Music } from "lucide-react"
|
import { Check, ClipboardList, Crown, Music } from "lucide-react"
|
||||||
import type { PlayerScore, RoomSnapshot } from "@nerdware/shared"
|
import { SiSpotify, SiYoutube } from "@icons-pack/react-simple-icons"
|
||||||
|
import type { BlindtestTrackInfo, PlayerScore, RoomSnapshot } from "@nerdware/shared"
|
||||||
import { Button } from "@workspace/ui/components/button"
|
import { Button } from "@workspace/ui/components/button"
|
||||||
import { useRoomStore } from "@/store/room"
|
import { useRoomStore } from "@/store/room"
|
||||||
import { Avatar } from "@/components/avatar"
|
import { Avatar } from "@/components/avatar"
|
||||||
|
|
||||||
|
/** Lien de recherche Spotify (le titre vidéo contient en général artiste + chanson). */
|
||||||
|
function spotifySearch(track: BlindtestTrackInfo): string {
|
||||||
|
const query = `${track.title} ${track.artist}`.trim()
|
||||||
|
return `https://open.spotify.com/search/${encodeURIComponent(query)}`
|
||||||
|
}
|
||||||
|
|
||||||
type Place = 1 | 2 | 3
|
type Place = 1 | 2 | 3
|
||||||
|
|
||||||
// Hauteur de marche, couleurs (or / argent / bronze) et taille d'avatar par place.
|
// Hauteur de marche, couleurs (or / argent / bronze) et taille d'avatar par place.
|
||||||
|
|
@ -198,43 +206,7 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gameTracks && gameTracks.length > 0 && (
|
{gameTracks && gameTracks.length > 0 && (
|
||||||
<section className="flex flex-col gap-2 text-left">
|
<TracksRecap tracks={gameTracks} />
|
||||||
<h3 className="text-muted-foreground flex items-center gap-1.5 text-sm font-medium">
|
|
||||||
<Music className="size-4" /> Les musiques de la partie
|
|
||||||
</h3>
|
|
||||||
<ul className="flex flex-col gap-2">
|
|
||||||
{gameTracks.map((t) => (
|
|
||||||
<li
|
|
||||||
key={t.youtubeId}
|
|
||||||
className="bg-muted/40 flex items-center gap-3 rounded-lg p-2"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={`https://img.youtube.com/vi/${t.youtubeId}/mqdefault.jpg`}
|
|
||||||
alt=""
|
|
||||||
className="h-10 w-16 shrink-0 rounded object-cover"
|
|
||||||
/>
|
|
||||||
<span className="min-w-0 flex-1">
|
|
||||||
<span className="line-clamp-1 text-xs font-medium">
|
|
||||||
{t.title}
|
|
||||||
</span>
|
|
||||||
<span className="text-muted-foreground line-clamp-1 text-[10px]">
|
|
||||||
ajouté par {t.submittedByName}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
href={t.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
title="Ouvrir sur YouTube"
|
|
||||||
>
|
|
||||||
<Button size="icon-sm" variant="secondary">
|
|
||||||
<ExternalLink />
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|
@ -256,3 +228,67 @@ export function GameEndView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TracksRecap({ tracks }: { tracks: BlindtestTrackInfo[] }) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
async function copyPlaylist() {
|
||||||
|
const text = tracks.map((t) => `${t.title} — ${t.artist}`).join("\n")
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 1500)
|
||||||
|
} catch {
|
||||||
|
// presse-papier indisponible : on ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col gap-2 text-left">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-muted-foreground flex items-center gap-1.5 text-sm font-medium">
|
||||||
|
<Music className="size-4" /> Les musiques de la partie
|
||||||
|
</h3>
|
||||||
|
<Button size="sm" variant="secondary" onClick={copyPlaylist}>
|
||||||
|
{copied ? <Check className="text-green-500" /> : <ClipboardList />}
|
||||||
|
{copied ? "Copié" : "Playlist"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ul className="flex flex-col gap-2">
|
||||||
|
{tracks.map((t) => (
|
||||||
|
<li
|
||||||
|
key={t.youtubeId}
|
||||||
|
className="bg-muted/40 flex items-center gap-3 rounded-lg p-2"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`https://img.youtube.com/vi/${t.youtubeId}/mqdefault.jpg`}
|
||||||
|
alt=""
|
||||||
|
className="h-10 w-16 shrink-0 rounded object-cover"
|
||||||
|
/>
|
||||||
|
<span className="min-w-0 flex-1">
|
||||||
|
<span className="line-clamp-1 text-xs font-medium">{t.title}</span>
|
||||||
|
<span className="text-muted-foreground line-clamp-1 text-[10px]">
|
||||||
|
ajouté par {t.submittedByName}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href={spotifySearch(t)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Chercher sur Spotify"
|
||||||
|
>
|
||||||
|
<Button size="icon-sm" variant="secondary">
|
||||||
|
<SiSpotify color="default" />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
<a href={t.url} target="_blank" rel="noreferrer" title="Ouvrir sur YouTube">
|
||||||
|
<Button size="icon-sm" variant="secondary">
|
||||||
|
<SiYoutube color="default" />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
Trash2,
|
Trash2,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { SiYoutube } from "@icons-pack/react-simple-icons"
|
||||||
import type {
|
import type {
|
||||||
BlindtestMode,
|
BlindtestMode,
|
||||||
GameType,
|
GameType,
|
||||||
|
|
@ -361,6 +362,16 @@ function TrackSubmission({
|
||||||
className="h-10 w-16 shrink-0 rounded object-cover"
|
className="h-10 w-16 shrink-0 rounded object-cover"
|
||||||
/>
|
/>
|
||||||
<span className="line-clamp-2 flex-1 text-xs">{t.title}</span>
|
<span className="line-clamp-2 flex-1 text-xs">{t.title}</span>
|
||||||
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=${t.youtubeId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Ouvrir sur YouTube"
|
||||||
|
>
|
||||||
|
<Button size="icon-sm" variant="secondary">
|
||||||
|
<SiYoutube color="default" />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
<Button
|
<Button
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|
@ -389,7 +400,7 @@ function TrackSubmission({
|
||||||
disabled={submitting || !url.trim()}
|
disabled={submitting || !url.trim()}
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
>
|
>
|
||||||
Ajouter
|
<SiYoutube color="default" /> Ajouter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
3
bun.lock
3
bun.lock
|
|
@ -40,6 +40,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dicebear/core": "^10",
|
"@dicebear/core": "^10",
|
||||||
"@dicebear/styles": "^10",
|
"@dicebear/styles": "^10",
|
||||||
|
"@icons-pack/react-simple-icons": "^13",
|
||||||
"@nerdware/shared": "workspace:*",
|
"@nerdware/shared": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.101.0",
|
"@tanstack/react-query": "^5.101.0",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
|
|
@ -295,6 +296,8 @@
|
||||||
|
|
||||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||||
|
|
||||||
|
"@icons-pack/react-simple-icons": ["@icons-pack/react-simple-icons@13.13.0", "", { "peerDependencies": { "react": "^16.13 || ^17 || ^18 || ^19" } }, "sha512-B5HhQMIpcSH4z8IZ8HFhD59CboHceKYMpPC9kAwGyKntvPdyJJv26DLu4Z1wAjcCLyrJhf11tMhiQGom9Rxb9g=="],
|
||||||
|
|
||||||
"@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="],
|
"@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="],
|
||||||
|
|
||||||
"@inquirer/checkbox": ["@inquirer/checkbox@4.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA=="],
|
"@inquirer/checkbox": ["@inquirer/checkbox@4.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA=="],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue