Design Prot Merge #1
@@ -20,6 +20,15 @@
|
|||||||
isExplicit: boolean;
|
isExplicit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LocalTrackTarget {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextMenuTarget =
|
||||||
|
| { kind: 'youtube'; song: YouTubeSong }
|
||||||
|
| { kind: 'local'; track: LocalTrackTarget };
|
||||||
|
|
||||||
let { data, form } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
let searchValue = $state(data.query);
|
let searchValue = $state(data.query);
|
||||||
@@ -28,7 +37,7 @@
|
|||||||
let ytItems = $state<YouTubeSong[]>([]);
|
let ytItems = $state<YouTubeSong[]>([]);
|
||||||
let ytContinuation = $state('');
|
let ytContinuation = $state('');
|
||||||
let ytLoadingMore = $state(false);
|
let ytLoadingMore = $state(false);
|
||||||
let contextMenu = $state<{ song: YouTubeSong; x: number; y: number; openLeft: boolean } | null>(
|
let contextMenu = $state<(ContextMenuTarget & { x: number; y: number; openLeft: boolean }) | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,13 +111,13 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openContextMenu(event: MouseEvent, song: YouTubeSong) {
|
function openContextMenu(event: MouseEvent, target: ContextMenuTarget) {
|
||||||
if (data.playlists.length === 0) return;
|
if (data.playlists.length === 0) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const menuWidth = 208;
|
const menuWidth = 208;
|
||||||
const submenuWidth = 224;
|
const submenuWidth = 224;
|
||||||
contextMenu = {
|
contextMenu = {
|
||||||
song,
|
...target,
|
||||||
x: Math.min(event.clientX, window.innerWidth - menuWidth - 8),
|
x: Math.min(event.clientX, window.innerWidth - menuWidth - 8),
|
||||||
y: Math.min(event.clientY, window.innerHeight - 60),
|
y: Math.min(event.clientY, window.innerHeight - 60),
|
||||||
openLeft: event.clientX + menuWidth + submenuWidth + 16 > window.innerWidth
|
openLeft: event.clientX + menuWidth + submenuWidth + 16 > window.innerWidth
|
||||||
@@ -196,7 +205,11 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<ul class="mt-6 grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-4">
|
<ul class="mt-6 grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-4">
|
||||||
{#each tracks.items as track, i (track.id)}
|
{#each tracks.items as track, i (track.id)}
|
||||||
<li class="group animate-enter" style="animation-delay:{i * 40}ms">
|
<li
|
||||||
|
class="group animate-enter relative"
|
||||||
|
style="animation-delay:{i * 40}ms"
|
||||||
|
oncontextmenu={(e) => openContextMenu(e, { kind: 'local', track })}
|
||||||
|
>
|
||||||
<button type="button" onclick={() => togglePlayLocal(i)} class="block w-full text-left">
|
<button type="button" onclick={() => togglePlayLocal(i)} class="block w-full text-left">
|
||||||
<Cover
|
<Cover
|
||||||
trackId={track.id}
|
trackId={track.id}
|
||||||
@@ -277,7 +290,7 @@
|
|||||||
<li
|
<li
|
||||||
class="group animate-enter relative"
|
class="group animate-enter relative"
|
||||||
style="animation-delay:{i * 40}ms"
|
style="animation-delay:{i * 40}ms"
|
||||||
oncontextmenu={(e) => openContextMenu(e, song)}
|
oncontextmenu={(e) => openContextMenu(e, { kind: 'youtube', song })}
|
||||||
>
|
>
|
||||||
<button type="button" onclick={() => togglePlayYouTube(i)} class="block w-full text-left">
|
<button type="button" onclick={() => togglePlayYouTube(i)} class="block w-full text-left">
|
||||||
<Cover
|
<Cover
|
||||||
@@ -375,7 +388,7 @@
|
|||||||
{#each data.playlists as playlist (playlist.id)}
|
{#each data.playlists as playlist (playlist.id)}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/addYouTubeToPlaylist"
|
action={contextMenu.kind === 'youtube' ? '?/addYouTubeToPlaylist' : '?/addTrack'}
|
||||||
use:enhance={() =>
|
use:enhance={() =>
|
||||||
({ update }) => {
|
({ update }) => {
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
@@ -383,11 +396,15 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input type="hidden" name="playlistId" value={playlist.id} />
|
<input type="hidden" name="playlistId" value={playlist.id} />
|
||||||
<input type="hidden" name="videoId" value={contextMenu.song.videoId} />
|
{#if contextMenu.kind === 'youtube'}
|
||||||
<input type="hidden" name="title" value={contextMenu.song.title} />
|
<input type="hidden" name="videoId" value={contextMenu.song.videoId} />
|
||||||
<input type="hidden" name="artist" value={contextMenu.song.artist} />
|
<input type="hidden" name="title" value={contextMenu.song.title} />
|
||||||
<input type="hidden" name="durationSeconds" value={contextMenu.song.durationSeconds} />
|
<input type="hidden" name="artist" value={contextMenu.song.artist} />
|
||||||
<input type="hidden" name="thumbnailUrl" value={contextMenu.song.thumbnailUrl} />
|
<input type="hidden" name="durationSeconds" value={contextMenu.song.durationSeconds} />
|
||||||
|
<input type="hidden" name="thumbnailUrl" value={contextMenu.song.thumbnailUrl} />
|
||||||
|
{:else}
|
||||||
|
<input type="hidden" name="trackId" value={contextMenu.track.id} />
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left text-sm text-neutral-200 transition hover:bg-white/5"
|
class="flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left text-sm text-neutral-200 transition hover:bg-white/5"
|
||||||
@@ -397,7 +414,7 @@
|
|||||||
trackIds={[]}
|
trackIds={[]}
|
||||||
hue={hueFor(playlist.id)}
|
hue={hueFor(playlist.id)}
|
||||||
size="small"
|
size="small"
|
||||||
class="h-8 w-8 flex-shrink-0 rounded-md"
|
class="h-8 w-8 flex-shrink-0 rounded-lg"
|
||||||
/>
|
/>
|
||||||
<span class="truncate">{playlist.name}</span>
|
<span class="truncate">{playlist.name}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user