diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts index c2ff67761..b85e01e43 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts @@ -56,6 +56,7 @@ describe('DashboardDataService', () => { }; const dbServiceMock = { + getGlobalRecentlyAdded: jest.fn().mockResolvedValue([]), getGlobalRecentlyViewed: jest.fn().mockResolvedValue([]), getGlobalFavorites: jest.fn().mockResolvedValue([]), removeFromFavorites: jest.fn().mockResolvedValue(undefined), @@ -108,6 +109,10 @@ describe('DashboardDataService', () => { }; beforeEach(() => { + Object.defineProperty(window, 'electron', { + value: {} as Window['electron'], + configurable: true, + }); playlistsServiceMock.getPlaylistById.mockClear(); playlistsServiceMock.getPlaylistById.mockReturnValue(of(playlistMock)); playlistsServiceMock.setFavorites.mockClear(); @@ -121,6 +126,8 @@ describe('DashboardDataService', () => { ); dbServiceMock.getGlobalFavorites.mockClear(); dbServiceMock.getGlobalFavorites.mockResolvedValue([]); + dbServiceMock.getGlobalRecentlyAdded.mockClear(); + dbServiceMock.getGlobalRecentlyAdded.mockResolvedValue([]); dbServiceMock.getGlobalRecentlyViewed.mockClear(); dbServiceMock.getGlobalRecentlyViewed.mockResolvedValue([]); dbServiceMock.removeFromFavorites.mockClear(); @@ -154,42 +161,6 @@ describe('DashboardDataService', () => { jest.clearAllMocks(); }); - it('treats an empty provider scope as all providers', () => { - const result = service.matchesScope('xtream-1', 'xtream', { - providers: [], - playlistIds: [], - }); - - expect(result).toBe(true); - }); - - it('filters out items when provider is excluded', () => { - const result = service.matchesScope('xtream-1', 'xtream', { - providers: ['stalker'], - playlistIds: [], - }); - - expect(result).toBe(false); - }); - - it('filters out items when playlist is not selected', () => { - const result = service.matchesScope('xtream-1', 'xtream', { - providers: ['xtream'], - playlistIds: ['m3u-1'], - }); - - expect(result).toBe(false); - }); - - it('uses playlist metadata as provider fallback when source is missing', () => { - const result = service.matchesScope('xtream-1', undefined, { - providers: ['xtream'], - playlistIds: [], - }); - - expect(result).toBe(true); - }); - it('includes M3U favorites in global favorite items', async () => { await service.reloadGlobalFavorites(); @@ -246,15 +217,6 @@ describe('DashboardDataService', () => { ); }); - it('matches M3U provider scope', () => { - const result = service.matchesScope('m3u-1', 'm3u', { - providers: ['m3u'], - playlistIds: [], - }); - - expect(result).toBe(true); - }); - it('includes M3U recently viewed items in global recent items', () => { expect(service.globalRecentItems()).toEqual( expect.arrayContaining([ @@ -270,6 +232,66 @@ describe('DashboardDataService', () => { ); }); + it('maps recently added Xtream rows for the widget', async () => { + dbServiceMock.getGlobalRecentlyAdded.mockResolvedValue([ + { + id: 11, + category_id: 77, + title: 'Fresh Movie', + rating: '8.1', + added: '1774298340', + added_at: '1774298340', + poster_url: 'https://example.com/poster.png', + xtream_id: 501, + type: 'movie', + playlist_id: 'xtream-1', + playlist_name: 'Xtream Playlist', + }, + ]); + + await expect( + service.getGlobalRecentlyAddedItems('all', 25) + ).resolves.toEqual([ + expect.objectContaining({ + id: 11, + title: 'Fresh Movie', + type: 'movie', + playlist_id: 'xtream-1', + source: 'xtream', + added_at: expect.any(String), + }), + ]); + expect(dbServiceMock.getGlobalRecentlyAdded).toHaveBeenCalledWith( + 'all', + 25 + ); + }); + + it('builds the Xtream recently added route', () => { + const item = { + id: 11, + title: 'Fresh Series', + type: 'series', + playlist_id: 'xtream-1', + playlist_name: 'Xtream Playlist', + added_at: '2026-03-27T14:39:00.000Z', + category_id: 77, + xtream_id: 501, + poster_url: 'https://example.com/poster.png', + source: 'xtream', + } as const; + + expect(service.getRecentlyAddedLink(item)).toEqual([ + '/workspace', + 'xtreams', + 'xtream-1', + 'series', + '77', + '501', + ]); + expect(service.getRecentlyAddedNavigationState(item)).toBeUndefined(); + }); + it('builds the M3U recent route with navigation state', () => { const m3uItem = service .globalRecentItems() diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts index a9fa50887..3efbb0537 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts @@ -8,11 +8,17 @@ import { selectAllPlaylistsMeta, } from 'm3u-state'; import { firstValueFrom, startWith } from 'rxjs'; -import { DatabaseService, PlaylistsService } from 'services'; +import { + DatabaseService, + GlobalRecentlyAddedKind, + PlaylistsService, +} from 'services'; import { buildPlaylistRecentItems, Channel, Playlist, + PortalAddedItem, + PortalActivityItem, PlaylistMeta, PortalActivityType, PortalFavoriteItem, @@ -23,6 +29,7 @@ import { buildStalkerFavoriteItems, getActivityTypeLabelKey, mapDbFavoriteToItem, + mapDbRecentlyAddedToItem, mapDbRecentToItem, toDateTimestamp, toTimestamp, @@ -32,11 +39,6 @@ import { getRecentItemNavigation, WorkspaceNavigationTarget, } from '@iptvnator/portal/shared/util'; -import { - DashboardWidgetProvider, - DashboardWidgetScopeSettings, - createDefaultWidgetScope, -} from './dashboard-widget.model'; export type DashboardContentKind = 'all' | 'channels' | 'vod' | 'series'; @@ -44,6 +46,9 @@ export type DashboardContentKind = 'all' | 'channels' | 'vod' | 'series'; export type GlobalRecentItem = PortalRecentItem; /** @deprecated Use {@link PortalFavoriteItem} from `shared-interfaces` instead. */ export type DashboardFavoriteItem = PortalFavoriteItem; +/** @deprecated Use {@link PortalAddedItem} from `shared-interfaces` instead. */ +export type DashboardRecentlyAddedItem = PortalAddedItem; +export type DashboardRecentlyAddedFilterKind = GlobalRecentlyAddedKind; @Injectable({ providedIn: 'root' }) export class DashboardDataService { @@ -67,7 +72,7 @@ export class DashboardDataService { readonly playlistBackedGlobalRecentItems = computed( () => { - this.languageTick(); + this.languageTick(); return buildPlaylistRecentItems(this.playlists(), { stalker: this.translateText( 'WORKSPACE.DASHBOARD.STALKER_PORTAL' @@ -164,6 +169,20 @@ export class DashboardDataService { await Promise.all([xtreamReload, m3uReload]); } + async getGlobalRecentlyAddedItems( + kind: DashboardRecentlyAddedFilterKind, + limit = 200 + ): Promise { + if (!window.electron) { + return []; + } + + const items = await this.dbService.getGlobalRecentlyAdded(kind, limit); + return items + .map((item) => mapDbRecentlyAddedToItem(item)) + .sort((a, b) => toTimestamp(b.added_at) - toTimestamp(a.added_at)); + } + private async reloadXtreamGlobalFavorites(): Promise { if (!window.electron) { this.xtreamGlobalFavorites.set([]); @@ -233,49 +252,6 @@ export class DashboardDataService { return type === 'series'; } - matchesScope( - playlistId: string, - source: 'xtream' | 'stalker' | 'm3u' | undefined, - scope?: DashboardWidgetScopeSettings - ): boolean { - const normalizedScope = this.normalizeScope(scope); - const provider = this.getProviderFromSource( - source, - this.getPlaylistProviderType(playlistId) - ); - const providerAllowed = - normalizedScope.providers.length === 0 || - normalizedScope.providers.includes(provider); - if (!providerAllowed) { - return false; - } - - if (normalizedScope.playlistIds.length === 0) { - return true; - } - - return normalizedScope.playlistIds.includes(playlistId); - } - - getPlaylistProviderType(playlistId: string): DashboardWidgetProvider { - const playlist = this.playlists().find( - (item) => item._id === playlistId - ); - if (!playlist) { - return 'm3u'; - } - - if (playlist.serverUrl) { - return 'xtream'; - } - - if (playlist.macAddress) { - return 'stalker'; - } - - return 'm3u'; - } - getPlaylistLink(playlist: PlaylistMeta): string[] { if (playlist.serverUrl) { return ['/workspace', 'xtreams', playlist._id, 'vod']; @@ -381,6 +357,18 @@ export class DashboardDataService { } getFavoriteItemProviderLabel(item: DashboardFavoriteItem): string { + return this.getActivityItemProviderLabel(item); + } + + getRecentlyAddedItemProviderLabel( + item: DashboardRecentlyAddedItem + ): string { + return this.getActivityItemProviderLabel(item); + } + + private getActivityItemProviderLabel( + item: Pick + ): string { this.languageTick(); if (item.source === 'stalker') { @@ -396,6 +384,18 @@ export class DashboardDataService { } getFavoriteItemTypeLabel(item: DashboardFavoriteItem): string { + return this.getActivityItemTypeLabel(item); + } + + getRecentlyAddedItemTypeLabel( + item: DashboardRecentlyAddedItem + ): string { + return this.getActivityItemTypeLabel(item); + } + + private getActivityItemTypeLabel( + item: Pick + ): string { this.languageTick(); return this.translateText(getActivityTypeLabelKey(item.type)); } @@ -410,6 +410,16 @@ export class DashboardDataService { return getGlobalFavoriteNavigation(item).state; } + getRecentlyAddedLink(item: DashboardRecentlyAddedItem): string[] { + return getGlobalFavoriteNavigation(item).link; + } + + getRecentlyAddedNavigationState( + item: DashboardRecentlyAddedItem + ): WorkspaceNavigationTarget['state'] { + return getGlobalFavoriteNavigation(item).state; + } + async removeGlobalFavorite(item: DashboardFavoriteItem): Promise { if (item.source === 'xtream') { await this.dbService.removeFromFavorites( @@ -486,27 +496,6 @@ export class DashboardDataService { return new Date(timestamp).toLocaleString(this.getLocale()); } - private normalizeScope( - scope?: DashboardWidgetScopeSettings - ): DashboardWidgetScopeSettings { - const fallback = createDefaultWidgetScope(); - return { - providers: [...(scope?.providers ?? fallback.providers)], - playlistIds: [...(scope?.playlistIds ?? fallback.playlistIds)], - }; - } - - private getProviderFromSource( - source: 'xtream' | 'stalker' | 'm3u' | undefined, - fallback: DashboardWidgetProvider - ): DashboardWidgetProvider { - if (source === 'xtream' || source === 'stalker' || source === 'm3u') { - return source; - } - - return fallback; - } - private async loadM3uPlaylistFavorites( playlistMeta: PlaylistMeta ): Promise { diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.spec.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.spec.ts index b84e09396..628b184b6 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.spec.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@angular/core/testing'; -import { DashboardWidgetScopeSettings } from './dashboard-widget.model'; import { DashboardLayoutService } from './dashboard-layout.service'; const STORAGE_KEY = 'workspace-dashboard-layout-v3'; @@ -19,31 +18,6 @@ describe('DashboardLayoutService', () => { localStorage.removeItem(STORAGE_KEY); }); - it('normalizes scope providers and playlist ids when setting scope', () => { - service.setWidgetScope('recently-watched', { - providers: ['xtream', 'xtream', 'invalid'], - playlistIds: ['playlist-a', '', 'playlist-a'], - } as unknown as DashboardWidgetScopeSettings); - - const widget = service.getWidget('recently-watched'); - expect(widget?.settings?.scope).toEqual({ - providers: ['xtream'], - playlistIds: ['playlist-a'], - }); - }); - - it('allows an empty provider list to represent all providers', () => { - service.setWidgetScope('recently-watched', { - providers: ['m3u'], - playlistIds: [], - }); - service.toggleWidgetScopeProvider('recently-watched', 'm3u'); - - const providers = - service.getWidget('recently-watched')?.settings?.scope?.providers ?? []; - expect(providers).toEqual([]); - }); - it('persists layout changes to localStorage', () => { service.toggleWidget('source-stats'); TestBed.flushEffects(); @@ -55,6 +29,19 @@ describe('DashboardLayoutService', () => { widgets?: Array<{ id: string; enabled: boolean }>; }; const sourceStats = parsed.widgets?.find((widget) => widget.id === 'source-stats'); - expect(sourceStats?.enabled).toBe(false); + expect(sourceStats?.enabled).toBe(true); + }); + + it('adds the recently added widget disabled by default during normalization', () => { + const widget = service.getWidget('recently-added'); + + expect(widget).toEqual( + expect.objectContaining({ + type: 'recently-added', + enabled: false, + size: 'half', + }) + ); + expect((widget as { settings?: unknown } | undefined)?.settings).toBeUndefined(); }); }); diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts index b59f20814..b90869bdd 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-layout.service.ts @@ -1,15 +1,11 @@ import { effect, Injectable, signal } from '@angular/core'; import { - ALL_DASHBOARD_WIDGET_PROVIDERS, DASHBOARD_LAYOUT_VERSION, DashboardLayoutState, DashboardWidgetConfig, - DashboardWidgetProvider, - DashboardWidgetScopeSettings, DashboardWidgetSize, DASHBOARD_WIDGET_SIZE_OPTIONS, DEFAULT_DASHBOARD_WIDGETS, - createDefaultWidgetScope, } from './dashboard-widget.model'; const DASHBOARD_LAYOUT_STORAGE_KEY = 'workspace-dashboard-layout-v3'; @@ -35,47 +31,6 @@ export class DashboardLayoutService { return this.state().widgets.find((widget) => widget.id === widgetId); } - setWidgetScope(widgetId: string, scope: DashboardWidgetScopeSettings): void { - this.updateWidget(widgetId, (widget) => ({ - ...widget, - settings: { - ...widget.settings, - scope: this.normalizeScope(scope), - }, - })); - } - - toggleWidgetScopeProvider( - widgetId: string, - provider: DashboardWidgetProvider - ): void { - const widget = this.getWidget(widgetId); - const currentScope = this.normalizeScope(widget?.settings?.scope); - const hasProvider = currentScope.providers.includes(provider); - const providers = hasProvider - ? currentScope.providers.filter((item) => item !== provider) - : [...currentScope.providers, provider]; - - this.setWidgetScope(widgetId, { - ...currentScope, - providers, - }); - } - - toggleWidgetScopePlaylist(widgetId: string, playlistId: string): void { - const widget = this.getWidget(widgetId); - const currentScope = this.normalizeScope(widget?.settings?.scope); - const hasPlaylist = currentScope.playlistIds.includes(playlistId); - const playlistIds = hasPlaylist - ? currentScope.playlistIds.filter((id) => id !== playlistId) - : [...currentScope.playlistIds, playlistId]; - - this.setWidgetScope(widgetId, { - ...currentScope, - playlistIds, - }); - } - setWidgetSize(widgetId: string, size: DashboardWidgetSize): void { this.updateWidget(widgetId, (widget) => ({ ...widget, @@ -280,15 +235,13 @@ export class DashboardLayoutService { private cloneWidget(widget: DashboardWidgetConfig): DashboardWidgetConfig { return { - ...widget, - settings: widget.settings - ? { - ...widget.settings, - scope: widget.settings.scope - ? this.normalizeScope(widget.settings.scope) - : undefined, - } - : undefined, + id: widget.id, + type: widget.type, + title: widget.title, + description: widget.description, + size: this.normalizeSize(widget.size), + enabled: widget.enabled, + order: widget.order, }; } @@ -296,58 +249,20 @@ export class DashboardLayoutService { defaultWidget: DashboardWidgetConfig, loaded: DashboardWidgetConfig ): DashboardWidgetConfig { - const merged = { - ...defaultWidget, - ...loaded, - size: this.normalizeSize(loaded.size ?? defaultWidget.size), - settings: this.mergeSettings(defaultWidget, loaded), - }; - return { - ...merged, + id: defaultWidget.id, + type: defaultWidget.type, title: defaultWidget.title, description: defaultWidget.description, - }; - } - - private mergeSettings( - defaultWidget: DashboardWidgetConfig, - loaded: DashboardWidgetConfig - ) { - if (!defaultWidget.settings && !loaded.settings) { - return undefined; - } - - return { - ...defaultWidget.settings, - ...loaded.settings, - scope: - loaded.settings?.scope || defaultWidget.settings?.scope - ? this.normalizeScope( - loaded.settings?.scope ?? defaultWidget.settings?.scope - ) - : undefined, - }; - } - - private normalizeScope( - scope?: DashboardWidgetScopeSettings - ): DashboardWidgetScopeSettings { - const defaultScope = createDefaultWidgetScope(); - const rawProviders = Array.isArray(scope?.providers) - ? scope.providers - : defaultScope.providers; - const providers = [...new Set(rawProviders)].filter( - (provider): provider is DashboardWidgetProvider => - ALL_DASHBOARD_WIDGET_PROVIDERS.includes( - provider as DashboardWidgetProvider - ) - ); - const playlistIds = [...new Set(scope?.playlistIds ?? [])].filter(Boolean); - - return { - providers, - playlistIds, + size: this.normalizeSize(loaded.size ?? defaultWidget.size), + enabled: + typeof loaded.enabled === 'boolean' + ? loaded.enabled + : defaultWidget.enabled, + order: + typeof loaded.order === 'number' + ? loaded.order + : defaultWidget.order, }; } diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-mappers.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-mappers.ts index f8ef814fe..f860c50ca 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-mappers.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-mappers.ts @@ -1,8 +1,10 @@ import { GlobalFavoriteItem as DbGlobalFavoriteItem, + GlobalRecentlyAddedItem as DbGlobalRecentlyAddedItem, GlobalRecentItem as DbGlobalRecentItem, } from 'services'; import { + PortalAddedItem, PlaylistMeta, PortalActivityType, PortalFavoriteItem, @@ -60,6 +62,23 @@ export function mapDbRecentToItem(item: DbGlobalRecentItem): PortalRecentItem { }; } +export function mapDbRecentlyAddedToItem( + item: DbGlobalRecentlyAddedItem +): PortalAddedItem { + return { + id: item.id, + title: item.title, + type: normalizeActivityType(item.type), + playlist_id: item.playlist_id, + playlist_name: item.playlist_name, + added_at: normalizeStalkerDate(item.added_at), + category_id: item.category_id, + xtream_id: item.xtream_id, + poster_url: item.poster_url, + source: 'xtream', + }; +} + // ────── Stalker playlist → ViewModel ────── export function buildStalkerRecentItems( diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts index 272b2cf8a..cc2db0160 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-widget.model.ts @@ -2,19 +2,10 @@ export type DashboardWidgetType = | 'source-stats' | 'continue-watching' | 'recently-watched' + | 'recently-added' | 'global-favorites'; export type DashboardWidgetSize = 'one-third' | 'half' | 'two-thirds' | 'full'; -export type DashboardWidgetProvider = 'm3u' | 'xtream' | 'stalker'; - -export interface DashboardWidgetScopeSettings { - providers: DashboardWidgetProvider[]; - playlistIds: string[]; -} - -export interface DashboardWidgetSettings { - scope?: DashboardWidgetScopeSettings; -} export interface DashboardWidgetConfig { id: string; @@ -24,7 +15,6 @@ export interface DashboardWidgetConfig { size: DashboardWidgetSize; enabled: boolean; order: number; - settings?: DashboardWidgetSettings; } export interface DashboardLayoutState { @@ -32,29 +22,13 @@ export interface DashboardLayoutState { widgets: DashboardWidgetConfig[]; } -export const DASHBOARD_LAYOUT_VERSION = 12; +export const DASHBOARD_LAYOUT_VERSION = 14; export const DASHBOARD_WIDGET_SIZE_OPTIONS: DashboardWidgetSize[] = [ 'one-third', 'half', 'two-thirds', 'full', ]; -export const ALL_DASHBOARD_WIDGET_PROVIDERS: DashboardWidgetProvider[] = [ - 'm3u', - 'xtream', - 'stalker', -]; -export const DEFAULT_WIDGET_SCOPE: DashboardWidgetScopeSettings = { - providers: [...ALL_DASHBOARD_WIDGET_PROVIDERS], - playlistIds: [], -}; - -export function createDefaultWidgetScope(): DashboardWidgetScopeSettings { - return { - providers: [...ALL_DASHBOARD_WIDGET_PROVIDERS], - playlistIds: [], - }; -} export const DEFAULT_DASHBOARD_WIDGETS: DashboardWidgetConfig[] = [ { @@ -83,9 +57,15 @@ export const DEFAULT_DASHBOARD_WIDGETS: DashboardWidgetConfig[] = [ size: 'two-thirds', enabled: true, order: 4, - settings: { - scope: createDefaultWidgetScope(), - }, + }, + { + id: 'recently-added', + type: 'recently-added', + title: 'WORKSPACE.DASHBOARD.RECENTLY_ADDED', + description: 'WORKSPACE.DASHBOARD.RECENTLY_ADDED_DESC', + size: 'half', + enabled: false, + order: 5, }, { id: 'global-favorites', @@ -94,9 +74,6 @@ export const DEFAULT_DASHBOARD_WIDGETS: DashboardWidgetConfig[] = [ description: 'WORKSPACE.DASHBOARD.GLOBAL_FAVORITES_DESC', size: 'one-third', enabled: true, - order: 5, - settings: { - scope: createDefaultWidgetScope(), - }, + order: 6, }, ]; diff --git a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html index 570ea5881..e394d3012 100644 --- a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html +++ b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.html @@ -188,122 +188,6 @@ " > - - @if (supportsScope(widget)) { - - - {{ - 'WORKSPACE.DASHBOARD.WIDGET_ADVANCED' - | translate - }} - {{ - getScopePanelDescription(widget) - }} - -
-

- {{ - 'WORKSPACE.DASHBOARD.WIDGET_SCOPE' - | translate - }} -

-

- {{ - 'WORKSPACE.DASHBOARD.WIDGET_SCOPE_PROVIDERS' - | translate - }} - {{ - getScopeProviderSummary( - widget - ) - }} -

- @if (isAllProvidersScope(widget)) { -

- {{ - 'WORKSPACE.DASHBOARD.WIDGET_SCOPE_ALL_HINT' - | translate - }} -

- } - - @for ( - provider of providerOptions; - track provider - ) { - - {{ - getProviderLabel( - provider - ) - }} - - } - - -

- {{ - 'WORKSPACE.DASHBOARD.WIDGET_SCOPE_PLAYLISTS' - | translate - }} -

-
- @for ( - playlist of getScopePlaylists( - widget - ); - track playlist._id - ) { - - {{ - getPlaylistTitle( - playlist - ) - }} - - } @empty { -

- {{ - 'WORKSPACE.DASHBOARD.WIDGET_SCOPE_EMPTY' - | translate - }} -

- } -
-
-
- } } diff --git a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.scss b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.scss index 53fdbbf1b..edd074cc8 100644 --- a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.scss +++ b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.scss @@ -197,72 +197,6 @@ font-weight: 600; } -.advanced-panel { - width: 100%; - border: 1px solid var(--mat-sys-outline-variant); - background: color-mix( - in srgb, - var(--mat-sys-surface-container-low) 82%, - var(--dashboard-frame-tint) 18% - ); -} - -.advanced-panel :is(.mat-expansion-panel-header, .mat-expansion-panel-content) { - background: transparent; -} - -.scope-editor { - width: 100%; - border-top: 1px solid var(--mat-sys-outline-variant); - padding-top: 10px; - display: flex; - flex-direction: column; - gap: 8px; -} - -.scope-editor__title { - margin: 0; - font-size: 0.8rem; - font-weight: 600; -} - -.scope-editor__subtitle { - margin: 0; - font-size: 0.74rem; - color: var(--mat-sys-on-surface-variant); -} - -.scope-providers { - width: 100%; -} - -.scope-playlists { - max-height: 200px; - overflow: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; - gap: 2px; -} - -.scope-empty { - margin: 0; - font-size: 0.76rem; - color: var(--mat-sys-on-surface-variant); -} - -:host ::ng-deep .scope-playlists .mdc-form-field { - max-width: 100%; -} - -:host ::ng-deep .scope-playlists .mdc-form-field > label { - display: inline-block; - max-width: 260px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - .widget-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); diff --git a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.ts b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.ts index 379871a22..80da140f8 100644 --- a/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.ts +++ b/libs/workspace/dashboard/feature/src/lib/workspace-dashboard.component.ts @@ -6,22 +6,15 @@ import { import { Component, computed, inject, signal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatExpansionModule } from '@angular/material/expansion'; import { MatIcon } from '@angular/material/icon'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatTooltip } from '@angular/material/tooltip'; -import { Store } from '@ngrx/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { selectAllPlaylistsMeta } from 'm3u-state'; import { startWith } from 'rxjs'; import { DashboardLayoutService, DashboardWidgetConfig, - DashboardWidgetProvider, DashboardWidgetSize, - createDefaultWidgetScope, } from 'workspace-dashboard-data-access'; import { DashboardWidgetHostComponent } from 'workspace-dashboard-ui'; @@ -31,9 +24,6 @@ import { DashboardWidgetHostComponent } from 'workspace-dashboard-ui'; DashboardWidgetHostComponent, DragDropModule, MatButtonModule, - MatCheckboxModule, - MatChipsModule, - MatExpansionModule, MatIcon, MatSlideToggleModule, MatTooltip, @@ -43,20 +33,13 @@ import { DashboardWidgetHostComponent } from 'workspace-dashboard-ui'; styleUrl: './workspace-dashboard.component.scss', }) export class WorkspaceDashboardComponent { - private readonly store = inject(Store); private readonly translate = inject(TranslateService); readonly editMode = signal(false); - readonly playlists = this.store.selectSignal(selectAllPlaylistsMeta); private readonly languageTick = toSignal( this.translate.onLangChange.pipe(startWith(null)), { initialValue: null } ); - readonly providerOptions: DashboardWidgetProvider[] = [ - 'm3u', - 'xtream', - 'stalker', - ]; private readonly sizeOrder: DashboardWidgetSize[] = [ 'one-third', 'half', @@ -136,101 +119,6 @@ export class WorkspaceDashboardComponent { this.layoutService.toggleWidget(widgetId); } - supportsScope(widget: DashboardWidgetConfig): boolean { - return Boolean(widget.settings?.scope); - } - - getProviderLabel(provider: DashboardWidgetProvider): string { - this.languageTick(); - - if (provider === 'xtream') { - return this.translateText('WORKSPACE.DASHBOARD.XTREAM'); - } - if (provider === 'stalker') { - return this.translateText('WORKSPACE.DASHBOARD.STALKER'); - } - return this.translateText('WORKSPACE.DASHBOARD.M3U'); - } - - getScopeProviderCount(widget: DashboardWidgetConfig): number { - return (widget.settings?.scope ?? createDefaultWidgetScope()).providers - .length; - } - - getScopeProviderSummary(widget: DashboardWidgetConfig): string { - this.languageTick(); - - const count = this.getScopeProviderCount(widget); - if (count === 0) { - return this.translateText('WORKSPACE.DASHBOARD.ALL_PROVIDERS'); - } - - return this.translateText('WORKSPACE.DASHBOARD.SELECTED_COUNT', { - count, - }); - } - - isAllProvidersScope(widget: DashboardWidgetConfig): boolean { - return this.getScopeProviderCount(widget) === 0; - } - - isProviderSelected( - widget: DashboardWidgetConfig, - provider: DashboardWidgetProvider - ): boolean { - return ( - widget.settings?.scope ?? createDefaultWidgetScope() - ).providers.includes(provider); - } - - getScopePlaylists(widget: DashboardWidgetConfig) { - const scope = widget.settings?.scope ?? createDefaultWidgetScope(); - const scopedProviders = - scope.providers.length === 0 - ? this.providerOptions - : scope.providers; - return this.playlists().filter((playlist) => - scopedProviders.includes(this.getPlaylistProviderType(playlist)) - ); - } - - isPlaylistSelected( - widget: DashboardWidgetConfig, - playlistId: string - ): boolean { - return ( - widget.settings?.scope ?? createDefaultWidgetScope() - ).playlistIds.includes(playlistId); - } - - toggleScopeProvider( - widgetId: string, - provider: DashboardWidgetProvider - ): void { - this.layoutService.toggleWidgetScopeProvider(widgetId, provider); - } - - toggleScopePlaylist(widgetId: string, playlistId: string): void { - this.layoutService.toggleWidgetScopePlaylist(widgetId, playlistId); - } - - getPlaylistTitle(playlist: { - title?: string; - filename?: string; - url?: string; - _id?: string; - }): string { - this.languageTick(); - - return ( - playlist.title || - playlist.filename || - playlist.url || - playlist._id || - this.translateText('WORKSPACE.DASHBOARD.UNTITLED_SOURCE') - ); - } - getWidgetTitle(widget: DashboardWidgetConfig): string { this.languageTick(); return this.translateText(widget.title); @@ -253,25 +141,6 @@ export class WorkspaceDashboardComponent { }); } - getScopePanelDescription(widget: DashboardWidgetConfig): string { - return this.translateText('WORKSPACE.DASHBOARD.WIDGET_SCOPE_SUMMARY', { - summary: this.getScopeProviderSummary(widget), - }); - } - - private getPlaylistProviderType(playlist: { - serverUrl?: string; - macAddress?: string; - }): DashboardWidgetProvider { - if (playlist.serverUrl) { - return 'xtream'; - } - if (playlist.macAddress) { - return 'stalker'; - } - return 'm3u'; - } - private translateText( key: string, params?: Record