v0.3 Phase 5d: Files page migrated to quietware skeleton
Same gated-layout pattern as Phase 5c. v0.2 surface untouched;
quietware path renders FilesPage via LumotiaPageSkeleton with the
four zones the page actually needs:
Capture header "File transcription" + "Transcribe audio files locally."
Primary surface drop zone / progress / transcript states
Action dock Browse files / Copy / Export
Mono footer formats · model · Local only
State + handlers preserved.
fileTranscript, segments, isDragOver, progress, progressText,
fileName, error, transcribing, showExportMenu — all unchanged.
handleBrowse, transcribeFiles, copyAll, handleExport — all
unchanged. Tauri drag-drop listeners stay live on onMount; new
MutationObserver for data-design joins them.
Primary surface state machine.
transcribing Upload icon, "Transcribing" label, mono file
name, LumotiaProgress (tone="transcribing", so
the blue Phase 4k progress fill applies).
Progress text below.
empty Dashed drop zone using --button-primary-bg
accent when isDragOver, neutral subtle border
otherwise. Empty-state typography mirrors
Dictation: Young Serif italic "Drop an audio
file.", Work Sans support, format list in
JetBrains Mono.
has transcript Full-bleed editable textarea.
Action dock.
Browse files (primary when no transcript; secondary when transcript
exists — at that point Export becomes the primary action).
Copy (tertiary, disabled until transcript exists).
Export (LumotiaMenu trigger — primary when transcript exists,
secondary otherwise).
Disabled controls render neutral per the Phase 4k token contract.
Mono footer.
Single line: "MP3 · WAV · M4A · MP4 · FLAC · OGG · {model} · Local only"
Capture script updated.
scripts/capture-quietware-screenshots.mjs now navigates to the
Files page via the sidebar nav button after capturing the root
route, and screenshots both quietware modes.
Verified.
- npm run check: 0 errors, 0 warnings across 5707 files.
- Headless captures confirm the new layout in both modes:
drop zone + action dock + mono footer with the right Phase 4j
colour grammar applied. Browse Files blue primary, disabled
actions neutral, dashed border picks up --button-primary-bg
when isDragOver.
Phase 5e (Tasks) and 5f (History) follow same pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,5 +83,25 @@ try {
|
|||||||
console.error(`root route skipped: ${e.message}`);
|
console.error(`root route skipped: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Files page — SPA navigation via the sidebar item.
|
||||||
|
try {
|
||||||
|
console.log("\n=== files page (best effort) ===");
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const items = Array.from(document.querySelectorAll('button, a, [role="button"], li'));
|
||||||
|
const target = items.find(el => (el.textContent || "").trim().toLowerCase() === "files");
|
||||||
|
if (target) target.click();
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
for (const mode of MODES.slice(1, 3)) {
|
||||||
|
await setMode(mode);
|
||||||
|
await page.waitForTimeout(400);
|
||||||
|
const file = `${OUT}/files__${mode.id}.png`;
|
||||||
|
await page.screenshot({ path: file, fullPage: false });
|
||||||
|
console.log(` files ${mode.id}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`files page skipped: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
console.log(`\nWrote screenshots to ${OUT}/`);
|
console.log(`\nWrote screenshots to ${OUT}/`);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte";
|
import LumotiaNotice from "$lib/ui/LumotiaNotice.svelte";
|
||||||
import LumotiaProgress from "$lib/ui/LumotiaProgress.svelte";
|
import LumotiaProgress from "$lib/ui/LumotiaProgress.svelte";
|
||||||
import LumotiaMenu from "$lib/ui/LumotiaMenu.svelte";
|
import LumotiaMenu from "$lib/ui/LumotiaMenu.svelte";
|
||||||
|
// v0.3 Phase 5d — quietware layout for Files page.
|
||||||
|
import LumotiaPageSkeleton from "$lib/ui/LumotiaPageSkeleton.svelte";
|
||||||
import { exportTranscript } from "$lib/utils/export.js";
|
import { exportTranscript } from "$lib/utils/export.js";
|
||||||
import { hasTauriRuntime } from "$lib/utils/runtime";
|
import { hasTauriRuntime } from "$lib/utils/runtime";
|
||||||
import { toasts } from "$lib/stores/toasts.svelte.js";
|
import { toasts } from "$lib/stores/toasts.svelte.js";
|
||||||
@@ -30,7 +32,25 @@
|
|||||||
let unlistenEnter = null;
|
let unlistenEnter = null;
|
||||||
let unlistenLeave = null;
|
let unlistenLeave = null;
|
||||||
|
|
||||||
|
// v0.3 Phase 5d — quietware layout switch. MutationObserver on
|
||||||
|
// <html data-design> mirrors the SettingsPage / DictationPage
|
||||||
|
// pattern. Without quietware the existing v0.2 layout renders.
|
||||||
|
let isQuietware = $state(false);
|
||||||
|
let quietwareObserver: MutationObserver | null = null;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
if (typeof document !== "undefined") {
|
||||||
|
const sync = () => {
|
||||||
|
isQuietware = document.documentElement.dataset.design === "quietware";
|
||||||
|
};
|
||||||
|
sync();
|
||||||
|
quietwareObserver = new MutationObserver(sync);
|
||||||
|
quietwareObserver.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["data-design"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
unlistenDrop = await listen("tauri://drag-drop", async (event) => {
|
unlistenDrop = await listen("tauri://drag-drop", async (event) => {
|
||||||
isDragOver = false;
|
isDragOver = false;
|
||||||
if (event.payload?.paths?.length > 0) {
|
if (event.payload?.paths?.length > 0) {
|
||||||
@@ -45,6 +65,8 @@
|
|||||||
if (unlistenDrop) unlistenDrop();
|
if (unlistenDrop) unlistenDrop();
|
||||||
if (unlistenEnter) unlistenEnter();
|
if (unlistenEnter) unlistenEnter();
|
||||||
if (unlistenLeave) unlistenLeave();
|
if (unlistenLeave) unlistenLeave();
|
||||||
|
quietwareObserver?.disconnect();
|
||||||
|
quietwareObserver = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleBrowse() {
|
async function handleBrowse() {
|
||||||
@@ -175,7 +197,117 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full overflow-y-auto animate-fade-in">
|
<div class="flex flex-col h-full {isQuietware ? '' : 'overflow-y-auto'} animate-fade-in">
|
||||||
|
{#if isQuietware}
|
||||||
|
<!-- =================================================================
|
||||||
|
v0.3 Phase 5d — quietware Files layout. Uses LumotiaPageSkeleton
|
||||||
|
with the same five-zone language as Dictation:
|
||||||
|
|
||||||
|
Capture header title + description
|
||||||
|
Slim notice nested inside the primary surface for errors
|
||||||
|
Primary surface drop zone / progress / transcript states
|
||||||
|
Action dock Browse Files / Copy / Export
|
||||||
|
Mono footer formats · model · Local only
|
||||||
|
|
||||||
|
All existing state and handlers preserved.
|
||||||
|
================================================================= -->
|
||||||
|
<LumotiaPageSkeleton>
|
||||||
|
{#snippet header()}
|
||||||
|
<div>
|
||||||
|
<h1 class="text-[18px] font-medium text-text leading-tight">File transcription</h1>
|
||||||
|
<p class="text-[12px] text-text-secondary mt-0.5">Transcribe audio files locally.</p>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet primary()}
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
{#if error}
|
||||||
|
<div class="px-6 pt-4">
|
||||||
|
<LumotiaNotice tone="danger" slim dismissible onDismiss={() => error = ""}>
|
||||||
|
{error}
|
||||||
|
</LumotiaNotice>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex-1 flex flex-col px-8 py-6 min-h-0 overflow-hidden">
|
||||||
|
{#if transcribing}
|
||||||
|
<!-- Progress state -->
|
||||||
|
<div class="flex-1 flex flex-col items-center justify-center gap-4 px-12 text-text-tertiary">
|
||||||
|
<Upload size={48} class="opacity-50" aria-hidden="true" />
|
||||||
|
<p class="text-[18px] font-medium text-text">Transcribing</p>
|
||||||
|
{#if fileName}
|
||||||
|
<p class="text-[13px] text-text-secondary text-center font-mono break-all">{fileName}</p>
|
||||||
|
{/if}
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<LumotiaProgress value={progress} max={100} tone="transcribing" ariaLabel="Transcription progress" showValue />
|
||||||
|
{#if progressText}
|
||||||
|
<p class="text-[12px] text-text-tertiary mt-2 text-center">{progressText}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if !fileTranscript}
|
||||||
|
<!-- Empty state: drop zone -->
|
||||||
|
<div
|
||||||
|
class="flex-1 flex flex-col items-center justify-center text-center gap-3 rounded-xl border-2 border-dashed transition-colors duration-150
|
||||||
|
{isDragOver
|
||||||
|
? 'border-[var(--button-primary-bg)] bg-[var(--color-info-bg)]'
|
||||||
|
: 'border-border-subtle text-text-tertiary'}"
|
||||||
|
role="region"
|
||||||
|
aria-label="Audio file drop zone"
|
||||||
|
>
|
||||||
|
<Upload size={56} class="opacity-50" aria-hidden="true" />
|
||||||
|
<p class="font-display italic text-[28px] text-text">Drop an audio file.</p>
|
||||||
|
<p class="text-[14px] text-text-secondary">Or use Browse files in the action bar.</p>
|
||||||
|
<p class="text-[12px] text-text-tertiary mt-2 font-mono">MP3 · WAV · M4A · MP4 · FLAC · OGG</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Transcript display -->
|
||||||
|
<textarea
|
||||||
|
class="font-transcript flex-1 w-full bg-transparent text-text resize-none placeholder:text-text-tertiary leading-relaxed"
|
||||||
|
placeholder="Transcribed text will appear here..."
|
||||||
|
bind:value={fileTranscript}
|
||||||
|
data-no-transition
|
||||||
|
aria-label="File transcript"
|
||||||
|
aria-live="polite"
|
||||||
|
></textarea>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action dock attached to the surface. -->
|
||||||
|
<div class="flex items-center gap-2 justify-end px-6 py-3 border-t border-border-subtle bg-bg-elevated/40">
|
||||||
|
<LumotiaButton
|
||||||
|
variant={fileTranscript ? "secondary" : "primary"}
|
||||||
|
size="sm"
|
||||||
|
disabled={transcribing}
|
||||||
|
onclick={handleBrowse}
|
||||||
|
>Browse files</LumotiaButton>
|
||||||
|
<LumotiaButton variant="tertiary" size="sm" onclick={copyAll} disabled={!fileTranscript}>Copy</LumotiaButton>
|
||||||
|
<LumotiaMenu
|
||||||
|
align="start"
|
||||||
|
items={[
|
||||||
|
{ value: "txt", label: "Plain text", onSelect: () => handleExport("txt") },
|
||||||
|
{ value: "md", label: "Markdown", onSelect: () => handleExport("md") },
|
||||||
|
{ value: "csv", label: "CSV", onSelect: () => handleExport("csv") },
|
||||||
|
{ value: "html",label: "HTML", onSelect: () => handleExport("html") },
|
||||||
|
{ value: "srt", label: "SRT subtitles", onSelect: () => handleExport("srt") },
|
||||||
|
{ value: "vtt", label: "WebVTT", onSelect: () => handleExport("vtt") },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{#snippet trigger()}
|
||||||
|
<LumotiaButton variant={fileTranscript ? "primary" : "secondary"} size="sm" disabled={!fileTranscript}>Export</LumotiaButton>
|
||||||
|
{/snippet}
|
||||||
|
</LumotiaMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet metadata()}
|
||||||
|
<div class="flex items-center justify-end">
|
||||||
|
MP3 · WAV · M4A · MP4 · FLAC · OGG · {settings.modelSize} · Local only
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</LumotiaPageSkeleton>
|
||||||
|
{:else}
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h2 class="font-display text-[26px] italic text-text px-7 pt-6 pb-4">File Transcription</h2>
|
<h2 class="font-display text-[26px] italic text-text px-7 pt-6 pb-4">File Transcription</h2>
|
||||||
|
|
||||||
@@ -274,4 +406,5 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</LumotiaMenu>
|
</LumotiaMenu>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user