diff --git a/apps/electron-backend-e2e/src/search.e2e.ts b/apps/electron-backend-e2e/src/search.e2e.ts index 381068e75..285ef9e05 100644 --- a/apps/electron-backend-e2e/src/search.e2e.ts +++ b/apps/electron-backend-e2e/src/search.e2e.ts @@ -580,9 +580,6 @@ test.describe('Electron Workspace Search', () => { xtreamQueries ); await expectWorkspaceSearchScope(app.mainWindow, 'Advanced search'); - await expect( - app.mainWindow.locator('app-search-layout app-search-form') - ).toHaveCount(0); await expect( xtreamSearchResultCards(app.mainWindow).first() ).toBeVisible({ timeout: 20000 }); @@ -901,9 +898,6 @@ test.describe('Electron Workspace Search', () => { /\/workspace\/stalker\/[^/]+\/search$/ ); await expectWorkspaceSearchScope(app.mainWindow, 'Advanced search'); - await expect( - app.mainWindow.locator('app-search-layout app-search-form') - ).toHaveCount(0); await fillWorkspaceSearch(app.mainWindow, sample.targetTitle); diff --git a/apps/web/src/app/app.component.spec.ts b/apps/web/src/app/app.component.spec.ts index 3dc225196..e8e80fb61 100644 --- a/apps/web/src/app/app.component.spec.ts +++ b/apps/web/src/app/app.component.spec.ts @@ -94,7 +94,6 @@ describe('AppComponent', () => { beforeEach(waitForAsync(() => { runtimeCapabilities = { isElectron: true, - isMacOS: false, }; epgBridge = { checkFreshness: jest.fn().mockResolvedValue({ diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 804899aa4..5332fcc7a 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -1,7 +1,6 @@ import { Component, effect, - HostBinding, inject, OnInit, signal, @@ -66,9 +65,6 @@ const debugAppComponent = createDevLogger('AppComponent'); }) export class AppComponent implements OnInit { readonly routeReady = signal(false); - @HostBinding('class.macos-platform') get isMacOS() { - return this.runtime.isMacOS; - } get usesCustomWindowControls() { return this.runtime.usesCustomWindowControls; } diff --git a/apps/web/src/app/app.routes.ts b/apps/web/src/app/app.routes.ts index 123f51372..6a2f87f5d 100644 --- a/apps/web/src/app/app.routes.ts +++ b/apps/web/src/app/app.routes.ts @@ -77,9 +77,6 @@ export const routes: Routes = [ }, { path: 'workspace', - data: { - layout: 'workspace', - }, resolve: { settingsReady: settingsReadyResolver, }, diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 6b6519765..95df4bbd4 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "حفظ", "UNSAVED_DIALOG_DISCARD": "تجاهل", "UNSAVED_DIALOG_INVALID_HINT": "بعض الحقول غير صالحة، لذلك لا يمكن حفظ التغييرات بعد.", - "BACK_TO_HOME": "العودة للرئيسية", "NEW_VERSION_AVAILABLE": "إصدار جديد متاح", "LATEST_VERSION": "أنت تستخدم الإصدار الأحدث", "STREAM_FORMAT": "تنسيق البث", @@ -1278,7 +1277,6 @@ "LIVE_TV": "البث المباشر", "MOVIES": "الأفلام", "SERIES": "المسلسلات", - "SEARCH": "بحث", "NO_FAVORITES": "لا توجد مفضلة بعد", "NO_RECENT_ITEMS": "لا توجد عناصر تمت مشاهدتها مؤخرًا", "REMOVE_FROM_RECENT": "إزالة من العناصر التي تمت مشاهدتها مؤخرًا", diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index b5e819ad5..67212700c 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "حفظ", "UNSAVED_DIALOG_DISCARD": "تجاهل", "UNSAVED_DIALOG_INVALID_HINT": "شي خانات ماشي صحيحين، علاش مايمكنش تسجل التغييرات دابا.", - "BACK_TO_HOME": "رجوع", "NEW_VERSION_AVAILABLE": "كاين إصدار جديد متوفر", "LATEST_VERSION": "راك تستعمل أحدث إصدار", "STREAM_FORMAT": "صيغة البث", @@ -1278,7 +1277,6 @@ "LIVE_TV": "البث المباشر", "MOVIES": "الأفلام", "SERIES": "السلاسل", - "SEARCH": "بحث", "NO_FAVORITES": "مازال ما كاينش مفضلة", "NO_RECENT_ITEMS": "ما كاينش عناصر متشافة مؤخراً", "REMOVE_FROM_RECENT": "حيد من المتشاف مؤخراً", diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 6540fbd6f..daadab7d4 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Захаваць", "UNSAVED_DIALOG_DISCARD": "Не захоўваць", "UNSAVED_DIALOG_INVALID_HINT": "Некаторыя палі запоўнены няправільна, таму змены пакуль нельга захаваць.", - "BACK_TO_HOME": "Назад", "NEW_VERSION_AVAILABLE": "Даступная новая версія праграмы", "LATEST_VERSION": "Вы выкарыстоўваеце апошнюю версію праграмы", "STREAM_FORMAT": "Фармат патоку", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Прамы эфір", "MOVIES": "Фільмы", "SERIES": "Серыялы", - "SEARCH": "Пошук", "NO_FAVORITES": "Пакуль няма выбранага", "NO_RECENT_ITEMS": "Няма нядаўна праглянутых элементаў", "REMOVE_FROM_RECENT": "Выдаліць з нядаўна праглянутага", diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index d12a5ec18..11962d897 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Speichern", "UNSAVED_DIALOG_DISCARD": "Verwerfen", "UNSAVED_DIALOG_INVALID_HINT": "Einige Felder sind ungültig, daher können die Änderungen noch nicht gespeichert werden.", - "BACK_TO_HOME": "Zurück", "NEW_VERSION_AVAILABLE": "EIne neue Version der Anwendung ist verfügbar", "LATEST_VERSION": "Sie nutzen die neueste Version der Anwendung", "STREAM_FORMAT": "Stream-Format", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Live-TV", "MOVIES": "Filme", "SERIES": "Serien", - "SEARCH": "Suche", "NO_FAVORITES": "Noch keine Favoriten", "NO_RECENT_ITEMS": "Keine zuletzt angesehenen Inhalte", "REMOVE_FROM_RECENT": "Aus „Zuletzt angesehen“ entfernen", diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 559940e19..f898d92ed 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Αποθήκευση", "UNSAVED_DIALOG_DISCARD": "Απόρριψη", "UNSAVED_DIALOG_INVALID_HINT": "Ορισμένα πεδία δεν είναι έγκυρα, επομένως οι αλλαγές δεν μπορούν να αποθηκευτούν ακόμη.", - "BACK_TO_HOME": "Πίσω", "NEW_VERSION_AVAILABLE": "Υπάρχει διαθέσιμη μια νέα έκδοση", "LATEST_VERSION": "Χρησιμοποιείτε την πιο πρόσφατη έκδοση", "STREAM_FORMAT": "Μορφή Stream", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Ζωντανή TV", "MOVIES": "Ταινίες", "SERIES": "Σειρές", - "SEARCH": "Αναζήτηση", "NO_FAVORITES": "Δεν υπάρχουν ακόμα αγαπημένα", "NO_RECENT_ITEMS": "Δεν υπάρχουν πρόσφατα προβληθέντα στοιχεία", "REMOVE_FROM_RECENT": "Αφαίρεση από τα πρόσφατα προβληθέντα", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 100427948..084229dc6 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Save", "UNSAVED_DIALOG_DISCARD": "Discard", "UNSAVED_DIALOG_INVALID_HINT": "Some fields are invalid, so the changes cannot be saved yet.", - "BACK_TO_HOME": "Back", "NEW_VERSION_AVAILABLE": "There is a new version available", "LATEST_VERSION": "You are using the latest version", "STREAM_FORMAT": "Stream Format", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Live TV", "MOVIES": "Movies", "SERIES": "Series", - "SEARCH": "Search", "NO_FAVORITES": "No favorites yet", "NO_RECENT_ITEMS": "No recently viewed items", "REMOVE_FROM_RECENT": "Remove from recently viewed", diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index faac6c3c7..53da10327 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Guardar", "UNSAVED_DIALOG_DISCARD": "Descartar", "UNSAVED_DIALOG_INVALID_HINT": "Algunos campos no son válidos, por lo que los cambios aún no se pueden guardar.", - "BACK_TO_HOME": "Volver al inicio", "NEW_VERSION_AVAILABLE": "Hay una nueva versión disponible", "LATEST_VERSION": "Estás usando la versión reciente", "STREAM_FORMAT": "Formato de transmision", @@ -1278,7 +1277,6 @@ "LIVE_TV": "TV en vivo", "MOVIES": "Películas", "SERIES": "Series", - "SEARCH": "Buscar", "NO_FAVORITES": "Aún no hay favoritos", "NO_RECENT_ITEMS": "No hay elementos vistos recientemente", "REMOVE_FROM_RECENT": "Quitar de vistos recientemente", diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index f4e6eb630..6d5697727 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Enregistrer", "UNSAVED_DIALOG_DISCARD": "Ne pas enregistrer", "UNSAVED_DIALOG_INVALID_HINT": "Certains champs sont invalides, les modifications ne peuvent donc pas encore être enregistrées.", - "BACK_TO_HOME": "Retourner à l'accueil", "NEW_VERSION_AVAILABLE": "Une nouvelle version est disponible", "LATEST_VERSION": "Vous utilisez la dernière version", "STREAM_FORMAT": "Format du flux", @@ -1278,7 +1277,6 @@ "LIVE_TV": "TV en direct", "MOVIES": "Films", "SERIES": "Séries", - "SEARCH": "Rechercher", "NO_FAVORITES": "Aucun favori pour le moment", "NO_RECENT_ITEMS": "Aucun élément récemment consulté", "REMOVE_FROM_RECENT": "Retirer des éléments récemment consultés", diff --git a/apps/web/src/assets/i18n/hu.json b/apps/web/src/assets/i18n/hu.json index 0e10bd5cf..303057267 100644 --- a/apps/web/src/assets/i18n/hu.json +++ b/apps/web/src/assets/i18n/hu.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Mentés", "UNSAVED_DIALOG_DISCARD": "Elvetés", "UNSAVED_DIALOG_INVALID_HINT": "Néhány mező érvénytelen, ezért a módosítások még nem menthetők.", - "BACK_TO_HOME": "Vissza", "NEW_VERSION_AVAILABLE": "Új verzió érhető el", "LATEST_VERSION": "A legújabb verziót használja", "STREAM_FORMAT": "Adatfolyam formátuma", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Élő tévéadás", "MOVIES": "Filmek", "SERIES": "Sorozatok", - "SEARCH": "Keresés", "NO_FAVORITES": "Még nincs kedvenc tartalom", "NO_RECENT_ITEMS": "Még nincs megtekintési előzmény", "REMOVE_FROM_RECENT": "Eltávolítás a megtekintési előzményekből", diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index b989b1ff9..665fd12c8 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Salva", "UNSAVED_DIALOG_DISCARD": "Non salvare", "UNSAVED_DIALOG_INVALID_HINT": "Alcuni campi non sono validi, quindi le modifiche non possono ancora essere salvate.", - "BACK_TO_HOME": "Indietro", "NEW_VERSION_AVAILABLE": "È disponibile una nuova versione", "LATEST_VERSION": "Questa versione è aggiornata", "STREAM_FORMAT": "Formato streaming", @@ -1278,7 +1277,6 @@ "LIVE_TV": "TV in diretta", "MOVIES": "Film", "SERIES": "Serie", - "SEARCH": "Cerca", "NO_FAVORITES": "Ancora nessun preferito", "NO_RECENT_ITEMS": "Nessun elemento visto di recente", "REMOVE_FROM_RECENT": "Rimuovi dai visti di recente", diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index 1999c648d..9ed6e3341 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "保存", "UNSAVED_DIALOG_DISCARD": "破棄", "UNSAVED_DIALOG_INVALID_HINT": "一部の項目が無効なため、変更をまだ保存できません。", - "BACK_TO_HOME": "戻る", "NEW_VERSION_AVAILABLE": "新しいバージョンが利用可能です", "LATEST_VERSION": "最新バージョンを使用中です", "STREAM_FORMAT": "ストリーム形式", @@ -1278,7 +1277,6 @@ "LIVE_TV": "ライブTV", "MOVIES": "映画", "SERIES": "シリーズ", - "SEARCH": "検索", "NO_FAVORITES": "まだお気に入りがありません", "NO_RECENT_ITEMS": "最近表示した項目はありません", "REMOVE_FROM_RECENT": "最近表示した項目から削除", diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index 03e6288b1..ea0e77435 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "저장", "UNSAVED_DIALOG_DISCARD": "저장 안 함", "UNSAVED_DIALOG_INVALID_HINT": "일부 항목이 유효하지 않아 변경 사항을 아직 저장할 수 없습니다.", - "BACK_TO_HOME": "홈으로", "NEW_VERSION_AVAILABLE": "새로운 버전이 있습니다", "LATEST_VERSION": "최신 버전을 사용하고 있습니다", "STREAM_FORMAT": "스트림 형식", @@ -1278,7 +1277,6 @@ "LIVE_TV": "라이브 TV", "MOVIES": "영화", "SERIES": "시리즈", - "SEARCH": "검색", "NO_FAVORITES": "아직 즐겨찾기가 없습니다", "NO_RECENT_ITEMS": "최근 본 항목이 없습니다", "REMOVE_FROM_RECENT": "최근 본 항목에서 제거", diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index 69f8d27c5..3b8fc4959 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Opslaan", "UNSAVED_DIALOG_DISCARD": "Verwerpen", "UNSAVED_DIALOG_INVALID_HINT": "Sommige velden zijn ongeldig, dus de wijzigingen kunnen nog niet worden opgeslagen.", - "BACK_TO_HOME": "Terug", "NEW_VERSION_AVAILABLE": "Een nieuwe versie is beschikbaar", "LATEST_VERSION": "Je gebruikt de nieuwste versie", "STREAM_FORMAT": "Streamformaat", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Live tv", "MOVIES": "Films", "SERIES": "Series", - "SEARCH": "Zoeken", "NO_FAVORITES": "Nog geen favorieten", "NO_RECENT_ITEMS": "Geen recent bekeken items", "REMOVE_FROM_RECENT": "Verwijderen uit recent bekeken", diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index c3eaf6069..f05786109 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Zapisz", "UNSAVED_DIALOG_DISCARD": "Odrzuć", "UNSAVED_DIALOG_INVALID_HINT": "Niektóre pola są nieprawidłowe, więc zmian nie można jeszcze zapisać.", - "BACK_TO_HOME": "Wróć", "NEW_VERSION_AVAILABLE": "Dostępna jest nowa wersja", "LATEST_VERSION": "Używasz najnowszej wersji", "STREAM_FORMAT": "Format strumienia", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Telewizja na żywo", "MOVIES": "Filmy", "SERIES": "Seriale", - "SEARCH": "Szukaj", "NO_FAVORITES": "Brak ulubionych", "NO_RECENT_ITEMS": "Brak ostatnio oglądanych", "REMOVE_FROM_RECENT": "Usuń z ostatnio oglądanych", diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index ab15f4548..e0e2e43a4 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Salvar", "UNSAVED_DIALOG_DISCARD": "Descartar", "UNSAVED_DIALOG_INVALID_HINT": "Alguns campos são inválidos, então as alterações ainda não podem ser salvas.", - "BACK_TO_HOME": "Voltar", "NEW_VERSION_AVAILABLE": "Há uma nova versão disponível", "LATEST_VERSION": "Você está usando a versão mais recente", "STREAM_FORMAT": "Formato de stream", @@ -1278,7 +1277,6 @@ "LIVE_TV": "TV ao vivo", "MOVIES": "Filmes", "SERIES": "Séries", - "SEARCH": "Pesquisar", "NO_FAVORITES": "Nenhum favorito ainda", "NO_RECENT_ITEMS": "Nenhum item visualizado recentemente", "REMOVE_FROM_RECENT": "Remover dos visualizados recentemente", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index 91867c5de..9da7e722b 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Сохранить", "UNSAVED_DIALOG_DISCARD": "Не сохранять", "UNSAVED_DIALOG_INVALID_HINT": "Некоторые поля заполнены неверно, поэтому изменения пока нельзя сохранить.", - "BACK_TO_HOME": "Назад", "NEW_VERSION_AVAILABLE": "Доступна новая версия приложения", "LATEST_VERSION": "Вы используете последнюю версию приложения", "STREAM_FORMAT": "Формат потока", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Live TV", "MOVIES": "Фильмы", "SERIES": "Сериалы", - "SEARCH": "Поиск", "NO_FAVORITES": "Пока нет избранного", "NO_RECENT_ITEMS": "Нет недавно просмотренных элементов", "REMOVE_FROM_RECENT": "Удалить из недавно просмотренных", diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index f54f32a7d..f207fa817 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "Kaydet", "UNSAVED_DIALOG_DISCARD": "Kaydetme", "UNSAVED_DIALOG_INVALID_HINT": "Bazı alanlar geçersiz olduğundan değişiklikler henüz kaydedilemiyor.", - "BACK_TO_HOME": "Ana sayfaya dön", "NEW_VERSION_AVAILABLE": "Yeni bir sürüm mevcut", "LATEST_VERSION": "En son sürümü kullanıyorsunuz", "STREAM_FORMAT": "Yayın Formatı", @@ -1278,7 +1277,6 @@ "LIVE_TV": "Canlı TV", "MOVIES": "Filmler", "SERIES": "Diziler", - "SEARCH": "Ara", "NO_FAVORITES": "Henüz favori yok", "NO_RECENT_ITEMS": "Son izlenen öğe yok", "REMOVE_FROM_RECENT": "Son izlenenlerden kaldır", diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index 9318a271e..5391ff5d0 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "保存", "UNSAVED_DIALOG_DISCARD": "放弃", "UNSAVED_DIALOG_INVALID_HINT": "部分字段无效,因此暂时无法保存更改。", - "BACK_TO_HOME": "回到主页", "NEW_VERSION_AVAILABLE": "有新版本可用 ", "LATEST_VERSION": "您使用的是最新版本", "STREAM_FORMAT": "流格式", @@ -1278,7 +1277,6 @@ "LIVE_TV": "直播电视", "MOVIES": "电影", "SERIES": "剧集", - "SEARCH": "搜索", "NO_FAVORITES": "暂无收藏", "NO_RECENT_ITEMS": "没有最近观看的项目", "REMOVE_FROM_RECENT": "从最近观看中移除", diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index 4e34981d6..b09af6277 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -394,7 +394,6 @@ "UNSAVED_DIALOG_SAVE": "儲存", "UNSAVED_DIALOG_DISCARD": "捨棄", "UNSAVED_DIALOG_INVALID_HINT": "部分欄位無效,因此暫時無法儲存變更。", - "BACK_TO_HOME": "返回主頁", "NEW_VERSION_AVAILABLE": "有新版本可用", "LATEST_VERSION": "您正在使用最新版本", "STREAM_FORMAT": "串流格式", @@ -1278,7 +1277,6 @@ "LIVE_TV": "直播電視", "MOVIES": "電影", "SERIES": "影集", - "SEARCH": "搜尋", "NO_FAVORITES": "尚無最愛項目", "NO_RECENT_ITEMS": "沒有最近觀看的項目", "REMOVE_FROM_RECENT": "從最近觀看中移除", diff --git a/apps/web/src/styles.scss b/apps/web/src/styles.scss index ffe72b68c..e2a6d22dc 100644 --- a/apps/web/src/styles.scss +++ b/apps/web/src/styles.scss @@ -102,10 +102,6 @@ letter-spacing: 0; } -app-root.macos-platform .current-playlist { - padding-top: 30px; -} - // Windows/Linux frameless title bar: reserve room in top-aligned drag // regions for the renderer-drawn window controls (3 × 46px buttons). // Body-level class so it also reaches cdk-overlay content (dialogs, menus). diff --git a/docs/architecture/workspace-shell.md b/docs/architecture/workspace-shell.md index 5566f9659..a161a3eee 100644 --- a/docs/architecture/workspace-shell.md +++ b/docs/architecture/workspace-shell.md @@ -65,19 +65,19 @@ Compatibility redirect: Provider route integration: -1. `apps/web/src/app/app.routes.ts` marks the `/workspace` root route with - `data.layout = 'workspace'`. -2. `isWorkspaceLayoutRoute(...)` treats that layout marker as inherited route - state for all descendants. -3. Xtream and Stalker parent routes attach route-scoped session providers that +1. Every routed view renders inside the workspace shell (the only top-level + paths outside `/workspace` are redirects), so views keep no standalone + layout branches: the shell header owns search, Back and the playlist + switcher. +2. Xtream and Stalker parent routes attach route-scoped session providers that bootstrap the active playlist, sync provider section state, and clean up provider-local state when the route is destroyed. -4. Xtream route bootstrap is DB-first for already imported Electron playlists: +3. Xtream route bootstrap is DB-first for already imported Electron playlists: if the requested section has persisted categories and content, the route hydrates from SQLite even when the portal status probe reports unavailable, expired, or inactive. Fresh/no-cache Xtream routes still use the status probe to block remote imports before the loading overlay starts. -5. Workspace routes no longer rely on nested provider shell components for +4. Workspace routes no longer rely on nested provider shell components for hidden local chrome. ## Shell Structure diff --git a/docs/architecture/zoneless-migration.md b/docs/architecture/zoneless-migration.md index 7af3e090c..a5eddb7c6 100644 --- a/docs/architecture/zoneless-migration.md +++ b/docs/architecture/zoneless-migration.md @@ -149,7 +149,6 @@ picker), so each marks itself on the form's `events` through ### libs/portal (8) - [x] `libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.ts` -- [x] `libs/portal/shared/ui/src/lib/components/search-form/search-form.component.ts` - [x] `libs/portal/shared/ui/src/lib/navigation/portal-rail-links.component.ts` - [x] `libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-catalog-detail.component.ts` - [x] `libs/portal/stalker/feature/src/lib/stalker-favorites-button/stalker-favorites-button.component.ts` diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-catchup-timeline.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-catchup-timeline.spec.ts index c169b5924..a4f9fc4c2 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-catchup-timeline.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-catchup-timeline.spec.ts @@ -116,7 +116,6 @@ describe('VideoPlayerComponent — catch-up timeline segments', () => { params: of({ id: playlistId(), view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-guide-gating.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-guide-gating.spec.ts index 8cab98b70..95d93298c 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-guide-gating.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-guide-gating.spec.ts @@ -100,7 +100,6 @@ describe('VideoPlayerComponent — guide gating', () => { params: of({ id: playlistId(), view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-movie-gate.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-movie-gate.spec.ts index 0e3e900c3..2ad11b09f 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-movie-gate.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-movie-gate.spec.ts @@ -153,7 +153,6 @@ describe('VideoPlayerComponent — M3U movie recognition gate', () => { params: of({ id: playlistId(), view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-recent-history.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-recent-history.spec.ts index 889b9f0ed..8cccecc35 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-recent-history.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-recent-history.spec.ts @@ -91,7 +91,6 @@ describe('VideoPlayerComponent — recently viewed history', () => { params: of({ id: playlistId(), view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-sidebar.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-sidebar.spec.ts index 8743ea02a..a93e9acea 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-sidebar.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player-sidebar.spec.ts @@ -171,7 +171,6 @@ describe('VideoPlayerComponent — collapsible channels rail', () => { params: routeParams$.asObservable(), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts index 72af02b4e..440cd0e54 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts @@ -144,7 +144,6 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => { params: of({ id: 'playlist-1', view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html index 274f29958..0a136aac4 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html @@ -36,7 +36,6 @@ { params: of({ id: playlistId(), view: 'all' }), queryParams: of({}), snapshot: { - data: { layout: 'workspace' }, queryParams: {}, }, }, diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts index 592f5bd55..c90ed40c0 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts @@ -87,7 +87,6 @@ import { getAdjacentChannelItem, getChannelItemByNumber, isTypingInInput, - isWorkspaceLayoutRoute, LiveEpgPanelState, LiveLayoutSidebarStateService, persistLiveEpgPanelState, @@ -719,7 +718,6 @@ export class VideoPlayerComponent readonly isDesktop = this.runtime.isElectron; readonly supportsEpg = this.runtime.supportsEpg; - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.activatedRoute); private unsubscribeRemoteChannelChange?: () => void; private unsubscribeRemoteCommand?: () => void; @@ -1750,7 +1748,7 @@ export class VideoPlayerComponent } private registerHeaderShortcut(): void { - if (!this.isWorkspaceLayout || !this.supportsEpg) { + if (!this.supportsEpg) { return; } diff --git a/libs/portal/shared/ui/src/index.ts b/libs/portal/shared/ui/src/index.ts index f99f0849d..d7fda96ed 100644 --- a/libs/portal/shared/ui/src/index.ts +++ b/libs/portal/shared/ui/src/index.ts @@ -9,7 +9,6 @@ export * from './lib/components/playlist-error-view/playlist-error-view.componen export { ProgressCapsuleComponent } from '@iptvnator/ui/components'; export * from './lib/components/portal-empty-state/portal-empty-state.component'; export * from './lib/components/channel-list-hidden-state/channel-list-hidden-state.component'; -export * from './lib/components/search-form/search-form.component'; export * from './lib/components/search-layout/search-layout.component'; export { WatchedBadgeComponent } from '@iptvnator/ui/components'; export * from './lib/components/unified-collection/unified-collection-page.component'; diff --git a/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.html b/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.html deleted file mode 100644 index 460295174..000000000 --- a/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.html +++ /dev/null @@ -1,42 +0,0 @@ -
- - - @if (filterConfig.length > 0) { -
- @for (filter of filterConfig; track filter.key) { - - @if (filter.translationKey) { - {{ filter.translationKey | translate }} - } @else { - {{ filter.label }} - } - - } -
- } -
diff --git a/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.ts b/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.ts deleted file mode 100644 index d9c9f114f..000000000 --- a/libs/portal/shared/ui/src/lib/components/search-form/search-form.component.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - Component, - ElementRef, - EventEmitter, - Input, - model, - Output, - ViewChild, - ChangeDetectionStrategy, -} from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatButtonModule } from '@angular/material/button'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { TranslatePipe } from '@ngx-translate/core'; - -export interface SearchFilters { - [key: string]: boolean; -} - -export interface SearchFilter { - key: string; - label: string; - translationKey?: string; -} - -@Component({ - selector: 'app-search-form', - standalone: true, - imports: [ - FormsModule, - MatFormFieldModule, - MatInputModule, - MatIconModule, - MatCheckboxModule, - MatButtonModule, - TranslatePipe, - ], - templateUrl: './search-form.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - styles: [ - ` - .search-container { - display: flex; - flex-direction: column; - gap: 16px; - } - - .recent-search { - display: flex; - align-items: center; - gap: 10px; - padding: 0 12px; - height: 42px; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); - } - - .recent-search mat-icon { - opacity: 0.6; - font-size: 18px; - width: 18px; - height: 18px; - } - - .recent-search input { - width: 100%; - border: 0; - background: transparent; - color: inherit; - font: inherit; - opacity: 0.95; - } - - .recent-search input::placeholder { - opacity: 0.5; - } - - .clear-btn { - width: 30px; - height: 30px; - min-width: 30px; - } - `, - ], -}) -export class SearchFormComponent { - @ViewChild('searchInput') searchInput!: ElementRef; - /** Empty means the generic "Search" label */ - @Input() placeholder = ''; - @Input() filters: SearchFilters = {}; - @Input() filterConfig: SearchFilter[] = []; - @Input() singleSelection = false; // Add this new input - searchTerm = model(''); - - @Output() filtersChange = new EventEmitter(); - @Output() search = new EventEmitter(); - - onFilterChange(changedKey: string) { - if (this.singleSelection) { - // Reset all other filters when in single selection mode - Object.keys(this.filters).forEach((key) => { - if (key !== changedKey) { - this.filters[key] = false; - } - }); - } - this.filtersChange.emit(this.filters); - this.search.emit(); - } - - onSearchTermChange() { - setTimeout(() => { - this.searchTerm.set(this.searchInput.nativeElement.value); - }, 500); - } - - clearSearch() { - this.searchInput.nativeElement.value = ''; - this.searchTerm.set(''); - } - - focusSearchInput() { - this.searchInput.nativeElement.focus(); - } -} diff --git a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.html b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.html index 55efad6b2..51bc15fe3 100644 --- a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.html +++ b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.html @@ -24,30 +24,11 @@ }} } - @if (showCloseButton()) { - - } - @if (showSearchInput()) { -
- -
- } - diff --git a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.scss b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.scss index 5d7d7600f..5dcc22b74 100644 --- a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.scss +++ b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.scss @@ -84,10 +84,6 @@ align-items: center; } } - - .search-input-container { - margin-bottom: 0; - } } .results-container { diff --git a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.ts b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.ts index da6e2d23e..9e3152459 100644 --- a/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.ts +++ b/libs/portal/shared/ui/src/lib/components/search-layout/search-layout.component.ts @@ -6,13 +6,11 @@ import { output, viewChild, } from '@angular/core'; -import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { TranslatePipe } from '@ngx-translate/core'; import { registerWorkspaceBack } from '@iptvnator/portal/shared/data-access'; import { InfiniteScrollDirective } from '../../directives/infinite-scroll.directive'; -import { SearchFormComponent } from '../search-form/search-form.component'; @Component({ selector: 'app-search-layout', @@ -20,9 +18,7 @@ import { SearchFormComponent } from '../search-form/search-form.component'; imports: [ InfiniteScrollDirective, MatIcon, - MatIconButton, MatProgressSpinner, - SearchFormComponent, TranslatePipe, ], templateUrl: './search-layout.component.html', @@ -30,7 +26,6 @@ import { SearchFormComponent } from '../search-form/search-form.component'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SearchLayoutComponent { - private readonly searchFormComponent = viewChild(SearchFormComponent); private readonly resultsContainer = viewChild>('resultsContainer'); @@ -46,9 +41,6 @@ export class SearchLayoutComponent { /** Whether search is in progress */ readonly isLoading = input(false); - /** Whether to show the close button (for dialog mode) */ - readonly showCloseButton = input(false); - /** * Whether the page offers Back (a nested search reached via navigation). * It is the workspace header's Back, registered while the results show; @@ -59,9 +51,6 @@ export class SearchLayoutComponent { /** Whether to show results count */ readonly showResultsCount = input(true); - /** Whether to show the inline search input */ - readonly showSearchInput = input(true); - /** Whether inline details are active and should replace the search body */ readonly showDetails = input(false); @@ -105,12 +94,6 @@ export class SearchLayoutComponent { 'PORTALS.SEARCH_VIEW.INITIAL_DESCRIPTION' ); - /** Emitted when search term changes */ - readonly searchTermChange = output(); - - /** Emitted when close button is clicked */ - readonly closeClick = output(); - /** Emitted by the header's Back while `backAvailable` is set */ readonly backClick = output(); @@ -128,11 +111,6 @@ export class SearchLayoutComponent { }); } - /** Focus the search input */ - focusSearchInput(): void { - this.searchFormComponent()?.focusSearchInput(); - } - /** * Scroll handoff for hosts whose inline detail replaces the results * (`showDetails`): the container is destroyed with the detail open and @@ -146,14 +124,6 @@ export class SearchLayoutComponent { this.resultsContainer()?.nativeElement.scrollTo?.({ top: scrollTop }); } - onSearchTermChange(term: string): void { - this.searchTermChange.emit(term); - } - - onCloseClick(): void { - this.closeClick.emit(); - } - /** Check if we should show the "no results" state */ get showNoResults(): boolean { return ( diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-clear-action.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-clear-action.ts index d32f8ce18..1075dc705 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-clear-action.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-clear-action.ts @@ -84,8 +84,6 @@ export function createClearCollectionAction(options: { } export interface ClearCollectionViewCommandHost { - /** Only the workspace shell renders a command palette to register into. */ - isWorkspaceLayout: boolean; mode: Signal; selectedContentType: Signal; /** Rows the command would clear; with none there is nothing to offer. */ @@ -110,7 +108,7 @@ export function setupClearCollectionViewCommand( }); effect((onCleanup) => { - if (!host.isWorkspaceLayout || host.currentTypeItems().length === 0) { + if (host.currentTypeItems().length === 0) { return; } diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.spec.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.spec.ts index 7892ed0f2..efdd4c2a8 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.spec.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.spec.ts @@ -203,7 +203,6 @@ describe('UnifiedCollectionPageComponent', () => { const workspaceRoute = { snapshot: { - data: { layout: 'workspace' }, paramMap: convertToParamMap({}), params: {}, }, @@ -672,7 +671,6 @@ describe('UnifiedCollectionPageComponent', () => { it('registers a workspace command when the current view has actionable items', async () => { fixture.detectChanges(); await fixture.whenStable(); - expect(fixture.componentInstance.isWorkspaceLayout).toBe(true); fixture.componentInstance.allItems.set([ { diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.ts index 919f42b0d..cbbbdc259 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-collection-page.component.ts @@ -31,7 +31,6 @@ import { CollectionScope, FavoritesChannelSortMode, getOpenLiveCollectionItemState, - isWorkspaceLayoutRoute, isTypingInInput, LiveLayoutSidebarStateService, OPEN_LIVE_COLLECTION_ITEM_STATE_KEY, @@ -110,11 +109,8 @@ export class UnifiedCollectionPageComponent implements AfterContentInit { LiveLayoutSidebarStateService ); readonly detailTemplate = contentChild(UnifiedCollectionDetailDirective); - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.route); - private readonly routeSearchTerm = queryParamSignal( - this.route, - 'q', - (value) => (value ?? '').trim() + readonly workspaceSearchTerm = queryParamSignal(this.route, 'q', (value) => + (value ?? '').trim() ); private readonly routePlaylistId = routeParamSignal( this.route, @@ -124,9 +120,6 @@ export class UnifiedCollectionPageComponent implements AfterContentInit { readonly playlistId = computed( () => this.playlistIdInput() ?? this.routePlaylistId() ); - readonly workspaceSearchTerm = computed(() => - this.isWorkspaceLayout ? this.routeSearchTerm() : '' - ); private readonly viewStateHistory = new CollectionViewStateHistory(); readonly isLoading = this.data.isLoading; @@ -247,7 +240,6 @@ export class UnifiedCollectionPageComponent implements AfterContentInit { constructor() { setupClearCollectionViewCommand({ - isWorkspaceLayout: this.isWorkspaceLayout, mode: this.mode, selectedContentType: this.selectedContentType, currentTypeItems: this.currentTypeItems, diff --git a/libs/portal/shared/util/src/lib/navigation/portal-route.utils.spec.ts b/libs/portal/shared/util/src/lib/navigation/portal-route.utils.spec.ts index 4ff19f141..17166b18a 100644 --- a/libs/portal/shared/util/src/lib/navigation/portal-route.utils.spec.ts +++ b/libs/portal/shared/util/src/lib/navigation/portal-route.utils.spec.ts @@ -17,7 +17,6 @@ import { BehaviorSubject } from 'rxjs'; import { extractPortalPlaylistId, extractPortalSection, - isWorkspaceLayoutRoute, queryParamSignal, routeParamSignal, resolveCurrentPortalPlaylistId, @@ -26,48 +25,6 @@ import { } from './portal-route.utils'; describe('portal-route.utils', () => { - it('detects workspace layout from route data', () => { - const route = { - snapshot: { - data: { - layout: 'workspace', - }, - }, - } as unknown as ActivatedRoute; - - expect(isWorkspaceLayoutRoute(route)).toBe(true); - }); - - it('detects workspace layout from ancestor route data', () => { - const parentRoute = { - snapshot: { - data: { - layout: 'workspace', - }, - }, - } as unknown as ActivatedRoute; - const childRoute = { - snapshot: { - data: {}, - }, - pathFromRoot: [ - { - snapshot: { - data: {}, - }, - }, - parentRoute, - { - snapshot: { - data: {}, - }, - }, - ], - } as unknown as ActivatedRoute; - - expect(isWorkspaceLayoutRoute(childRoute)).toBe(true); - }); - it('extracts Xtream sections from workspace and local URLs', () => { expect(extractPortalSection('/xtreams/123/search', 'xtreams')).toBe( 'search' diff --git a/libs/portal/shared/util/src/lib/navigation/portal-route.utils.ts b/libs/portal/shared/util/src/lib/navigation/portal-route.utils.ts index 050baee42..c679d1eb2 100644 --- a/libs/portal/shared/util/src/lib/navigation/portal-route.utils.ts +++ b/libs/portal/shared/util/src/lib/navigation/portal-route.utils.ts @@ -25,17 +25,6 @@ const PORTAL_SECTIONS = new Set([ 'vod', ]); -export function isWorkspaceLayoutRoute(route: ActivatedRoute): boolean { - const routeChain = - Array.isArray(route.pathFromRoot) && route.pathFromRoot.length > 0 - ? route.pathFromRoot - : [route]; - - return routeChain.some( - (currentRoute) => currentRoute.snapshot.data['layout'] === 'workspace' - ); -} - export function queryParamSignal( route: ActivatedRoute, key: string, diff --git a/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.html b/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.html index 45b3fe7bd..0c3db9387 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.html +++ b/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.html @@ -3,14 +3,12 @@ [resultsCount]="resultsCount" [isLoading]="isInitialSearchLoading()" [showResultsCount]="true" - [showSearchInput]="!isWorkspaceLayout" - [backAvailable]="isWorkspaceLayout" + [backAvailable]="true" [showDetails]="showingDetails" [nearEndHasMore]="searchHasMore()" [nearEndAppending]="isAppendingSearchResults()" [nearEndRenderedCount]="resultsCount" [nearEndResetKey]="searchScrollResetKey()" - (searchTermChange)="updateSearchTerm($event)" (backClick)="goBack()" (nearEnd)="loadMoreSearchResults()" > diff --git a/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts b/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts index ef4c207a6..b78fdaa5c 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts @@ -37,7 +37,6 @@ import { StalkerStore } from '@iptvnator/portal/stalker/data-access'; import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util'; import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access'; import { - isWorkspaceLayoutRoute, PORTAL_EXTERNAL_PLAYBACK, PORTAL_PLAYBACK_POSITIONS, PORTAL_PLAYER, @@ -117,8 +116,6 @@ export class StalkerSearchComponent { series: false, vod: true, }); - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.activatedRoute); - readonly filterConfig: StalkerFilter[] = [ { key: 'vod', @@ -279,10 +276,6 @@ export class StalkerSearchComponent { return this.searchResults().length; } - updateSearchTerm(term: string) { - this.searchTerm.set(term); - } - updateFilter(key: StalkerSearchContentType, value: boolean) { if (value) { // Single selection mode - set clicked filter, disable others diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-auto-open-queue.component.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-auto-open-queue.component.spec.ts index de016edda..15544dcc7 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-auto-open-queue.component.spec.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-auto-open-queue.component.spec.ts @@ -114,9 +114,6 @@ describe('Xtream live auto-open playback queue', () => { queryParamMap: convertToParamMap({}), }, queryParamMap: query, - pathFromRoot: [ - { snapshot: { data: { layout: 'workspace' } } }, - ], }, }, { provide: Router, useValue: { events, navigate: jest.fn() } }, diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.catchup-timeline.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.catchup-timeline.spec.ts index 12c69cdcf..1ca35d793 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.catchup-timeline.spec.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.catchup-timeline.spec.ts @@ -128,9 +128,6 @@ describe('LiveStreamLayoutComponent catch-up timeline segments', () => { queryParamMap: convertToParamMap({}), }, queryParamMap: of(convertToParamMap({})), - pathFromRoot: [ - { snapshot: { data: { layout: 'workspace' } } }, - ], }, }, { diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts index c65ae9ba5..036c281a7 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.spec.ts @@ -206,13 +206,6 @@ describe('LiveStreamLayoutComponent', () => { queryParamMap: convertToParamMap({}), }, queryParamMap: routeQueryParamMap.asObservable(), - pathFromRoot: [ - { - snapshot: { - data: { layout: 'workspace' }, - }, - }, - ], }, }, { diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.ts index 00431fcac..e007cc59f 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.ts @@ -47,7 +47,6 @@ import { getAdjacentChannelItem, getChannelItemByNumber, isTypingInInput, - isWorkspaceLayoutRoute, LiveEpgPanelState, persistLiveEpgPanelState, persistPortalChannelSortMode, @@ -216,14 +215,8 @@ export class LiveStreamLayoutComponent ); readonly isElectron = this.runtime.isElectron; readonly supportsEpg = this.runtime.supportsEpg; - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.route); - private readonly routeSearchTerm = queryParamSignal( - this.route, - 'q', - (value) => (value ?? '').trim() - ); - readonly workspaceSearchTerm = computed(() => - this.isWorkspaceLayout ? this.routeSearchTerm() : '' + readonly workspaceSearchTerm = queryParamSignal(this.route, 'q', (value) => + (value ?? '').trim() ); readonly showLiveChannelSidebar = computed( () => !!this.selectedCategoryId() || !!this.workspaceSearchTerm() diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.parental-lock.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.parental-lock.spec.ts index 2c10375fc..a58a2b13f 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.parental-lock.spec.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.parental-lock.spec.ts @@ -113,9 +113,6 @@ describe('LiveStreamLayoutComponent parental lock', () => { queryParamMap: convertToParamMap({}), }, queryParamMap: of(convertToParamMap({})), - pathFromRoot: [ - { snapshot: { data: { layout: 'workspace' } } }, - ], }, }, { diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.sidebar-levels.spec.ts b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.sidebar-levels.spec.ts index 26f3b060c..55690b6ca 100644 --- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.sidebar-levels.spec.ts +++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.sidebar-levels.spec.ts @@ -159,9 +159,6 @@ describe('LiveStreamLayoutComponent sidebar levels', () => { queryParamMap: convertToParamMap({}), }, queryParamMap: of(convertToParamMap({})), - pathFromRoot: [ - { snapshot: { data: { layout: 'workspace' } } }, - ], }, }, { diff --git a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.html b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.html index 24b0bcb1b..e7bc86f8f 100644 --- a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.html +++ b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.html @@ -2,18 +2,14 @@ [searchTerm]="searchTerm()" [resultsCount]="xtreamStore.searchResults().length" [isLoading]="xtreamStore.isSearching()" - [showCloseButton]="false" [backAvailable]="backAvailable" [showResultsCount]="!isGlobalSearch || !groupByPlaylist()" - [showSearchInput]="showInlineSearchInput" [minSearchLength]="minSearchLength" [initialDescriptionKey]="initialDescriptionKey" [nearEndHasMore]="hasMoreSearchResults()" [nearEndAppending]="isAppendingSearchResults()" [nearEndRenderedCount]="renderedSearchResultsCount()" [nearEndResetKey]="searchResetIdentity()" - (searchTermChange)="updateSearchTerm($event)" - (closeClick)="onCloseDialog()" (backClick)="goBack()" (nearEnd)="onResultsNearEnd()" > @@ -40,16 +36,6 @@ {{ 'PORTALS.SEARCH_VIEW.VISIBLE_CATEGORIES_ONLY' | translate }} } - @if (showGlobalCloseButton) { - - } diff --git a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.spec.ts b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.spec.ts index 0b08aacc2..5cd30c85a 100644 --- a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.spec.ts +++ b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.spec.ts @@ -1,6 +1,5 @@ import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'; import { of } from 'rxjs'; import { DatabaseService } from '@iptvnator/services'; @@ -17,10 +16,6 @@ jest.mock('@iptvnator/portal/shared/ui', () => ({ SearchLayoutComponent: class {}, })); -/** The component is opened as a route, not a dialog: `@Optional()` resolves nothing. */ -const NO_DIALOG_REF = - undefined as unknown as MatDialogRef; - const DEFAULT_SEARCH_FILTERS: SearchFilters = { live: true, movie: true, @@ -78,7 +73,7 @@ class MockXtreamStore { setSelectedContentType = jest.fn(); } -describe('SearchResultsComponent initialQuery contract', () => { +describe('SearchResultsComponent global search', () => { let routerNavigateMock: jest.Mock; beforeEach(() => { @@ -102,7 +97,6 @@ describe('SearchResultsComponent initialQuery contract', () => { snapshot: { data: { isGlobalSearch: true, - layout: 'workspace', }, queryParamMap: convertToParamMap({ q: '' }), }, @@ -119,60 +113,24 @@ describe('SearchResultsComponent initialQuery contract', () => { }); }); - it('applies initialQuery when opened as global search', () => { - const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - initialQuery: 'matrix', - }, - NO_DIALOG_REF - ) - ); - - const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; - expect(store.setSearchTerm).toHaveBeenCalledWith('matrix'); - expect(component.searchTerm()).toBe('matrix'); - }); - - it('does not force search term when initialQuery is not provided', () => { - TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) - ); - - const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; - expect(store.setSearchTerm).not.toHaveBeenCalled(); - }); - it('clears the workspace global search term when the route q param is empty', () => { const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; store.searchTerm.set('matrix'); const component = TestBed.runInInjectionContext( - () => new SearchResultsComponent(null, NO_DIALOG_REF) + () => new SearchResultsComponent() ); TestBed.flushEffects(); expect(store.setSearchTerm).toHaveBeenCalledWith(''); expect(component.searchTerm()).toBe(''); + expect(component.isGlobalSearch).toBe(true); + expect(component.backAvailable).toBe(false); }); it('navigates global search selection to workspace xtream route', () => { const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); component.selectItem( @@ -191,13 +149,7 @@ describe('SearchResultsComponent initialQuery contract', () => { it('navigates M3U global search selection to the M3U player with route state', () => { const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const channel = { id: 'channel-news', @@ -262,13 +214,7 @@ describe('SearchResultsComponent initialQuery contract', () => { .mockResolvedValueOnce(freshResults); const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; @@ -309,13 +255,7 @@ describe('SearchResultsComponent initialQuery contract', () => { .mockResolvedValueOnce(secondPage); const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; @@ -362,13 +302,7 @@ describe('SearchResultsComponent initialQuery contract', () => { .mockReturnValueOnce(freshSearch.promise); const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; store.searchResults.set([createSearchItem()]); @@ -409,13 +343,7 @@ describe('SearchResultsComponent initialQuery contract', () => { databaseService.globalSearchContent.mockResolvedValueOnce(nextPage); const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; store.searchResults.set([createSearchItem({ id: 1, xtream_id: 1 })]); @@ -465,13 +393,7 @@ describe('SearchResultsComponent initialQuery contract', () => { databaseService.globalSearchContent.mockResolvedValueOnce(firstPage); const component = TestBed.runInInjectionContext( - () => - new SearchResultsComponent( - { - isGlobalSearch: true, - }, - NO_DIALOG_REF - ) + () => new SearchResultsComponent() ); const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; @@ -505,7 +427,7 @@ describe('SearchResultsComponent in-portal result window', () => { provide: ActivatedRoute, useValue: { snapshot: { - data: { layout: 'workspace' }, + data: {}, queryParamMap: convertToParamMap({ q: '' }), }, queryParamMap: of(convertToParamMap({ q: '' })), @@ -524,7 +446,7 @@ describe('SearchResultsComponent in-portal result window', () => { it('windows the full result set, reveals further chunks, and resets on a new result set', () => { const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; const component = TestBed.runInInjectionContext( - () => new SearchResultsComponent(null, NO_DIALOG_REF) + () => new SearchResultsComponent() ); const items = Array.from({ length: 130 }, (_, index) => createSearchItem({ @@ -536,6 +458,7 @@ describe('SearchResultsComponent in-portal result window', () => { store.searchResults.set(items); expect(component.isGlobalSearch).toBe(false); + expect(component.backAvailable).toBe(true); expect(component.visibleInPortalResults()).toHaveLength(60); expect(component.renderedSearchResultsCount()).toBe(60); expect(component.hasMoreSearchResults()).toBe(true); @@ -563,7 +486,7 @@ describe('SearchResultsComponent in-portal result window', () => { it('changes the reset identity on filter-only transitions', () => { const store = TestBed.inject(XtreamStore) as unknown as MockXtreamStore; const component = TestBed.runInInjectionContext( - () => new SearchResultsComponent(null, NO_DIALOG_REF) + () => new SearchResultsComponent() ); const initialIdentity = component.searchResetIdentity(); diff --git a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.ts b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.ts index 737eee026..23299dfe5 100644 --- a/libs/portal/xtream/feature/src/lib/search-results/search-results.component.ts +++ b/libs/portal/xtream/feature/src/lib/search-results/search-results.component.ts @@ -5,20 +5,11 @@ import { computed, effect, inject, - Inject, linkedSignal, - Optional, signal, - viewChild, } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { MatIconButton } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; import { MatIcon } from '@angular/material/icon'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { ActivatedRoute, Router } from '@angular/router'; @@ -29,7 +20,6 @@ import { SearchLayoutComponent } from '@iptvnator/portal/shared/ui'; import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access'; import { buildXtreamNavigationTarget, - isWorkspaceLayoutRoute, queryParamSignal, workspacePortalCommands, } from '@iptvnator/portal/shared/util'; @@ -45,11 +35,6 @@ import { isM3uGlobalSearchResult, } from '@iptvnator/shared/interfaces'; -interface SearchResultsData { - isGlobalSearch: boolean; - initialQuery?: string; -} - interface GlobalSearchResultGroup { playlistId: string; playlistName: string; @@ -92,9 +77,7 @@ function groupResultsByPlaylistId( ContentCardComponent, FormsModule, MatCheckboxModule, - MatDialogModule, MatIcon, - MatIconButton, MatProgressSpinner, SearchLayoutComponent, TranslatePipe, @@ -104,14 +87,12 @@ function groupResultsByPlaylistId( styleUrls: ['./search-results.component.scss'], }) export class SearchResultsComponent implements AfterViewInit { - readonly searchLayoutComponent = viewChild(SearchLayoutComponent); readonly xtreamStore = inject(XtreamStore); readonly router = inject(Router); readonly activatedRoute = inject(ActivatedRoute); readonly databaseService = inject(DatabaseService); private readonly backNavigation = inject(WorkspaceBackNavigationService); private readonly logger = createLogger('XtreamSearchResults'); - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.activatedRoute); readonly routeSearchTerm = queryParamSignal( this.activatedRoute, 'q', @@ -137,7 +118,8 @@ export class SearchResultsComponent implements AfterViewInit { private static readonly TYPE_FILTERS_STORAGE_KEY = 'global-search-type-filters'; - isGlobalSearch = false; + readonly isGlobalSearch = + this.activatedRoute.snapshot.data?.['isGlobalSearch'] === true; /** Whether to group global search results by playlist */ readonly groupByPlaylist = signal( @@ -246,15 +228,7 @@ export class SearchResultsComponent implements AfterViewInit { } } - constructor( - @Optional() @Inject(MAT_DIALOG_DATA) data: SearchResultsData | null, - @Optional() public dialogRef: MatDialogRef - ) { - this.isGlobalSearch = - data?.isGlobalSearch || - this.activatedRoute.snapshot.data?.['isGlobalSearch'] === true; - const initialQuery = (data?.initialQuery ?? '').trim(); - + constructor() { if (this.isGlobalSearch) { const savedFilters = localStorage.getItem( SearchResultsComponent.TYPE_FILTERS_STORAGE_KEY @@ -273,10 +247,6 @@ export class SearchResultsComponent implements AfterViewInit { // Ignore malformed storage value and keep defaults. } } - - if (initialQuery) { - this.xtreamStore.setSearchTerm(initialQuery); - } } effect((onCleanup) => { @@ -290,10 +260,6 @@ export class SearchResultsComponent implements AfterViewInit { }); effect(() => { - if (!this.isWorkspaceLayout) { - return; - } - const queryTerm = this.routeSearchTerm(); if (queryTerm === this.searchTerm()) { return; @@ -305,11 +271,6 @@ export class SearchResultsComponent implements AfterViewInit { ngAfterViewInit() { this.xtreamStore.setSelectedContentType('vod'); - if (this.showInlineSearchInput) { - setTimeout(() => { - this.searchLayoutComponent()?.focusSearchInput(); - }); - } } executeSearch() { @@ -330,13 +291,6 @@ export class SearchResultsComponent implements AfterViewInit { } } - /** - * Update search term in the store - */ - updateSearchTerm(term: string) { - this.xtreamStore.setSearchTerm(term); - } - /** * Update a single filter in the store */ @@ -526,19 +480,11 @@ export class SearchResultsComponent implements AfterViewInit { imageUrl: item.poster_url, }); - if (this.isGlobalSearch) { - this.dialogRef?.close(); - } - void this.router.navigate(navigation.link, { state: navigation.state, }); } - onCloseDialog() { - this.dialogRef?.close(); - } - /** History Back; the portal's default section when it opened the session. */ goBack(): void { this.backNavigation.back(() => @@ -569,25 +515,13 @@ export class SearchResultsComponent implements AfterViewInit { return this.groupedResults(); } - get showInlineSearchInput(): boolean { - return !this.isWorkspaceLayout; - } - - get showGlobalCloseButton(): boolean { - return this.isGlobalSearch && !this.isWorkspaceLayout; - } - /** * The in-portal search is a nested view (reached from the toolbar * search box or an actor page), so it offers the header's Back; the - * global search is a top-level sidebar destination, and the dialog has - * its own close button — a Back there would navigate the page behind - * the open dialog. + * global search is a top-level sidebar destination. */ get backAvailable(): boolean { - return ( - this.isWorkspaceLayout && !this.isGlobalSearch && !this.dialogRef - ); + return !this.isGlobalSearch; } get minSearchLength(): number { @@ -618,10 +552,6 @@ export class SearchResultsComponent implements AfterViewInit { return; } - if (this.isGlobalSearch) { - this.dialogRef?.close(); - } - void this.router.navigate( ['/workspace', 'playlists', item.playlist_id, 'all'], { diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts index 83f4d2096..adedc6619 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.spec.ts @@ -106,7 +106,6 @@ describe('ChannelListContainerComponent', () => { const route = { snapshot: { - data: { layout: 'workspace' }, paramMap: convertToParamMap({}), queryParamMap: convertToParamMap({}), params: {}, @@ -115,7 +114,6 @@ describe('ChannelListContainerComponent', () => { pathFromRoot: [ { snapshot: { - data: { layout: 'workspace' }, paramMap: convertToParamMap({}), params: {}, }, diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts index 8e94db1f7..f02b7efb2 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.ts @@ -22,10 +22,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { EpgService } from '@iptvnator/epg/data-access'; import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util'; -import { - isWorkspaceLayoutRoute, - queryParamSignal, -} from '@iptvnator/portal/shared/util'; +import { queryParamSignal } from '@iptvnator/portal/shared/util'; import { ChannelActions, FavoritesActions, @@ -219,7 +216,6 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { readonly sidebarToggleRequested = output(); /** Groups view only: the group whose channels are currently listed. */ readonly selectedGroupChange = output(); - readonly isWorkspaceLayout = isWorkspaceLayoutRoute(this.route); private readonly routeSearchTerm = queryParamSignal( this.route, 'q', @@ -230,7 +226,7 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { if (hostTerm !== null) { return foldSearchText(hostTerm.trim()); } - return this.isWorkspaceLayout ? this.routeSearchTerm() : ''; + return this.routeSearchTerm(); }); readonly currentUrl = toSignal( diff --git a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.html b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.html index 6707bb45e..5259b84b0 100644 --- a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.html +++ b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.html @@ -1,23 +1,3 @@ -@if (showPlaylistHeader()) { -
- - - -
-} - { - it('re-words the channel count after a runtime language switch', () => { - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SidebarComponent], - providers: [ - { - provide: PlaylistContextFacade, - useValue: { activePlaylist: signal(null) }, - }, - ], - }).overrideComponent(SidebarComponent, { - set: { template: '', imports: [] }, - }); - const translate = TestBed.inject(TranslateService); - translate.setTranslation('en', { - HOME: { PLAYLISTS: { CHANNELS: 'channels' } }, - }); - translate.setTranslation('ru', { - HOME: { PLAYLISTS: { CHANNELS: 'каналов' } }, - }); - translate.use('en'); - const fixture = TestBed.createComponent(SidebarComponent); - fixture.componentRef.setInput('channels', [{}, {}]); - - expect(fixture.componentInstance.subtitle()).toBe('2 channels'); - - translate.use('ru'); - - expect(fixture.componentInstance.subtitle()).toBe('2 каналов'); - }); -}); diff --git a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts index a1f0eaa2e..82044b18a 100644 --- a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts +++ b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts @@ -1,18 +1,10 @@ import { Component, - computed, inject, input, output, ChangeDetectionStrategy, } from '@angular/core'; -import { MatIconButton } from '@angular/material/button'; -import { MatIcon } from '@angular/material/icon'; -import { MatTooltip } from '@angular/material/tooltip'; -import { RouterLink } from '@angular/router'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { injectTranslationTick } from '@iptvnator/services'; -import { PlaylistSwitcherComponent } from '@iptvnator/playlist/shared/ui'; import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util'; import { Channel } from '@iptvnator/shared/interfaces'; import { ChannelListContainerComponent } from '@iptvnator/ui/components'; @@ -20,22 +12,12 @@ import { ChannelListContainerComponent } from '@iptvnator/ui/components'; @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', - styleUrls: ['./sidebar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - ChannelListContainerComponent, - MatIcon, - MatIconButton, - MatTooltip, - PlaylistSwitcherComponent, - RouterLink, - TranslatePipe, - ], + imports: [ChannelListContainerComponent], }) export class SidebarComponent { readonly channels = input([]); readonly channelsLoading = input(false); - readonly showPlaylistHeader = input(true); readonly activeView = input('all'); readonly sidebarWidth = input(null); readonly sidebarWidthRequested = output(); @@ -45,32 +27,8 @@ export class SidebarComponent { readonly selectedGroupChange = output(); private readonly playlistContext = inject(PlaylistContextFacade); - private readonly translate = inject(TranslateService); - private readonly languageTick = injectTranslationTick(); readonly activePlaylist = this.playlistContext.activePlaylist; - readonly playlistTitle = computed(() => { - const playlist = this.activePlaylist(); - - return ( - playlist?.title || - playlist?.filename || - playlist?.url || - playlist?.portalUrl || - 'Untitled playlist' - ); - }); - - /** Worded per language: the tick re-runs this after a switch. */ - readonly subtitle = computed(() => { - this.languageTick(); - if (this.channelsLoading()) { - return this.translate.instant('CHANNELS.LOADING'); - } - - const count = this.channels()?.length ?? 0; - return `${count} ${this.translate.instant('HOME.PLAYLISTS.CHANNELS')}`; - }); onSidebarWidthRequested(width: number): void { this.sidebarWidthRequested.emit(width); diff --git a/libs/ui/styles/_portal-sidebar.scss b/libs/ui/styles/_portal-sidebar.scss index aedd5c499..98da1c56f 100644 --- a/libs/ui/styles/_portal-sidebar.scss +++ b/libs/ui/styles/_portal-sidebar.scss @@ -42,11 +42,6 @@ app-region: no-drag; } - .switcher { - flex: 1; - min-width: 0; - } - .manage-categories-btn { margin-left: auto; }