mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
fix(downloads): keep renderer download state global
This commit is contained in:
1 parent
505face7e4
commit
117bd75f01
11 files changed
+102
-23
No files matched your search
@@ -191,9 +191,9 @@ export class DownloadsComponent {
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const playlistId = this.playlistId();
|
||||
this.playlistId();
|
||||
this.collectionContext.setCategoryId('all');
|
||||
void this.downloadsService.loadDownloads(playlistId || undefined);
|
||||
void this.downloadsService.loadDownloads();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ type DownloadsElectronStub = {
|
||||
[number, string]
|
||||
>;
|
||||
downloadsSelectFolder?: jest.Mock<Promise<string | null>, []>;
|
||||
downloadsGetList: jest.Mock<Promise<DownloadItem[]>, [string?]>;
|
||||
downloadsGetList: jest.Mock<Promise<DownloadItem[]>, []>;
|
||||
};
|
||||
|
||||
describe('DownloadsService', () => {
|
||||
@@ -121,7 +121,7 @@ describe('DownloadsService', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('tracks loading and loaded state around a successful download list request', async () => {
|
||||
it('loads the global download list without a playlist filter and tracks request state', async () => {
|
||||
const item = createDownload(1);
|
||||
const pending = createDeferred<DownloadItem[]>();
|
||||
const electron = {
|
||||
@@ -130,11 +130,11 @@ describe('DownloadsService', () => {
|
||||
testWindow.electron = electron;
|
||||
const service = createService();
|
||||
|
||||
const request = service.loadDownloads('playlist-1');
|
||||
const request = service.loadDownloads();
|
||||
|
||||
expect(service.isLoadingDownloads()).toBe(true);
|
||||
expect(service.hasLoadedDownloads()).toBe(false);
|
||||
expect(electron.downloadsGetList).toHaveBeenCalledWith('playlist-1');
|
||||
expect(electron.downloadsGetList).toHaveBeenCalledWith();
|
||||
|
||||
pending.resolve([item]);
|
||||
await request;
|
||||
@@ -245,8 +245,8 @@ describe('DownloadsService', () => {
|
||||
testWindow.electron = electron;
|
||||
const service = createService();
|
||||
|
||||
const firstRequest = service.loadDownloads('playlist-old');
|
||||
const secondRequest = service.loadDownloads('playlist-new');
|
||||
const firstRequest = service.loadDownloads();
|
||||
const secondRequest = service.loadDownloads();
|
||||
|
||||
expect(service.isLoadingDownloads()).toBe(true);
|
||||
|
||||
@@ -262,14 +262,8 @@ describe('DownloadsService', () => {
|
||||
|
||||
expect(service.downloads()).toEqual([latestItem]);
|
||||
expect(service.isLoadingDownloads()).toBe(false);
|
||||
expect(electron.downloadsGetList).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'playlist-old'
|
||||
);
|
||||
expect(electron.downloadsGetList).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'playlist-new'
|
||||
);
|
||||
expect(electron.downloadsGetList).toHaveBeenNthCalledWith(1);
|
||||
expect(electron.downloadsGetList).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
|
||||
it('reports paused content and resumes it by content identity', async () => {
|
||||
|
||||
@@ -106,14 +106,14 @@ export class DownloadsService implements OnDestroy {
|
||||
/**
|
||||
* Load downloads from the backend
|
||||
*/
|
||||
async loadDownloads(playlistId?: string): Promise<void> {
|
||||
async loadDownloads(): Promise<void> {
|
||||
if (!this.isAvailable()) return;
|
||||
|
||||
const requestId = ++this.loadDownloadsRequestId;
|
||||
this._isLoadingDownloads.set(true);
|
||||
|
||||
try {
|
||||
const list = await window.electron.downloadsGetList(playlistId);
|
||||
const list = await window.electron.downloadsGetList();
|
||||
if (requestId === this.loadDownloadsRequestId) {
|
||||
this.downloads.set(list);
|
||||
this._hasLoadedDownloads.set(true);
|
||||
|
||||
+9
@@ -121,6 +121,15 @@
|
||||
<span class="download-activity-bar__fill"></span>
|
||||
</span>
|
||||
}
|
||||
@if (activeDownloadsCount() > 0) {
|
||||
<span
|
||||
class="download-count-badge"
|
||||
data-test-id="global-download-count"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ activeDownloadsCount() }}
|
||||
</span>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="header-shortcut"
|
||||
|
||||
+22
@@ -214,6 +214,28 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.download-count-badge {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -4px;
|
||||
z-index: 11;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid var(--mat-sys-surface-container-low);
|
||||
border-radius: 999px;
|
||||
color: var(--app-selection-on-color, var(--mat-sys-on-primary));
|
||||
background: var(--app-selection-color, var(--mat-sys-primary));
|
||||
box-shadow: 0 2px 8px var(--app-selection-glow);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.download-activity-bar {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
|
||||
+28
@@ -172,4 +172,32 @@ describe('WorkspaceShellHeaderComponent', () => {
|
||||
|
||||
expect(chips).toEqual(['Movies / All Items', 'Loaded channels only']);
|
||||
});
|
||||
|
||||
it('renders the global active download count without duplicating the button name', () => {
|
||||
fixture.componentRef.setInput('isElectron', true);
|
||||
fixture.componentRef.setInput('activeDownloadsCount', 3);
|
||||
fixture.detectChanges();
|
||||
|
||||
const badge: HTMLElement | null = fixture.nativeElement.querySelector(
|
||||
'[data-test-id="global-download-count"]'
|
||||
);
|
||||
|
||||
expect(badge?.textContent?.trim()).toBe('3');
|
||||
expect(badge?.getAttribute('aria-hidden')).toBe('true');
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('.download-activity-bar')
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render a global download badge when no downloads are active', () => {
|
||||
fixture.componentRef.setInput('isElectron', true);
|
||||
fixture.componentRef.setInput('activeDownloadsCount', 0);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-test-id="global-download-count"]'
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
+5
-1
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
ElementRef,
|
||||
input,
|
||||
output,
|
||||
@@ -52,7 +53,10 @@ export class WorkspaceShellHeaderComponent {
|
||||
readonly isElectron = input(false);
|
||||
readonly hasNoPlaylists = input(false);
|
||||
readonly isDownloadsView = input(false);
|
||||
readonly hasActiveDownloads = input(false);
|
||||
readonly activeDownloadsCount = input(0);
|
||||
readonly hasActiveDownloads = computed(
|
||||
() => this.activeDownloadsCount() > 0
|
||||
);
|
||||
/**
|
||||
* When true the playlist switcher + the "+ Add source" / refresh /
|
||||
* bulk-action buttons are hidden — those controls scope to a
|
||||
|
||||
+15
@@ -376,6 +376,21 @@ describe('WorkspaceShellFacade', () => {
|
||||
expect(facade.isMacOS).toBe(false);
|
||||
});
|
||||
|
||||
it('exposes the global active download count and derives activity when supported', () => {
|
||||
downloadsActiveCountSignal.set(3);
|
||||
|
||||
expect(facade.activeDownloadsCount()).toBe(3);
|
||||
expect(facade.hasActiveDownloads()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the active download count when downloads are unsupported', () => {
|
||||
runtime.supportsDownloads = false;
|
||||
downloadsActiveCountSignal.set(3);
|
||||
|
||||
expect(facade.activeDownloadsCount()).toBe(0);
|
||||
expect(facade.hasActiveDownloads()).toBe(false);
|
||||
});
|
||||
|
||||
it('routes dashboard search Enter into the active Xtream playlist search', () => {
|
||||
const xtreamStore = TestBed.inject(
|
||||
XtreamStore
|
||||
|
||||
+4
-1
@@ -112,8 +112,11 @@ export class WorkspaceShellFacade {
|
||||
readonly isRefreshingPlaylist = this.header.isRefreshingPlaylist;
|
||||
readonly headerBulkAction = this.header.headerBulkAction;
|
||||
readonly playlistSubtitle = this.header.playlistSubtitle;
|
||||
readonly activeDownloadsCount = computed(() =>
|
||||
this.supportsDownloads ? this.downloadsService.activeCount() : 0
|
||||
);
|
||||
readonly hasActiveDownloads = computed(
|
||||
() => this.supportsDownloads && this.downloadsService.activeCount() > 0
|
||||
() => this.activeDownloadsCount() > 0
|
||||
);
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
[isElectron]="facade.isElectron"
|
||||
[hasNoPlaylists]="facade.hasNoPlaylists()"
|
||||
[isDownloadsView]="facade.isDownloadsView()"
|
||||
[hasActiveDownloads]="facade.hasActiveDownloads()"
|
||||
[activeDownloadsCount]="facade.activeDownloadsCount()"
|
||||
[isSettingsRoute]="facade.isSettingsRoute()"
|
||||
(refreshPlaylistRequested)="facade.refreshCurrentPlaylist()"
|
||||
(searchChanged)="facade.onSearchInput($event)"
|
||||
|
||||
+6
-2
@@ -58,7 +58,7 @@ class MockWorkspaceShellHeaderComponent {
|
||||
readonly isElectron = input(false);
|
||||
readonly hasNoPlaylists = input(false);
|
||||
readonly isDownloadsView = input(false);
|
||||
readonly hasActiveDownloads = input(false);
|
||||
readonly activeDownloadsCount = input(0);
|
||||
readonly isSettingsRoute = input(false);
|
||||
readonly headerBulkAction = input<WorkspaceHeaderBulkAction | null>(null);
|
||||
readonly searchChanged = output<string>();
|
||||
@@ -154,7 +154,7 @@ class MockWorkspaceShellFacade {
|
||||
readonly isRefreshingPlaylist = signal(false);
|
||||
readonly hasNoPlaylists = signal(false);
|
||||
readonly isDownloadsView = signal(false);
|
||||
readonly hasActiveDownloads = signal(false);
|
||||
readonly activeDownloadsCount = signal(3);
|
||||
readonly headerBulkAction = signal<WorkspaceHeaderBulkAction | null>(null);
|
||||
readonly showContextPanel = signal(true);
|
||||
readonly contextPanel = signal<WorkspaceShellContextPanel>('settings');
|
||||
@@ -246,6 +246,10 @@ describe('WorkspaceShellComponent', () => {
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('app-workspace-shell-header')
|
||||
).not.toBeNull();
|
||||
const header = fixture.debugElement.query(
|
||||
By.directive(MockWorkspaceShellHeaderComponent)
|
||||
).componentInstance as MockWorkspaceShellHeaderComponent;
|
||||
expect(header.activeDownloadsCount()).toBe(3);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'app-workspace-shell-context-sidebar'
|
||||
|
||||
Reference in new issue
Block a user