feat(downloads): add focused offline detail routes

This commit is contained in:
4gray committed 2026-08-01 16:29:50 +02:00
1 parent 95cdf83aa2
commit 05ea3ad636
7 files changed
+191 -21

No files matched your search

+7
View File
@@ -111,6 +111,13 @@ export const routes: Routes = [
(c) => c.GlobalSearchResultsComponent
),
},
{
path: 'downloads/:downloadId',
loadComponent: () =>
import('@iptvnator/portal/downloads/feature').then(
(c) => c.DownloadOfflineDetailComponent
),
},
{
path: 'downloads',
loadComponent: () =>
@@ -1 +1,2 @@
export * from './lib/downloads.component';
export * from './lib/offline-detail/download-offline-detail.component';
@@ -0,0 +1,10 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PortalDetailShellComponent } from '@iptvnator/ui/components';
@Component({
selector: 'app-download-offline-detail',
imports: [PortalDetailShellComponent],
template: '<app-portal-detail-shell [isLoading]="true" />',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DownloadOfflineDetailComponent {}
@@ -11,6 +11,11 @@ const loadDownloadsComponent: ComponentLoader = () =>
(c) => c.DownloadsComponent
);
const loadDownloadOfflineDetailComponent: ComponentLoader = () =>
import('@iptvnator/portal/downloads/feature').then(
(c) => c.DownloadOfflineDetailComponent
);
const loadStalkerLiveStreamLayoutComponent: ComponentLoader = () =>
import('./stalker-live-stream-layout/stalker-live-stream-layout.component').then(
(c) => c.StalkerLiveStreamLayoutComponent
@@ -129,6 +134,10 @@ export function createStalkerRoutes(): Route[] {
path: 'actor/:personId',
loadComponent: loadStalkerActorRouteComponent,
},
{
path: 'downloads/:downloadId',
loadComponent: loadDownloadOfflineDetailComponent,
},
{
path: 'downloads',
loadComponent: loadDownloadsComponent,
@@ -9,6 +9,11 @@ const loadDownloadsComponent: ComponentLoader = () =>
(c) => c.DownloadsComponent
);
const loadDownloadOfflineDetailComponent: ComponentLoader = () =>
import('@iptvnator/portal/downloads/feature').then(
(c) => c.DownloadOfflineDetailComponent
);
const loadXtreamContentGateComponent: ComponentLoader = () =>
import('./xtream-content-gate.component').then(
(c) => c.XtreamContentGateComponent
@@ -83,11 +88,13 @@ export function createXtreamRoutes(): Route[] {
children: [
{
path: '',
loadComponent: loadCategoryContentViewComponent,
loadComponent:
loadCategoryContentViewComponent,
},
{
path: ':categoryId',
loadComponent: loadCategoryContentViewComponent,
loadComponent:
loadCategoryContentViewComponent,
},
{
path: ':categoryId/:vodId',
@@ -101,11 +108,13 @@ export function createXtreamRoutes(): Route[] {
children: [
{
path: '',
loadComponent: loadCategoryContentViewComponent,
loadComponent:
loadCategoryContentViewComponent,
},
{
path: ':categoryId',
loadComponent: loadCategoryContentViewComponent,
loadComponent:
loadCategoryContentViewComponent,
},
{
path: ':categoryId/:serialId',
@@ -137,6 +146,10 @@ export function createXtreamRoutes(): Route[] {
loadComponent: loadXtreamCollectionRouteComponent,
data: { mode: 'recent', portalType: 'xtream' },
},
{
path: 'downloads/:downloadId',
loadComponent: loadDownloadOfflineDetailComponent,
},
{
path: 'downloads',
loadComponent: loadDownloadsComponent,
@@ -54,6 +54,128 @@ describe('workspace-shell-route.utils', () => {
expect(parseWorkspaceShellRoute('/workspace/downloads')).toEqual(
expect.objectContaining({
kind: 'downloads',
section: null,
contextPanel: 'none',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
});
it('disables collection shell state for focused download details', () => {
expect(
parseWorkspaceShellRoute(
'/workspace/downloads/42?query=matrix#technical-details'
)
).toEqual(
expect.objectContaining({
kind: 'downloads',
context: null,
section: null,
contextPanel: 'none',
searchMode: 'none',
usesQuerySearch: false,
})
);
expect(
parseWorkspaceShellRoute('/workspace/xtreams/pl-1/downloads/42')
).toEqual(
expect.objectContaining({
kind: 'portal',
context: {
provider: 'xtreams',
playlistId: 'pl-1',
},
section: 'downloads',
contextPanel: 'none',
searchMode: 'none',
usesQuerySearch: false,
})
);
expect(
parseWorkspaceShellRoute('/workspace/stalker/pl-1/downloads/42')
).toEqual(
expect.objectContaining({
kind: 'portal',
context: {
provider: 'stalker',
playlistId: 'pl-1',
},
section: 'downloads',
contextPanel: 'none',
searchMode: 'none',
usesQuerySearch: false,
})
);
});
it('keeps manager shell state outside exact focused download routes', () => {
expect(parseWorkspaceShellRoute('/workspace/downloads/')).toEqual(
expect.objectContaining({
kind: 'downloads',
contextPanel: 'none',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
expect(
parseWorkspaceShellRoute('/workspace/downloads/42/extra')
).toEqual(
expect.objectContaining({
kind: 'downloads',
contextPanel: 'none',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
expect(
parseWorkspaceShellRoute('/workspace/xtreams/pl-1/downloads')
).toEqual(
expect.objectContaining({
kind: 'portal',
section: 'downloads',
contextPanel: 'collection',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
expect(
parseWorkspaceShellRoute(
'/workspace/xtreams/pl-1/downloads/42/extra'
)
).toEqual(
expect.objectContaining({
kind: 'portal',
section: 'downloads',
contextPanel: 'collection',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
expect(
parseWorkspaceShellRoute('/workspace/stalker/pl-1/downloads')
).toEqual(
expect.objectContaining({
kind: 'portal',
section: 'downloads',
contextPanel: 'collection',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
expect(
parseWorkspaceShellRoute(
'/workspace/stalker/pl-1/downloads/42/extra'
)
).toEqual(
expect.objectContaining({
kind: 'portal',
section: 'downloads',
contextPanel: 'collection',
searchMode: 'local-filter',
usesQuerySearch: true,
})
);
});
@@ -103,9 +225,7 @@ describe('workspace-shell-route.utils', () => {
});
it('parses Stalker portal routes and detects context-panel/query-search state', () => {
expect(
parseWorkspaceShellRoute('/workspace/stalker/pl-2/itv')
).toEqual(
expect(parseWorkspaceShellRoute('/workspace/stalker/pl-2/itv')).toEqual(
expect.objectContaining({
kind: 'portal',
context: {
@@ -15,11 +15,7 @@ export type WorkspaceShellPageKind =
| 'unknown';
export type WorkspaceShellContextPanel =
| 'none'
| 'sources'
| 'settings'
| 'category'
| 'collection';
'none' | 'sources' | 'settings' | 'category' | 'collection';
export interface WorkspacePortalContext {
provider: PortalProvider;
@@ -27,10 +23,7 @@ export interface WorkspacePortalContext {
}
export type WorkspaceShellSearchMode =
| 'none'
| 'local-filter'
| 'remote-search'
| 'advanced-only';
'none' | 'local-filter' | 'remote-search' | 'advanced-only';
export interface WorkspaceShellRoute {
kind: WorkspaceShellPageKind;
@@ -266,6 +259,11 @@ export function parseWorkspaceShellRoute(url: string): WorkspaceShellRoute {
playlistId: segments[2],
};
const section = asPortalRailSection(sectionSegment);
const isFocusedDownloadDetail =
(provider === 'xtreams' || provider === 'stalker') &&
section === 'downloads' &&
segments.length === 5 &&
Boolean(segments[4]);
return {
kind: 'portal',
@@ -275,9 +273,15 @@ export function parseWorkspaceShellRoute(url: string): WorkspaceShellRoute {
section === 'favorites' &&
(provider === 'xtreams' || provider === 'stalker') &&
queryParams.get('scope') === 'all',
searchMode: resolveRouteSearchMode('portal', context, section),
usesQuerySearch: usesWorkspaceRouteQuerySearch(context, section),
contextPanel: resolveContextPanel('portal', context, section),
searchMode: isFocusedDownloadDetail
? 'none'
: resolveRouteSearchMode('portal', context, section),
usesQuerySearch: isFocusedDownloadDetail
? false
: usesWorkspaceRouteQuerySearch(context, section),
contextPanel: isFocusedDownloadDetail
? 'none'
: resolveContextPanel('portal', context, section),
};
}
@@ -298,7 +302,11 @@ export function parseWorkspaceShellRoute(url: string): WorkspaceShellRoute {
: page === 'downloads'
? 'downloads'
: 'unknown';
const searchMode = resolveRouteSearchMode(kind, null, null);
const isFocusedDownloadDetail =
kind === 'downloads' && segments.length === 3 && Boolean(segments[2]);
const searchMode = isFocusedDownloadDetail
? 'none'
: resolveRouteSearchMode(kind, null, null);
return {
kind,
@@ -308,6 +316,8 @@ export function parseWorkspaceShellRoute(url: string): WorkspaceShellRoute {
searchMode,
usesQuerySearch:
searchMode === 'local-filter' || searchMode === 'remote-search',
contextPanel: resolveContextPanel(kind, null, null),
contextPanel: isFocusedDownloadDetail
? 'none'
: resolveContextPanel(kind, null, null),
};
}