feat: add Listening Mode overlay with voice picker, speed, sleep timer, and chapter list
Some checks failed
Release / Test backend (push) Has been cancelled
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Some checks failed
Release / Test backend (push) Has been cancelled
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Adds a full-screen listening mode accessible via the headphones button in the mini-player bar. Moves voice selector, speed, auto-next, and sleep timer out of the reader settings panel into the new overlay. Voices are stored in AudioStore so ListeningMode can read them without prop drilling.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import { cn } from '$lib/utils';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { locales, getLocale } from '$lib/paraglide/runtime.js';
|
||||
import ListeningMode from '$lib/components/ListeningMode.svelte';
|
||||
|
||||
let { children, data }: { children: Snippet; data: LayoutData } = $props();
|
||||
|
||||
@@ -34,6 +35,7 @@
|
||||
// Chapter list drawer state for the mini-player
|
||||
let chapterDrawerOpen = $state(false);
|
||||
let activeChapterEl = $state<HTMLElement | null>(null);
|
||||
let listeningModeOpen = $state(false);
|
||||
|
||||
function setIfActive(node: HTMLElement, isActive: boolean) {
|
||||
if (isActive) activeChapterEl = node;
|
||||
@@ -287,7 +289,7 @@
|
||||
audioEl.currentTime = Math.min(audioEl.duration || 0, audioEl.currentTime + 30);
|
||||
}
|
||||
|
||||
const speedSteps = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0];
|
||||
const speedSteps = [0.75, 1.0, 1.25, 1.5, 2.0];
|
||||
|
||||
function cycleSpeed() {
|
||||
const idx = speedSteps.indexOf(audioStore.speed);
|
||||
@@ -993,6 +995,21 @@
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<!-- Headphones: open listening mode -->
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onclick={() => (listeningModeOpen = true)}
|
||||
title="Listening mode"
|
||||
aria-label="Open listening mode"
|
||||
class="text-(--color-muted) hover:text-(--color-text) flex-shrink-0"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 18v-6a9 9 0 0118 0v6"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3zM3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3z"/>
|
||||
</svg>
|
||||
</Button>
|
||||
|
||||
<!-- Dismiss -->
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -1008,4 +1025,9 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Listening mode full-screen overlay -->
|
||||
{#if listeningModeOpen}
|
||||
<ListeningMode onclose={() => (listeningModeOpen = false)} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user