Separate collector package and file columns

Keep package names in the Name column and render file counts or individual filenames in a dedicated File column. Rebalance responsive table tracks and preserve accessible package context for virtualized file rows.
This commit is contained in:
Sucukdeluxe
2026-08-26 16:32:49 +02:00
parent eed8d1e970
commit 65e3b5b2d0
3 changed files with 48 additions and 28 deletions
@@ -314,8 +314,8 @@ function CollectorPackageGroup({ row, model, actions, selected, focusIndexStart,
type="button"
>{row.collapsed ? "+" : ""}</button>
<strong title={row.name}>{row.name}</strong>
<small>{row.totalCount} Dateien</small>
</span>
<span className="collector-file-cell is-package" role="cell">{row.totalCount} Dateien</span>
<span className="collector-size-cell" role="cell">{packageSize(row)}</span>
<span className="collector-hoster-cell" role="cell">
{row.hosters.map(formatHosterLabel).map((hoster) => <CollectorHosterLabel hoster={hoster} key={hoster.title} />)}
@@ -335,7 +335,8 @@ function CollectorPackageGroup({ row, model, actions, selected, focusIndexStart,
<span className="collector-column-select" role="cell">
<input aria-label={`${link.fileName} auswählen`} checked={selected.has(link.id)} data-collector-focus-index={interaction.focusIndex} onChange={(event) => actions.onLinkSelectionChange(link.id, event.target.checked)} tabIndex={interaction.tabIndex} 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 aria-label={`Paket ${row.name}`} className="collector-name-cell is-empty" role="cell" />
<span className="collector-file-cell" 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"><CollectorHosterLabel hoster={hoster} /></span>
<span className={`collector-status-cell is-${link.availability}`} role="cell">{linkAvailability(link.availability)}</span>
@@ -428,7 +429,8 @@ export function CollectorContent({ model, actions }: CollectorViewProps): ReactE
<DataTableHeader className="collector-table-header">
<div aria-rowindex={1} className="collector-table-header-row" role="row" style={collectorHeaderScrollStyle(viewport.scrollLeft)}>
<span aria-label="Auswahl" className="collector-column-select" role="columnheader" />
<span role="columnheader">Name</span>
<span className="collector-column-name" role="columnheader">Name</span>
<span className="collector-column-file" role="columnheader">Datei</span>
<span role="columnheader">Größe</span>
<span role="columnheader">Hoster</span>
<span role="columnheader">Status</span>
+33 -21
View File
@@ -197,8 +197,8 @@
.collector-file-row {
align-items: center;
display: grid;
grid-template-columns: 48px minmax(260px, 2.4fr) minmax(110px, 0.8fr) minmax(90px, 0.65fr) minmax(115px, 0.9fr) minmax(130px, 1fr) minmax(150px, 1fr);
min-width: 960px;
grid-template-columns: 48px minmax(200px, 1.4fr) minmax(300px, 2fr) minmax(110px, 0.75fr) minmax(90px, 0.65fr) minmax(115px, 0.85fr) minmax(130px, 1fr) minmax(150px, 1fr);
min-width: 1200px;
}
.collector-table-header-row {
@@ -217,11 +217,11 @@
padding: 0 12px;
}
.collector-table-header-row > span:nth-child(2) {
.collector-column-name {
padding-left: 48px;
}
.collector-table-header-row > span:nth-child(n+3) {
.collector-table-header-row > span:nth-child(n+4) {
display: flex;
justify-content: center;
text-align: center;
@@ -247,7 +247,7 @@
.collector-virtual-spacer {
flex: 0 0 var(--collector-virtual-total-height);
height: var(--collector-virtual-total-height);
min-width: 960px;
min-width: 1200px;
position: relative;
}
@@ -338,20 +338,22 @@
}
.collector-name-cell strong,
.collector-name-cell.is-file {
.collector-file-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collector-name-cell small {
color: var(--ui-text-muted);
white-space: nowrap;
.collector-file-cell {
align-items: center;
color: var(--ui-text);
display: flex;
gap: 8px;
min-width: 0;
}
.collector-name-cell.is-file {
color: var(--ui-text);
padding-left: 32px;
.collector-file-cell.is-package {
color: var(--ui-text-muted);
}
.collector-collapse-button {
@@ -521,6 +523,19 @@
}
}
@media (max-width: 1530px) {
.collector-table-header-row,
.collector-package-row,
.collector-file-row {
grid-template-columns: 44px minmax(180px, 1.2fr) minmax(260px, 1.7fr) minmax(98px, 0.75fr) minmax(78px, 0.65fr) minmax(100px, 0.85fr) minmax(112px, 1fr) minmax(132px, 1fr);
min-width: 1040px;
}
.collector-virtual-spacer {
min-width: 1040px;
}
}
@media (max-width: 1366px) {
.collector-view {
grid-template-columns: 56px minmax(0, 1fr);
@@ -542,13 +557,6 @@
.collector-action {
padding: 0 9px;
}
.collector-table-header-row,
.collector-package-row,
.collector-file-row {
grid-template-columns: 44px minmax(220px, 2.2fr) minmax(98px, 0.8fr) minmax(78px, 0.65fr) minmax(100px, 0.9fr) minmax(112px, 1fr) minmax(132px, 1fr);
min-width: 820px;
}
}
@media (max-width: 1120px) {
@@ -568,7 +576,11 @@
.collector-table-header-row,
.collector-package-row,
.collector-file-row {
grid-template-columns: 42px minmax(200px, 2fr) minmax(92px, 0.8fr) minmax(72px, 0.65fr) minmax(94px, 0.9fr) minmax(106px, 1fr) minmax(124px, 1fr);
min-width: 760px;
grid-template-columns: 42px minmax(170px, 1.1fr) minmax(240px, 1.6fr) minmax(92px, 0.75fr) minmax(72px, 0.65fr) minmax(94px, 0.85fr) minmax(106px, 1fr) minmax(124px, 1fr);
min-width: 940px;
}
.collector-virtual-spacer {
min-width: 940px;
}
}
+10 -4
View File
@@ -388,10 +388,12 @@ describe("CollectorView", () => {
it("renders expandable package and file rows with preview columns", () => {
const html = renderToStaticMarkup(<CollectorView actions={createActions()} model={buildCollectorWorkspaceViewModel(packages, "all", "", false, [], [], "", true)} />);
for (const heading of ["Name", "Größe", "Hoster", "Status", "Verfügbarkeit", "Hinzugefügt"]) expect(html).toContain(`>${heading}<`);
for (const heading of ["Name", "Datei", "Größe", "Hoster", "Status", "Verfügbarkeit", "Hinzugefügt"]) expect(html).toContain(`>${heading}<`);
expect(html).toContain("SBS14HD");
expect(html).toContain("SBS14HD.part01.rar");
expect(html).toContain("SBS14HD.part02.rar");
expect(html).toMatch(/collector-package-row[\s\S]*collector-name-cell[\s\S]*<strong[^>]*>SBS14HD<\/strong>[\s\S]*collector-file-cell is-package[^>]*>2 Dateien<[\s\S]*collector-size-cell/);
expect(html).toMatch(/collector-file-row[\s\S]*aria-label="Paket SBS14HD" class="collector-name-cell is-empty"[^>]*><\/span>[\s\S]*collector-file-cell[^>]*>[\s\S]*SBS14HD\.part01\.rar[\s\S]*collector-size-cell/);
expect(html).toContain("2/2 online");
expect(html).toContain(">Online<");
expect(html).toContain("aria-label=\"SBS14HD einklappen\"");
@@ -535,11 +537,15 @@ describe("CollectorView", () => {
expect(css).toMatch(/@media \(prefers-reduced-motion: reduce\)[\s\S]*\.collector-virtual-spacer\.is-motion-enabled \.collector-virtual-package\s*\{[^}]*transition-duration:\s*300ms, 300ms !important;/s);
expect(css).toMatch(/\.collector-package-items-frame\.is-expanding\s*\{[^}]*animation:\s*collector-items-expand/s);
expect(css).not.toMatch(/\.collector-package-items-frame\.is-animated\s*\{[^}]*animation:\s*collector-items-expand/s);
expect(css).toMatch(/\.collector-table-header-row > span:nth-child\(2\)\s*\{[^}]*padding-left:\s*48px;/s);
expect(css).toMatch(/\.collector-name-cell\.is-file\s*\{[^}]*padding-left:\s*32px;/s);
expect(css).toMatch(/\.collector-column-name\s*\{[^}]*padding-left:\s*48px;/s);
expect(css).toMatch(/\.collector-name-cell strong,\s*\.collector-file-cell\s*\{[^}]*text-overflow:\s*ellipsis;/s);
expect(css).toMatch(/\.collector-file-cell\s*\{[^}]*display:\s*flex;/s);
expect(css).toMatch(/\.collector-table-header,\s*\.collector-table-body\s*\{[^}]*scrollbar-gutter:\s*stable;/s);
expect(css).toMatch(/\.collector-size-cell,\s*\.collector-hoster-cell,\s*\.collector-status-cell,\s*\.collector-availability-cell,\s*\.collector-added-cell\s*\{[^}]*text-align:\s*center;/s);
expect(css).toMatch(/\.collector-table-header-row > span:nth-child\(n\+3\)\s*\{[^}]*justify-content:\s*center;[^}]*text-align:\s*center;/s);
expect(css).toMatch(/\.collector-table-header-row > span:nth-child\(n\+4\)\s*\{[^}]*justify-content:\s*center;[^}]*text-align:\s*center;/s);
expect(css).toMatch(/@media \(max-width: 1530px\)[\s\S]*grid-template-columns:[^;]+;[\s\S]*min-width:\s*1040px;/s);
expect(css).toMatch(/@media \(max-width: 1530px\)[\s\S]*\.collector-virtual-spacer\s*\{[^}]*min-width:\s*1040px;/s);
expect(css).toMatch(/@media \(max-width: 1120px\)[\s\S]*\.collector-virtual-spacer\s*\{[^}]*min-width:\s*940px;/s);
expect(collectorHeaderScrollStyle(37)).toEqual({ transform: "translateX(-37px)" });
});