diff --git a/apps/web/src/app/app.component.spec.ts b/apps/web/src/app/app.component.spec.ts index b5a6ccc92..7792752d1 100644 --- a/apps/web/src/app/app.component.spec.ts +++ b/apps/web/src/app/app.component.spec.ts @@ -12,6 +12,7 @@ import { DataService } from 'services'; import { Language, Settings, + StartupBehavior, STORE_KEY, StreamFormat, Theme, @@ -37,6 +38,8 @@ const DEFAULT_SETTINGS: Settings = { streamFormat: StreamFormat.M3u8StreamFormat, language: Language.ENGLISH, showCaptions: false, + showDashboard: true, + startupBehavior: StartupBehavior.FirstView, showExternalPlaybackBar: true, theme: Theme.SystemTheme, mpvPlayerPath: '', diff --git a/apps/web/src/app/app.routes.spec.ts b/apps/web/src/app/app.routes.spec.ts index 8c7741be6..5ee8a3d91 100644 --- a/apps/web/src/app/app.routes.spec.ts +++ b/apps/web/src/app/app.routes.spec.ts @@ -1,8 +1,10 @@ describe('app routes', () => { let workspaceChildren: Array<{ + canActivate?: unknown[]; data?: Record; loadComponent?: unknown; path?: string; + redirectTo?: unknown; }> = []; beforeAll(async () => { @@ -50,4 +52,20 @@ describe('app routes', () => { }); expect(typeof globalRecentRoute?.loadComponent).toBe('function'); }); + + it('uses a dynamic redirect for the default /workspace child route', async () => { + const workspaceDefaultRoute = workspaceChildren.find( + (route) => route.path === '' + ); + + expect(typeof workspaceDefaultRoute?.redirectTo).toBe('function'); + }); + + it('protects the dashboard route behind a visibility guard', async () => { + const dashboardRoute = workspaceChildren.find( + (route) => route.path === 'dashboard' + ); + + expect(dashboardRoute?.canActivate).toHaveLength(1); + }); }); diff --git a/apps/web/src/app/app.routes.ts b/apps/web/src/app/app.routes.ts index 6e2d36a85..8ff52ba1b 100644 --- a/apps/web/src/app/app.routes.ts +++ b/apps/web/src/app/app.routes.ts @@ -1,5 +1,20 @@ -import { Routes } from '@angular/router'; +import { inject } from '@angular/core'; +import { Router, Routes } from '@angular/router'; import { provideM3uWorkspaceRouteSession } from '@iptvnator/playlist/m3u/feature-player'; +import { WorkspaceStartupPreferencesService } from '@iptvnator/workspace/shell/util'; + +const workspaceEntryRedirect = async () => + inject(WorkspaceStartupPreferencesService).resolveInitialWorkspacePath(); + +const dashboardAccessGuard = async () => { + const startupPreferences = inject(WorkspaceStartupPreferencesService); + const router = inject(Router); + const redirectPath = await startupPreferences.resolveDashboardPath(); + + return redirectPath === '/workspace/dashboard' + ? true + : router.parseUrl(redirectPath); +}; export const routes: Routes = [ { @@ -20,10 +35,11 @@ export const routes: Routes = [ { path: '', pathMatch: 'full', - redirectTo: 'dashboard', + redirectTo: workspaceEntryRedirect, }, { path: 'dashboard', + canActivate: [dashboardAccessGuard], loadComponent: () => import('workspace-dashboard-feature').then( (c) => c.WorkspaceDashboardComponent diff --git a/apps/web/src/app/settings/settings.component.html b/apps/web/src/app/settings/settings.component.html index 9f1fc5c8e..e9cfb7f13 100644 --- a/apps/web/src/app/settings/settings.component.html +++ b/apps/web/src/app/settings/settings.component.html @@ -119,6 +119,62 @@ +
+
+

{{ 'SETTINGS.SHOW_DASHBOARD' | translate }}

+

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

+
+
+ +
+
+ +
+
+

{{ 'SETTINGS.STARTUP_BEHAVIOR' | translate }}

+

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

+
+
+ + {{ + 'SETTINGS.STARTUP_BEHAVIOR' | translate + }} + + @for ( + option of startupBehaviorOptions; + track option.value + ) { + + {{ option.labelKey | translate }} + + } + + +
+
+

{{ 'SETTINGS.SHOW_CAPTIONS' | translate }}

diff --git a/apps/web/src/app/settings/settings.component.spec.ts b/apps/web/src/app/settings/settings.component.spec.ts index 9c5f793c2..a504c8a54 100644 --- a/apps/web/src/app/settings/settings.component.spec.ts +++ b/apps/web/src/app/settings/settings.component.spec.ts @@ -28,6 +28,7 @@ import { DialogService } from 'components'; import { DataService, PlaylistsService } from 'services'; import { Language, + StartupBehavior, StreamFormat, Theme, VideoPlayer, @@ -58,6 +59,8 @@ const DEFAULT_SETTINGS = { streamFormat: StreamFormat.M3u8StreamFormat, language: Language.ENGLISH, showCaptions: false, + showDashboard: true, + startupBehavior: StartupBehavior.FirstView, showExternalPlaybackBar: true, theme: Theme.SystemTheme, mpvPlayerPath: '', @@ -405,6 +408,18 @@ describe('SettingsComponent', () => { expect(component.settingsForm.dirty).toBeTruthy(); }); + it('renders workspace startup controls with the expected defaults', () => { + const nativeElement = fixture.nativeElement as HTMLElement; + + expect( + nativeElement.querySelector('[data-test-id="toggle-show-dashboard"]') + ).not.toBeNull(); + expect(component.settingsForm.value.showDashboard).toBe(true); + expect(component.settingsForm.value.startupBehavior).toBe( + StartupBehavior.FirstView + ); + }); + 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 b324a59ae..d532ead66 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -44,6 +44,7 @@ import { DataService, PlaylistsService } from 'services'; import { Language, Playlist, + StartupBehavior, StreamFormat, Theme, VideoPlayer, @@ -70,6 +71,11 @@ interface ThemeOption { labelKey: string; } +interface StartupBehaviorOption { + value: StartupBehavior; + labelKey: string; +} + @Component({ templateUrl: './settings.component.html', styleUrls: ['./settings.component.scss'], @@ -179,6 +185,17 @@ export class SettingsComponent implements OnInit, OnDestroy { }, ]; + readonly startupBehaviorOptions: StartupBehaviorOption[] = [ + { + value: StartupBehavior.FirstView, + labelKey: 'SETTINGS.STARTUP_BEHAVIOR_FIRST_VIEW', + }, + { + value: StartupBehavior.RestoreLastView, + labelKey: 'SETTINGS.STARTUP_BEHAVIOR_RESTORE_LAST_VIEW', + }, + ]; + /** Settings form object */ settingsForm = this.formBuilder.group({ player: [VideoPlayer.VideoJs], @@ -186,6 +203,8 @@ export class SettingsComponent implements OnInit, OnDestroy { streamFormat: StreamFormat.M3u8StreamFormat, language: Language.ENGLISH, showCaptions: false, + showDashboard: true, + startupBehavior: StartupBehavior.FirstView, showExternalPlaybackBar: true, theme: Theme.SystemTheme, mpvPlayerPath: '', diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index a9625b81e..c75b50f05 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -243,6 +243,8 @@ "SETTINGS_SAVED": "Einstellungen wurden erfolgreich gespeichert!", "THEME": "Farbdesign", "SHOW_CAPTIONS": "Untertitel anzeigen", + "SHOW_DASHBOARD": "Dashboard anzeigen", + "SHOW_DASHBOARD_DESCRIPTION": "Dashboard in der Workspace-Leiste verfügbar halten. Wenn deaktiviert, wird Quellen zur ersten Workspace-Seite.", "STREAM_FORMAT_DESCRIPTION": "Standard-Streamformat auswählen (Xtream)", "REMOVE_ALL": "Playlist entfernen", "REMOVE_ALL_BUTTON": "Entfernen", @@ -274,6 +276,10 @@ "VLC_PLAYER_PATH_LABEL": "VLC-Pfad", "VLC_PLAYER_PATH_DESCRIPTION": "Legen Sie den Pfad zum VLC-Player in Ihrem System fest. \nEs wird der bereitgestellte Pfad zu einer VLC-Binärdatei anstelle des Standardpfads für unser System verwendet.", "THEME_DESCRIPTION": "Wählen Sie das visuelle Thema der Anwendung aus", + "STARTUP_BEHAVIOR": "Startansicht", + "STARTUP_BEHAVIOR_DESCRIPTION": "Legen Sie fest, ob IPTVnator die erste verfügbare Workspace-Ansicht oder Ihre zuletzt geöffnete Abschnittsansicht startet. Erste Ansicht bedeutet Dashboard, wenn aktiviert, sonst Quellen.", + "STARTUP_BEHAVIOR_FIRST_VIEW": "Erste verfügbare Ansicht", + "STARTUP_BEHAVIOR_RESTORE_LAST_VIEW": "Letzte Ansicht wiederherstellen", "EPG_NOTE": "Derzeit ist EPG nur in der Electron-basierten Version von IPTVnator verfügbar.", "VLC_PLAYER_PATH": "VLC-Playerpfad", "IMPORT_EXPORT_DATA_DESCRIPTION": "Exportieren oder importieren Sie Wiedergabelisten im JSON-Format, das in der Anwendung verwendet wird.", @@ -398,10 +404,18 @@ "COPY_STREAM_URL": "Stream-URL kopieren", "ERROR_VIEW": { "DELETE": "Löschen", + "IMPORT_CANCELLED": { + "TITLE": "Import abgebrochen", + "DESCRIPTION": "Der Playlist-Import wurde abgebrochen. Wiederholen Sie ihn, wenn Sie diese Quelle erneut laden möchten." + }, "ACCOUNT_EXPIRED": { "TITLE": "Konto abgelaufen", "DESCRIPTION": "Dieses Konto ist abgelaufen. \nBitte überprüfen Sie die Anmeldeinformationen in den Playlist-Einstellungen oder entfernen Sie sie." }, + "ACCOUNT_INACTIVE": { + "TITLE": "Konto inaktiv", + "DESCRIPTION": "Dieses Konto ist inaktiv. Bitte überprüfen Sie die Anmeldeinformationen in den Playlist-Einstellungen oder entfernen Sie es." + }, "HOME": "Startseite", "EMPTY_CATEGORY": { "TITLE": "Leere Kategorie", @@ -419,6 +433,10 @@ "TITLE": "Portal nicht gefunden", "DESCRIPTION": "Bitte überprüfen Sie die Anmeldeinformationen und die Portal-URL in den Playlist-Einstellungen oder entfernen Sie sie." }, + "PORTAL_UNAVAILABLE": { + "TITLE": "Portal nicht erreichbar", + "DESCRIPTION": "Das Portal konnte nicht erreicht werden. Überprüfen Sie die Server-URL oder versuchen Sie es erneut, wenn die Quelle wieder verfügbar ist." + }, "UNKNOWN_ERROR": { "TITLE": "Etwas ist schief gelaufen", "DESCRIPTION": "Unbekannter Fehler, bitte versuchen Sie es später noch einmal" @@ -590,6 +608,7 @@ "RAIL_DASHBOARD": "Dashboard", "OPEN_DASHBOARD": "Dashboard öffnen", "RAIL_SOURCES": "Quellen", + "OPEN_SOURCES": "Quellen öffnen", "RAIL_SETTINGS": "Einstellungen", "OPEN_SETTINGS": "Einstellungen öffnen", "RAIL_LIBRARY": "Bibliothek", @@ -620,6 +639,7 @@ "NO_SECTION_ACTIONS": "Keine Aktionen für diesen Abschnitt", "PLAYLIST_INFO": "Playlist-Info", "ACCOUNT_INFO": "Kontoinfo", + "REFRESH_PLAYLIST": "Playlist aktualisieren", "CLEAR_COMPLETED_DOWNLOADS_ALL": "Abgeschlossene Downloads löschen (alle Playlists)", "CLEAR_COMPLETED_DOWNLOADS_ALL_ARIA": "Abgeschlossene Downloads für alle Playlists löschen", "CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST": "Abgeschlossene Downloads löschen (diese Playlist)", @@ -632,12 +652,18 @@ "NO_SOURCES_AVAILABLE": "Keine Quellen verfügbar", "ONE_SOURCE_AVAILABLE": "1 Quelle verfügbar", "SOURCES_AVAILABLE": "{{count}} Quellen verfügbar", - "XTREAM_IMPORT_PREPARING": "Playlist-Daten werden vorbereitet...", - "XTREAM_IMPORT_SAVING": "Playlist-Inhalte werden gespeichert...", - "XTREAM_IMPORT_RESTORING_FAVORITES": "Favoriten werden wiederhergestellt...", - "XTREAM_IMPORT_RESTORING_RECENT": "Zuletzt angesehene Inhalte werden wiederhergestellt...", - "XTREAM_IMPORT_CANCEL": "Import abbrechen", - "XTREAM_IMPORT_CANCELLING": "Import wird abgebrochen...", + "XTREAM_IMPORT_TITLE": "Playlist wird synchronisiert", + "XTREAM_IMPORT_REMOTE_BADGE": "Remote-Quelle", + "XTREAM_IMPORT_LOCAL_BADGE": "Lokale Bibliothek", + "XTREAM_IMPORT_DETAIL_REMOTE": "Warte auf die Antwort des Providers.", + "XTREAM_IMPORT_DETAIL_LOCAL": "Bereitet schnellere Playlist-Wechsel für den nächsten Besuch vor.", + "XTREAM_IMPORT_PREPARING": "Lokale Bibliothek wird vorbereitet...", + "XTREAM_IMPORT_LOADING": "Playlist-Daten werden von der Quelle geladen...", + "XTREAM_IMPORT_SAVING": "Playlist-Daten werden lokal gespeichert...", + "XTREAM_IMPORT_RESTORING_FAVORITES": "Favoriten werden aus der lokalen Bibliothek wiederhergestellt...", + "XTREAM_IMPORT_RESTORING_RECENT": "Zuletzt angesehene Inhalte werden aus der lokalen Bibliothek wiederhergestellt...", + "XTREAM_IMPORT_CANCEL": "Synchronisierung stoppen", + "XTREAM_IMPORT_CANCELLING": "Synchronisierung wird gestoppt...", "COMMANDS": { "GLOBAL_SEARCH_LABEL": "Alle Xtream-Playlists durchsuchen", "GLOBAL_SEARCH_DESCRIPTION": "Globale Suchüberlagerung öffnen", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 197a73723..a15972c1e 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -200,6 +200,8 @@ "SETTINGS_SAVED": "Success! Configuration was saved.", "THEME": "Visual theme", "SHOW_CAPTIONS": "Show subtitles", + "SHOW_DASHBOARD": "Show dashboard", + "SHOW_DASHBOARD_DESCRIPTION": "Keep Dashboard available in the workspace rail. When disabled, Sources becomes the first workspace page.", "ADD_EPG_SOURCE": "Add EPG source", "CLEAR_EPG_DATA": "Clear EPG data", "CLEAR_EPG_DIALOG": { @@ -212,6 +214,10 @@ "STREAM_FORMAT_DESCRIPTION": "Select default stream format (xtream)", "LANGUAGE_DESCRIPTION": "Select language of the application", "THEME_DESCRIPTION": "Select visual theme of the application", + "STARTUP_BEHAVIOR": "Startup view", + "STARTUP_BEHAVIOR_DESCRIPTION": "Choose whether IPTVnator opens the first available workspace view or restores your last section-level view. First view means Dashboard when enabled, otherwise Sources.", + "STARTUP_BEHAVIOR_FIRST_VIEW": "First available view", + "STARTUP_BEHAVIOR_RESTORE_LAST_VIEW": "Restore last view", "SHOW_CAPTIONS_DESCRIPTION": "Show or hide subtitles by default", "SHOW_EXTERNAL_PLAYBACK_BAR": "Show now playing footer", "SHOW_EXTERNAL_PLAYBACK_BAR_DESCRIPTION": "Keep the external-player status bar visible at the bottom of the workspace on desktop.", @@ -469,10 +475,18 @@ "ACCOUNT_INFO": "Account info" }, "ERROR_VIEW": { + "IMPORT_CANCELLED": { + "TITLE": "Import cancelled", + "DESCRIPTION": "The playlist import was cancelled. Retry when you want to continue loading this source." + }, "ACCOUNT_EXPIRED": { "TITLE": "Account expired", "DESCRIPTION": "This account has been expired. Please check the credentials in playlist settings or remove it." }, + "ACCOUNT_INACTIVE": { + "TITLE": "Account inactive", + "DESCRIPTION": "This account is inactive. Please check the credentials in playlist settings or remove it." + }, "UNAUTHORIZED": { "TITLE": "Unauthorized", "DESCRIPTION": "Please check the credentials in playlist settings or remove it." @@ -481,6 +495,10 @@ "TITLE": "Portal not found", "DESCRIPTION": "Please check the credentials and portal url in playlist settings or remove it." }, + "PORTAL_UNAVAILABLE": { + "TITLE": "Portal unavailable", + "DESCRIPTION": "The portal could not be reached. Check the server URL or retry when the source is available again." + }, "EMPTY_CATEGORY": { "TITLE": "No content in this category", "DESCRIPTION": "Select another category or switch the content type." @@ -661,6 +679,7 @@ "RAIL_DASHBOARD": "Dashboard", "OPEN_DASHBOARD": "Open dashboard", "RAIL_SOURCES": "Sources", + "OPEN_SOURCES": "Open sources", "RAIL_SETTINGS": "Settings", "OPEN_SETTINGS": "Open settings", "RAIL_LIBRARY": "Library", @@ -691,6 +710,7 @@ "NO_SECTION_ACTIONS": "No actions for this section", "PLAYLIST_INFO": "Playlist info", "ACCOUNT_INFO": "Account info", + "REFRESH_PLAYLIST": "Refresh playlist", "CLEAR_COMPLETED_DOWNLOADS_ALL": "Clear completed downloads (all playlists)", "CLEAR_COMPLETED_DOWNLOADS_ALL_ARIA": "Clear completed downloads for all playlists", "CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST": "Clear completed downloads (this playlist)", @@ -703,12 +723,18 @@ "NO_SOURCES_AVAILABLE": "No sources available", "ONE_SOURCE_AVAILABLE": "1 source available", "SOURCES_AVAILABLE": "{{count}} sources available", - "XTREAM_IMPORT_PREPARING": "Preparing playlist data...", - "XTREAM_IMPORT_SAVING": "Saving playlist content...", - "XTREAM_IMPORT_RESTORING_FAVORITES": "Restoring favorites...", - "XTREAM_IMPORT_RESTORING_RECENT": "Restoring recently viewed...", - "XTREAM_IMPORT_CANCEL": "Cancel import", - "XTREAM_IMPORT_CANCELLING": "Cancelling import...", + "XTREAM_IMPORT_TITLE": "Syncing playlist", + "XTREAM_IMPORT_REMOTE_BADGE": "Remote source", + "XTREAM_IMPORT_LOCAL_BADGE": "Local library", + "XTREAM_IMPORT_DETAIL_REMOTE": "Waiting on the provider response.", + "XTREAM_IMPORT_DETAIL_LOCAL": "Preparing faster playlist switching for the next visit.", + "XTREAM_IMPORT_PREPARING": "Preparing local library...", + "XTREAM_IMPORT_LOADING": "Fetching playlist data from source...", + "XTREAM_IMPORT_SAVING": "Saving playlist data locally...", + "XTREAM_IMPORT_RESTORING_FAVORITES": "Restoring favorites from local library...", + "XTREAM_IMPORT_RESTORING_RECENT": "Restoring recently viewed from local library...", + "XTREAM_IMPORT_CANCEL": "Stop sync", + "XTREAM_IMPORT_CANCELLING": "Stopping sync...", "COMMANDS": { "GLOBAL_SEARCH_LABEL": "Search all Xtream playlists", "GLOBAL_SEARCH_DESCRIPTION": "Open global search overlay", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index fad1fccec..158ecc0a6 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -181,6 +181,8 @@ "SETTINGS_SAVED": "Новые настройки были успешно сохранены!", "THEME": "Тема оформления", "SHOW_CAPTIONS": "Отображать субтитры", + "SHOW_DASHBOARD": "Показывать панель", + "SHOW_DASHBOARD_DESCRIPTION": "Оставить Dashboard доступным в боковой панели рабочего пространства. Если отключить, первой страницей будет Источники.", "ADD_EPG_SOURCE": "Добавить источник EPG", "CLEAR_EPG_DATA": "Очистить данные EPG", "CLEAR_EPG_DIALOG": { @@ -193,6 +195,10 @@ "STREAM_FORMAT_DESCRIPTION": "Выбрать формат потока по умолчанию (Xtream)", "LANGUAGE_DESCRIPTION": "Выбрать язык приложения", "THEME_DESCRIPTION": "Выбрать визуальную тему оформления", + "STARTUP_BEHAVIOR": "Начальный экран", + "STARTUP_BEHAVIOR_DESCRIPTION": "Выберите, должен ли IPTVnator открывать первый доступный экран рабочего пространства или восстанавливать последний экран уровня раздела. Первый экран означает Dashboard, если он включён, иначе Источники.", + "STARTUP_BEHAVIOR_FIRST_VIEW": "Первый доступный экран", + "STARTUP_BEHAVIOR_RESTORE_LAST_VIEW": "Восстановить последний экран", "SHOW_CAPTIONS_DESCRIPTION": "Показать или скрыть субтитры по умолчанию.", "VERSION_DESCRIPTION": "Текущая версия приложения", "EPG_NOTE": "На данный момент EPG доступен только в нативной версии IPTVnator.", @@ -393,10 +399,18 @@ "COPY_STREAM_URL": "Копировать URL-адрес потока", "ERROR_VIEW": { "DELETE": "Удалить", + "IMPORT_CANCELLED": { + "TITLE": "Импорт отменён", + "DESCRIPTION": "Импорт плейлиста был отменён. Повторите попытку, когда захотите снова загрузить этот источник." + }, "ACCOUNT_EXPIRED": { "TITLE": "Срок действия учетной записи истек", "DESCRIPTION": "Срок действия этой учетной записи истек. \nПожалуйста, проверьте учетные данные в настройках плейлиста или удалите его." }, + "ACCOUNT_INACTIVE": { + "TITLE": "Учётная запись неактивна", + "DESCRIPTION": "Эта учётная запись неактивна. Проверьте учётные данные в настройках плейлиста или удалите источник." + }, "HOME": "На главную", "EMPTY_CATEGORY": { "TITLE": "Пустая категория", @@ -410,6 +424,10 @@ "TITLE": "Портал не найден", "DESCRIPTION": "Пожалуйста, проверьте учетные данные и URL портала в настройках плейлиста или удалите его." }, + "PORTAL_UNAVAILABLE": { + "TITLE": "Портал недоступен", + "DESCRIPTION": "Не удалось подключиться к порталу. Проверьте URL сервера или повторите попытку позже." + }, "NO_CATEGORY_SELECTED": { "TITLE": "Категория не выбрана", "DESCRIPTION": "Пожалуйста, выберите категорию для просмотра содержимого" @@ -567,6 +585,7 @@ "RAIL_DASHBOARD": "Панель", "OPEN_DASHBOARD": "Открыть панель", "RAIL_SOURCES": "Источники", + "OPEN_SOURCES": "Открыть источники", "RAIL_SETTINGS": "Настройки", "OPEN_SETTINGS": "Открыть настройки", "RAIL_LIBRARY": "Библиотека", @@ -597,6 +616,7 @@ "NO_SECTION_ACTIONS": "Для этого раздела нет действий", "PLAYLIST_INFO": "Информация о плейлисте", "ACCOUNT_INFO": "Информация об аккаунте", + "REFRESH_PLAYLIST": "Обновить плейлист", "CLEAR_COMPLETED_DOWNLOADS_ALL": "Очистить завершенные загрузки (все плейлисты)", "CLEAR_COMPLETED_DOWNLOADS_ALL_ARIA": "Очистить завершенные загрузки для всех плейлистов", "CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST": "Очистить завершенные загрузки (этот плейлист)", @@ -609,12 +629,18 @@ "NO_SOURCES_AVAILABLE": "Нет доступных источников", "ONE_SOURCE_AVAILABLE": "Доступен 1 источник", "SOURCES_AVAILABLE": "Доступно источников: {{count}}", - "XTREAM_IMPORT_PREPARING": "Подготовка данных плейлиста...", - "XTREAM_IMPORT_SAVING": "Сохранение контента плейлиста...", - "XTREAM_IMPORT_RESTORING_FAVORITES": "Восстановление избранного...", - "XTREAM_IMPORT_RESTORING_RECENT": "Восстановление недавно просмотренного...", - "XTREAM_IMPORT_CANCEL": "Отменить импорт", - "XTREAM_IMPORT_CANCELLING": "Отмена импорта...", + "XTREAM_IMPORT_TITLE": "Синхронизация плейлиста", + "XTREAM_IMPORT_REMOTE_BADGE": "Удалённый источник", + "XTREAM_IMPORT_LOCAL_BADGE": "Локальная библиотека", + "XTREAM_IMPORT_DETAIL_REMOTE": "Ожидание ответа от провайдера.", + "XTREAM_IMPORT_DETAIL_LOCAL": "Подготавливает более быстрые переключения плейлиста при следующем открытии.", + "XTREAM_IMPORT_PREPARING": "Подготовка локальной библиотеки...", + "XTREAM_IMPORT_LOADING": "Получение данных плейлиста из источника...", + "XTREAM_IMPORT_SAVING": "Сохранение данных плейлиста локально...", + "XTREAM_IMPORT_RESTORING_FAVORITES": "Восстановление избранного из локальной библиотеки...", + "XTREAM_IMPORT_RESTORING_RECENT": "Восстановление недавно просмотренного из локальной библиотеки...", + "XTREAM_IMPORT_CANCEL": "Остановить синхронизацию", + "XTREAM_IMPORT_CANCELLING": "Остановка синхронизации...", "COMMANDS": { "GLOBAL_SEARCH_LABEL": "Искать во всех плейлистах Xtream", "GLOBAL_SEARCH_DESCRIPTION": "Открыть глобальный поиск", diff --git a/libs/services/src/lib/settings-store.service.ts b/libs/services/src/lib/settings-store.service.ts index b2320aa59..08c718915 100644 --- a/libs/services/src/lib/settings-store.service.ts +++ b/libs/services/src/lib/settings-store.service.ts @@ -11,6 +11,7 @@ import { firstValueFrom } from 'rxjs'; import { Language, Settings, + StartupBehavior, STORE_KEY, StreamFormat, Theme, @@ -22,6 +23,8 @@ const DEFAULT_SETTINGS: Settings = { streamFormat: StreamFormat.M3u8StreamFormat, language: Language.ENGLISH, showCaptions: false, + showDashboard: true, + startupBehavior: StartupBehavior.FirstView, showExternalPlaybackBar: true, theme: Theme.SystemTheme, mpvPlayerPath: '', @@ -74,6 +77,8 @@ export const SettingsStore = signalStore( streamFormat: store.streamFormat(), language: store.language(), showCaptions: store.showCaptions(), + showDashboard: store.showDashboard(), + startupBehavior: store.startupBehavior(), showExternalPlaybackBar: store.showExternalPlaybackBar(), theme: store.theme(), mpvPlayerPath: store.mpvPlayerPath(), diff --git a/libs/shared/interfaces/src/lib/settings.interface.ts b/libs/shared/interfaces/src/lib/settings.interface.ts index 6078de6bd..78a74fcab 100644 --- a/libs/shared/interfaces/src/lib/settings.interface.ts +++ b/libs/shared/interfaces/src/lib/settings.interface.ts @@ -13,6 +13,11 @@ export enum VideoPlayer { ArtPlayer = 'artplayer', } +export enum StartupBehavior { + FirstView = 'first-view', + RestoreLastView = 'restore-last-view', +} + /** * Describes all available settings options of the application */ @@ -22,6 +27,8 @@ export interface Settings { streamFormat: StreamFormat; language: Language; showCaptions: boolean; + showDashboard: boolean; + startupBehavior: StartupBehavior; /** Show the desktop footer bar for external playback status */ showExternalPlaybackBar?: boolean; theme: Theme;