import type { ReactElement } from "react"; import { RollingMetricValue } from "../../ui/RollingMetricValue"; import { SlidingSelection } from "../../ui/SlidingSelection"; import type { DownloadsViewModelCore, DownloadDisplayMode, DownloadSidebarFilter } from "./downloads-model"; import { DownloadsTableHeader, type DownloadSortColumn, type DownloadsTableActions } from "./DownloadsTable"; import { VirtualizedDownloadsBody } from "./VirtualizedDownloadsBody"; import "./downloads.css"; const integerFormatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 0 }); export type DailyScheduleStartDay = "today" | "tomorrow"; export interface DownloadsStatusModel { packages: number; links: number; session: string; sessionBytes: number; total: string; totalBytes: number; remaining: string; remainingBytes: number; remainingTooltip?: string; hosters: number; speed: string; eta: string; } export interface DownloadsViewModel extends DownloadsViewModelCore { running: boolean; paused: boolean; canStart: boolean; canPause: boolean; canStop: boolean; actionBusy: boolean; reconnectSeconds: number; reconnectReason: string; clipboardWatcher: boolean; scheduleActive: boolean; scheduleOpen: boolean; scheduleTime: string; scheduleTimeValid: boolean; scheduleStartDay: DailyScheduleStartDay; scheduleLabel: string; packageSpeedBps: Record; editingPackageId: string | null; editingName: string; columnOrder: readonly string[]; gridTemplate: string; sortColumn?: DownloadSortColumn; sortDirection?: "asc" | "desc"; disclosureRevision: number; animationsEnabled: boolean; status: DownloadsStatusModel; } export interface DownloadsViewActions extends DownloadsTableActions { onResetColumnLayout: () => void; onDisplayModeChange: (mode: DownloadDisplayMode) => void; onFilterChange: (filter: DownloadSidebarFilter) => void; onProviderFilterChange: (provider: string) => void; onQueryChange: (query: string) => void; onAddLinks: () => void; onStartDownloads: () => void; onPauseDownloads: () => void; onStopDownloads: () => void; onToggleSchedule: () => void; onScheduleTimeChange: (value: string) => void; onScheduleStartDayChange: (value: DailyScheduleStartDay) => void; onActivateSchedule: () => void; onCancelSchedule: () => void; onMoveSelectionUp: () => void; onMoveSelectionDown: () => void; onRenameSelection: () => void; onRemoveSelection: () => void; onToggleClipboardWatcher: () => void; onClearAll: () => void; onToggleAllPackages: () => void; onShowAllPackages: () => void; } const filters: Array<{ id: DownloadSidebarFilter; label: string }> = [ { id: "all", label: "Alle" }, { id: "active", label: "Aktiv" }, { id: "queued", label: "Wartend" }, { id: "paused", label: "Pausiert" }, { id: "completed", label: "Fertig" }, { id: "failed", label: "Fehler" } ]; export function DownloadsSidebar({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement { return ( ); } export function DownloadsSidebarStatus({ model }: { model: DownloadsViewModel }): ReactElement { const speed = model.status.speed.replace(/^Geschwindigkeit:\s*/i, ""); const eta = model.status.eta.replace(/^ETA:\s*/i, ""); const entries: Array<{ label: string; metric: string; numericValue: number; value: string; tooltip?: string }> = [ { label: "Pakete", metric: "packages", numericValue: model.status.packages, value: integerFormatter.format(model.status.packages) }, { label: "Links", metric: "links", numericValue: model.status.links, value: integerFormatter.format(model.status.links) }, { label: "Sitzung", metric: "session", numericValue: model.status.sessionBytes, value: model.status.session }, { label: "Verbleibend", metric: "remaining", numericValue: model.status.remainingBytes, value: model.status.remaining, tooltip: model.status.remainingTooltip }, { label: "Gesamt", metric: "total", numericValue: model.status.totalBytes, value: model.status.total }, { label: "Hoster", metric: "hosters", numericValue: model.status.hosters, value: integerFormatter.format(model.status.hosters) } ]; return
{entries.map((entry) => { const tooltipId = entry.tooltip ? `downloads-status-${entry.metric}-tooltip` : undefined; return
{entry.label}{entry.tooltip ? {entry.tooltip} : null}
; })}
Geschwindigkeit{speed}
ETA{eta}
; } export function DownloadsToolbar({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement { const hasSelection = model.actionableSelectedIds.length > 0; const hasSelectedPackage = model.actionableSelectedPackageIds.length > 0; const onePackage = model.actionableSelectedPackageIds.length === 1 && model.actionableSelectedIds.length === 1; const scheduleSlotOpen = model.scheduleActive || model.scheduleOpen; const scheduleSlotClass = `downloads-schedule-slot ${scheduleSlotOpen ? "is-open" : "is-closed"}${model.animationsEnabled ? "" : " is-motion-disabled"}`; return (
{!model.scheduleActive ? : null} {model.scheduleActive ? <>Geplant: {model.scheduleLabel} : <> actions.onScheduleTimeChange(event.target.value)} type="time" value={model.scheduleTime} />}
); } function tableState(model: DownloadsViewModel): ReactElement | null { if (model.empty) return
Noch keine DownloadsFüge Links hinzu, um den ersten Download zu starten.
; if (model.filteredEmpty) return
Keine passenden DownloadsPasse Filter oder Suche an.
; return null; } export function DownloadsContent({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement { const state = tableState(model); return (
); } export function DownloadsFooter({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement { return ( ); } export function DownloadsView({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement { return (
); }