import { useEffect, useState, type ChangeEvent, type KeyboardEvent, type MouseEvent, type PointerEvent, type ReactElement, type UIEvent } from "react"; import { DataTable, DataTableBody, DataTableEmpty, DataTableHeader } from "../../ui/DataTable"; import { Toolbar, ToolbarGroup, ToolbarSearch } from "../../ui/Toolbar"; import { SlidingSelection } from "../../ui/SlidingSelection"; import { createHistoryTableColumnWidths, getHistoryTableGridTemplate, getHistoryTableMinWidth, HISTORY_TABLE_COLUMN_IDS, paginateHistoryRows, resizeHistoryTableColumn, type HistoryFilter, type HistoryPage, type HistoryRow, type HistoryTableColumnId, type HistoryTableColumnWidths, type HistoryViewModel } from "./history-model"; import "./history.css"; export interface HistoryViewActions { onFilterChange: (filter: HistoryFilter) => void; onQueryChange: (value: string) => void; onToggleSelection: (entryId: string) => void; onToggleSelectAll: (visibleIds: string[]) => void; onToggleExpansion: (entryId: string) => void; onRestore: (entryIds: string[]) => void; onReveal: (entryId: string) => void; onRemove: (entryIds: string[]) => void; onClearSelection: () => void; onClearHistory: () => void; onContextMenu: (entryId: string, x: number, y: number) => void; } export interface HistoryViewProps { model: HistoryViewModel; actions: HistoryViewActions; } const filterItems: Array<{ id: HistoryFilter; label: string }> = [ { id: "all", label: "Alle Einträge" }, { id: "today", label: "Heute" }, { id: "week", label: "Letzte 7 Tage" }, { id: "older", label: "Älter" }, { id: "completed", label: "Fertig" }, { id: "deleted", label: "Gelöscht" }, { id: "failed", label: "Fehlgeschlagen" } ]; const HISTORY_TABLE_COLUMNS = ["Paket / Datei", "Status", "Größe", "Hoster", "Gestartet", "Beendet"] as const; const HISTORY_TABLE_COLUMN_STORAGE_KEY = "mdd.history-table-columns.v1"; let historyTableResizeSession: { column: HistoryTableColumnId; startX: number; initial: HistoryTableColumnWidths } | null = null; function loadHistoryTableColumnWidths(): HistoryTableColumnWidths { try { const stored = typeof window === "undefined" ? null : window.localStorage.getItem(HISTORY_TABLE_COLUMN_STORAGE_KEY); return createHistoryTableColumnWidths(stored ? JSON.parse(stored) : undefined); } catch { return createHistoryTableColumnWidths(); } } function applyHistoryTableColumnWidths(source: HTMLElement, widths: HistoryTableColumnWidths): void { const table = source.closest(".history-table"); if (!table) return; const template = getHistoryTableGridTemplate(widths); const minWidth = `${getHistoryTableMinWidth(widths)}px`; table.querySelectorAll(".history-table-header-row, .history-row, .history-detail-row").forEach((row) => { if (!row.classList.contains("history-detail-row")) { row.style.gridTemplateColumns = template; } row.style.minWidth = minWidth; }); } function persistHistoryTableColumnWidths(widths: HistoryTableColumnWidths): void { try { window.localStorage.setItem(HISTORY_TABLE_COLUMN_STORAGE_KEY, JSON.stringify(widths)); } catch { } } function syncHistoryTableScroll(event: UIEvent): void { const header = event.currentTarget.parentElement?.querySelector(".history-table-header"); if (header) { header.scrollLeft = event.currentTarget.scrollLeft; } } function HistoryRowDetails({ row, minWidth }: { row: HistoryRow; minWidth: number }): ReactElement { return (
Provider
{row.providerLabel}
Dateien
{row.fileCount}
Dauer
{row.durationLabel}
Durchschnitt
{row.averageSpeedLabel}
Zielordner
{row.outputDir || "—"}
URLs
{row.urls?.length ? row.urls.join("\n") : "—"}
); } export function HistorySidebar({ model, actions }: HistoryViewProps): ReactElement { return (
Verlauf {filterItems.map((item) => ( ))}
); } export function HistoryToolbar({ model, actions }: HistoryViewProps): ReactElement { const selectedIds = model.selectedIds; const selectedSet = new Set(selectedIds); const restorable = model.rows.some((row) => selectedSet.has(row.id) && (row.urls?.length ?? 0) > 0); return ( ) => actions.onQueryChange(event.target.value)} placeholder="Name, Pfad, Hoster oder Provider" value={model.query} /> ); } export function historyPageStatusLabel(page: HistoryPage): string { return `Seite ${page.page} von ${page.totalPages}`; } export function HistoryPagination({ page, onPageChange }: { page: HistoryPage; onPageChange: (page: number) => void; }): ReactElement { return ( ); } interface HistoryContentPageProps extends HistoryViewProps { page: HistoryPage; onPageChange: (page: number) => void; } export function HistoryContentPage({ model, actions, page, onPageChange }: HistoryContentPageProps): ReactElement { const selected = new Set(model.selectedIds); const expanded = new Set(model.expandedIds); const visibleIds = page.rows.map((row) => row.id); const allVisibleSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id)); const showEmpty = !model.loading && !model.error && model.rows.length === 0; const emptyTitle = model.totalCount === 0 && !model.query && model.filter === "all" ? "Noch kein Verlauf" : "Keine passenden Einträge"; const announcement = model.loading ? { role: "status" as const, live: "polite" as const, message: "Verlauf wird geladen. Die gespeicherten Einträge werden geladen." } : model.error ? { role: "alert" as const, live: "assertive" as const, message: `${model.error}. Öffne die Ansicht erneut, um es noch einmal zu versuchen.` } : null; const columnWidths = loadHistoryTableColumnWidths(); const gridTemplateColumns = getHistoryTableGridTemplate(columnWidths); const minWidth = getHistoryTableMinWidth(columnWidths); const beginResize = (event: PointerEvent, column: HistoryTableColumnId): void => { event.preventDefault(); event.stopPropagation(); event.currentTarget.setPointerCapture?.(event.pointerId); historyTableResizeSession = { column, startX: event.clientX, initial: loadHistoryTableColumnWidths() }; }; const continueResize = (event: PointerEvent): void => { const active = historyTableResizeSession; if (!active) return; const next = resizeHistoryTableColumn(active.initial, active.column, event.clientX - active.startX); applyHistoryTableColumnWidths(event.currentTarget, next); persistHistoryTableColumnWidths(next); }; const finishResize = (event: PointerEvent): void => { if (!historyTableResizeSession) return; event.currentTarget.releasePointerCapture?.(event.pointerId); historyTableResizeSession = null; }; const resizeWithKeyboard = (event: KeyboardEvent, column: HistoryTableColumnId): void => { if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; event.preventDefault(); event.stopPropagation(); const next = resizeHistoryTableColumn(loadHistoryTableColumnWidths(), column, event.key === "ArrowRight" ? 16 : -16); applyHistoryTableColumnWidths(event.currentTarget, next); persistHistoryTableColumnWidths(next); }; return (

Verlauf

actions.onToggleSelectAll(visibleIds)} type="checkbox" /> {HISTORY_TABLE_COLUMNS.map((column, index) => ( {column}
{model.loading ? ( ) : model.error ? ( ) : showEmpty ? ( ) : ( page.rows.map((row) => { const isSelected = selected.has(row.id); const isExpanded = expanded.has(row.id); const onContextMenu = (event: MouseEvent): void => { event.preventDefault(); event.stopPropagation(); event.currentTarget.querySelector(".history-row-action button")?.focus({ preventScroll: true }); actions.onContextMenu(row.id, event.clientX, event.clientY); }; return (
actions.onToggleSelection(row.id)} type="checkbox" /> {row.name} {row.statusLabel} {row.sizeLabel} {row.hoster} {row.startedLabel} {row.completedLabel}
{isExpanded ? : null}
); }) )}
{announcement ? (
{announcement.message}
) : null}
); } function PaginatedHistoryContent({ model, actions }: HistoryViewProps): ReactElement { const [requestedPage, setRequestedPage] = useState(1); const page = paginateHistoryRows(model.rows, requestedPage); useEffect(() => { setRequestedPage((current) => current === page.page ? current : page.page); }, [page.page]); return ( ); } export function HistoryContent({ model, actions }: HistoryViewProps): ReactElement { return ; } export function HistoryFooter(_props: Pick): null { return null; } export function HistoryView({ model, actions }: HistoryViewProps): ReactElement { return (
); }