fix: suppress mini-bar for float/minimal player styles; persist float position
This commit is contained in:
@@ -170,6 +170,21 @@ class AudioStore {
|
|||||||
return this.status === 'ready' || this.status === 'generating' || this.status === 'loading';
|
return this.status === 'ready' || this.status === 'generating' || this.status === 'loading';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true the persistent mini-bar in +layout.svelte is hidden.
|
||||||
|
* Set by the chapter reader page when playerStyle is 'float' or 'minimal'
|
||||||
|
* so the in-page player is the sole control surface.
|
||||||
|
* Cleared when leaving the chapter page (page destroy / onDestroy effect).
|
||||||
|
*/
|
||||||
|
suppressMiniBar = $state(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position of the draggable float overlay (bottom-right anchor offsets).
|
||||||
|
* Stored here (module singleton) so the position survives chapter navigation.
|
||||||
|
* x > 0 = moved left; y > 0 = moved up.
|
||||||
|
*/
|
||||||
|
floatPos = $state({ x: 0, y: 0 });
|
||||||
|
|
||||||
/** True when the currently loaded track matches slug+chapter */
|
/** True when the currently loaded track matches slug+chapter */
|
||||||
isCurrentChapter(slug: string, chapter: number): boolean {
|
isCurrentChapter(slug: string, chapter: number): boolean {
|
||||||
return this.slug === slug && this.chapter === chapter;
|
return this.slug === slug && this.chapter === chapter;
|
||||||
|
|||||||
@@ -945,19 +945,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Float player drag state ──────────────────────────────────────────────
|
// ── Float player drag state ──────────────────────────────────────────────
|
||||||
/** Position of the floating overlay (bottom-right anchor by default). */
|
// floatPos lives on audioStore (singleton) so position survives chapter navigation.
|
||||||
let floatPos = $state({ x: 0, y: 0 });
|
|
||||||
let floatDragging = $state(false);
|
let floatDragging = $state(false);
|
||||||
let floatDragStart = $state({ mx: 0, my: 0, ox: 0, oy: 0 });
|
let floatDragStart = $state({ mx: 0, my: 0, ox: 0, oy: 0 });
|
||||||
|
|
||||||
function onFloatPointerDown(e: PointerEvent) {
|
function onFloatPointerDown(e: PointerEvent) {
|
||||||
floatDragging = true;
|
floatDragging = true;
|
||||||
floatDragStart = { mx: e.clientX, my: e.clientY, ox: floatPos.x, oy: floatPos.y };
|
floatDragStart = { mx: e.clientX, my: e.clientY, ox: audioStore.floatPos.x, oy: audioStore.floatPos.y };
|
||||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||||
}
|
}
|
||||||
function onFloatPointerMove(e: PointerEvent) {
|
function onFloatPointerMove(e: PointerEvent) {
|
||||||
if (!floatDragging) return;
|
if (!floatDragging) return;
|
||||||
floatPos = {
|
audioStore.floatPos = {
|
||||||
x: floatDragStart.ox + (e.clientX - floatDragStart.mx),
|
x: floatDragStart.ox + (e.clientX - floatDragStart.mx),
|
||||||
y: floatDragStart.oy + (e.clientY - floatDragStart.my)
|
y: floatDragStart.oy + (e.clientY - floatDragStart.my)
|
||||||
};
|
};
|
||||||
@@ -1459,8 +1458,8 @@
|
|||||||
<div
|
<div
|
||||||
class="fixed z-[55] select-none"
|
class="fixed z-[55] select-none"
|
||||||
style="
|
style="
|
||||||
bottom: calc(4.5rem + {-floatPos.y}px);
|
bottom: calc(1rem + {-audioStore.floatPos.y}px);
|
||||||
right: calc(1rem + {-floatPos.x}px);
|
right: calc(1rem + {-audioStore.floatPos.x}px);
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -512,7 +512,7 @@
|
|||||||
style="display:none"
|
style="display:none"
|
||||||
></audio>
|
></audio>
|
||||||
|
|
||||||
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active}>
|
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active && !audioStore.suppressMiniBar}>
|
||||||
<!-- Navigation progress bar — shown while SSR is running for any page transition -->
|
<!-- Navigation progress bar — shown while SSR is running for any page transition -->
|
||||||
{#if navigating}
|
{#if navigating}
|
||||||
<div class="fixed top-0 left-0 right-0 z-[100] h-1 bg-(--color-surface-2)">
|
<div class="fixed top-0 left-0 right-0 z-[100] h-1 bg-(--color-surface-2)">
|
||||||
@@ -959,7 +959,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Persistent mini-player bar ─────────────────────────────────────────── -->
|
<!-- ── Persistent mini-player bar ─────────────────────────────────────────── -->
|
||||||
{#if audioStore.active}
|
{#if audioStore.active && !audioStore.suppressMiniBar}
|
||||||
<div class="fixed bottom-0 left-0 right-0 z-50 bg-(--color-surface) border-t border-(--color-border) shadow-2xl">
|
<div class="fixed bottom-0 left-0 right-0 z-50 bg-(--color-surface) border-t border-(--color-border) shadow-2xl">
|
||||||
|
|
||||||
<!-- Generation progress bar (sits at very top of the bar) -->
|
<!-- Generation progress bar (sits at very top of the bar) -->
|
||||||
|
|||||||
@@ -100,6 +100,33 @@
|
|||||||
document.documentElement.style.setProperty('--reading-max-width', READ_WIDTHS[layout.readWidth]);
|
document.documentElement.style.setProperty('--reading-max-width', READ_WIDTHS[layout.readWidth]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Suppress mini-bar for float / minimal player styles ──────────────────────
|
||||||
|
// The in-page player is the sole control surface for these styles; the layout
|
||||||
|
// mini-bar would be a duplicate. Clear on page destroy so the mini-bar returns
|
||||||
|
// on other pages (library, catalogue, etc.) where audio may still be playing.
|
||||||
|
$effect(() => {
|
||||||
|
audioStore.suppressMiniBar = layout.playerStyle === 'float' || layout.playerStyle === 'minimal';
|
||||||
|
return () => { audioStore.suppressMiniBar = false; };
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Persist float overlay position across reloads ─────────────────────────────
|
||||||
|
const FLOAT_POS_KEY = 'reader_float_pos_v1';
|
||||||
|
if (browser) {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(FLOAT_POS_KEY);
|
||||||
|
if (saved) {
|
||||||
|
const p = JSON.parse(saved) as { x: number; y: number };
|
||||||
|
if (typeof p.x === 'number' && typeof p.y === 'number') audioStore.floatPos = p;
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
$effect(() => {
|
||||||
|
const pos = audioStore.floatPos;
|
||||||
|
if (browser && (pos.x !== 0 || pos.y !== 0)) {
|
||||||
|
try { localStorage.setItem(FLOAT_POS_KEY, JSON.stringify(pos)); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ── Scroll progress bar ──────────────────────────────────────────────────────
|
// ── Scroll progress bar ──────────────────────────────────────────────────────
|
||||||
let scrollProgress = $state(0);
|
let scrollProgress = $state(0);
|
||||||
|
|
||||||
@@ -797,7 +824,7 @@
|
|||||||
{#if layout.readMode === 'scroll' && !layout.focusMode}
|
{#if layout.readMode === 'scroll' && !layout.focusMode}
|
||||||
{@const atTop = scrollProgress <= 0.01}
|
{@const atTop = scrollProgress <= 0.01}
|
||||||
{@const atBottom = scrollProgress >= 0.99}
|
{@const atBottom = scrollProgress >= 0.99}
|
||||||
<div class="fixed right-4 {audioStore.active ? 'bottom-[5.5rem]' : 'bottom-8'} z-40 flex flex-col gap-2 transition-all">
|
<div class="fixed right-4 {audioStore.active && !audioStore.suppressMiniBar ? 'bottom-[5.5rem]' : 'bottom-8'} z-40 flex flex-col gap-2 transition-all">
|
||||||
<!-- Up button / Prev chapter -->
|
<!-- Up button / Prev chapter -->
|
||||||
{#if atTop && data.prev}
|
{#if atTop && data.prev}
|
||||||
<a
|
<a
|
||||||
@@ -856,7 +883,7 @@
|
|||||||
|
|
||||||
<!-- ── Focus mode floating nav ───────────────────────────────────────────── -->
|
<!-- ── Focus mode floating nav ───────────────────────────────────────────── -->
|
||||||
{#if layout.focusMode}
|
{#if layout.focusMode}
|
||||||
<div class="fixed {audioStore.active ? 'bottom-[4.5rem]' : 'bottom-6'} left-1/2 -translate-x-1/2 z-50 max-w-[calc(100vw-2rem)]">
|
<div class="fixed {audioStore.active && !audioStore.suppressMiniBar ? 'bottom-[4.5rem]' : 'bottom-6'} left-1/2 -translate-x-1/2 z-50 max-w-[calc(100vw-2rem)]">
|
||||||
<div class="flex items-center divide-x divide-(--color-border) rounded-full bg-(--color-surface-2)/95 backdrop-blur border border-(--color-border) shadow-lg text-xs text-(--color-muted) overflow-hidden">
|
<div class="flex items-center divide-x divide-(--color-border) rounded-full bg-(--color-surface-2)/95 backdrop-blur border border-(--color-border) shadow-lg text-xs text-(--color-muted) overflow-hidden">
|
||||||
<!-- Prev chapter -->
|
<!-- Prev chapter -->
|
||||||
{#if data.prev}
|
{#if data.prev}
|
||||||
|
|||||||
Reference in New Issue
Block a user