web-player: remove dead code and unused props
This commit is contained in:
@@ -50,7 +50,7 @@ The design system lives in `src/routes/layout.css` (`@theme` tokens plus `@utili
|
|||||||
|
|
||||||
- **Radii:** always `rounded-tag/thumb/control/art/art-lg/panel/panel-lg` (or `rounded-full` for pills and circles). Never `rounded-sm/md/lg/xl/2xl/3xl` or a bare `rounded`.
|
- **Radii:** always `rounded-tag/thumb/control/art/art-lg/panel/panel-lg` (or `rounded-full` for pills and circles). Never `rounded-sm/md/lg/xl/2xl/3xl` or a bare `rounded`.
|
||||||
- **Square sizes:** use `size-*` instead of repeating `h-N w-N` (icons: `size-icon-xs/sm/md/lg/xl`; covers: `size-cover-xs` through `size-cover-hero`).
|
- **Square sizes:** use `size-*` instead of repeating `h-N w-N` (icons: `size-icon-xs/sm/md/lg/xl`; covers: `size-cover-xs` through `size-cover-hero`).
|
||||||
- **Typography:** use the recipes `text-display-1/2/3/4`, `text-eyebrow`, `text-body`, `text-count`, with tracking through `tracking-display`/`tracking-eyebrow` rather than a loose `tracking-[…]`.
|
- **Typography:** use the recipes `text-display-1/2/3/4`, `text-eyebrow`, `text-body`, with tracking through `tracking-display`/`tracking-eyebrow` rather than a loose `tracking-[…]`.
|
||||||
- **Spacing:** stick to the Tailwind scale in `.5` steps and avoid quarters like `.25`/`.75`, unless the semantic token calls for it (for example `mb-4.5` in `SectionHeading`).
|
- **Spacing:** stick to the Tailwind scale in `.5` steps and avoid quarters like `.25`/`.75`, unless the semantic token calls for it (for example `mb-4.5` in `SectionHeading`).
|
||||||
- **Colors:** always use `--mf-*`/`text-*`/`bg-*`/`border-*` from `theme.css`/`tokens.ts`. Never a literal hex or `rgba()` inside a `.svelte` file; those literals only belong in `theme.css`/`tokens.ts` as the source of truth.
|
- **Colors:** always use `--mf-*`/`text-*`/`bg-*`/`border-*` from `theme.css`/`tokens.ts`. Never a literal hex or `rgba()` inside a `.svelte` file; those literals only belong in `theme.css`/`tokens.ts` as the source of truth.
|
||||||
- **Inline `style="…"` and arbitrary `-[…]` values:** only the ones whitelisted in `scripts/check-tokens.mjs` (the stagger variable `--i`, tile hue, menu position, progress width, `text-[clamp(…)]`, `leading-[…]` on login's display type, `grid-cols-[auto_1fr]`, `max-h-[85dvh]`, `my-`/`py-[Ndvh]` for vertical centering, `transition-[filter]` on genre tiles, and `bg-[image:var(--mf-*)]`). Everything else should become a token.
|
- **Inline `style="…"` and arbitrary `-[…]` values:** only the ones whitelisted in `scripts/check-tokens.mjs` (the stagger variable `--i`, tile hue, menu position, progress width, `text-[clamp(…)]`, `leading-[…]` on login's display type, `grid-cols-[auto_1fr]`, `max-h-[85dvh]`, `my-`/`py-[Ndvh]` for vertical centering, `transition-[filter]` on genre tiles, and `bg-[image:var(--mf-*)]`). Everything else should become a token.
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
{#if visiblePlaylists.length > 0}
|
{#if visiblePlaylists.length > 0}
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Crear playlist"
|
label="Crear playlist"
|
||||||
tone="plain"
|
plain
|
||||||
size="xs"
|
size="xs"
|
||||||
onclick={() => createPlaylistModal.show()}
|
onclick={() => createPlaylistModal.show()}
|
||||||
>
|
>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => createPlaylistModal.show()}
|
onclick={() => createPlaylistModal.show()}
|
||||||
class="flex items-center gap-2 rounded-control px-3 py-2 text-xs text-fg-2 transition-colors hover:bg-hover hover:text-fg-2"
|
class="flex items-center gap-2 rounded-control px-3 py-2 text-xs text-fg-2 transition-colors hover:bg-hover"
|
||||||
>
|
>
|
||||||
<Plus class="size-3.5" />
|
<Plus class="size-3.5" />
|
||||||
Crear tu primera lista
|
Crear tu primera lista
|
||||||
|
|||||||
@@ -54,14 +54,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="hidden items-center gap-3.5 px-6.5 pt-4 pb-3 sm:flex">
|
<header class="hidden items-center gap-3.5 px-6.5 pt-4 pb-3 sm:flex">
|
||||||
<IconButton
|
<IconButton label="Atrás" shape="round" surface size="xs" onclick={() => history.back()}>
|
||||||
label="Atrás"
|
|
||||||
shape="round"
|
|
||||||
tone="subtle"
|
|
||||||
surface
|
|
||||||
size="xs"
|
|
||||||
onclick={() => history.back()}
|
|
||||||
>
|
|
||||||
<ChevronLeft class="size-icon-sm" />
|
<ChevronLeft class="size-icon-sm" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
@@ -91,9 +84,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="ml-auto shrink-0">
|
||||||
|
|
||||||
<div class="shrink-0">
|
|
||||||
{#if user}
|
{#if user}
|
||||||
<AccountMenu {user} {accountUrl}>
|
<AccountMenu {user} {accountUrl}>
|
||||||
{#snippet trigger({ toggle, open })}
|
{#snippet trigger({ toggle, open })}
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden w-(--mf-player-side-w) shrink-0 items-center justify-end gap-3.5 sm:flex">
|
<div class="flex w-(--mf-player-side-w) shrink-0 items-center justify-end gap-3.5">
|
||||||
<IconButton
|
<IconButton
|
||||||
label={player.muted ? 'Activar sonido' : 'Silenciar'}
|
label={player.muted ? 'Activar sonido' : 'Silenciar'}
|
||||||
tone="plain"
|
plain
|
||||||
size="xs"
|
size="xs"
|
||||||
onclick={() => player.toggleMute()}
|
onclick={() => player.toggleMute()}
|
||||||
>
|
>
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Cola de reproducción"
|
label="Cola de reproducción"
|
||||||
tone="plain"
|
plain
|
||||||
size="xs"
|
size="xs"
|
||||||
pressed={queuePanel.open}
|
pressed={queuePanel.open}
|
||||||
onclick={() => queuePanel.toggle()}
|
onclick={() => queuePanel.toggle()}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
>
|
>
|
||||||
<div class="mb-5 flex items-center justify-between">
|
<div class="mb-5 flex items-center justify-between">
|
||||||
<h2 class="font-display text-base font-medium tracking-tight text-fg">En cola</h2>
|
<h2 class="font-display text-base font-medium tracking-tight text-fg">En cola</h2>
|
||||||
<IconButton label="Cerrar cola" tone="plain" size="xs" onclick={() => queuePanel.close()}>
|
<IconButton label="Cerrar cola" plain size="xs" onclick={() => queuePanel.close()}>
|
||||||
<X class="size-icon-md" />
|
<X class="size-icon-md" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
{#if !compact}
|
{#if !compact}
|
||||||
<IconButton
|
<IconButton
|
||||||
label={isLiked ? 'Quitar de Me gusta' : 'Añadir a Me gusta'}
|
label={isLiked ? 'Quitar de Me gusta' : 'Añadir a Me gusta'}
|
||||||
tone="plain"
|
plain
|
||||||
size="md"
|
size="md"
|
||||||
pressed={isLiked}
|
pressed={isLiked}
|
||||||
onclick={toggleLike}
|
onclick={toggleLike}
|
||||||
|
|||||||
@@ -81,10 +81,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
{#if helperText}
|
{#if helperText}
|
||||||
<p class="text-sm text-fg-2">{helperText}</p>
|
<p class="text-sm text-fg-2">{helperText}</p>
|
||||||
{:else}
|
|
||||||
<span></span>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex shrink-0 items-center gap-3">
|
<div class="ml-auto flex shrink-0 items-center gap-3">
|
||||||
<Button type="button" variant="secondary" onclick={onCancel}>Cancelar</Button>
|
<Button type="button" variant="secondary" onclick={onCancel}>Cancelar</Button>
|
||||||
<Button type="submit" disabled={submitting || !canSubmit}>
|
<Button type="submit" disabled={submitting || !canSubmit}>
|
||||||
{submitting ? submittingLabel : submitLabel}
|
{submitting ? submittingLabel : submitLabel}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<a
|
<a
|
||||||
href="/user/{ownerUserId}"
|
href="/user/{ownerUserId}"
|
||||||
onclick={(e) => e.stopPropagation()}
|
onclick={(e) => e.stopPropagation()}
|
||||||
class="block max-w-full truncate hover:text-fg-2 hover:underline {klass}"
|
class="block max-w-full truncate hover:underline {klass}"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
size: Size;
|
size: Size;
|
||||||
shape?: 'auto' | 'round';
|
shape?: 'auto' | 'round';
|
||||||
imageSize?: 'small' | 'medium' | 'large';
|
imageSize?: 'small' | 'medium' | 'large';
|
||||||
fallback?: 'music' | 'gradient' | 'none';
|
|
||||||
alt?: string;
|
alt?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
size,
|
size,
|
||||||
shape = 'auto',
|
shape = 'auto',
|
||||||
imageSize,
|
imageSize,
|
||||||
fallback = 'music',
|
|
||||||
alt = '',
|
alt = '',
|
||||||
class: klass = '',
|
class: klass = '',
|
||||||
children
|
children
|
||||||
@@ -116,7 +114,7 @@
|
|||||||
onerror={() => (singleFailed = true)}
|
onerror={() => (singleFailed = true)}
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
{:else if fallback === 'music'}
|
{:else}
|
||||||
<div class="grid h-full w-full place-items-center bg-surface">
|
<div class="grid h-full w-full place-items-center bg-surface">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor" class="h-2/5 w-2/5 text-fg-2">
|
<svg viewBox="0 0 24 24" fill="currentColor" class="h-2/5 w-2/5 text-fg-2">
|
||||||
<path
|
<path
|
||||||
@@ -124,10 +122,6 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{:else if fallback === 'gradient'}
|
|
||||||
<div class="h-full w-full bg-(image:--mf-cover-grad)"></div>
|
|
||||||
{:else}
|
|
||||||
<div class="h-full w-full bg-surface"></div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
size?: number;
|
|
||||||
barWidth?: number;
|
|
||||||
paused?: boolean;
|
paused?: boolean;
|
||||||
overlay?: boolean;
|
overlay?: boolean;
|
||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let { paused = false, overlay = false, class: klass = '' }: Props = $props();
|
||||||
size = 13,
|
|
||||||
barWidth = 2,
|
|
||||||
paused = false,
|
|
||||||
overlay = false,
|
|
||||||
class: klass = ''
|
|
||||||
}: Props = $props();
|
|
||||||
|
|
||||||
const delays = [0, 0.3, 0.6];
|
const delays = [0, 0.3, 0.6];
|
||||||
</script>
|
</script>
|
||||||
@@ -30,11 +22,11 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-end justify-center gap-0.5 {klass}" style="height:{size}px">
|
<div class="flex h-3.5 items-end justify-center gap-0.5 {klass}">
|
||||||
{#each delays as delay (delay)}
|
{#each delays as delay (delay)}
|
||||||
<span
|
<span
|
||||||
class="rounded-full bg-accent"
|
class="h-full w-0.5 rounded-full bg-accent"
|
||||||
style="width:{barWidth}px; height:100%; animation:equalize .9s ease-in-out {delay}s infinite; animation-play-state:{paused
|
style="animation:equalize .9s ease-in-out {delay}s infinite; animation-play-state:{paused
|
||||||
? 'paused'
|
? 'paused'
|
||||||
: 'running'}"
|
: 'running'}"
|
||||||
></span>
|
></span>
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
following: boolean;
|
following: boolean;
|
||||||
userId?: string;
|
|
||||||
invalidateAll?: boolean;
|
invalidateAll?: boolean;
|
||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { following: initial, userId, invalidateAll = false, class: klass = '' }: Props = $props();
|
let { following: initial, invalidateAll = false, class: klass = '' }: Props = $props();
|
||||||
|
|
||||||
let following = $state(untrack(() => initial));
|
let following = $state(untrack(() => initial));
|
||||||
let submitting = $state(false);
|
let submitting = $state(false);
|
||||||
@@ -30,9 +29,6 @@
|
|||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if userId}
|
|
||||||
<input type="hidden" name="userId" value={userId} />
|
|
||||||
{/if}
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
class?: string;
|
class?: string;
|
||||||
art?: Snippet;
|
art?: Snippet;
|
||||||
overlay?: Snippet;
|
overlay?: Snippet;
|
||||||
badge?: Snippet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -34,8 +33,7 @@
|
|||||||
titleClass = '',
|
titleClass = '',
|
||||||
class: klass = '',
|
class: klass = '',
|
||||||
art,
|
art,
|
||||||
overlay,
|
overlay
|
||||||
badge
|
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -61,7 +59,6 @@
|
|||||||
<span class="min-w-0 truncate text-sm {active ? 'text-accent-soft' : 'text-fg'} {titleClass}"
|
<span class="min-w-0 truncate text-sm {active ? 'text-accent-soft' : 'text-fg'} {titleClass}"
|
||||||
>{title}</span
|
>{title}</span
|
||||||
>
|
>
|
||||||
{@render badge?.()}
|
|
||||||
</div>
|
</div>
|
||||||
<ArtistLink name={artist} {ownerUserId} class="mt-0.5 text-xs text-fg-2" />
|
<ArtistLink name={artist} {ownerUserId} class="mt-0.5 text-xs text-fg-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -138,8 +138,6 @@
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
use:pressable={() => onPlay(i)}
|
use:pressable={() => onPlay(i)}
|
||||||
onclick={(event) => event.stopPropagation()}
|
|
||||||
onkeydown={(event) => event.stopPropagation()}
|
|
||||||
class="flex min-w-0 flex-1 text-left"
|
class="flex min-w-0 flex-1 text-left"
|
||||||
>
|
>
|
||||||
<MediaIdentity
|
<MediaIdentity
|
||||||
@@ -173,13 +171,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{#if rowAction}
|
{#if rowAction}
|
||||||
{#if rowAction.action}
|
{#if rowAction.action}
|
||||||
<form
|
<form method="POST" action={rowAction.action} use:enhance>
|
||||||
method="POST"
|
|
||||||
action={rowAction.action}
|
|
||||||
use:enhance={() =>
|
|
||||||
async ({ update }) =>
|
|
||||||
update()}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="trackId" value={track.id} />
|
<input type="hidden" name="trackId" value={track.id} />
|
||||||
<IconButton type="submit" label={rowAction.label} size="xs" revealOnHover>
|
<IconButton type="submit" label={rowAction.label} size="xs" revealOnHover>
|
||||||
<rowAction.icon class="size-icon-sm" />
|
<rowAction.icon class="size-icon-sm" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
type?: 'button' | 'submit';
|
type?: 'button' | 'submit';
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
shape?: 'square' | 'round';
|
shape?: 'square' | 'round';
|
||||||
tone?: 'muted' | 'subtle' | 'plain';
|
plain?: boolean;
|
||||||
pressed?: boolean;
|
pressed?: boolean;
|
||||||
surface?: boolean;
|
surface?: boolean;
|
||||||
revealOnHover?: boolean;
|
revealOnHover?: boolean;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
type = 'button',
|
type = 'button',
|
||||||
size = 'sm',
|
size = 'sm',
|
||||||
shape = 'square',
|
shape = 'square',
|
||||||
tone = 'muted',
|
plain = false,
|
||||||
pressed,
|
pressed,
|
||||||
surface = false,
|
surface = false,
|
||||||
revealOnHover = false,
|
revealOnHover = false,
|
||||||
@@ -40,18 +40,12 @@
|
|||||||
lg: 'size-10'
|
lg: 'size-10'
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEXT: Record<'muted' | 'subtle' | 'plain', string> = {
|
|
||||||
muted: 'text-fg-2 hover:text-fg',
|
|
||||||
subtle: 'text-fg-2 hover:text-fg',
|
|
||||||
plain: 'text-fg-2 hover:text-fg'
|
|
||||||
};
|
|
||||||
|
|
||||||
const background = $derived(
|
const background = $derived(
|
||||||
surface ? 'bg-surface-2 hover:bg-surface-hover' : tone === 'plain' ? '' : 'hover:bg-hover'
|
surface ? 'bg-surface-2 hover:bg-surface-hover' : plain ? '' : 'hover:bg-hover'
|
||||||
);
|
);
|
||||||
|
|
||||||
const classes = $derived(
|
const classes = $derived(
|
||||||
`grid place-items-center transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:outline-none ${SIZE[size]} ${shape === 'round' ? 'rounded-full' : 'rounded-control'} ${pressed ? 'text-accent' : TEXT[tone]} ${background} ${
|
`grid place-items-center transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:outline-none ${SIZE[size]} ${shape === 'round' ? 'rounded-full' : 'rounded-control'} ${pressed ? 'text-accent' : 'text-fg-2 hover:text-fg'} ${background} ${
|
||||||
revealOnHover ? 'sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100' : ''
|
revealOnHover ? 'sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100' : ''
|
||||||
} ${klass}`
|
} ${klass}`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
class="relative z-(--z-raised) flex-1 px-4 py-1.5 text-center text-xs font-medium transition-colors {value ===
|
class="relative z-(--z-raised) flex-1 px-4 py-1.5 text-center text-xs font-medium transition-colors {value ===
|
||||||
option.value
|
option.value
|
||||||
? 'text-ink'
|
? 'text-ink'
|
||||||
: 'text-fg-2 hover:text-fg-2'}"
|
: 'text-fg-2'}"
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { DEFAULT_ACCENT } from '$lib/theme/color';
|
import { DEFAULT_ACCENT } from '$lib/theme/color';
|
||||||
import { extractAccent, type Accent } from '$lib/theme/palette';
|
import { extractAccent } from '$lib/theme/palette';
|
||||||
import { shuffle } from '$lib/data/collections';
|
import { shuffle } from '$lib/data/collections';
|
||||||
import { dialog } from '$lib/state/dialog.svelte';
|
import { dialog } from '$lib/state/dialog.svelte';
|
||||||
import { ListenTracker } from './listenTracker';
|
import { ListenTracker } from './listenTracker';
|
||||||
@@ -17,12 +17,6 @@ export interface PlayerTrack {
|
|||||||
listensCount?: number | string;
|
listensCount?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Playlist {
|
|
||||||
id: string | number;
|
|
||||||
name: string;
|
|
||||||
trackIds: (string | number)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueueItem {
|
export interface QueueItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -139,7 +133,6 @@ class PlayerState {
|
|||||||
muted = $state(browser ? readStorage(MUTED_KEY) === 'true' : false);
|
muted = $state(browser ? readStorage(MUTED_KEY) === 'true' : false);
|
||||||
loading = $state(false);
|
loading = $state(false);
|
||||||
recentlyPlayed = $state<PlayerTrack[]>([]);
|
recentlyPlayed = $state<PlayerTrack[]>([]);
|
||||||
playlists = $state<Playlist[]>([]);
|
|
||||||
shuffle = $state(false);
|
shuffle = $state(false);
|
||||||
repeat = $state(false);
|
repeat = $state(false);
|
||||||
|
|
||||||
@@ -147,7 +140,7 @@ class PlayerState {
|
|||||||
|
|
||||||
#audio: HTMLAudioElement | null = null;
|
#audio: HTMLAudioElement | null = null;
|
||||||
#loadToken = 0;
|
#loadToken = 0;
|
||||||
#accentCache = new Map<string, Accent>();
|
#accentCache = new Map<string, string>();
|
||||||
#rafId: number | null = null;
|
#rafId: number | null = null;
|
||||||
#listen = new ListenTracker();
|
#listen = new ListenTracker();
|
||||||
|
|
||||||
@@ -157,10 +150,6 @@ class PlayerState {
|
|||||||
this.current.duration > 0 ? Math.min(100, (this.progress / this.current.duration) * 100) : 0
|
this.current.duration > 0 ? Math.min(100, (this.progress / this.current.duration) * 100) : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
get isPlaying() {
|
|
||||||
return this.playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
#audioEl(): HTMLAudioElement | null {
|
#audioEl(): HTMLAudioElement | null {
|
||||||
if (!browser) return null;
|
if (!browser) return null;
|
||||||
if (this.#audio) return this.#audio;
|
if (this.#audio) return this.#audio;
|
||||||
@@ -301,14 +290,14 @@ class PlayerState {
|
|||||||
const key = String(id);
|
const key = String(id);
|
||||||
const cached = this.#accentCache.get(key);
|
const cached = this.#accentCache.get(key);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
this.accentColor = cached.accent;
|
this.accentColor = cached;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await extractAccent(`/api/tracks/${id}/cover?size=small`);
|
const result = await extractAccent(`/api/tracks/${id}/cover?size=small`);
|
||||||
if (this.currentId !== id) return;
|
if (this.currentId !== id) return;
|
||||||
if (result) {
|
if (result) {
|
||||||
this.#accentCache.set(key, result);
|
this.#accentCache.set(key, result);
|
||||||
this.accentColor = result.accent;
|
this.accentColor = result;
|
||||||
} else {
|
} else {
|
||||||
this.accentColor = null;
|
this.accentColor = null;
|
||||||
}
|
}
|
||||||
@@ -395,12 +384,6 @@ class PlayerState {
|
|||||||
this.#syncMediaSessionMetadata(track);
|
this.#syncMediaSessionMetadata(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistTracks(ids: (string | number)[]): PlayerTrack[] {
|
|
||||||
return ids
|
|
||||||
.map((id) => this.tracks.find((t) => t.id === id))
|
|
||||||
.filter((t): t is PlayerTrack => Boolean(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
playQueue(list: QueueItem[], startIndex = 0) {
|
playQueue(list: QueueItem[], startIndex = 0) {
|
||||||
if (list.length === 0) return;
|
if (list.length === 0) return;
|
||||||
this.tracks = list.map(toTrack);
|
this.tracks = list.map(toTrack);
|
||||||
@@ -415,10 +398,6 @@ class PlayerState {
|
|||||||
else this.playQueue(list, index);
|
else this.playQueue(list, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
playNextItem(item: QueueItem) {
|
|
||||||
this.playNext([item]);
|
|
||||||
}
|
|
||||||
|
|
||||||
playNext(items: QueueItem[]) {
|
playNext(items: QueueItem[]) {
|
||||||
if (items.length === 0) return;
|
if (items.length === 0) return;
|
||||||
if (this.currentId === null) {
|
if (this.currentId === null) {
|
||||||
@@ -452,13 +431,6 @@ class PlayerState {
|
|||||||
this.#loadCurrent();
|
this.#loadCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
playPlaylist(id: string | number) {
|
|
||||||
const playlist = this.playlists.find((p) => p.id === id);
|
|
||||||
if (!playlist) return;
|
|
||||||
const items = this.playlistTracks(playlist.trackIds);
|
|
||||||
if (items.length > 0) this.playQueue(items, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
const audio = this.#audioEl();
|
const audio = this.#audioEl();
|
||||||
if (!audio || this.currentId === null) return;
|
if (!audio || this.currentId === null) return;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function sessionKey(): Promise<Uint8Array> {
|
|||||||
return keyPromise;
|
return keyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sessionCookieOptions(url: URL) {
|
function sessionCookieOptions(url: URL) {
|
||||||
return {
|
return {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: 'lax' as const,
|
sameSite: 'lax' as const,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function createTrackMenu() {
|
|||||||
},
|
},
|
||||||
playNext() {
|
playNext() {
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
player.playNextItem(toQueueItem(state.track));
|
player.playNext([toQueueItem(state.track)]);
|
||||||
state = null;
|
state = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { ACCENT_LIGHTNESS } from './color';
|
import { ACCENT_LIGHTNESS } from './color';
|
||||||
|
|
||||||
export interface Accent {
|
|
||||||
accent: string;
|
|
||||||
gradient: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function srgbToLinear(channel: number): number {
|
function srgbToLinear(channel: number): number {
|
||||||
const c = channel / 255;
|
const c = channel / 255;
|
||||||
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||||
@@ -45,7 +40,7 @@ function loadImage(url: string): Promise<HTMLImageElement> {
|
|||||||
|
|
||||||
const BUCKETS = 36;
|
const BUCKETS = 36;
|
||||||
|
|
||||||
export async function extractAccent(url: string): Promise<Accent | null> {
|
export async function extractAccent(url: string): Promise<string | null> {
|
||||||
if (!browser) return null;
|
if (!browser) return null;
|
||||||
|
|
||||||
let img: HTMLImageElement;
|
let img: HTMLImageElement;
|
||||||
@@ -104,11 +99,6 @@ export async function extractAccent(url: string): Promise<Accent | null> {
|
|||||||
|
|
||||||
const h = hue.toFixed(1);
|
const h = hue.toFixed(1);
|
||||||
const c = chroma.toFixed(3);
|
const c = chroma.toFixed(3);
|
||||||
const c2 = (chroma * 0.85).toFixed(3);
|
|
||||||
const h2 = ((hue + 25) % 360).toFixed(1);
|
|
||||||
|
|
||||||
return {
|
return `oklch(${ACCENT_LIGHTNESS}% ${c} ${h})`;
|
||||||
accent: `oklch(${ACCENT_LIGHTNESS}% ${c} ${h})`,
|
|
||||||
gradient: `linear-gradient(135deg, oklch(74% ${c} ${h}), oklch(56% ${c2} ${h2}))`
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { createApiClient, unwrapOrError } from '$lib/server/api';
|
import { createApiClient, unwrapOrError } from '$lib/server/api';
|
||||||
import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/playlistActions';
|
import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/playlistActions';
|
||||||
import { followUserAction, unfollowUserAction } from '$lib/server/followActions';
|
|
||||||
import {
|
import {
|
||||||
EXPLORE_ALBUMS_PAGE_SIZE,
|
EXPLORE_ALBUMS_PAGE_SIZE,
|
||||||
EXPLORE_PAGE_SIZE as PAGE_SIZE,
|
EXPLORE_PAGE_SIZE as PAGE_SIZE,
|
||||||
@@ -66,7 +65,5 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
addTrack: addTrackAction,
|
addTrack: addTrackAction,
|
||||||
addAlbumToPlaylist: addAlbumToPlaylistAction,
|
addAlbumToPlaylist: addAlbumToPlaylistAction
|
||||||
follow: followUserAction,
|
|
||||||
unfollow: unfollowUserAction
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { createApiClient, optionalUser, unwrapOrError } from '$lib/server/api';
|
import { createApiClient, optionalUser, unwrapOrError } from '$lib/server/api';
|
||||||
import { fetchFollowList } from '$lib/server/follows';
|
import { fetchFollowList } from '$lib/server/follows';
|
||||||
import { followUserAction, unfollowUserAction } from '$lib/server/followActions';
|
|
||||||
import { FOLLOW_LIST_PAGE_SIZE } from '$lib/config';
|
import { FOLLOW_LIST_PAGE_SIZE } from '$lib/config';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, locals, url, fetch, parent }) => {
|
export const load: PageServerLoad = async ({ params, locals, url, fetch, parent }) => {
|
||||||
@@ -25,8 +24,3 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
|
|||||||
viewerId: viewer?.sub ?? null
|
viewerId: viewer?.sub ?? null
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
|
||||||
follow: followUserAction,
|
|
||||||
unfollow: unfollowUserAction
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
import PageHeader from '$lib/components/ui/layout/PageHeader.svelte';
|
import PageHeader from '$lib/components/ui/layout/PageHeader.svelte';
|
||||||
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
|
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
|
||||||
import InfiniteScroll from '$lib/components/ui/primitives/InfiniteScroll.svelte';
|
import InfiniteScroll from '$lib/components/ui/primitives/InfiniteScroll.svelte';
|
||||||
import Alert from '$lib/components/ui/primitives/Alert.svelte';
|
|
||||||
import UserRow from '$lib/components/ui/media/UserRow.svelte';
|
import UserRow from '$lib/components/ui/media/UserRow.svelte';
|
||||||
import { appendUnique } from '$lib/data/collections';
|
import { appendUnique } from '$lib/data/collections';
|
||||||
import { plural } from '$lib/utils/format';
|
import { plural } from '$lib/utils/format';
|
||||||
|
|
||||||
let { data, form } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
type ListUser = NonNullable<typeof data.users>['items'][number];
|
type ListUser = NonNullable<typeof data.users>['items'][number];
|
||||||
|
|
||||||
@@ -62,10 +61,6 @@
|
|||||||
: plural(total, 'usuario', 'usuarios')}
|
: plural(total, 'usuario', 'usuarios')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if form?.message}
|
|
||||||
<Alert class="mb-4">{form.message}</Alert>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if data.forbidden}
|
{#if data.forbidden}
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={Lock}
|
icon={Lock}
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
|
|||||||
redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountUrl = authConfig.issuer
|
const accountUrl = `${authConfig.issuer.replace(/\/+$/, '')}/ui/console`;
|
||||||
? `${authConfig.issuer.replace(/\/+$/, '')}/ui/console`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (!locals.user) {
|
if (!locals.user) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -82,14 +82,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility media-grid {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(var(--mf-grid-min-mobile), 1fr));
|
|
||||||
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(var(--mf-grid-min), 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@utility track-grid {
|
@utility track-grid {
|
||||||
grid-template-columns: var(--mf-track-cols-mobile);
|
grid-template-columns: var(--mf-track-cols-mobile);
|
||||||
|
|
||||||
@@ -116,10 +108,6 @@
|
|||||||
@utility text-body {
|
@utility text-body {
|
||||||
@apply text-sm leading-body;
|
@apply text-sm leading-body;
|
||||||
}
|
}
|
||||||
@utility text-count {
|
|
||||||
@apply text-xs tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
@utility btn-transition {
|
@utility btn-transition {
|
||||||
transition-property:
|
transition-property:
|
||||||
color, background-color, border-color, outline-color, opacity, box-shadow, filter;
|
color, background-color, border-color, outline-color, opacity, box-shadow, filter;
|
||||||
|
|||||||
Reference in New Issue
Block a user