Redesign history tab with package-card style, collapsible details, right-align remove button
Some checks are pending
Build and Release / build (push) Waiting to run
Some checks are pending
Build and Release / build (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e35fc1f31a
commit
cb66661d9b
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.28",
|
"version": "1.5.29",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
DownloadItem,
|
DownloadItem,
|
||||||
DownloadStats,
|
DownloadStats,
|
||||||
DuplicatePolicy,
|
DuplicatePolicy,
|
||||||
|
HistoryEntry,
|
||||||
PackageEntry,
|
PackageEntry,
|
||||||
StartConflictEntry,
|
StartConflictEntry,
|
||||||
UiSnapshot,
|
UiSnapshot,
|
||||||
@ -15,7 +16,7 @@ import type {
|
|||||||
UpdateInstallProgress
|
UpdateInstallProgress
|
||||||
} from "../shared/types";
|
} from "../shared/types";
|
||||||
|
|
||||||
type Tab = "collector" | "downloads" | "statistics" | "settings";
|
type Tab = "collector" | "downloads" | "history" | "statistics" | "settings";
|
||||||
type SettingsSubTab = "allgemein" | "accounts" | "entpacken" | "geschwindigkeit" | "bereinigung" | "updates";
|
type SettingsSubTab = "allgemein" | "accounts" | "entpacken" | "geschwindigkeit" | "bereinigung" | "updates";
|
||||||
|
|
||||||
interface CollectorTab {
|
interface CollectorTab {
|
||||||
@ -480,6 +481,25 @@ export function App(): ReactElement {
|
|||||||
const [linkPopup, setLinkPopup] = useState<LinkPopupState | null>(null);
|
const [linkPopup, setLinkPopup] = useState<LinkPopupState | null>(null);
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ ids: Set<string>; dontAsk: boolean } | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<{ ids: Set<string>; dontAsk: boolean } | null>(null);
|
||||||
|
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([]);
|
||||||
|
const [historyCollapsed, setHistoryCollapsed] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
// Load history when tab changes to history
|
||||||
|
useEffect(() => {
|
||||||
|
if (tab !== "history") return;
|
||||||
|
const loadHistory = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const entries = await window.rd.getHistory();
|
||||||
|
console.log("History loaded:", entries);
|
||||||
|
if (mountedRef.current && entries) {
|
||||||
|
setHistoryEntries(entries);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load history:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void loadHistory();
|
||||||
|
}, [tab]);
|
||||||
|
|
||||||
const currentCollectorTab = collectorTabs.find((t) => t.id === activeCollectorTab) ?? collectorTabs[0];
|
const currentCollectorTab = collectorTabs.find((t) => t.id === activeCollectorTab) ?? collectorTabs[0];
|
||||||
|
|
||||||
@ -1960,6 +1980,7 @@ export function App(): ReactElement {
|
|||||||
<button className={tab === "downloads" ? "tab active" : "tab"} onClick={() => setTab("downloads")}>Downloads</button>
|
<button className={tab === "downloads" ? "tab active" : "tab"} onClick={() => setTab("downloads")}>Downloads</button>
|
||||||
<button className={tab === "collector" ? "tab active" : "tab"} onClick={() => setTab("collector")}>Linksammler</button>
|
<button className={tab === "collector" ? "tab active" : "tab"} onClick={() => setTab("collector")}>Linksammler</button>
|
||||||
<button className={tab === "settings" ? "tab active" : "tab"} onClick={() => setTab("settings")}>Einstellungen</button>
|
<button className={tab === "settings" ? "tab active" : "tab"} onClick={() => setTab("settings")}>Einstellungen</button>
|
||||||
|
<button className={tab === "history" ? "tab active" : "tab"} onClick={() => setTab("history")}>Verlauf</button>
|
||||||
<button className={tab === "statistics" ? "tab active" : "tab"} onClick={() => setTab("statistics")}>Statistiken</button>
|
<button className={tab === "statistics" ? "tab active" : "tab"} onClick={() => setTab("statistics")}>Statistiken</button>
|
||||||
<div className="tab-actions">
|
<div className="tab-actions">
|
||||||
{tab === "downloads" && (
|
{tab === "downloads" && (
|
||||||
@ -2138,6 +2159,67 @@ export function App(): ReactElement {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tab === "history" && (
|
||||||
|
<section className="history-view">
|
||||||
|
<div className="history-toolbar">
|
||||||
|
<span className="history-count">{historyEntries.length} Paket{historyEntries.length !== 1 ? "e" : ""} im Verlauf</span>
|
||||||
|
{historyEntries.length > 0 && (
|
||||||
|
<button className="btn btn-danger" onClick={() => { void window.rd.clearHistory().then(() => setHistoryEntries([])); }}>Verlauf leeren</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{historyEntries.length === 0 && <div className="empty">Noch keine abgeschlossenen Pakete im Verlauf.</div>}
|
||||||
|
{historyEntries.map((entry) => {
|
||||||
|
const collapsed = historyCollapsed[entry.id] ?? true;
|
||||||
|
return (
|
||||||
|
<article key={entry.id} className="package-card history-card">
|
||||||
|
<header onClick={() => setHistoryCollapsed((prev) => ({ ...prev, [entry.id]: !collapsed }))} style={{ cursor: "pointer" }}>
|
||||||
|
<div className="pkg-columns">
|
||||||
|
<div className="pkg-col pkg-col-name">
|
||||||
|
<button className="pkg-toggle" title={collapsed ? "Ausklappen" : "Einklappen"}>{collapsed ? "+" : "\u2212"}</button>
|
||||||
|
<h4>{entry.name}</h4>
|
||||||
|
</div>
|
||||||
|
<span className="pkg-col pkg-col-progress">{entry.status === "completed" ? "100%" : "-"}</span>
|
||||||
|
<span className="pkg-col pkg-col-size">{humanSize(entry.totalBytes)}</span>
|
||||||
|
<span className="pkg-col pkg-col-downloaded">{humanSize(entry.downloadedBytes)}</span>
|
||||||
|
<span className="pkg-col pkg-col-hoster">{entry.provider ? providerLabels[entry.provider] : "-"}</span>
|
||||||
|
<span className="pkg-col pkg-col-status">{entry.status === "completed" ? "Abgeschlossen" : "Gelöscht"}</span>
|
||||||
|
<span className="pkg-col pkg-col-speed">-</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="progress"><div className="progress-dl" style={{ width: entry.status === "completed" ? "100%" : "0%" }} /></div>
|
||||||
|
{!collapsed && (
|
||||||
|
<div className="history-details">
|
||||||
|
<div className="history-detail-grid">
|
||||||
|
<span className="history-label">Abgeschlossen am</span>
|
||||||
|
<span>{new Date(entry.completedAt).toLocaleString("de-DE")}</span>
|
||||||
|
<span className="history-label">Dateien</span>
|
||||||
|
<span>{entry.fileCount} Datei{entry.fileCount !== 1 ? "en" : ""}</span>
|
||||||
|
<span className="history-label">Gesamtgröße</span>
|
||||||
|
<span>{humanSize(entry.totalBytes)}</span>
|
||||||
|
<span className="history-label">Heruntergeladen</span>
|
||||||
|
<span>{humanSize(entry.downloadedBytes)}</span>
|
||||||
|
<span className="history-label">Dauer</span>
|
||||||
|
<span>{entry.durationSeconds >= 3600 ? `${Math.floor(entry.durationSeconds / 3600)}h ${Math.floor((entry.durationSeconds % 3600) / 60)}min` : entry.durationSeconds >= 60 ? `${Math.floor(entry.durationSeconds / 60)}min ${entry.durationSeconds % 60}s` : `${entry.durationSeconds}s`}</span>
|
||||||
|
<span className="history-label">Durchschnitt</span>
|
||||||
|
<span>{entry.durationSeconds > 0 ? formatSpeedMbps(Math.round(entry.downloadedBytes / entry.durationSeconds)) : "-"}</span>
|
||||||
|
<span className="history-label">Provider</span>
|
||||||
|
<span>{entry.provider ? providerLabels[entry.provider] : "-"}</span>
|
||||||
|
<span className="history-label">Zielordner</span>
|
||||||
|
<span className="history-path" title={entry.outputDir}>{entry.outputDir || "-"}</span>
|
||||||
|
<span className="history-label">Status</span>
|
||||||
|
<span>{entry.status === "completed" ? "Abgeschlossen" : "Gelöscht"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="history-actions">
|
||||||
|
<button className="btn" onClick={() => { void window.rd.removeHistoryEntry(entry.id).then(() => setHistoryEntries((prev) => prev.filter((e) => e.id !== entry.id))); }}>Eintrag entfernen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{tab === "statistics" && (
|
{tab === "statistics" && (
|
||||||
<section className="statistics-view">
|
<section className="statistics-view">
|
||||||
<article className="card stats-overview">
|
<article className="card stats-overview">
|
||||||
|
|||||||
@ -1102,6 +1102,60 @@ body,
|
|||||||
background: linear-gradient(90deg, #22c55e, #4ade80);
|
background: linear-gradient(90deg, #22c55e, #4ade80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* History Tab */
|
||||||
|
.history-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px 12px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-card {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-details {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 140px 1fr;
|
||||||
|
gap: 4px 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-label {
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-path {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user