From 101d9ba162e1743155ac7b364b2d228556eebb0e Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 14 Jun 2026 10:54:57 +0200 Subject: [PATCH] feat(dashboard): add configurable dashboard rails --- .../src/dashboard-activation.e2e.ts | 75 +++- apps/electron-backend-e2e/src/settings.e2e.ts | 62 ++++ .../settings-dashboard-section.component.html | 164 +++++++++ .../settings-dashboard-section.component.ts | 24 ++ .../settings-general-section.component.html | 15 - .../src/app/settings/settings-options.spec.ts | 57 ++- apps/web/src/app/settings/settings-options.ts | 6 + .../src/app/settings/settings.component.html | 5 + .../app/settings/settings.component.spec.ts | 61 +++- .../src/app/settings/settings.component.ts | 51 ++- apps/web/src/assets/i18n/ar.json | 23 +- apps/web/src/assets/i18n/ary.json | 23 +- apps/web/src/assets/i18n/by.json | 23 +- apps/web/src/assets/i18n/de.json | 23 +- apps/web/src/assets/i18n/el.json | 23 +- apps/web/src/assets/i18n/en.json | 23 +- apps/web/src/assets/i18n/es.json | 23 +- apps/web/src/assets/i18n/fr.json | 23 +- apps/web/src/assets/i18n/it.json | 23 +- apps/web/src/assets/i18n/ja.json | 23 +- apps/web/src/assets/i18n/ko.json | 23 +- apps/web/src/assets/i18n/nl.json | 23 +- apps/web/src/assets/i18n/pl.json | 23 +- apps/web/src/assets/i18n/pt.json | 23 +- apps/web/src/assets/i18n/ru.json | 23 +- apps/web/src/assets/i18n/tr.json | 23 +- apps/web/src/assets/i18n/zh.json | 23 +- apps/web/src/assets/i18n/zhtw.json | 23 +- .../src/lib/settings-store.service.spec.ts | 116 +++++++ .../src/lib/settings-store.service.ts | 23 +- .../interfaces/src/lib/settings.interface.ts | 47 +++ .../src/lib/dashboard-data.service.ts | 6 +- libs/workspace/dashboard/feature/README.md | 56 ++- .../lib/rails/dashboard-rail.component.html | 1 + .../src/lib/rails/dashboard-rail.component.ts | 1 + .../workspace-dashboard-rails.component.html | 325 +++++++++++------- .../workspace-dashboard-rails.component.scss | 113 ++++++ ...orkspace-dashboard-rails.component.spec.ts | 75 +++- .../workspace-dashboard-rails.component.ts | 236 +++++++++---- 39 files changed, 1685 insertions(+), 248 deletions(-) create mode 100644 apps/web/src/app/settings/settings-dashboard-section.component.html create mode 100644 apps/web/src/app/settings/settings-dashboard-section.component.ts create mode 100644 libs/services/src/lib/settings-store.service.spec.ts diff --git a/apps/electron-backend-e2e/src/dashboard-activation.e2e.ts b/apps/electron-backend-e2e/src/dashboard-activation.e2e.ts index bf8e7c139..0b99de0f4 100644 --- a/apps/electron-backend-e2e/src/dashboard-activation.e2e.ts +++ b/apps/electron-backend-e2e/src/dashboard-activation.e2e.ts @@ -5,6 +5,7 @@ import { clickCategoryByNameExact, clickFirstGridListCard, closeElectronApp, + expectVisibleContentCardTitle, defaultXtreamPassword, defaultXtreamUsername, expectPathname, @@ -81,21 +82,63 @@ test.describe('Dashboard Activation', () => { seriesFixture.categoryName ); const seriesTitle = await clickFirstGridListCard(app.mainWindow); + await addCurrentDetailToFavorites(app.mainWindow); await playFirstSeriesEpisode(app.mainWindow); await goToDashboard(app.mainWindow); - // v0.22 dashboard: the mixed Global Favorites rail was removed. - // Favorited live channels are promoted into the favorites-first - // Live rail; clicking one still opens the Xtream favorites + // Favorited movies/series are shown as cover cards in their own + // rail. The rail's "See all" opens Global Favorites directly on + // Movies instead of defaulting back to Live TV. + await expectDashboardRail( + app.mainWindow, + 'dashboard-favorite-vod-rail' + ); + await app.mainWindow + .getByTestId('dashboard-favorite-vod-rail-manage-all') + .click(); + await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/); + await expectVisibleContentCardTitle(app.mainWindow, movieTitle); + + await app.mainWindow.goBack(); + await expectPathname(app.mainWindow, /\/workspace\/dashboard$/); + + await dashboardRailCardByTitle( + app.mainWindow, + 'dashboard-favorite-vod-rail', + movieTitle + ).click(); + await expectInlineCollectionDetail(app.mainWindow, { + pathname: /\/workspace\/global-favorites$/, + title: movieTitle, + }); + + await app.mainWindow.goBack(); + await expectPathname(app.mainWindow, /\/workspace\/dashboard$/); + + await dashboardRailCardByTitle( + app.mainWindow, + 'dashboard-favorite-vod-rail', + seriesTitle + ).click(); + await expectInlineCollectionDetail(app.mainWindow, { + pathname: /\/workspace\/global-favorites$/, + title: seriesTitle, + }); + + await app.mainWindow.goBack(); + await expectPathname(app.mainWindow, /\/workspace\/dashboard$/); + + // Live favorites no longer fall back to recent live history. + // Clicking a favorited live card still opens the Xtream favorites // collection with the channel playing. await expectDashboardRail( app.mainWindow, - 'dashboard-live-recent-rail' + 'dashboard-live-favorites-rail' ); await dashboardRailCardByTitle( app.mainWindow, - 'dashboard-live-recent-rail', + 'dashboard-live-favorites-rail', liveTitle ).click(); await app.mainWindow.waitForURL( @@ -115,6 +158,28 @@ test.describe('Dashboard Activation', () => { await app.mainWindow.goBack(); await app.mainWindow.waitForURL(/\/workspace\/dashboard$/); + // Once the live card has been played, the separate recently + // watched live rail appears with the same channel layout. + await expectDashboardRail( + app.mainWindow, + 'dashboard-recent-live-rail' + ); + await dashboardRailCardByTitle( + app.mainWindow, + 'dashboard-recent-live-rail', + liveTitle + ).click(); + await expectPathname( + app.mainWindow, + /\/workspace\/xtreams\/[^/]+\/recent$/ + ); + await expect( + channelItemByTitle(app.mainWindow, liveTitle).first() + ).toBeVisible({ timeout: 20000 }); + + await app.mainWindow.goBack(); + await expectPathname(app.mainWindow, /\/workspace\/dashboard$/); + // Movies and series that were played land in the Continue // Watching rail (formerly "recently-watched"); clicking a card // opens it inline in the global-recent collection detail view. diff --git a/apps/electron-backend-e2e/src/settings.e2e.ts b/apps/electron-backend-e2e/src/settings.e2e.ts index 31cf08999..f0d834b90 100644 --- a/apps/electron-backend-e2e/src/settings.e2e.ts +++ b/apps/electron-backend-e2e/src/settings.e2e.ts @@ -217,6 +217,21 @@ test.describe('Electron Settings', () => { 'mat-checkbox[formcontrolname="showDashboard"] input[type="checkbox"]' ) .uncheck(); + for (const toggleId of [ + 'toggle-dashboard-hero', + 'toggle-dashboard-rail-continue-watching', + 'toggle-dashboard-rail-live-favorites', + 'toggle-dashboard-rail-recently-watched-live', + 'toggle-dashboard-rail-favorite-movies-and-series', + 'toggle-dashboard-rail-recent-sources', + 'toggle-dashboard-rail-xtream-recently-added', + ]) { + await expect( + firstLaunch.mainWindow + .getByTestId(toggleId) + .locator('input[type="checkbox"]') + ).toBeDisabled(); + } await saveSettings(firstLaunch.mainWindow); } finally { await closeElectronApp(firstLaunch); @@ -241,6 +256,53 @@ test.describe('Electron Settings', () => { } }); + test('@settings @dashboard @electron hides an individually disabled dashboard rail while dashboard remains enabled', async ({ + dataDir, + }) => { + const app = await launchElectronApp(dataDir); + + try { + await importM3uPlaylistFromNativeDialog(app, m3uFixturePath); + await app.mainWindow.waitForURL(/\/workspace\/playlists\/.+/); + + await goToDashboard(app.mainWindow); + await expect( + app.mainWindow.getByTestId('dashboard-recent-sources-rail') + ).toBeVisible({ timeout: 20000 }); + + await openSettings(app.mainWindow); + await app.mainWindow + .locator('.settings-section-item') + .filter({ hasText: 'Dashboard' }) + .first() + .click(); + await expect( + app.mainWindow + .getByTestId('toggle-show-dashboard') + .locator('input[type="checkbox"]') + ).toBeChecked(); + await app.mainWindow + .getByTestId('toggle-dashboard-rail-recent-sources') + .locator('input[type="checkbox"]') + .uncheck(); + await saveSettings(app.mainWindow); + + await goToDashboard(app.mainWindow); + await app.mainWindow.waitForURL(/\/workspace\/dashboard$/); + await expect( + app.mainWindow.getByTestId('dashboard-recent-sources-rail') + ).toHaveCount(0); + await expect( + app.mainWindow.getByRole('link', { + name: 'Dashboard', + exact: true, + }) + ).toBeVisible(); + } finally { + await closeElectronApp(app); + } + }); + test('restores the last section-level view across restart when configured', async ({ dataDir, }) => { diff --git a/apps/web/src/app/settings/settings-dashboard-section.component.html b/apps/web/src/app/settings/settings-dashboard-section.component.html new file mode 100644 index 000000000..57b690a0a --- /dev/null +++ b/apps/web/src/app/settings/settings-dashboard-section.component.html @@ -0,0 +1,164 @@ +
+
+
+ dashboard +
+
+

{{ 'SETTINGS.DASHBOARD' | translate }}

+

{{ 'SETTINGS.DASHBOARD_SUBTITLE' | translate }}

+
+
+ +
+
+

{{ 'SETTINGS.SHOW_DASHBOARD' | translate }}

+

+ {{ 'SETTINGS.SHOW_DASHBOARD_DESCRIPTION' | translate }} +

+
+
+ +
+
+ +
+
+
+

{{ 'SETTINGS.DASHBOARD_HERO' | translate }}

+

+ {{ 'SETTINGS.DASHBOARD_HERO_DESCRIPTION' | translate }} +

+
+
+ +
+
+ +
+
+

{{ 'SETTINGS.DASHBOARD_RAIL_CONTINUE' | translate }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_CONTINUE_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+ +
+
+

{{ + 'SETTINGS.DASHBOARD_RAIL_LIVE_FAVORITES' | translate + }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+ +
+
+

{{ + 'SETTINGS.DASHBOARD_RAIL_RECENT_LIVE' | translate + }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+ +
+
+

{{ + 'SETTINGS.DASHBOARD_RAIL_FAVORITE_VOD' | translate + }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+ +
+
+

{{ 'SETTINGS.DASHBOARD_RAIL_SOURCES' | translate }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_SOURCES_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+ +
+
+

{{ + 'SETTINGS.DASHBOARD_RAIL_XTREAM_ADDED' | translate + }}

+

+ {{ + 'SETTINGS.DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION' + | translate + }} +

+
+
+ +
+
+
+
diff --git a/apps/web/src/app/settings/settings-dashboard-section.component.ts b/apps/web/src/app/settings/settings-dashboard-section.component.ts new file mode 100644 index 000000000..11b544b4d --- /dev/null +++ b/apps/web/src/app/settings/settings-dashboard-section.component.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { Component, input, ViewEncapsulation } from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; +import { TranslateModule } from '@ngx-translate/core'; + +@Component({ + selector: 'app-settings-dashboard-section', + imports: [ + CommonModule, + MatCheckboxModule, + MatIconModule, + ReactiveFormsModule, + TranslateModule, + ], + templateUrl: './settings-dashboard-section.component.html', + encapsulation: ViewEncapsulation.None, + styles: [':host { display: contents; }'], +}) +export class SettingsDashboardSectionComponent { + readonly form = input.required(); + readonly activeSection = input.required(); +} diff --git a/apps/web/src/app/settings/settings-general-section.component.html b/apps/web/src/app/settings/settings-general-section.component.html index 4e831237e..a234e1b66 100644 --- a/apps/web/src/app/settings/settings-general-section.component.html +++ b/apps/web/src/app/settings/settings-general-section.component.html @@ -113,21 +113,6 @@ -
-
-

{{ 'SETTINGS.SHOW_DASHBOARD' | translate }}

-

- {{ 'SETTINGS.SHOW_DASHBOARD_DESCRIPTION' | translate }} -

-
-
- -
-
-

{{ 'SETTINGS.STARTUP_BEHAVIOR' | translate }}

diff --git a/apps/web/src/app/settings/settings-options.spec.ts b/apps/web/src/app/settings/settings-options.spec.ts index 856d0566d..ac7a8d6de 100644 --- a/apps/web/src/app/settings/settings-options.spec.ts +++ b/apps/web/src/app/settings/settings-options.spec.ts @@ -44,6 +44,40 @@ describe('buildSettingsSectionNavItems', () => { return ids; } + function collectSettingsComponentSectionOrder(): string[] { + const html = readFileSync( + resolve(settingsDir, 'settings.component.html'), + 'utf-8' + ); + const idsByComponent = new Map(); + + for (const fileName of readdirSync(settingsDir)) { + const componentMatch = + /^settings-(.+)-section\.component\.html$/.exec(fileName); + if (!componentMatch) { + continue; + } + const sectionHtml = readFileSync( + resolve(settingsDir, fileName), + 'utf-8' + ); + const rootMatch = /]*\sid="([^"]+)"/i.exec( + sectionHtml + ); + if (rootMatch) { + idsByComponent.set( + `app-settings-${componentMatch[1]}-section`, + rootMatch[1] + ); + } + } + + return Array.from( + html.matchAll(/ idsByComponent.get(match[0].slice(1)) + ).filter((id): id is string => Boolean(id)); + } + it('exposes feature-specific items only when their runtime capabilities are supported', () => { const supportedItems = buildSettingsSectionNavItems({ supportsEpg: true, @@ -55,7 +89,7 @@ describe('buildSettingsSectionNavItems', () => { }); expect(supportedItems.map((item) => item.id)).toEqual( - expect.arrayContaining(['epg', 'remote-control']) + expect.arrayContaining(['dashboard', 'epg', 'remote-control']) ); expect( unsupportedItems.find((item) => item.id === 'epg')?.visible @@ -85,4 +119,25 @@ describe('buildSettingsSectionNavItems', () => { const unreachable = [...templateIds].filter((id) => !navIds.has(id)); expect(unreachable).toEqual([]); }); + + it('keeps Dashboard below EPG in both the settings rail and content order', () => { + const expectedOrder = [ + 'general', + 'playback', + 'epg', + 'dashboard', + 'remote-control', + 'backup', + 'reset', + 'about', + ]; + + expect( + buildSettingsSectionNavItems({ + supportsEpg: true, + supportsRemoteControl: true, + }).map((item) => item.id) + ).toEqual(expectedOrder); + expect(collectSettingsComponentSectionOrder()).toEqual(expectedOrder); + }); }); diff --git a/apps/web/src/app/settings/settings-options.ts b/apps/web/src/app/settings/settings-options.ts index a2275a994..8cf1572f1 100644 --- a/apps/web/src/app/settings/settings-options.ts +++ b/apps/web/src/app/settings/settings-options.ts @@ -113,6 +113,12 @@ export function buildSettingsSectionNavItems({ icon: 'calendar_month', visible: supportsEpg, }, + { + id: 'dashboard', + label: 'SETTINGS.NAV_DASHBOARD', + icon: 'dashboard', + visible: true, + }, { // Must match the section's HTML id (`remote-control`) so the // settings-section-scroll directive can resolve the anchor. diff --git a/apps/web/src/app/settings/settings.component.html b/apps/web/src/app/settings/settings.component.html index 440c65a18..9f1205ca4 100644 --- a/apps/web/src/app/settings/settings.component.html +++ b/apps/web/src/app/settings/settings.component.html @@ -67,6 +67,11 @@ /> } + + @if (supportsRemoteControl) { { }); }); - it('renders workspace startup controls with the expected defaults', () => { + it('renders dashboard controls with the expected defaults', () => { const nativeElement = fixture.nativeElement as HTMLElement; expect( @@ -1115,12 +1126,60 @@ describe('SettingsComponent', () => { '[data-test-id="toggle-show-dashboard"]' ) ).not.toBeNull(); + expect( + nativeElement.querySelector( + '[data-test-id="toggle-dashboard-hero"]' + ) + ).not.toBeNull(); + expect( + nativeElement.querySelector( + '[data-test-id="toggle-dashboard-rail-live-favorites"]' + ) + ).not.toBeNull(); + expect( + nativeElement.querySelector( + '[data-test-id="toggle-dashboard-rail-recently-watched-live"]' + ) + ).not.toBeNull(); + expect( + nativeElement.querySelector( + '[data-test-id="toggle-dashboard-rail-favorite-movies-and-series"]' + ) + ).not.toBeNull(); expect(component.settingsForm.value.showDashboard).toBe(true); + expect(component.settingsForm.value.dashboardRails).toEqual( + DEFAULT_DASHBOARD_RAILS + ); expect(component.settingsForm.value.startupBehavior).toBe( StartupBehavior.FirstView ); }); + it('disables dashboard surface controls when the dashboard is off', async () => { + const showDashboard = component.settingsForm.get('showDashboard'); + const dashboardRails = component.settingsForm.get('dashboardRails'); + + showDashboard?.setValue(false); + await fixture.whenStable(); + + expect(dashboardRails?.disabled).toBe(true); + expect( + component.settingsForm.get('dashboardRails.hero')?.disabled + ).toBe(true); + expect( + component.settingsForm.get('dashboardRails.continueWatching') + ?.disabled + ).toBe(true); + + showDashboard?.setValue(true); + await fixture.whenStable(); + + expect(dashboardRails?.enabled).toBe(true); + expect( + component.settingsForm.get('dashboardRails.hero')?.enabled + ).toBe(true); + }); + it('should save settings on submit', async () => { const mockStore = settingsStore as unknown as MockSettingsStore; mockStore.updateSettings.mockResolvedValue(undefined); diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index a348c5b3a..20110f602 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -4,11 +4,13 @@ import { computed, inject, Input, + DestroyRef, OnDestroy, OnInit, signal, ViewEncapsulation, } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormArray, FormBuilder, @@ -51,8 +53,10 @@ import { import { EmbeddedMpvSupport, CoverSize, + DEFAULT_DASHBOARD_RAILS_SETTINGS, Language, normalizeExternalPlayerArguments, + normalizeDashboardRailsSettings, Settings, StartupBehavior, StreamFormat, @@ -63,6 +67,7 @@ import { SettingsStore } from '../services/settings-store.service'; import { SettingsService } from './../services/settings.service'; import { SettingsAboutSectionComponent } from './settings-about-section.component'; import { SettingsBackupSectionComponent } from './settings-backup-section.component'; +import { SettingsDashboardSectionComponent } from './settings-dashboard-section.component'; import { SettingsDeleteAllPlaylistsDialogComponent, SettingsDeleteAllPlaylistsDialogData, @@ -103,6 +108,7 @@ import { SettingsSectionScrollDirective } from './settings-section-scroll.direct MatDialogModule, SettingsAboutSectionComponent, SettingsBackupSectionComponent, + SettingsDashboardSectionComponent, SettingsEpgSectionComponent, SettingsGeneralSectionComponent, SettingsPlaybackSectionComponent, @@ -116,6 +122,7 @@ export class SettingsComponent implements OnInit, OnDestroy { public dataService = inject(DataService); private epgService = inject(EpgService); private formBuilder = inject(FormBuilder); + private destroyRef = inject(DestroyRef); private playlistsService = inject(PlaylistsService); private router = inject(Router); private settingsService = inject(SettingsService); @@ -204,6 +211,19 @@ export class SettingsComponent implements OnInit, OnDestroy { language: Language.ENGLISH, showCaptions: false, showDashboard: true, + dashboardRails: this.formBuilder.group({ + hero: DEFAULT_DASHBOARD_RAILS_SETTINGS.hero, + continueWatching: + DEFAULT_DASHBOARD_RAILS_SETTINGS.continueWatching, + liveFavorites: DEFAULT_DASHBOARD_RAILS_SETTINGS.liveFavorites, + recentlyWatchedLive: + DEFAULT_DASHBOARD_RAILS_SETTINGS.recentlyWatchedLive, + favoriteMoviesAndSeries: + DEFAULT_DASHBOARD_RAILS_SETTINGS.favoriteMoviesAndSeries, + recentSources: DEFAULT_DASHBOARD_RAILS_SETTINGS.recentSources, + xtreamRecentlyAdded: + DEFAULT_DASHBOARD_RAILS_SETTINGS.xtreamRecentlyAdded, + }), startupBehavior: StartupBehavior.FirstView, showExternalPlaybackBar: true, theme: Theme.SystemTheme, @@ -303,6 +323,7 @@ export class SettingsComponent implements OnInit, OnDestroy { // Wait for settings to load before setting the form await this.settingsStore.loadSettings(); this.setSettings(); + this.bindDashboardControlsEnabledState(); void this.loadEmbeddedMpvSupport(); this.checkAppVersion(); void this.fetchLocalIpAddresses(); @@ -378,6 +399,9 @@ export class SettingsComponent implements OnInit, OnDestroy { setSettings() { const currentSettings = this.settingsStore.getSettings(); this.settingsForm.patchValue(currentSettings); + this.syncDashboardControlsEnabledState( + currentSettings.showDashboard ?? true + ); if (this.supportsEpg && currentSettings.epgUrl) { this.epgUrl.clear(); @@ -385,6 +409,28 @@ export class SettingsComponent implements OnInit, OnDestroy { } } + private bindDashboardControlsEnabledState(): void { + this.settingsForm + .get('showDashboard') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((showDashboard) => + this.syncDashboardControlsEnabledState(showDashboard ?? true) + ); + } + + private syncDashboardControlsEnabledState(showDashboard: boolean): void { + const dashboardRails = this.settingsForm.get('dashboardRails'); + if (!dashboardRails) { + return; + } + + if (showDashboard) { + dashboardRails.enable({ emitEvent: false }); + } else { + dashboardRails.disable({ emitEvent: false }); + } + } + selectTheme(theme: Theme): void { if (this.settingsForm.value.theme === theme) { return; @@ -519,7 +565,7 @@ export class SettingsComponent implements OnInit, OnDestroy { private createSettingsFromFormValue(): Settings { const currentSettings = this.settingsStore.getSettings(); - const value = this.settingsForm.value; + const value = this.settingsForm.getRawValue(); const epgUrl = Array.isArray(value.epgUrl) ? value.epgUrl.filter( (url): url is string => typeof url === 'string' @@ -533,6 +579,9 @@ export class SettingsComponent implements OnInit, OnDestroy { language: value.language ?? Language.ENGLISH, showCaptions: value.showCaptions ?? false, showDashboard: value.showDashboard ?? true, + dashboardRails: normalizeDashboardRailsSettings( + value.dashboardRails + ), startupBehavior: value.startupBehavior ?? StartupBehavior.FirstView, showExternalPlaybackBar: value.showExternalPlaybackBar ?? true, theme: value.theme ?? Theme.SystemTheme, diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 1f80c9af6..a635153a3 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "الأرقام فقط مسموح بها", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "داكن", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "فيلم", "TYPE_SERIES": "مسلسل", "SEASON_EPISODE_BADGE": "م{{season}}·ح{{episode}}", - "NOT_YET_SYNCED": "لم تتم المزامنة بعد" + "NOT_YET_SYNCED": "لم تتم المزامنة بعد", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index 1d37aafcc..a617c041e 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "غير الأرقام مسموحة", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "داكن", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "فيلم", "TYPE_SERIES": "سلسلة", "SEASON_EPISODE_BADGE": "م{{season}}·ح{{episode}}", - "NOT_YET_SYNCED": "مازال ما تزامنش" + "NOT_YET_SYNCED": "مازال ما تزامنش", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 5f6dbe5ad..7c39d2209 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Дапускаюцца толькі лічбы", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Цёмная", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Фільм", "TYPE_SERIES": "Серыял", "SEASON_EPISODE_BADGE": "С{{season}}·Э{{episode}}", - "NOT_YET_SYNCED": "Яшчэ не сінхранізавана" + "NOT_YET_SYNCED": "Яшчэ не сінхранізавана", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index 98b7922a2..f1a83f3f8 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Nur Ziffern sind erlaubt", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Wählen Sie, welche Dashboard-Rails im Workspace angezeigt werden.", + "DASHBOARD_HERO": "Hero-Banner", + "DASHBOARD_HERO_DESCRIPTION": "Zeigt den großen oberen Banner für das zuletzt verwendete Element.", + "DASHBOARD_RAIL_CONTINUE": "Weiter schauen", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Zeigt zuletzt angesehene Filme und Serien als Cover-Karten.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Jetzt live auf Ihren Favoriten", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Zeigt favorisierte Live-Kanäle mit aktuellem EPG-Fortschritt.", + "DASHBOARD_RAIL_RECENT_LIVE": "Zuletzt angesehenes Live-TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Zeigt Live-Kanäle, die Sie kürzlich geöffnet haben, mit aktuellem EPG-Fortschritt.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorisierte Filme & Serien", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Zeigt favorisierte Filme und Serien als Cover-Karten.", + "DASHBOARD_RAIL_SOURCES": "Zuletzt verwendete Quellen", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Zeigt Schnellzugriffe auf zuletzt verwendete Playlists und Portale.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Neu auf Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Zeigt die neuesten Xtream-Filme, Serien und Kanäle." }, "THEMES": { "DARK_THEME": "Dunkel", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Serie", "SEASON_EPISODE_BADGE": "St{{season}} · F{{episode}}", - "NOT_YET_SYNCED": "Noch nicht synchronisiert" + "NOT_YET_SYNCED": "Noch nicht synchronisiert", + "RECENTLY_WATCHED_LIVE_TV": "Zuletzt angesehenes Live-TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorisierte Filme & Serien" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 7b9e973f3..775bf208d 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Επιτρέπονται μόνο αριθμητικά ψηφία", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Σκούρο", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Ταινία", "TYPE_SERIES": "Σειρά", "SEASON_EPISODE_BADGE": "Σ{{season}}·Ε{{episode}}", - "NOT_YET_SYNCED": "Δεν έχει συγχρονιστεί ακόμα" + "NOT_YET_SYNCED": "Δεν έχει συγχρονιστεί ακόμα", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index f0add855e..8fadf88f0 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Only digits are allowed", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Dark", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Movie", "TYPE_SERIES": "Series", "SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "Not yet synced" + "NOT_YET_SYNCED": "Not yet synced", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index 0ce3be118..a36753a51 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Solo se permiten digitos", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Oscuro", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Película", "TYPE_SERIES": "Serie", "SEASON_EPISODE_BADGE": "T{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "Aún no sincronizado" + "NOT_YET_SYNCED": "Aún no sincronizado", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index 50130074a..ab0150f91 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Seuls les chiffres sont autorisés", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Sombre", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Série", "SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "Pas encore synchronisé" + "NOT_YET_SYNCED": "Pas encore synchronisé", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index f24d32eb1..0099b270f 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Sono consentite solo cifre", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Scuro", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Serie", "SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "Non ancora sincronizzato" + "NOT_YET_SYNCED": "Non ancora sincronizzato", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index 3e45ce33d..e7b5bfcbb 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "数字のみ入力できます", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "ダーク", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "映画", "TYPE_SERIES": "シリーズ", "SEASON_EPISODE_BADGE": "S{{season}}・E{{episode}}", - "NOT_YET_SYNCED": "未同期" + "NOT_YET_SYNCED": "未同期", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index 82a38ca01..f00557a87 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "숫자만 입력할 수 있습니다", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "어둡게", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "영화", "TYPE_SERIES": "시리즈", "SEASON_EPISODE_BADGE": "시즌{{season}} {{episode}}화", - "NOT_YET_SYNCED": "아직 동기화되지 않음" + "NOT_YET_SYNCED": "아직 동기화되지 않음", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index 1c56af772..62f211028 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Alleen cijfers zijn toegestaan", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Donker", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Series", "SEASON_EPISODE_BADGE": "S{{season}}·A{{episode}}", - "NOT_YET_SYNCED": "Nog niet gesynchroniseerd" + "NOT_YET_SYNCED": "Nog niet gesynchroniseerd", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index cc288d25e..e2c93f1a4 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Dozwolone są tylko cyfry", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Ciemny", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Serial", "SEASON_EPISODE_BADGE": "S{{season}}·O{{episode}}", - "NOT_YET_SYNCED": "Jeszcze niezsynchronizowane" + "NOT_YET_SYNCED": "Jeszcze niezsynchronizowane", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index ad485a7b7..e49fbfc48 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Apenas dígitos são permitidos", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Escuro", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Filme", "TYPE_SERIES": "Série", "SEASON_EPISODE_BADGE": "T{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "Ainda não sincronizado" + "NOT_YET_SYNCED": "Ainda não sincronizado", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index d99dcab30..1d6495e75 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Разрешены только цифры", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Панель", + "DASHBOARD": "Панель", + "DASHBOARD_SUBTITLE": "Выберите, какие секции отображаются на панели рабочего пространства.", + "DASHBOARD_HERO": "Верхний баннер", + "DASHBOARD_HERO_DESCRIPTION": "Показывать большой верхний баннер для последнего элемента.", + "DASHBOARD_RAIL_CONTINUE": "Продолжить просмотр", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Показывать недавно просмотренные фильмы и сериалы в виде обложек.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Сейчас в эфире на избранных каналах", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Показывать избранные live-каналы с текущим прогрессом EPG.", + "DASHBOARD_RAIL_RECENT_LIVE": "Недавно просмотренное прямое ТВ", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Показывать live-каналы, которые вы недавно открывали, с текущим прогрессом EPG.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Избранные фильмы и сериалы", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Показывать избранные фильмы и сериалы в виде обложек.", + "DASHBOARD_RAIL_SOURCES": "Недавно использованные источники", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Показывать быстрый доступ к недавно использованным плейлистам и порталам.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Недавно добавленное на Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Показывать новые фильмы, сериалы и каналы Xtream." }, "THEMES": { "DARK_THEME": "Тёмная", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Фильм", "TYPE_SERIES": "Сериал", "SEASON_EPISODE_BADGE": "С{{season}}·Э{{episode}}", - "NOT_YET_SYNCED": "Еще не синхронизировано" + "NOT_YET_SYNCED": "Еще не синхронизировано", + "RECENTLY_WATCHED_LIVE_TV": "Недавно просмотренное прямое ТВ", + "FAVORITE_MOVIES_AND_SERIES": "Избранные фильмы и сериалы" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index 58ff1e31c..126c30ca6 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "Sadece rakamlara izin verilir", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "Koyu", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "Film", "TYPE_SERIES": "Dizi", "SEASON_EPISODE_BADGE": "S{{season}}·B{{episode}}", - "NOT_YET_SYNCED": "Henüz eşitlenmedi" + "NOT_YET_SYNCED": "Henüz eşitlenmedi", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index 6dedcf2dd..49c2e475d 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "仅允许输入数字", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "深色", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "电影", "TYPE_SERIES": "剧集", "SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "尚未同步" + "NOT_YET_SYNCED": "尚未同步", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index 310488386..4bfcbde42 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -377,7 +377,24 @@ "PORT_PATTERN": "僅允許數字", "REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…", "REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}", - "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again." + "PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.", + "NAV_DASHBOARD": "Dashboard", + "DASHBOARD": "Dashboard", + "DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.", + "DASHBOARD_HERO": "Hero banner", + "DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.", + "DASHBOARD_RAIL_CONTINUE": "Continue Watching", + "DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.", + "DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites", + "DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.", + "DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV", + "DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.", + "DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series", + "DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.", + "DASHBOARD_RAIL_SOURCES": "Recently used sources", + "DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.", + "DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream", + "DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels." }, "THEMES": { "DARK_THEME": "深色", @@ -890,7 +907,9 @@ "TYPE_MOVIE": "電影", "TYPE_SERIES": "影集", "SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}", - "NOT_YET_SYNCED": "尚未同步" + "NOT_YET_SYNCED": "尚未同步", + "RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV", + "FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series" }, "SHELL": { "BRAND_ALT": "IPTVnator", diff --git a/libs/services/src/lib/settings-store.service.spec.ts b/libs/services/src/lib/settings-store.service.spec.ts new file mode 100644 index 000000000..e118efe44 --- /dev/null +++ b/libs/services/src/lib/settings-store.service.spec.ts @@ -0,0 +1,116 @@ +import { Injector } from '@angular/core'; +import { StorageMap } from '@ngx-pwa/local-storage'; +import { of } from 'rxjs'; +import { + DashboardRailsSettings, + Language, + Settings, + StartupBehavior, + STORE_KEY, + StreamFormat, + Theme, + VideoPlayer, +} from '@iptvnator/shared/interfaces'; +import { SettingsStore } from './settings-store.service'; + +const expectedDashboardRails = ( + overrides: Partial = {} +): DashboardRailsSettings => ({ + hero: true, + continueWatching: true, + liveFavorites: true, + recentlyWatchedLive: true, + favoriteMoviesAndSeries: true, + recentSources: true, + xtreamRecentlyAdded: true, + ...overrides, +}); + +describe('SettingsStore dashboard rail settings', () => { + let storedSettings: Partial | null; + let injector: Injector; + let storage: { + get: jest.Mock; + set: jest.Mock; + }; + + beforeEach(() => { + storedSettings = null; + storage = { + get: jest.fn(() => of(storedSettings)), + set: jest.fn(() => of(undefined)), + }; + + injector = Injector.create({ + providers: [ + SettingsStore, + { + provide: StorageMap, + useValue: storage, + }, + ], + }); + }); + + it('uses enabled dashboard rail defaults when no settings are stored', async () => { + const store = injector.get(SettingsStore); + + await store.loadSettings(); + + expect(store.getSettings().dashboardRails).toEqual( + expectedDashboardRails() + ); + }); + + it('deep-merges partial stored dashboard rail settings with enabled defaults', async () => { + storedSettings = { + player: VideoPlayer.VideoJs, + streamFormat: StreamFormat.M3u8StreamFormat, + openStreamOnDoubleClick: false, + language: Language.ENGLISH, + showCaptions: false, + showDashboard: true, + startupBehavior: StartupBehavior.FirstView, + showExternalPlaybackBar: true, + theme: Theme.SystemTheme, + mpvPlayerPath: '', + mpvPlayerArguments: '', + mpvReuseInstance: false, + vlcPlayerPath: '', + vlcPlayerArguments: '', + vlcReuseInstance: false, + remoteControl: false, + remoteControlPort: 8765, + epgUrl: [], + dashboardRails: { + recentSources: false, + }, + } as unknown as Partial; + + const store = injector.get(SettingsStore); + + await store.loadSettings(); + await store.updateSettings({ + dashboardRails: { + ...store.getSettings().dashboardRails, + liveFavorites: false, + }, + }); + + expect(store.getSettings().dashboardRails).toEqual( + expectedDashboardRails({ + liveFavorites: false, + recentSources: false, + }) + ); + expect(storage.set).toHaveBeenCalledWith( + STORE_KEY.Settings, + expect.objectContaining({ + dashboardRails: expectedDashboardRails({ + liveFavorites: false, + recentSources: false, + }), + }) + ); + }); +}); diff --git a/libs/services/src/lib/settings-store.service.ts b/libs/services/src/lib/settings-store.service.ts index 94e48daef..22764b660 100644 --- a/libs/services/src/lib/settings-store.service.ts +++ b/libs/services/src/lib/settings-store.service.ts @@ -9,6 +9,7 @@ import { import { StorageMap } from '@ngx-pwa/local-storage'; import { firstValueFrom } from 'rxjs'; import { + DEFAULT_DASHBOARD_RAILS_SETTINGS, ElectronBridgeTrustOptions, Language, Settings, @@ -17,6 +18,7 @@ import { StreamFormat, Theme, VideoPlayer, + normalizeDashboardRailsSettings, } from '@iptvnator/shared/interfaces'; const DEFAULT_SETTINGS: Settings = { @@ -41,6 +43,7 @@ const DEFAULT_SETTINGS: Settings = { downloadFolder: '', recordingFolder: '', coverSize: 'medium', + dashboardRails: DEFAULT_DASHBOARD_RAILS_SETTINGS, preferUploadedEpgOverXtream: false, trustedPrivateNetworkEpgUrls: [], trustedInsecureTlsHosts: [], @@ -95,9 +98,13 @@ export const SettingsStore = signalStore( storage.get(STORE_KEY.Settings) ); if (stored) { + const storedSettings = stored as Partial; patchState(store, { ...DEFAULT_SETTINGS, - ...(stored as Settings), + ...storedSettings, + dashboardRails: normalizeDashboardRailsSettings( + storedSettings.dashboardRails + ), }); void this.sanitizeEmbeddedMpvSelection().catch((error) => { console.warn( @@ -113,7 +120,16 @@ export const SettingsStore = signalStore( }, async updateSettings(settings: Partial) { - patchState(store, settings); + patchState(store, { + ...settings, + ...(settings.dashboardRails !== undefined + ? { + dashboardRails: normalizeDashboardRailsSettings( + settings.dashboardRails + ), + } + : {}), + }); // Save the complete settings object, not just the partial update const completeSettings = this.getSettings(); try { @@ -157,6 +173,9 @@ export const SettingsStore = signalStore( store.recordingFolder?.() ?? DEFAULT_SETTINGS.recordingFolder, coverSize: store.coverSize?.() ?? DEFAULT_SETTINGS.coverSize, + dashboardRails: normalizeDashboardRailsSettings( + store.dashboardRails?.() + ), preferUploadedEpgOverXtream: store.preferUploadedEpgOverXtream?.() ?? DEFAULT_SETTINGS.preferUploadedEpgOverXtream, diff --git a/libs/shared/interfaces/src/lib/settings.interface.ts b/libs/shared/interfaces/src/lib/settings.interface.ts index ebf0d0398..302900767 100644 --- a/libs/shared/interfaces/src/lib/settings.interface.ts +++ b/libs/shared/interfaces/src/lib/settings.interface.ts @@ -21,6 +21,51 @@ export enum StartupBehavior { export type CoverSize = 'small' | 'medium' | 'large'; +export interface DashboardRailsSettings { + hero: boolean; + continueWatching: boolean; + liveFavorites: boolean; + recentlyWatchedLive: boolean; + favoriteMoviesAndSeries: boolean; + recentSources: boolean; + xtreamRecentlyAdded: boolean; +} + +export const DEFAULT_DASHBOARD_RAILS_SETTINGS: DashboardRailsSettings = { + hero: true, + continueWatching: true, + liveFavorites: true, + recentlyWatchedLive: true, + favoriteMoviesAndSeries: true, + recentSources: true, + xtreamRecentlyAdded: true, +}; + +export type DashboardRailsSettingsInput = Partial< + Record +>; + +export function normalizeDashboardRailsSettings( + settings?: DashboardRailsSettingsInput | null +): DashboardRailsSettings { + const normalized = { ...DEFAULT_DASHBOARD_RAILS_SETTINGS }; + + if (!settings) { + return normalized; + } + + const keys = Object.keys( + DEFAULT_DASHBOARD_RAILS_SETTINGS + ) as (keyof DashboardRailsSettings)[]; + for (const key of keys) { + if (typeof settings[key] === 'boolean') { + normalized[key] = settings[key]; + } + } + + return normalized; +} + /** * Describes all available settings options of the application */ @@ -58,6 +103,8 @@ export interface Settings { recordingFolder?: string; /** Cover/poster sizing preset applied across grids and rails */ coverSize?: CoverSize; + /** Per-rail dashboard visibility preferences. Missing keys default on. */ + dashboardRails?: DashboardRailsSettings; /** * When true, the locally-parsed XMLTV programs (loaded from `epgUrl`) * take precedence over the Xtream provider's EPG for live TV channels. 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 5daccec16..8eef38a39 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 @@ -265,10 +265,8 @@ export class DashboardDataService { this.globalRecentItems().filter((item) => item.type === 'live') ); - // Favorited live channels — the dashboard's "Live now" rail prefers this - // source so the EPG enrichment lands on the channels users actually care - // about. Falls back to globalRecentLiveItems when this is empty so a - // fresh-install user still sees something useful. + // Favorited live channels — the dashboard's "Live now" rail uses this + // source only. Recently watched live channels have their own dashboard rail. readonly globalFavoriteLiveItems = computed(() => this.globalFavoriteItems().filter((item) => item.type === 'live') ); diff --git a/libs/workspace/dashboard/feature/README.md b/libs/workspace/dashboard/feature/README.md index 1ebf51e65..bc9d0083c 100644 --- a/libs/workspace/dashboard/feature/README.md +++ b/libs/workspace/dashboard/feature/README.md @@ -1,3 +1,55 @@ -# feature +# Workspace Dashboard Feature -This library was generated with [Nx](https://nx.dev). +This library owns the workspace dashboard rails UI. The dashboard is a +read-only surface over existing playlist, recent, favorites, EPG, and Xtream +catalog data; it should not introduce Electron IPC, SQLite schema, or route +contracts on its own. + +## Dashboard Surfaces + +The dashboard renders a surface only when the matching setting is enabled. Data +rails also require the underlying data slice to have at least one item. + +- `hero` shows the large top banner for the most recent global item. When that + item is a live TV channel, the hero looks up the current XMLTV programme and + displays the programme title, time range, and EPG progress bar when data is + available. +- `continueWatching` shows recent movies and series from + `DashboardDataService.globalRecentVodItems()` using cover cards with playback + progress when a saved resume position is available. +- `liveFavorites` shows favorited live TV channels from + `DashboardDataService.globalFavoriteLiveItems()` using the channel layout + with EPG title, time range, and progress when XMLTV data is available. +- `recentlyWatchedLive` shows recently watched live TV channels from + `DashboardDataService.globalRecentLiveItems()` using the same channel layout. +- `favoriteMoviesAndSeries` shows favorited movies and series from + `DashboardDataService.globalFavoriteItems()`, excluding live favorites, using + cover cards. +- `recentSources` shows recently used playlist/source entries. +- `xtreamRecentlyAdded` shows recently added Xtream catalog items. + +## Settings + +Per-surface visibility lives on `Settings.dashboardRails` as a +`DashboardRailsSettings` object. Every surface defaults to enabled. Stored +settings are deep-merged with `DEFAULT_DASHBOARD_RAILS_SETTINGS` by +`SettingsStore`, so existing users and older partial settings keep newly added +surfaces visible unless they explicitly turn them off. + +The global `showDashboard` flag remains a top-level `Settings` property because +workspace startup and route guards already depend on that contract. The Settings +UI groups `showDashboard` and the per-surface checkboxes in the Dashboard +section. When `showDashboard` is off, the per-surface checkboxes are disabled +because the dashboard route itself is hidden. + +## Navigation + +Rail cards use the navigation state provided by `DashboardDataService` for the +underlying item. Rail "See all" links may also pass router state: + +- live rails open the relevant collection on Live TV. +- cover rails for movies/series open Global Recent or Global Favorites on + Movies when movie items are present, otherwise on Series. + +This keeps the global collection pages from defaulting to Live TV when a +dashboard rail is clearly about movies or series. diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html index 8b91b148a..a7fbceff0 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html @@ -12,6 +12,7 @@ diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts index ae38601f9..587540172 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts @@ -96,6 +96,7 @@ export class DashboardRailComponent implements AfterViewInit, OnDestroy { readonly label = input.required(); readonly items = input.required(); readonly seeAllLink = input(null); + readonly seeAllState = input | null>(null); readonly aspectRatio = input('2 / 3'); readonly layout = input('cover'); readonly testId = input(null); diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html index 1ed8e2679..bc702ab78 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html @@ -14,150 +14,237 @@ loading the first global recent item, nothing if no recent items exist. Each block below renders as soon as ITS data is ready — no longer pinned on the slowest rail's I/O. --> - @if (hero(); as item) { - - - @if (item.backdropUrl; as backdropUrl) { - + + - -

{{ item.title }}

-

{{ item.subtitle }}

- - @if (item.watchProgress !== null && item.watchProgress !== undefined) { - + + +

{{ item.title }}

+

{{ item.subtitle }}

+ + @if ( + item.contentType === 'live' && + (item.nowPlayingTitle || + item.nowPlayingTimeRange || + (item.nowPlayingProgress !== null && + item.nowPlayingProgress !== undefined)) + ) { + + + LIVE + @if (item.nowPlayingTimeRange) { + {{ + item.nowPlayingTimeRange + }} + } + + @if (item.nowPlayingTitle) { + {{ + item.nowPlayingTitle + }} + } + + + } + + @if (item.watchProgress !== null && item.watchProgress !== undefined) { + + + @if (item.remainingLabel) { + {{ + item.remainingLabel.key + | translate + : item.remainingLabel.params + }} + · + } + {{ + 'WORKSPACE.DASHBOARD.PERCENT_WATCHED' + | translate + : { value: item.watchProgress } + }} + + } + + + play_arrow {{ - 'WORKSPACE.DASHBOARD.PERCENT_WATCHED' - | translate: { value: item.watchProgress } + 'WORKSPACE.DASHBOARD.CONTINUE_WATCHING' + | translate }} - } - - - play_arrow - {{ - 'WORKSPACE.DASHBOARD.CONTINUE_WATCHING' | translate - }} -
-
- } @else if (data.globalRecentLoading()) { - + + } @else if (data.globalRecentLoading()) { + + } } - @if (continueWatchingCards().length > 0) { + @if ( + dashboardRails().continueWatching && + continueWatchingCards().length > 0 + ) { } - - @if (liveOnFavoritesCardsEnriched().length > 0) { + + @if ( + dashboardRails().liveFavorites && + liveFavoriteCardsEnriched().length > 0 + ) { } @if ( + dashboardRails().recentlyWatchedLive && + recentLiveCardsEnriched().length > 0 + ) { + + } + + @if ( + dashboardRails().favoriteMoviesAndSeries && + favoriteMoviesAndSeriesCards().length > 0 + ) { + + } + + @if ( + dashboardRails().continueWatching && + dashboardRails().recentlyWatchedLive && continueWatchingCards().length === 0 && - liveOnFavoritesCards().length === 0 && + recentLiveCards().length === 0 && data.globalRecentLoading() ) {
} - - - @if (sourceCards().length > 0) { + @if (dashboardRails().recentSources && sourceCards().length > 0) { - } @else if (!data.playlistsLoaded()) { + } @else if (dashboardRails().recentSources && !data.playlistsLoaded()) {