refactor(downloads): simplify ready cards

This commit is contained in:
4gray committed 2026-08-01 16:29:50 +02:00
1 parent 8a5b29ea4d
commit 63b87da5f3
4 files changed
+119 -47

No files matched your search

@@ -72,12 +72,6 @@
</span>
}
</button>
<span class="download-library__offline">
<mat-icon aria-hidden="true"
>download_done</mat-icon
>
{{ 'DOWNLOADS.OFFLINE' | translate }}
</span>
</div>
<div class="download-library__body">
@@ -169,14 +163,59 @@
}
</div>
<div class="download-library__metadata">
@if (entity.sourceName.trim()) {
<span>{{ entity.sourceName }}</span>
<span aria-hidden="true">·</span>
}
<span>{{
formatBytes(entity.trackedBytes)
}}</span>
</div>
<div
class="download-library__actions"
[attr.aria-label]="entity.title"
>
<button
mat-icon-button
type="button"
[matMenuTriggerFor]="seriesMenu"
[attr.aria-label]="
('DOWNLOADS.MORE_ACTIONS'
| translate) +
': ' +
entity.title
"
[matTooltip]="
'DOWNLOADS.MORE_ACTIONS' | translate
"
>
<mat-icon aria-hidden="true">
more_vert
</mat-icon>
</button>
<mat-menu #seriesMenu="matMenu">
<app-download-source-menu-header
[sourceName]="entity.sourceName"
/>
<button
mat-menu-item
type="button"
[attr.aria-label]="
('DOWNLOADS.OPEN_EPISODES'
| translate) +
': ' +
entity.title
"
(click)="openEpisodes(entity)"
>
<mat-icon aria-hidden="true">
video_library
</mat-icon>
<span>
{{
'DOWNLOADS.OPEN_EPISODES'
| translate
}}
</span>
</button>
</mat-menu>
</div>
</div>
</article>
} @else {
@@ -253,12 +292,6 @@
</span>
}
</button>
<span class="download-library__offline">
<mat-icon aria-hidden="true"
>download_done</mat-icon
>
{{ 'DOWNLOADS.OFFLINE' | translate }}
</span>
</div>
<div class="download-library__body">
@@ -316,10 +349,6 @@
</span>
}
<div class="download-library__metadata">
@if (entity.sourceName.trim()) {
<span>{{ entity.sourceName }}</span>
<span aria-hidden="true">·</span>
}
<span>{{
formatBytes(entity.trackedBytes)
}}</span>
@@ -382,6 +411,9 @@
>
</button>
<mat-menu #itemMenu="matMenu">
<app-download-source-menu-header
[sourceName]="entity.sourceName"
/>
<button
mat-menu-item
type="button"
@@ -127,33 +127,6 @@
}
}
.download-library__offline {
position: absolute;
top: 10px;
left: 10px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 8px;
border: 1px solid
color-mix(in srgb, var(--mat-sys-on-primary) 18%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--mat-sys-primary) 88%, transparent);
color: var(--mat-sys-on-primary);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.045em;
line-height: 1;
text-transform: uppercase;
pointer-events: none;
mat-icon {
width: 14px;
height: 14px;
font-size: 14px;
}
}
.download-library__body {
display: grid;
grid-template-columns: minmax(0, 1fr);
@@ -36,6 +36,11 @@ const TRANSLATIONS = {
SOURCE_PLAYLIST_MISSING:
'This download can no longer open because its source was removed.',
},
PORTALS: {
MULTI_SOURCE: {
SOURCE: 'Source',
},
},
};
function item(overrides: Partial<DownloadItem>): DownloadItem {
@@ -218,6 +223,66 @@ describe('DownloadLibraryComponent', () => {
expect(fallbackEpisodeCard.textContent).toContain('Episode');
});
it('keeps ready cards clean while preserving tracked size', () => {
const section = byTestId('downloads-library-section');
const movieCard = byTestId('download-library-movie-9');
const seriesCard = byTestId('download-library-series-playlist-a-77');
const episodeCard = byTestId('download-library-episode-21');
expect(section.textContent).not.toContain('Offline');
expect(movieCard.textContent).not.toContain('Cinema');
expect(seriesCard.textContent).not.toContain('Living room');
expect(episodeCard.textContent).not.toContain('Archive');
expect(movieCard.textContent).toContain('2.4 MB');
});
it('puts the movie source ahead of its overflow commands', async () => {
const card = byTestId('download-library-movie-9');
await click(button(card, 'More actions: Moonrise'));
const header = document.querySelector<HTMLElement>(
'.download-source-menu-header'
);
const copy = button(document, 'Copy download URL: Moonrise');
const remove = button(document, 'Remove from manager: Moonrise');
expect(header?.textContent?.replace(/\s+/g, ' ').trim()).toBe(
'Source Cinema'
);
expect(
header &&
header.compareDocumentPosition(copy) &
Node.DOCUMENT_POSITION_FOLLOWING
).toBeTruthy();
expect(
copy.compareDocumentPosition(remove) &
Node.DOCUMENT_POSITION_FOLLOWING
).toBeTruthy();
});
it('opens downloaded episodes from the series overflow without a file action', async () => {
const card = byTestId('download-library-series-playlist-a-77');
const episodeGroups: DownloadSeriesCardViewModel[] = [];
const actions: unknown[] = [];
component.episodesOpened.subscribe((group) =>
episodeGroups.push(group)
);
component.itemAction.subscribe((action) => actions.push(action));
await click(button(card, 'More actions: Northwind'));
const header = document.querySelector<HTMLElement>(
'.download-source-menu-header'
);
expect(header?.textContent?.replace(/\s+/g, ' ').trim()).toBe(
'Source Living room'
);
await click(button(document, 'Open downloaded episodes: Northwind'));
expect(episodeGroups).toEqual([SERIES]);
expect(actions).toEqual([]);
});
it('omits the named library section when no completed entity is visible', async () => {
fixture.componentRef.setInput('entities', []);
await fixture.whenStable();
@@ -19,6 +19,7 @@ import type {
DownloadLibraryEntity,
DownloadSeriesCardViewModel,
} from './download-library.viewmodel';
import { DownloadSourceMenuHeaderComponent } from './download-source-menu-header.component';
@Component({
selector: 'app-download-library',
@@ -29,6 +30,7 @@ import type {
MatMenuModule,
MatTooltipModule,
TranslatePipe,
DownloadSourceMenuHeaderComponent,
],
templateUrl: './download-library.component.html',
styleUrl: './download-library.component.scss',