refactor(runtime): gate downloads navigation by capability

This commit is contained in:
4gray committed 2026-05-22 13:38:20 +03:00
1 parent f626e176d2
commit 676e8bb5da
4 files changed
+44 -9

No files matched your search

@@ -1,7 +1,7 @@
import { buildPortalRailLinks } from './portal-rail-links';
describe('buildPortalRailLinks', () => {
it('builds Xtream links with scoped tooltip labels on Electron', () => {
it('builds Xtream links with scoped tooltip labels when downloads are supported', () => {
const links = buildPortalRailLinks({
provider: 'xtreams',
playlistId: 'xtream-1',
@@ -29,7 +29,7 @@ export interface CommandBuilderContext {
hasActivePlaylist: boolean;
hasXtreamPlaylists: boolean;
canRefreshPlaylist: boolean;
isElectron: boolean;
supportsDownloads: boolean;
showDashboard: boolean;
translate: TranslateFn;
router: Router;
@@ -139,8 +139,13 @@ export function getPlaylistCommandDefinitions(
export function getGlobalCommandDefinitions(
ctx: CommandBuilderContext
): WorkspaceCommandContribution[] {
const { route, hasXtreamPlaylists, isElectron, showDashboard, actions } =
ctx;
const {
route,
hasXtreamPlaylists,
supportsDownloads,
showDashboard,
actions,
} = ctx;
return [
{
@@ -185,7 +190,7 @@ export function getGlobalCommandDefinitions(
descriptionKey:
'WORKSPACE.SHELL.COMMANDS.OPEN_DOWNLOADS_DESCRIPTION',
priority: 40,
visible: isElectron && route.kind !== 'downloads',
visible: supportsDownloads && route.kind !== 'downloads',
run: () => actions.openDownloadsShortcut(),
},
{
@@ -18,6 +18,7 @@ import {
import { StalkerStore } from '@iptvnator/portal/stalker/data-access';
import { XtreamStore } from '@iptvnator/portal/xtream/data-access';
import {
DownloadsService,
PlaylistsService,
RuntimeCapabilitiesService,
SettingsStore,
@@ -129,6 +130,7 @@ describe('WorkspaceShellFacade', () => {
typeof signal<PlaylistSignalMeta | null>
>;
let playlistsSignal: ReturnType<typeof signal<PlaylistSignalMeta[]>>;
let downloadsActiveCountSignal: ReturnType<typeof signal<number>>;
let refreshPreparationSignal: ReturnType<
typeof signal<XtreamRefreshPreparationState | null>
>;
@@ -142,6 +144,7 @@ describe('WorkspaceShellFacade', () => {
let runtime: {
isElectron: boolean;
isMacOS: boolean;
supportsDownloads: boolean;
};
beforeEach(() => {
@@ -149,6 +152,7 @@ describe('WorkspaceShellFacade', () => {
runtime = {
isElectron: true,
isMacOS: true,
supportsDownloads: true,
};
activePlaylistSignal = signal({
@@ -161,6 +165,7 @@ describe('WorkspaceShellFacade', () => {
{ _id: 'pl-1', serverUrl: 'http://example.com' },
{ _id: 'pl-2', macAddress: '00:11:22:33' },
]);
downloadsActiveCountSignal = signal(0);
refreshPreparationSignal = signal<XtreamRefreshPreparationState | null>(
null
);
@@ -273,6 +278,12 @@ describe('WorkspaceShellFacade', () => {
provide: PlaylistsService,
useValue: playlistsService,
},
{
provide: DownloadsService,
useValue: {
activeCount: downloadsActiveCountSignal,
},
},
{
provide: MatDialog,
useValue: {
@@ -685,6 +696,20 @@ describe('WorkspaceShellFacade', () => {
expect(commands.every((command) => command.enabled)).toBe(true);
});
it('hides the downloads command when downloads are unsupported', () => {
(facade as unknown as { supportsDownloads: boolean }).supportsDownloads =
false;
activePlaylistSignal.set(null);
playlistsSignal.set([]);
facade.currentUrl.set('/workspace/dashboard');
const commands = facade.commandPaletteCommands();
expect(commands.map((command) => command.id)).not.toContain(
'open-downloads'
);
});
it('includes M3U navigation, playlist actions, and Multi-EPG on playlist routes', () => {
const headerContext = TestBed.inject(WorkspaceHeaderContextService);
@@ -100,7 +100,7 @@ export class WorkspaceShellFacade {
private readonly runtime = inject(RuntimeCapabilitiesService);
private readonly downloadsService = inject(DownloadsService);
readonly hasActiveDownloads = computed(
() => this.isElectron && this.downloadsService.activeCount() > 0
() => this.supportsDownloads && this.downloadsService.activeCount() > 0
);
private readonly languageTick = toSignal(
this.translate.onLangChange.pipe(startWith(null)),
@@ -146,6 +146,11 @@ export class WorkspaceShellFacade {
get isMacOS(): boolean {
return this.runtime.isMacOS;
}
get supportsDownloads(): boolean {
return this.runtime.supportsDownloads;
}
readonly currentUrl = signal(this.router.url);
readonly currentRoute = computed(() =>
parseWorkspaceShellRoute(this.currentUrl())
@@ -437,7 +442,7 @@ export class WorkspaceShellFacade {
buildPortalRailLinks({
provider: context.provider,
playlistId: context.playlistId,
supportsDownloads: this.isElectron,
supportsDownloads: this.supportsDownloads,
workspace: true,
}).primary,
context.provider,
@@ -456,7 +461,7 @@ export class WorkspaceShellFacade {
buildPortalRailLinks({
provider: context.provider,
playlistId: context.playlistId,
supportsDownloads: this.isElectron,
supportsDownloads: this.supportsDownloads,
workspace: true,
}).secondary.filter((link) => link.section !== 'downloads'),
context.provider,
@@ -801,7 +806,7 @@ export class WorkspaceShellFacade {
(playlist) => !!playlist.serverUrl
),
canRefreshPlaylist: this.canRefreshPlaylist(),
isElectron: this.isElectron,
supportsDownloads: this.supportsDownloads,
showDashboard: this.showDashboard(),
translate: (key, params) => this.translateText(key, params),
router: this.router,