feat: deliver the redesigned desktop workspace
Rebuild downloads, link collection, settings, history, and statistics around a responsive desktop shell with compact account and queue tables, contextual navigation, persistent update affordances, unified overlays, and accessible keyboard interactions. Add safe history-folder reveal IPC, responsive 2560/1920/1366/1120 coverage, deterministic visual fixtures, focused component regressions, and release-tree exclusions for internal working files. Bump the public application version to 2.0.13.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import {
|
||||
loadVisualCapture,
|
||||
prepareVisualCapture,
|
||||
type MainViewId
|
||||
} from "./ui-driver";
|
||||
|
||||
const views: Array<{ id: MainViewId; name: string }> = [
|
||||
{ id: "downloads", name: "Downloads" },
|
||||
{ id: "collector", name: "Linksammler" },
|
||||
{ id: "settings", name: "Einstellungen" },
|
||||
{ id: "history", name: "Verlauf" },
|
||||
{ id: "statistics", name: "Statistiken" }
|
||||
];
|
||||
|
||||
function DriverTestApp({ updateAvailable }: { updateAvailable: boolean }) {
|
||||
const [activeView, setActiveView] = useState<MainViewId>("downloads");
|
||||
const [collectorOpen, setCollectorOpen] = useState(false);
|
||||
const [collectorValue, setCollectorValue] = useState("");
|
||||
const [collectorRows, setCollectorRows] = useState<string[]>([]);
|
||||
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
||||
const [updateDialogOpen, setUpdateDialogOpen] = useState(updateAvailable);
|
||||
const [updateTooltipOpen, setUpdateTooltipOpen] = useState(false);
|
||||
const [infoOpen, setInfoOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav aria-label="Hauptnavigation">
|
||||
{views.map((view) => (
|
||||
<button
|
||||
key={view.id}
|
||||
type="button"
|
||||
aria-current={activeView === view.id ? "page" : undefined}
|
||||
onClick={() => setActiveView(view.id)}
|
||||
>
|
||||
{view.name}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
<main data-visual-active-view={activeView}>
|
||||
{activeView === "downloads" && (
|
||||
<>
|
||||
<div data-visual-region="downloads-sidebar">Download-Seitenleiste</div>
|
||||
<div data-visual-region="downloads-sidebar-status">3 Pakete aktiv</div>
|
||||
<div data-visual-region="downloads-toolbar">Download-Werkzeuge</div>
|
||||
<div data-visual-region="downloads-table-body">
|
||||
<div role="row">Dokumentation Staffel 1</div>
|
||||
</div>
|
||||
<div data-visual-region="downloads-pagination">Seite 1 von 1</div>
|
||||
<button type="button" onClick={() => setInfoOpen((open) => !open)}>Informationen</button>
|
||||
{infoOpen && <section aria-label="Informationen zu Downloads">Drei Downloads sind sichtbar.</section>}
|
||||
</>
|
||||
)}
|
||||
{activeView === "collector" && (
|
||||
<>
|
||||
<div data-visual-region="collector-sidebar">Linksammler-Seitenleiste</div>
|
||||
<div data-visual-region="collector-toolbar">Linksammler-Werkzeuge</div>
|
||||
<button type="button" onClick={() => setCollectorOpen(true)}>Links hinzufügen</button>
|
||||
{collectorRows.length === 0 && <div data-visual-region="collector-empty-state">Keine Links</div>}
|
||||
<div data-visual-region="collector-table-body">
|
||||
{collectorRows.map((link) => <div role="row" key={link}>{link}</div>)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeView === "settings" && (
|
||||
<>
|
||||
<div data-visual-region="settings-sidebar">Einstellungs-Seitenleiste</div>
|
||||
<button type="button">Accounts</button>
|
||||
<div data-visual-region="accounts-table-body"><div role="row">Visual Account</div></div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={updateDialogOpen}
|
||||
onClick={() => setAccountMenuOpen((open) => !open)}
|
||||
>
|
||||
Kontomenü
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{activeView === "history" && (
|
||||
<>
|
||||
<div data-visual-region="history-sidebar">Verlauf-Seitenleiste</div>
|
||||
<div data-visual-region="history-toolbar">Verlauf-Werkzeuge</div>
|
||||
<div data-visual-region="history-table-body"><div role="row">Naturfilm Sammlung</div></div>
|
||||
<div data-visual-region="history-pagination">Seite 1 von 1</div>
|
||||
</>
|
||||
)}
|
||||
{activeView === "statistics" && (
|
||||
<>
|
||||
<div data-visual-region="statistics-sidebar">Statistik-Seitenleiste</div>
|
||||
<div data-visual-region="statistics-kpis">919,82 GB</div>
|
||||
<div data-visual-region="statistics-chart">Download-Verlauf</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
{collectorOpen && (
|
||||
<div role="dialog" aria-label="Links hinzufügen">
|
||||
<textarea aria-label="Links" value={collectorValue} onChange={(event) => setCollectorValue(event.target.value)} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setCollectorRows(collectorValue.split(/\r?\n/).map((value) => value.trim()).filter(Boolean));
|
||||
setCollectorOpen(false);
|
||||
}}
|
||||
>
|
||||
Übernehmen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{updateDialogOpen && (
|
||||
<div role="dialog" aria-label="Update installieren">
|
||||
<strong>Update installieren</strong>
|
||||
<button type="button" onClick={() => setUpdateDialogOpen(false)}>Später</button>
|
||||
</div>
|
||||
)}
|
||||
{accountMenuOpen && (
|
||||
<div role="menu" aria-label="Kontomenü">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Update verfügbar"
|
||||
onMouseEnter={() => setUpdateTooltipOpen(true)}
|
||||
onFocus={() => setUpdateTooltipOpen(true)}
|
||||
>
|
||||
Update verfügbar
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{updateTooltipOpen && <div role="tooltip" aria-label="Update verfügbar">Version 9.9.9 verfügbar</div>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function startDriverTest(): Promise<void> {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) {
|
||||
throw new Error("Root element fehlt");
|
||||
}
|
||||
const captureName = new URLSearchParams(window.location.search).get("capture");
|
||||
if (!captureName) {
|
||||
throw new Error("Capture fehlt");
|
||||
}
|
||||
try {
|
||||
const capture = await loadVisualCapture(captureName);
|
||||
createRoot(rootElement).render(<DriverTestApp updateAvailable={capture.scenario === "update"} />);
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
||||
await prepareVisualCapture(capture, document);
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
||||
document.documentElement.dataset.visualReady = "true";
|
||||
} catch (error) {
|
||||
delete document.documentElement.dataset.visualReady;
|
||||
rootElement.dataset.visualError = "true";
|
||||
rootElement.textContent = `Visual-Harness-Fehler: ${error instanceof Error ? error.message : String(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
void startDriverTest();
|
||||
Reference in New Issue
Block a user