diff --git a/docs/architecture/nx-workspace-boundaries.md b/docs/architecture/nx-workspace-boundaries.md index 402f43056..4207660c8 100644 --- a/docs/architecture/nx-workspace-boundaries.md +++ b/docs/architecture/nx-workspace-boundaries.md @@ -76,3 +76,9 @@ buildable dependency paths to `dist/` during `@nx/js:tsc` builds. If a change needs a dependency in the opposite direction, move the shared contract into a lower-level library instead of weakening boundaries. + +Portal collection orchestration that reads/writes favorites, recent items, live +playback, or EPG data belongs in `libs/portal/shared/data-access`, not +`libs/portal/shared/util`. That keeps pure collection helpers importable by +Xtream/Stalker data-access libraries while allowing shared UI to use +provider-specific collection services without creating cycles. diff --git a/libs/portal/shared/data-access/jest.config.ts b/libs/portal/shared/data-access/jest.config.ts new file mode 100644 index 000000000..12e944773 --- /dev/null +++ b/libs/portal/shared/data-access/jest.config.ts @@ -0,0 +1,21 @@ +export default { + displayName: 'portal-shared-data-access', + preset: '../../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../../coverage/libs/portal/shared/data-access', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/portal/shared/data-access/project.json b/libs/portal/shared/data-access/project.json new file mode 100644 index 000000000..70121aad7 --- /dev/null +++ b/libs/portal/shared/data-access/project.json @@ -0,0 +1,21 @@ +{ + "name": "portal-shared-data-access", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/portal/shared/data-access/src", + "prefix": "lib", + "projectType": "library", + "tags": ["scope:portal", "domain:portal-shared", "type:data-access"], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/portal/shared/data-access/jest.config.ts", + "tsConfig": "libs/portal/shared/data-access/tsconfig.spec.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/libs/portal/shared/data-access/src/index.ts b/libs/portal/shared/data-access/src/index.ts new file mode 100644 index 000000000..ca8babad6 --- /dev/null +++ b/libs/portal/shared/data-access/src/index.ts @@ -0,0 +1 @@ +export * from './lib/collection'; diff --git a/libs/portal/shared/data-access/src/lib/collection/index.ts b/libs/portal/shared/data-access/src/lib/collection/index.ts new file mode 100644 index 000000000..7ae7c22f2 --- /dev/null +++ b/libs/portal/shared/data-access/src/lib/collection/index.ts @@ -0,0 +1,3 @@ +export * from './unified-favorites-data.service'; +export * from './unified-recent-data.service'; +export * from './stream-resolver.service'; diff --git a/libs/portal/shared/util/src/lib/collection/stream-resolver.service.spec.ts b/libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.spec.ts similarity index 99% rename from libs/portal/shared/util/src/lib/collection/stream-resolver.service.spec.ts rename to libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.spec.ts index 2d96ac850..517492b7e 100644 --- a/libs/portal/shared/util/src/lib/collection/stream-resolver.service.spec.ts +++ b/libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.spec.ts @@ -7,7 +7,7 @@ import { import { StalkerSessionService } from '@iptvnator/portal/stalker/data-access'; import { DataService, PlaylistsService } from '@iptvnator/services'; import { Playlist } from '@iptvnator/shared/interfaces'; -import { UnifiedCollectionItem } from './unified-collection-item.interface'; +import { UnifiedCollectionItem } from '@iptvnator/portal/shared/util'; import { ResolvedLiveCollectionDetail, StreamResolverService, diff --git a/libs/portal/shared/util/src/lib/collection/stream-resolver.service.ts b/libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.ts similarity index 99% rename from libs/portal/shared/util/src/lib/collection/stream-resolver.service.ts rename to libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.ts index 56c9d0cb6..0b67ea525 100644 --- a/libs/portal/shared/util/src/lib/collection/stream-resolver.service.ts +++ b/libs/portal/shared/data-access/src/lib/collection/stream-resolver.service.ts @@ -15,7 +15,7 @@ import { XtreamUrlService, } from '@iptvnator/portal/xtream/data-access'; import { StalkerSessionService } from '@iptvnator/portal/stalker/data-access'; -import { UnifiedCollectionItem } from './unified-collection-item.interface'; +import { UnifiedCollectionItem } from '@iptvnator/portal/shared/util'; type PlaylistWithChannels = Playlist & { readonly playlist?: { readonly items?: Channel[] }; diff --git a/libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.spec.ts b/libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.spec.ts similarity index 99% rename from libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.spec.ts rename to libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.spec.ts index 8aa5664ab..91826b2af 100644 --- a/libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.spec.ts +++ b/libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.spec.ts @@ -9,7 +9,7 @@ import { PlaylistMeta, StalkerPortalItem, } from '@iptvnator/shared/interfaces'; -import { UnifiedCollectionItem } from './unified-collection-item.interface'; +import { UnifiedCollectionItem } from '@iptvnator/portal/shared/util'; import { UnifiedFavoritesDataService } from './unified-favorites-data.service'; describe('UnifiedFavoritesDataService', () => { diff --git a/libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.ts b/libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.ts similarity index 96% rename from libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.ts rename to libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.ts index 3ca6b5e9e..cb53d8c00 100644 --- a/libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.ts +++ b/libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.ts @@ -19,14 +19,12 @@ import { import { buildCollectionUid, buildXtreamCollectionUid, - UnifiedCollectionItem, -} from './unified-collection-item.interface'; -import { CollectionScope } from './scope-toggle.service'; -import { + CollectionScope, isStalkerItem, + UnifiedCollectionItem, xtreamContentType, XtreamFavoriteRow, -} from './collection-helpers'; +} from '@iptvnator/portal/shared/util'; const GLOBAL_FAVORITES_ORDER_KEY = 'global-favorites-channel-order-v1'; @@ -346,18 +344,22 @@ export class UnifiedFavoritesDataService { private async clearXtreamFavorites( items: UnifiedCollectionItem[] ): Promise { - if (!window.electron) { + const electron = window.electron; + if (!electron) { return; } await Promise.all( items - .filter((item) => item.contentId != null) + .filter( + ( + item + ): item is UnifiedCollectionItem & { + readonly contentId: number; + } => item.contentId != null + ) .map((item) => - window.electron!.dbRemoveFavorite( - item.contentId!, - item.playlistId - ) + electron.dbRemoveFavorite(item.contentId, item.playlistId) ) ); } @@ -679,9 +681,11 @@ export class UnifiedFavoritesDataService { const unordered: UnifiedCollectionItem[] = []; for (const item of items) { const pos = orderMap.get(item.uid); - pos != null - ? ordered.push({ ...item, position: pos }) - : unordered.push(item); + if (pos != null) { + ordered.push({ ...item, position: pos }); + } else { + unordered.push(item); + } } ordered.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)); unordered.sort( @@ -693,14 +697,18 @@ export class UnifiedFavoritesDataService { } private buildXtreamPositionUpdates(items: UnifiedCollectionItem[]) { - return items - .filter( - (item) => item.sourceType === 'xtream' && item.contentId != null - ) - .map((item, index) => ({ - content_id: item.contentId!, - position: index, - })); + const updates: { content_id: number; position: number }[] = []; + + for (const item of items) { + if (item.sourceType === 'xtream' && item.contentId != null) { + updates.push({ + content_id: item.contentId, + position: updates.length, + }); + } + } + + return updates; } private groupItemsByPlaylist( diff --git a/libs/portal/shared/util/src/lib/collection/unified-recent-data.service.spec.ts b/libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.spec.ts similarity index 99% rename from libs/portal/shared/util/src/lib/collection/unified-recent-data.service.spec.ts rename to libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.spec.ts index f7c3e50fd..2557e7dd9 100644 --- a/libs/portal/shared/util/src/lib/collection/unified-recent-data.service.spec.ts +++ b/libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.spec.ts @@ -8,7 +8,7 @@ import { Playlist, PlaylistMeta, } from '@iptvnator/shared/interfaces'; -import { UnifiedCollectionItem } from './unified-collection-item.interface'; +import { UnifiedCollectionItem } from '@iptvnator/portal/shared/util'; import { UnifiedRecentDataService } from './unified-recent-data.service'; describe('UnifiedRecentDataService', () => { diff --git a/libs/portal/shared/util/src/lib/collection/unified-recent-data.service.ts b/libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.ts similarity index 99% rename from libs/portal/shared/util/src/lib/collection/unified-recent-data.service.ts rename to libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.ts index 4264c067d..bb1f657dc 100644 --- a/libs/portal/shared/util/src/lib/collection/unified-recent-data.service.ts +++ b/libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.ts @@ -22,10 +22,10 @@ import { import { buildCollectionUid, buildXtreamCollectionUid, + CollectionScope, UnifiedCollectionItem, -} from './unified-collection-item.interface'; -import { CollectionScope } from './scope-toggle.service'; -import { xtreamContentType } from './collection-helpers'; + xtreamContentType, +} from '@iptvnator/portal/shared/util'; type PlaylistWithChannels = Playlist & { readonly playlist?: { readonly items?: Channel[] }; diff --git a/libs/portal/shared/data-access/src/test-setup.ts b/libs/portal/shared/data-access/src/test-setup.ts new file mode 100644 index 000000000..0d88648e3 --- /dev/null +++ b/libs/portal/shared/data-access/src/test-setup.ts @@ -0,0 +1,6 @@ +import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; + +setupZoneTestEnv({ + errorOnUnknownElements: true, + errorOnUnknownProperties: true, +}); diff --git a/libs/portal/shared/data-access/tsconfig.json b/libs/portal/shared/data-access/tsconfig.json new file mode 100644 index 000000000..dd63ae991 --- /dev/null +++ b/libs/portal/shared/data-access/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "isolatedModules": true, + "target": "es2022", + "moduleResolution": "bundler", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "emitDecoratorMetadata": false, + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/portal/shared/data-access/tsconfig.lib.json b/libs/portal/shared/data-access/tsconfig.lib.json new file mode 100644 index 000000000..7fba372dc --- /dev/null +++ b/libs/portal/shared/data-access/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/portal/shared/data-access/tsconfig.spec.json b/libs/portal/shared/data-access/tsconfig.spec.json new file mode 100644 index 000000000..c0bb248be --- /dev/null +++ b/libs/portal/shared/data-access/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"], + "moduleResolution": "node10" + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/portal/shared/ui/src/index.ts b/libs/portal/shared/ui/src/index.ts index 5f9767729..a95b5fa20 100644 --- a/libs/portal/shared/ui/src/index.ts +++ b/libs/portal/shared/ui/src/index.ts @@ -6,7 +6,6 @@ export * from './lib/components/global-favorites-list/global-favorites-list.comp export * from './lib/components/grid-list/grid-list.component'; export * from './lib/components/playlist-error-view/playlist-error-view.component'; export { ProgressCapsuleComponent } from '@iptvnator/ui/components'; -export * from './lib/components/portal-collection-live-shell/portal-collection-live-shell.component'; export * from './lib/components/portal-collection-shell/portal-collection-shell.component'; export * from './lib/components/portal-empty-state/portal-empty-state.component'; export * from './lib/components/search-form/search-form.component'; diff --git a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.html b/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.html deleted file mode 100644 index 41c3228e4..000000000 --- a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.html +++ /dev/null @@ -1,55 +0,0 @@ - - -
- @if (hasSelection()) { - @if (streamUrl() && isEmbeddedPlayer()) { -
- -
- } - -
-
- @if (epgItems().length > 0) { - - } - - @if (isLoadingEpg()) { -
- -
- } - - @if (hasMoreEpg() && !isLoadingEpg()) { -
- -
- } -
-
- } @else { - - } -
diff --git a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.scss b/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.scss deleted file mode 100644 index 30fee3519..000000000 --- a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.scss +++ /dev/null @@ -1,81 +0,0 @@ -@use '@angular/material' as mat; -@use '../../styles/portal-layout' as portal; - -@include portal.live-layout; - -// ─── Sidebar panel header ───────────────────────────────────────────────────── -.category-content-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; - padding: 8px 12px; - min-height: 48px; - flex-shrink: 0; - background: var(--app-widget-header-bg, var(--mat-sys-surface-container-high)); - border-bottom: 1px solid var(--app-separator); - app-region: drag; - - // Own buttons (direct children scoped to this component) - button { - @include mat.form-field-density(-5); - app-region: no-drag; - } - - // Projected buttons (liveHeaderActions slot) — view encapsulation - // prevents the rule above from reaching them, so ng-deep is required. - ::ng-deep button { - app-region: no-drag; - } -} - -.category-meta { - min-width: 0; - display: flex; - align-items: baseline; - gap: 8px; - flex: 1; -} - -// ─── Title + count chip — now aligned with category-content-view ───────────── -.category-title { - margin: 0; - font-size: 0.94rem; - font-weight: 600; - letter-spacing: -0.01em; - color: var(--mat-sys-on-surface); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -// Informational badge — primary-tinted pill with definition ring -.category-subtitle { - white-space: nowrap; - flex-shrink: 0; - font-size: 0.70rem; - font-weight: 700; - letter-spacing: 0.05em; - text-transform: capitalize; - line-height: 1; - color: var(--mat-sys-primary); - background: color-mix(in srgb, var(--mat-sys-primary) 10%, var(--mat-sys-surface-container-high)); - padding: 4px 10px; - border-radius: 999px; - box-shadow: - inset 0 0 0 1px color-mix(in srgb, var(--mat-sys-primary) 30%, transparent), - 0 1px 3px rgba(0, 0, 0, 0.18); -} - -// ─── EPG controls ───────────────────────────────────────────────────────────── -.loading-indicator { - display: flex; - justify-content: center; - padding: 16px; -} - -.load-more-epg { - display: flex; - justify-content: center; - padding: 8px 0; -} diff --git a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.ts b/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.ts deleted file mode 100644 index 0b0d7cb8c..000000000 --- a/libs/portal/shared/ui/src/lib/components/portal-collection-live-shell/portal-collection-live-shell.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - input, - output, -} from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { TranslatePipe } from '@ngx-translate/core'; -import { ResizableDirective } from '@iptvnator/ui/components'; -import { EpgItem } from '@iptvnator/shared/interfaces'; -import { WebPlayerViewComponent } from '@iptvnator/ui/playback'; -import { EpgViewComponent } from '@iptvnator/ui/shared-portals'; -import { PortalEmptyStateComponent } from '../portal-empty-state/portal-empty-state.component'; - -@Component({ - selector: 'app-portal-collection-live-shell', - imports: [ - EpgViewComponent, - MatButtonModule, - MatProgressSpinnerModule, - PortalEmptyStateComponent, - ResizableDirective, - TranslatePipe, - WebPlayerViewComponent, - ], - templateUrl: './portal-collection-live-shell.component.html', - styleUrl: './portal-collection-live-shell.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class PortalCollectionLiveShellComponent { - readonly title = input('Favorites'); - readonly subtitle = input('Live TV'); - readonly count = input(0); - readonly streamUrl = input(''); - readonly isEmbeddedPlayer = input(true); - readonly hasSelection = input(false); - readonly epgItems = input([]); - readonly isLoadingEpg = input(false); - readonly hasMoreEpg = input(false); - - readonly loadMoreEpgClicked = output(); - - onLoadMoreEpg(): void { - this.loadMoreEpgClicked.emit(); - } -} 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 a567dbbd6..9e2aa5e2b 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 @@ -19,10 +19,12 @@ import { OPEN_COLLECTION_DETAIL_STATE_KEY, ScopeToggleService, UnifiedCollectionItem, - UnifiedFavoritesDataService, - UnifiedRecentDataService, WorkspaceViewCommandService, } from '@iptvnator/portal/shared/util'; +import { + UnifiedFavoritesDataService, + UnifiedRecentDataService, +} from '@iptvnator/portal/shared/data-access'; import { selectAllPlaylistsMeta, selectPlaylistsLoadingFlag } from '@iptvnator/m3u-state'; import { BehaviorSubject } from 'rxjs'; import { PlaylistMeta } from '@iptvnator/shared/interfaces'; 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 5a8c486a5..f043383f3 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 @@ -48,10 +48,12 @@ import { ScopeToggleService, STALKER_RETURN_TO_STATE_KEY, UnifiedCollectionItem, - UnifiedFavoritesDataService, - UnifiedRecentDataService, WorkspaceViewCommandService, } from '@iptvnator/portal/shared/util'; +import { + UnifiedFavoritesDataService, + UnifiedRecentDataService, +} from '@iptvnator/portal/shared/data-access'; import { selectAllPlaylistsMeta, selectPlaylistsLoadingFlag } from '@iptvnator/m3u-state'; import { EmptyStateComponent } from '@iptvnator/playlist/shared/ui'; import { UnifiedLiveTabComponent } from './unified-live-tab.component'; diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.spec.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.spec.ts index 5a81f9e6e..1b21e07a3 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.spec.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.spec.ts @@ -40,11 +40,13 @@ import { DEFAULT_FAVORITES_CHANNEL_SORT_MODE, PORTAL_PLAYER, FavoritesChannelSortMode, - StreamResolverService, UnifiedCollectionItem, UnifiedFavoriteChannel, - UnifiedRecentDataService, } from '@iptvnator/portal/shared/util'; +import { + StreamResolverService, + UnifiedRecentDataService, +} from '@iptvnator/portal/shared/data-access'; @Directive({ selector: '[appResizable]', diff --git a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts index 5329c5e74..19a0c55ef 100644 --- a/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts +++ b/libs/portal/shared/ui/src/lib/components/unified-collection/unified-live-tab.component.ts @@ -22,13 +22,15 @@ import { OpenLiveCollectionItemState, PORTAL_PLAYER, persistLiveEpgPanelState, - ResolvedLiveCollectionDetail, restoreLiveEpgPanelState, - StreamResolverService, UnifiedCollectionItem, UnifiedFavoriteChannel, - UnifiedRecentDataService, } from '@iptvnator/portal/shared/util'; +import { + ResolvedLiveCollectionDetail, + StreamResolverService, + UnifiedRecentDataService, +} from '@iptvnator/portal/shared/data-access'; import { EpgDateNavigationDirection, EpgListComponent, diff --git a/libs/portal/shared/util/src/lib/collection/index.ts b/libs/portal/shared/util/src/lib/collection/index.ts index 50330be65..d75038c94 100644 --- a/libs/portal/shared/util/src/lib/collection/index.ts +++ b/libs/portal/shared/util/src/lib/collection/index.ts @@ -1,6 +1,4 @@ export * from './unified-collection-item.interface'; export * from './unified-favorite-channel.interface'; +export * from './collection-helpers'; export * from './scope-toggle.service'; -export * from './unified-favorites-data.service'; -export * from './unified-recent-data.service'; -export * from './stream-resolver.service'; diff --git a/libs/portal/shared/util/src/lib/collection/scope-toggle.service.ts b/libs/portal/shared/util/src/lib/collection/scope-toggle.service.ts index 1acd6904d..30bcdbaea 100644 --- a/libs/portal/shared/util/src/lib/collection/scope-toggle.service.ts +++ b/libs/portal/shared/util/src/lib/collection/scope-toggle.service.ts @@ -1,19 +1,22 @@ import { Injectable, signal } from '@angular/core'; export type CollectionScope = 'playlist' | 'all'; +type CollectionScopeSignal = ReturnType>; const STORAGE_PREFIX = 'collection-scope-'; @Injectable({ providedIn: 'root' }) export class ScopeToggleService { - private readonly scopes = new Map>>(); + private readonly scopes = new Map(); - getScope(viewKey: string): ReturnType> { - if (!this.scopes.has(viewKey)) { + getScope(viewKey: string): CollectionScopeSignal { + let scope = this.scopes.get(viewKey); + if (!scope) { const persisted = this.readFromStorage(viewKey); - this.scopes.set(viewKey, signal(persisted)); + scope = signal(persisted); + this.scopes.set(viewKey, scope); } - return this.scopes.get(viewKey)!; + return scope; } setScope(viewKey: string, value: CollectionScope): void { diff --git a/libs/portal/shared/util/src/lib/navigation/workspace-portal-navigation.spec.ts b/libs/portal/shared/util/src/lib/navigation/workspace-portal-navigation.spec.ts index 6a0c5a936..ab9a748d0 100644 --- a/libs/portal/shared/util/src/lib/navigation/workspace-portal-navigation.spec.ts +++ b/libs/portal/shared/util/src/lib/navigation/workspace-portal-navigation.spec.ts @@ -7,15 +7,11 @@ import { getCollectionViewState, getOpenCollectionDetailItemState, getOpenLiveCollectionItemState, - getOpenStalkerItemState, getRecentItemNavigation, - getStalkerReturnToState, getUnifiedCollectionNavigation, OPEN_COLLECTION_DETAIL_STATE_KEY, matchesOpenLiveCollectionItem, OPEN_LIVE_COLLECTION_ITEM_STATE_KEY, - OPEN_STALKER_ITEM_STATE_KEY, - STALKER_RETURN_TO_STATE_KEY, } from './workspace-portal-navigation'; describe('workspace-portal-navigation', () => { diff --git a/libs/ui/styles/_portal-layout.scss b/libs/ui/styles/_portal-layout.scss index b18f4ec71..3ef3a351c 100644 --- a/libs/ui/styles/_portal-layout.scss +++ b/libs/ui/styles/_portal-layout.scss @@ -1,6 +1,5 @@ // ─── Shared mixin for live-stream portal layouts ────────────────────────────── -// Used by: xtream live-stream-layout, stalker live-stream-layout, -// portal-collection-live-shell +// Used by: xtream live-stream-layout and stalker live-stream-layout. // // Include this mixin in a component's SCSS to get the shared flex host+sidebar // skeleton, then add only the component-specific rules underneath. diff --git a/tsconfig.base.json b/tsconfig.base.json index 57a4047ec..5743bd2d9 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,6 +24,9 @@ "@iptvnator/portal/shared/ui": [ "libs/portal/shared/ui/src/index.ts" ], + "@iptvnator/portal/shared/data-access": [ + "libs/portal/shared/data-access/src/index.ts" + ], "@iptvnator/portal/shared/util": [ "libs/portal/shared/util/src/index.ts" ],