feat(collector): show hoster icons with tooltips
Render known hosters with their existing provider icons in collector package and file rows while retaining the full hoster name as a tooltip. Fall back to the compact text label if an icon cannot be loaded.
This commit is contained in:
@@ -22,6 +22,7 @@ All notable changes to Multi-Debrid Downloader are documented in this file.
|
||||
|
||||
- Rebuilt the link collector as a package-oriented preview with expandable file rows, resolved metadata, availability filters, stable selection, and selected or complete transfer to Downloads.
|
||||
- Route pasted links, clipboard detections, text files, drag-and-drop, and DLC containers through inspection before they enter the download queue.
|
||||
- Show known hosters as icons with full-name tooltips and a text fallback when an icon cannot be loaded.
|
||||
|
||||
## [2.0.54] - 2026-08-21
|
||||
|
||||
|
||||
@@ -58,6 +58,14 @@ function packageSize(row: CollectorWorkspacePackageRow): string {
|
||||
return `${row.unknownSizeCount > 0 ? "≥ " : ""}${humanSize(row.totalBytes)}`;
|
||||
}
|
||||
|
||||
function CollectorHosterLabel({ hoster }: { hoster: ReturnType<typeof formatHosterLabel> }): ReactElement {
|
||||
return (
|
||||
<span className="collector-hoster-label" title={hoster.title}>
|
||||
{hoster.iconSrc ? <><img alt="" className="collector-hoster-icon" data-hoster={hoster.title.toLowerCase()} onError={(event) => { event.currentTarget.hidden = true; event.currentTarget.nextElementSibling?.removeAttribute("hidden"); }} src={hoster.iconSrc} /><span hidden>{hoster.compact}</span></> : hoster.compact}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function CollectorSidebar({ model, actions }: CollectorViewProps): ReactElement {
|
||||
return (
|
||||
<div aria-label="Linksammler-Filter" className="collector-sidebar" data-visual-region="collector-sidebar">
|
||||
@@ -103,7 +111,7 @@ function CollectorPackageGroup({ row, model, actions }: { row: CollectorWorkspac
|
||||
<span className="collector-column-select" role="cell"><input aria-checked={partiallySelected ? "mixed" : allSelected} aria-label={`Paket ${row.name} auswählen`} checked={allSelected} onChange={(event) => actions.onPackageSelectionChange(row.id, event.target.checked)} ref={(node) => { if (node) node.indeterminate = partiallySelected; }} type="checkbox" /></span>
|
||||
<span className="collector-name-cell" role="cell"><button aria-expanded={!row.collapsed} aria-label={row.collapsed ? `${row.name} ausklappen` : `${row.name} einklappen`} className="collector-collapse-button" onClick={() => actions.onPackageCollapseChange(row.id)} type="button">{row.collapsed ? "+" : "−"}</button><strong title={row.name}>{row.name}</strong><small>{row.totalCount} Dateien</small></span>
|
||||
<span className="collector-size-cell" role="cell">{packageSize(row)}</span>
|
||||
<span className="collector-hoster-cell" role="cell">{row.hosters.map(formatHosterLabel).map((hoster) => <span key={hoster.title} title={hoster.title}>{hoster.compact}</span>)}</span>
|
||||
<span className="collector-hoster-cell" role="cell">{row.hosters.map(formatHosterLabel).map((hoster) => <CollectorHosterLabel hoster={hoster} key={hoster.title} />)}</span>
|
||||
<span className="collector-status-cell" role="cell">{packageStatus(row)}</span>
|
||||
<span className={`collector-availability-cell is-${row.offlineCount === row.totalCount ? "offline" : row.onlineCount === row.totalCount ? "online" : "unknown"}`} role="cell">{packageAvailability(row)}</span>
|
||||
<span className="collector-added-cell" role="cell">{formatDateTime(row.addedAt)}</span>
|
||||
@@ -115,7 +123,7 @@ function CollectorPackageGroup({ row, model, actions }: { row: CollectorWorkspac
|
||||
<span className="collector-column-select" role="cell"><input aria-label={`${link.fileName} auswählen`} checked={selected.has(link.id)} onChange={(event) => actions.onLinkSelectionChange(link.id, event.target.checked)} type="checkbox" /></span>
|
||||
<span className="collector-name-cell is-file" role="cell" title={link.url}><span className={`collector-link-state is-${link.availability}`} />{link.fileName}</span>
|
||||
<span className="collector-size-cell" role="cell">{link.fileSizeBytes === null ? "Unbekannt" : humanSize(link.fileSizeBytes)}</span>
|
||||
<span className="collector-hoster-cell" role="cell"><span title={hoster.title}>{hoster.compact}</span></span>
|
||||
<span className="collector-hoster-cell" role="cell"><CollectorHosterLabel hoster={hoster} /></span>
|
||||
<span className="collector-status-cell" role="cell">{link.status === "ready" ? "Bereit" : link.status === "offline" ? "Offline" : "Ungeprüft"}</span>
|
||||
<span className={`collector-availability-cell is-${link.availability}`} role="cell">{link.availability === "online" ? "Online" : link.availability === "offline" ? "Offline" : "Ungeprüft"}</span>
|
||||
<span className="collector-added-cell" role="cell">{formatDateTime(link.addedAt)}</span>
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
.collector-size-cell, .collector-added-cell { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||||
.collector-hoster-cell { align-items: center; display: flex; gap: 5px; }
|
||||
.collector-hoster-cell span { color: var(--ui-text); font-weight: 600; }
|
||||
.collector-hoster-label { align-items: center; display: inline-flex; justify-content: center; min-width: 18px; }
|
||||
.collector-hoster-icon { display: block; height: 18px; object-fit: contain; width: 18px; }
|
||||
.collector-hoster-icon[data-hoster="rapidgator"] { transform: translateY(-4px) scale(2); }
|
||||
.collector-status-cell, .collector-added-cell { color: var(--ui-text-muted); }
|
||||
.collector-availability-cell { font-weight: 600; white-space: nowrap; }
|
||||
.collector-availability-cell.is-online { color: var(--ui-success); }
|
||||
|
||||
@@ -127,6 +127,29 @@ describe("CollectorView", () => {
|
||||
expect(html).not.toContain(">Lokal<");
|
||||
});
|
||||
|
||||
it("renders known hosters as icons with their full name as tooltip", () => {
|
||||
const ddownloadPackages: CollectorPackage[] = [{
|
||||
id: "package-ddownload",
|
||||
name: "Archive",
|
||||
addedAt: 3000,
|
||||
links: [{
|
||||
id: "link-ddownload",
|
||||
url: "https://ddownload.com/ntwscdw62gyb",
|
||||
fileName: "Archive.part01.rar",
|
||||
fileSizeBytes: 526_385_152,
|
||||
hoster: "ddownload",
|
||||
availability: "online",
|
||||
status: "ready",
|
||||
addedAt: 3000
|
||||
}]
|
||||
}];
|
||||
const html = renderToStaticMarkup(<CollectorContent actions={createActions()} model={buildCollectorWorkspaceViewModel(ddownloadPackages, "all", "", false, [], [], "", true)} />);
|
||||
|
||||
expect(html).toContain('class="collector-hoster-label" title="DDownload"');
|
||||
expect(html).toContain('class="collector-hoster-icon" data-hoster="ddownload" src="./provider-icons/ddownload.ico"');
|
||||
expect(html).not.toContain('title="DDownload">DD<');
|
||||
});
|
||||
|
||||
it("renders collapsed packages without child rows", () => {
|
||||
const html = renderToStaticMarkup(<CollectorContent actions={createActions()} model={buildCollectorWorkspaceViewModel(packages, "all", "", false, [], ["package-sbs"], "", false)} />);
|
||||
expect(html).toContain("aria-label=\"SBS14HD ausklappen\"");
|
||||
|
||||
Reference in New Issue
Block a user