fix: polish download status and application menus
Animate nested application menus while keeping hidden actions out of keyboard navigation and respecting reduced-motion preferences. Normalize Mega-Debrid service labels, simplify active download and extraction statuses, and preserve full diagnostics in tooltips. Brand the Windows development runtime and isolate concurrent launchers with PID-aware cleanup and regression coverage.
This commit is contained in:
+33
-25
@@ -5679,15 +5679,17 @@ export function App(): ReactElement {
|
||||
onMouseLeave={() => setOpenSubmenu(null)}
|
||||
>
|
||||
<button className="menu-submenu-trigger">Sicherung</button>
|
||||
{openSubmenu === "sicherung" && (
|
||||
<div className="menu-submenu-dropdown">
|
||||
<div
|
||||
aria-hidden={openSubmenu !== "sicherung"}
|
||||
{...(openSubmenu !== "sicherung" ? { inert: "" } : {})}
|
||||
className={`menu-submenu-dropdown${openSubmenu === "sicherung" ? " is-open" : ""}`}
|
||||
>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onExportBackup(); }}>Exportieren</button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onImportBackup(); }}>Importieren</button>
|
||||
<div className="menu-separator" />
|
||||
<button className="menu-dropdown-item" onClick={() => { void onCreateOnlineBackup(); }}>Online-Schlüssel exportieren</button>
|
||||
<button className="menu-dropdown-item" onClick={onOpenOnlineBackupImport}>Online-Schlüssel importieren</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-separator" />
|
||||
<button className="menu-dropdown-item" onClick={onMenuRestart}>
|
||||
@@ -5827,15 +5829,17 @@ export function App(): ReactElement {
|
||||
onMouseLeave={() => setOpenSubmenu(null)}
|
||||
>
|
||||
<button className="menu-submenu-trigger">Logs öffnen</button>
|
||||
{openSubmenu === "hilfe-log" && (
|
||||
<div className="menu-submenu-dropdown">
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openLog().catch(() => {}); }}><span>Haupt-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openAuditLog().catch(() => {}); }}><span>Audit-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openRenameLog().catch(() => {}); }}><span>Rename-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openSessionLog().catch(() => {}); }}><span>Session-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openTraceLog().catch(() => {}); }}><span>Trace-Log</span></button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
aria-hidden={openSubmenu !== "hilfe-log"}
|
||||
{...(openSubmenu !== "hilfe-log" ? { inert: "" } : {})}
|
||||
className={`menu-submenu-dropdown${openSubmenu === "hilfe-log" ? " is-open" : ""}`}
|
||||
>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openLog().catch(() => {}); }}><span>Haupt-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openAuditLog().catch(() => {}); }}><span>Audit-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openRenameLog().catch(() => {}); }}><span>Rename-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openSessionLog().catch(() => {}); }}><span>Session-Log</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void window.rd.openTraceLog().catch(() => {}); }}><span>Trace-Log</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="menu-submenu"
|
||||
@@ -5843,13 +5847,15 @@ export function App(): ReactElement {
|
||||
onMouseLeave={() => setOpenSubmenu(null)}
|
||||
>
|
||||
<button className="menu-submenu-trigger">Remote-Support</button>
|
||||
{openSubmenu === "hilfe-remote" && (
|
||||
<div className="menu-submenu-dropdown">
|
||||
<div
|
||||
aria-hidden={openSubmenu !== "hilfe-remote"}
|
||||
{...(openSubmenu !== "hilfe-remote" ? { inert: "" } : {})}
|
||||
className={`menu-submenu-dropdown${openSubmenu === "hilfe-remote" ? " is-open" : ""}`}
|
||||
>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onOpenRemoteDiagnostics(); }}><span>Ferndiagnose …</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onExportSupportBundle(); }}><span>Support-Bundle exportieren</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onToggleSupportTrace(); }}><span>{supportTraceEnabled ? "Support-Trace deaktivieren" : "Support-Trace aktivieren"}</span></button>
|
||||
</div>
|
||||
)}
|
||||
<button className="menu-dropdown-item" onClick={() => { void onExportSupportBundle(); }}><span>Support-Bundle exportieren</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onToggleSupportTrace(); }}><span>{supportTraceEnabled ? "Support-Trace deaktivieren" : "Support-Trace aktivieren"}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-separator" />
|
||||
<button className="menu-dropdown-item" onClick={() => { void onShowRecentErrors(); }}>
|
||||
@@ -5861,12 +5867,14 @@ export function App(): ReactElement {
|
||||
onMouseLeave={() => setOpenSubmenu(null)}
|
||||
>
|
||||
<button className="menu-submenu-trigger">Diagnose</button>
|
||||
{openSubmenu === "hilfe-diagnose" && (
|
||||
<div className="menu-submenu-dropdown">
|
||||
<button className="menu-dropdown-item" onClick={() => { void onRunDebugSetupCheck(); }}><span>Debug-Setup prüfen</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onRotateDebugToken(); }}><span>Debug-Token rotieren</span></button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
aria-hidden={openSubmenu !== "hilfe-diagnose"}
|
||||
{...(openSubmenu !== "hilfe-diagnose" ? { inert: "" } : {})}
|
||||
className={`menu-submenu-dropdown${openSubmenu === "hilfe-diagnose" ? " is-open" : ""}`}
|
||||
>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onRunDebugSetupCheck(); }}><span>Debug-Setup prüfen</span></button>
|
||||
<button className="menu-dropdown-item" onClick={() => { void onRotateDebugToken(); }}><span>Debug-Token rotieren</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-separator" />
|
||||
<button className="menu-dropdown-item" onClick={() => { closeMenus(); void onCheckUpdates(); }}>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function compactProviderLabels(labels: string[]): string {
|
||||
}
|
||||
|
||||
export function normalizeDownloadServiceLabel(label: string): string {
|
||||
return [...new Set(label.split(",").map((entry) => entry.trim().replace(/\s+(Web|API)\s+\(\1 Account\)$/i, " $1")).filter(Boolean))].join(", ");
|
||||
return [...new Set(label.split(",").map((entry) => entry.trim().replace(/^(Mega-Debrid)\s+(Web|API)(?:\s+\([^)]*\))?$/i, "$1 $2")).filter(Boolean))].join(", ");
|
||||
}
|
||||
|
||||
export function compactDownloadServiceLabel(label: string): string {
|
||||
|
||||
@@ -612,6 +612,22 @@
|
||||
.md-application-menu-tree .menu-submenu-dropdown {
|
||||
right: 100%;
|
||||
left: auto;
|
||||
opacity: 0;
|
||||
transform: translateX(8px);
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 160ms ease,
|
||||
transform 220ms cubic-bezier(0.22, 0.76, 0.22, 1),
|
||||
visibility 0s linear 220ms;
|
||||
}
|
||||
|
||||
.md-application-menu-tree .menu-submenu-dropdown.is-open {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.md-overlay-host {
|
||||
@@ -909,4 +925,13 @@
|
||||
.md-application-menu-tree .menu-dropdown.is-open {
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
|
||||
.md-application-menu-tree .menu-submenu-dropdown {
|
||||
transition-duration: 0.01ms !important;
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
|
||||
.md-application-menu-tree .menu-submenu-dropdown.is-open {
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,9 +122,13 @@ function progress(value: number): number {
|
||||
export function compactDownloadStatus(value: string): string {
|
||||
const status = value.trim();
|
||||
if (/Link wird umgewandelt/i.test(status)) return "Umwandeln";
|
||||
if (/Download läuft\b/i.test(status)) return "DL läuft";
|
||||
const extracting = status.match(/(Entpacken\s+\d+%)/i);
|
||||
return extracting?.[1] ?? status;
|
||||
if (/Download läuft\b/i.test(status)) return "Download läuft";
|
||||
if (/Download running\b/i.test(status)) return "Download running";
|
||||
const extracting = status.match(/Entpacken\s+(\d+)%/i);
|
||||
if (extracting) return `Entpacken - ${extracting[1]}%`;
|
||||
const extractingEnglish = status.match(/Extracting\s+(\d+)%/i);
|
||||
if (extractingEnglish) return `Extracting - ${extractingEnglish[1]}%`;
|
||||
return status;
|
||||
}
|
||||
|
||||
function DownloadMeter({ value, text }: { value: number; text: string }): ReactElement {
|
||||
@@ -140,10 +144,11 @@ function DownloadMeter({ value, text }: { value: number; text: string }): ReactE
|
||||
}
|
||||
|
||||
function DownloadStatusCell({ status, title }: { status: string; title?: string }): ReactElement {
|
||||
const visibleStatus = compactDownloadStatus(status);
|
||||
return (
|
||||
<span aria-label={status} className="downloads-cell downloads-status-cell" title={title || status}>
|
||||
<span aria-hidden="true" className="downloads-status-full">{status}</span>
|
||||
<span aria-hidden="true" className="downloads-status-compact">{compactDownloadStatus(status)}</span>
|
||||
<span aria-label={visibleStatus} className="downloads-cell downloads-status-cell" title={title || status}>
|
||||
<span aria-hidden="true" className="downloads-status-full">{visibleStatus}</span>
|
||||
<span aria-hidden="true" className="downloads-status-compact">{visibleStatus}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -410,8 +415,12 @@ function packageCell(row: DownloadPackageRow, column: string, packageSpeedBps: n
|
||||
if (column === "prio") return <span className="downloads-cell">{entry.priority === "high" ? "Hoch" : entry.priority === "low" ? "Niedrig" : ""}</span>;
|
||||
if (column === "status") {
|
||||
const audio = entry.audioStripSummary ? formatAudioStripSummary(entry.audioStripSummary) : null;
|
||||
const status = `${stats.done}/${stats.total}${stats.failed > 0 ? ` · ${stats.failed} Fehler` : ""}${stats.cancelled > 0 ? ` · ${stats.cancelled} abgebrochen` : ""}${entry.postProcessLabel ? ` · ${entry.postProcessLabel}` : ""}${audio ? ` · ${audio.text}` : ""}`;
|
||||
const title = audio?.tooltip ? `${status}\n${audio.tooltip}` : status;
|
||||
const details = `${stats.done}/${stats.total}${stats.failed > 0 ? ` · ${stats.failed} Fehler` : ""}${stats.cancelled > 0 ? ` · ${stats.cancelled} abgebrochen` : ""}${entry.postProcessLabel ? ` · ${entry.postProcessLabel}` : ""}${audio ? ` · ${audio.text}` : ""}`;
|
||||
const downloading = entry.status === "downloading" || entry.status === "validating" || row.items.some((item) => item.status === "downloading" || item.status === "validating");
|
||||
const status = entry.postProcessLabel && /Entpacken\s+\d+%/i.test(entry.postProcessLabel)
|
||||
? entry.postProcessLabel
|
||||
: downloading ? "Download läuft" : details;
|
||||
const title = audio?.tooltip ? `${details}\n${audio.tooltip}` : details;
|
||||
return <DownloadStatusCell status={status} title={title} />;
|
||||
}
|
||||
if (column === "speed") return <span className="downloads-cell">{packageSpeedBps > 0 ? formatSpeedMbps(packageSpeedBps) : ""}</span>;
|
||||
|
||||
Reference in New Issue
Block a user