diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 3908234..fa36604 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -96,6 +96,7 @@ import { } from "./views/statistics/StatisticsView"; import { buildDownloadsViewModel, formatRemainingDownloadBytes, getDownloadQueueTotalBytes, getDownloadSpeedBps, getPendingDownloadItemCount, getRemainingDownloadBytes, type DownloadDisplayMode, type DownloadSidebarFilter } from "./views/downloads/downloads-model"; import { downloadColumnDefinitions, type DownloadSortColumn } from "./views/downloads/DownloadsTable"; +import { DeleteConfirmationDialog } from "./views/downloads/DeleteConfirmationDialog"; import { beginDownloadColumnDrag, clearDownloadColumnDrag, settleDownloadColumnDrag, updateDownloadColumnDrag, type DownloadColumnDragSession } from "./views/downloads/column-drag"; import { DownloadsContent, @@ -5911,25 +5912,21 @@ export function App(): ReactElement { if (pkgCount > 0) parts.push(`${pkgCount} Paket(e)`); if (itemCount > 0) parts.push(`${itemCount} Link(s)`); return ( - setDeleteConfirm(null)} open title="Bist Du Dir sicher?"> - Möchtest Du wirklich diese Aufräumaktion(en) durchführen?Ausgewählte Links löschen - Zu erledigende Aufgaben:{parts.join(" + ")} löschen ? {totalRemaining} Link(s) verbleiben! - - setDeleteConfirm((prev) => prev ? { ...prev, dontAsk: e.target.checked } : prev)} /> - Nicht mehr anzeigen - - - setDeleteConfirm(null)}>Abbrechen - { - if (deleteConfirm.dontAsk) { - setSettingsDraft((prev) => ({ ...prev, confirmDeleteSelection: false })); - void window.rd.updateSettings({ confirmDeleteSelection: false }).catch(() => {}); - } - executeDeleteSelection(deleteConfirm.ids); - setDeleteConfirm(null); - }}>Fortfahren - - + setDeleteConfirm(null)} + onConfirm={() => { + if (deleteConfirm.dontAsk) { + setSettingsDraft((prev) => ({ ...prev, confirmDeleteSelection: false })); + void window.rd.updateSettings({ confirmDeleteSelection: false }).catch(() => {}); + } + executeDeleteSelection(deleteConfirm.ids); + setDeleteConfirm(null); + }} + onDontAskChange={(checked) => setDeleteConfirm((prev) => prev ? { ...prev, dontAsk: checked } : prev)} + /> ); })() : null} diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 5c92b24..b3c73d1 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -3253,6 +3253,16 @@ td { flex-wrap: wrap; } +.delete-confirm-footer { + display: grid; + justify-items: end; + gap: 8px; +} + +.delete-confirm-dont-ask { + justify-content: flex-end; +} + .account-modal-actions { justify-content: space-between; position: sticky; diff --git a/src/renderer/views/downloads/DeleteConfirmationDialog.tsx b/src/renderer/views/downloads/DeleteConfirmationDialog.tsx new file mode 100644 index 0000000..b20f860 --- /dev/null +++ b/src/renderer/views/downloads/DeleteConfirmationDialog.tsx @@ -0,0 +1,37 @@ +import type { ReactElement } from "react"; +import { Dialog } from "../../ui/Dialog"; + +interface DeleteConfirmationDialogProps { + dontAsk: boolean; + parts: string[]; + totalRemaining: number; + onCancel: () => void; + onConfirm: () => void; + onDontAskChange: (checked: boolean) => void; +} + +export function DeleteConfirmationDialog({ + dontAsk, + parts, + totalRemaining, + onCancel, + onConfirm, + onDontAskChange +}: DeleteConfirmationDialogProps): ReactElement { + return ( + + Möchtest Du wirklich diese Aufräumaktion(en) durchführen?Ausgewählte Links löschen + Zu erledigende Aufgaben:{parts.join(" + ")} löschen ? {totalRemaining} Link(s) verbleiben! + + + Abbrechen + Fortfahren + + + onDontAskChange(event.target.checked)} /> + Nicht mehr anzeigen + + + + ); +} diff --git a/tests/app-shell.test.tsx b/tests/app-shell.test.tsx index 7fe37f2..7239ad4 100644 --- a/tests/app-shell.test.tsx +++ b/tests/app-shell.test.tsx @@ -35,6 +35,16 @@ describe("desktop shell", () => { expect(removal).toContain('title: "Ausgewählte Links löschen"'); }); + it("places the delete confirmation opt-out below the right-aligned actions", () => { + const source = readFileSync(new URL("../src/renderer/views/downloads/DeleteConfirmationDialog.tsx", import.meta.url), "utf8"); + const css = readFileSync(new URL("../src/renderer/styles.css", import.meta.url), "utf8"); + + expect(source).toContain('className="delete-confirm-footer"'); + expect(source.indexOf('className="modal-actions"')).toBeLessThan(source.indexOf("Nicht mehr anzeigen")); + expect(source).toContain('className="toggle-line delete-confirm-dont-ask"'); + expect(css).toMatch(/\.delete-confirm-footer\s*{[^}]*display:\s*grid;[^}]*justify-items:\s*end;/s); + }); + it("does not stack renderer latency on the manager cadence for large active queues", () => { expect(getSnapshotRenderDelay(2_470, true, "downloads")).toBe(0); expect(getSnapshotRenderDelay(2_470, true, "statistics")).toBe(800); diff --git a/tests/visual/main.tsx b/tests/visual/main.tsx index 8ad148e..fd9d671 100644 --- a/tests/visual/main.tsx +++ b/tests/visual/main.tsx @@ -1,6 +1,7 @@ import type { ReactElement } from "react"; import { createRoot } from "react-dom/client"; import { App } from "../../src/renderer/App"; +import { DeleteConfirmationDialog } from "../../src/renderer/views/downloads/DeleteConfirmationDialog"; import type { ElectronApi } from "../../src/shared/preload-api"; import "../../src/renderer/theme.css"; import "../../src/renderer/styles.css"; @@ -75,6 +76,19 @@ export function renderVisualApp(render: (element: ReactElement) => void): void { render(); } +export function renderDeleteConfirmationDialog(render: (element: ReactElement) => void): void { + render( + {}} + onConfirm={() => {}} + onDontAskChange={() => {}} + /> + ); +} + export async function markVisualReady( scenario: VisualScenario, rootElement: VisualHarnessRoot, @@ -154,6 +168,12 @@ export async function startVisualHarness( runtime.marker.visualScenario = scenario; const root = runtime.createRoot(rootElement); + if (new URLSearchParams(runtime.search).get("dialog") === "delete-confirmation") { + renderDeleteConfirmationDialog((element) => root.render(element)); + await waitForVisualFrames(runtime.requestFrame); + runtime.marker.visualReady = "true"; + return; + } renderVisualApp((element) => root.render(element)); const readyOptions = {
Möchtest Du wirklich diese Aufräumaktion(en) durchführen?Ausgewählte Links löschen
Zu erledigende Aufgaben:{parts.join(" + ")} löschen ? {totalRemaining} Link(s) verbleiben!