web-player: split player, navigation and accent state into focused modules
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { isSectionActive, appNavLinks } from '$lib/state/navigation.svelte';
|
||||
import { appNavLinks, isNavActive } from './navLinks';
|
||||
import { playlistCover } from '$lib/utils/hrefs';
|
||||
import { liked } from '$lib/player/liked.svelte';
|
||||
import { createPlaylistModal } from '$lib/state/playlists.svelte';
|
||||
import { createPlaylistModal } from '$lib/state/panels.svelte';
|
||||
import type { SessionUser } from '$lib/types';
|
||||
import ListRow from '../ui/media/ListRow.svelte';
|
||||
import IconButton from '../ui/primitives/IconButton.svelte';
|
||||
@@ -14,7 +15,7 @@
|
||||
interface SidebarPlaylist {
|
||||
id: string;
|
||||
name: string;
|
||||
coverTrackIds: (string | number)[];
|
||||
coverTrackIds: string[];
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -29,10 +30,6 @@
|
||||
const visiblePlaylists = $derived(playlists.slice(0, SIDEBAR_PLAYLISTS_LIMIT));
|
||||
|
||||
const navLinks = $derived(appNavLinks(user, { playlists: playlistCount, liked: liked.count }));
|
||||
|
||||
function isActive(href: string) {
|
||||
return isSectionActive(href, page.url.pathname, page.data.section);
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside
|
||||
@@ -45,7 +42,7 @@
|
||||
|
||||
<nav class="flex flex-col gap-1">
|
||||
{#each navLinks as link (link.href)}
|
||||
{@const active = isActive(link.href)}
|
||||
{@const active = isNavActive(link.href)}
|
||||
<a
|
||||
href={link.href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
@@ -86,9 +83,7 @@
|
||||
{active}
|
||||
size="xs"
|
||||
title={playlist.name}
|
||||
coverSrc="/api/playlists/{playlist.id}/cover?size=small&v={encodeURIComponent(
|
||||
playlist.updatedAt
|
||||
)}"
|
||||
coverSrc={playlistCover(playlist, 'small')}
|
||||
trackIds={playlist.coverTrackIds}
|
||||
class="px-3 py-2"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { isSectionActive, appNavLinks } from '$lib/state/navigation.svelte';
|
||||
import { appNavLinks, isNavActive } from './navLinks';
|
||||
import type { SessionUser } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
@@ -11,10 +10,6 @@
|
||||
let { user, class: klass = '' }: Props = $props();
|
||||
|
||||
const links = $derived(appNavLinks(user).filter((link) => link.primary));
|
||||
|
||||
function isActive(href: string) {
|
||||
return isSectionActive(href, page.url.pathname, page.data.section);
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav
|
||||
@@ -22,7 +17,7 @@
|
||||
style="padding-bottom:calc(0.4375rem + var(--mf-safe-b))"
|
||||
>
|
||||
{#each links as link (link.href)}
|
||||
{@const active = isActive(link.href)}
|
||||
{@const active = isNavActive(link.href)}
|
||||
<a
|
||||
href={link.href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/state';
|
||||
import type { SessionUser } from '$lib/types';
|
||||
import { SEARCH_DEBOUNCE_MS, SEARCH_MIN_LENGTH } from '$lib/config';
|
||||
import { searchHref } from '$lib/state/navigation.svelte';
|
||||
import { searchHref } from '$lib/utils/hrefs';
|
||||
import Search from '@lucide/svelte/icons/search';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { page } from '$app/state';
|
||||
import type { LucideIcon } from '@lucide/svelte';
|
||||
import Home from '@lucide/svelte/icons/house';
|
||||
import Compass from '@lucide/svelte/icons/compass';
|
||||
import ListMusic from '@lucide/svelte/icons/list-music';
|
||||
import Heart from '@lucide/svelte/icons/heart';
|
||||
import Disc from '@lucide/svelte/icons/disc-2';
|
||||
import Folder from '@lucide/svelte/icons/folder';
|
||||
import Upload from '@lucide/svelte/icons/upload';
|
||||
import type { SessionUser } from '$lib/types';
|
||||
|
||||
export interface AppNavLink {
|
||||
href: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
count?: number;
|
||||
primary: boolean;
|
||||
}
|
||||
|
||||
export interface AppNavCounts {
|
||||
playlists?: number;
|
||||
liked?: number;
|
||||
}
|
||||
|
||||
export function isNavActive(href: string): boolean {
|
||||
const section: string | null | undefined = page.data.section;
|
||||
if (section !== undefined) return section === href;
|
||||
return href === '/' ? page.url.pathname === '/' : page.url.pathname.startsWith(href);
|
||||
}
|
||||
|
||||
export function appNavLinks(user: SessionUser | null, counts: AppNavCounts = {}): AppNavLink[] {
|
||||
if (!user) return [{ href: '/explore', label: 'Descubrir', icon: Compass, primary: true }];
|
||||
return [
|
||||
{ href: '/', label: 'Inicio', icon: Home, primary: true },
|
||||
{ href: '/explore', label: 'Descubrir', icon: Compass, primary: true },
|
||||
{
|
||||
href: '/playlists',
|
||||
label: 'Playlists',
|
||||
icon: ListMusic,
|
||||
count: counts.playlists,
|
||||
primary: true
|
||||
},
|
||||
{ href: '/liked', label: 'Me gusta', icon: Heart, count: counts.liked, primary: true },
|
||||
{ href: '/albums', label: 'Mis álbumes', icon: Disc, primary: false },
|
||||
{ href: '/library', label: 'Canciones subidas', icon: Folder, primary: false },
|
||||
{ href: '/upload', label: 'Subir música', icon: Upload, primary: false }
|
||||
];
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
import Slider from '$lib/components/ui/primitives/Slider.svelte';
|
||||
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
|
||||
import { player } from '$lib/player/player.svelte';
|
||||
import { queuePanel } from '$lib/player/queuePanel.svelte';
|
||||
import { queuePanel } from '$lib/state/panels.svelte';
|
||||
import { fmtTime } from '$lib/utils/format';
|
||||
import Volume1 from '@lucide/svelte/icons/volume-1';
|
||||
import Volume2 from '@lucide/svelte/icons/volume-2';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { player } from '$lib/player/player.svelte';
|
||||
import { queuePanel } from '$lib/player/queuePanel.svelte';
|
||||
import { queuePanel } from '$lib/state/panels.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/media/MediaIdentity.svelte';
|
||||
import ListRow from '$lib/components/ui/media/ListRow.svelte';
|
||||
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
name?: string | null;
|
||||
ownerUserId?: string | number | null;
|
||||
ownerUserId?: string | null;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { trackCover } from '$lib/utils/hrefs';
|
||||
|
||||
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'hero' | 'fill';
|
||||
|
||||
interface Props {
|
||||
src?: string | null;
|
||||
trackIds?: (string | number)[];
|
||||
trackIds?: string[];
|
||||
size: Size;
|
||||
shape?: 'auto' | 'round';
|
||||
imageSize?: 'small' | 'medium' | 'large';
|
||||
@@ -76,7 +77,6 @@
|
||||
const showSrc = $derived(!!src && !srcFailed);
|
||||
const showMosaic = $derived(!showSrc && trackIds.length >= 4);
|
||||
const singleId = $derived(!showSrc && !showMosaic && trackIds.length > 0 ? trackIds[0] : null);
|
||||
const showSingle = $derived(singleId !== null && !singleFailed);
|
||||
</script>
|
||||
|
||||
<div class="relative overflow-hidden {DIMENSION[size]} {radius} {klass}">
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="grid h-full w-full grid-cols-2 grid-rows-2">
|
||||
{#each trackIds.slice(0, 4) as id (id)}
|
||||
<img
|
||||
src="/api/tracks/{id}/cover?size=small"
|
||||
src={trackCover(id, 'small')}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@@ -104,9 +104,9 @@
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if showSingle}
|
||||
{:else if singleId !== null && !singleFailed}
|
||||
<img
|
||||
src="/api/tracks/{singleId}/cover?size={resolvedImageSize}"
|
||||
src={trackCover(singleId, resolvedImageSize)}
|
||||
{alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
variant?: 'row' | 'card';
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
subtitleHref?: string | number | null;
|
||||
subtitleHref?: string | null;
|
||||
explicit?: boolean;
|
||||
trackId?: string | number;
|
||||
trackIds?: (string | number)[];
|
||||
trackId?: string;
|
||||
trackIds?: string[];
|
||||
coverSrc?: string | null;
|
||||
art?: Snippet;
|
||||
overlay?: Snippet;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
src?: string | null;
|
||||
trackIds?: (string | number)[];
|
||||
trackIds?: string[];
|
||||
onPlay?: (event: MouseEvent) => void;
|
||||
art?: Snippet;
|
||||
oncontextmenu?: (event: MouseEvent) => void;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import ArtistLink from './ArtistLink.svelte';
|
||||
|
||||
interface Props {
|
||||
trackId?: string | number;
|
||||
trackIds?: (string | number)[];
|
||||
trackId?: string;
|
||||
trackIds?: string[];
|
||||
title: string;
|
||||
artist?: string | null;
|
||||
ownerUserId?: string | number | null;
|
||||
ownerUserId?: string | null;
|
||||
coverSrc?: string | null;
|
||||
size?: 'xs' | 'sm' | 'lg';
|
||||
explicit?: boolean;
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
<script lang="ts" module>
|
||||
import { MENU_WIDTH, SUBMENU_WIDTH } from '$lib/config';
|
||||
|
||||
export interface MenuPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
openLeft: boolean;
|
||||
}
|
||||
|
||||
export function contextMenuPosition(event: MouseEvent): MenuPosition {
|
||||
event.preventDefault();
|
||||
return {
|
||||
x: Math.max(8, Math.min(event.clientX, window.innerWidth - MENU_WIDTH - 8)),
|
||||
y: Math.max(8, Math.min(event.clientY, window.innerHeight - 60)),
|
||||
openLeft: event.clientX + MENU_WIDTH + SUBMENU_WIDTH + 16 > window.innerWidth
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { playlistCover } from '$lib/utils/hrefs';
|
||||
import type { LucideIcon } from '@lucide/svelte';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import MediaIdentity from '../media/MediaIdentity.svelte';
|
||||
@@ -32,7 +14,7 @@
|
||||
onClose: () => void;
|
||||
items: { icon: LucideIcon; label: string; onclick: () => void }[];
|
||||
playlistAction?: { action: string; fields: Record<string, string>; label: string };
|
||||
playlists?: { id: string; name: string }[];
|
||||
playlists?: { id: string; name: string; updatedAt: string }[];
|
||||
}
|
||||
|
||||
let { x, y, openLeft, onClose, items, playlistAction, playlists = [] }: Props = $props();
|
||||
@@ -97,7 +79,7 @@
|
||||
>
|
||||
<MediaIdentity
|
||||
title={playlist.name}
|
||||
coverSrc="/api/playlists/{playlist.id}/cover?size=small"
|
||||
coverSrc={playlistCover(playlist, 'small')}
|
||||
size="xs"
|
||||
titleClass="text-fg-2"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||
import { previousPage } from '$lib/state/navigation.svelte';
|
||||
import { previousPage } from '$lib/state/history.svelte';
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
|
||||
Reference in New Issue
Block a user