refactor: enhance live stream layout with loading state and caching

- Added `XtreamCachedContentScope` type to manage content loading states.
- Updated live stream layout to show a loading skeleton when content is being fetched.
- Introduced styles for loading content in the live stream layout.
- Enhanced unit tests to cover loading states and caching behavior.
- Modified `XtreamWorkspaceRouteSession` to handle cached content loading and state management.
- Improved handling of optional connection values in playlist data.
- Updated workspace context panel to reflect import states and loading conditions.

Entire-Checkpoint: c2cea9c530e6
This commit is contained in:
4gray committed 2026-04-30 18:54:58 +02:00
1 parent a6e886116c
commit cbedb22698
15 files changed
+1211 -120

No files matched your search

@@ -31,6 +31,7 @@ describe('WorkspaceContextPanelComponent', () => {
const xtreamSelectedTypeContentState =
signal<XtreamContentLoadState>('loading');
const xtreamImportPhase = signal<string | null>('loading-live');
const xtreamIsImporting = signal(true);
const xtreamIsLoadingCategories = signal(false);
const xtreamStore = {
@@ -44,6 +45,7 @@ describe('WorkspaceContextPanelComponent', () => {
selectedTypeCountsReady: computed(
() => xtreamSelectedTypeContentState() === 'ready'
),
isImporting: xtreamIsImporting,
currentImportPhase: xtreamImportPhase,
isLoadingCategories: xtreamIsLoadingCategories,
setSelectedItem: jest.fn(),
@@ -75,6 +77,7 @@ describe('WorkspaceContextPanelComponent', () => {
xtreamSelectedCategoryId.set(null);
xtreamSelectedTypeContentState.set('loading');
xtreamImportPhase.set('loading-live');
xtreamIsImporting.set(true);
xtreamIsLoadingCategories.set(false);
xtreamStore.setSelectedItem.mockClear();
xtreamStore.setSelectedCategory.mockClear();
@@ -137,9 +140,9 @@ describe('WorkspaceContextPanelComponent', () => {
const status = fixture.nativeElement.querySelector(
'.context-inline-status'
) as HTMLElement | null;
const manageButton = fixture.nativeElement.querySelector(
'.context-header__action'
) as HTMLButtonElement | null;
const manageButton = Array.from(
fixture.nativeElement.querySelectorAll('.context-header__action')
).at(-1) as HTMLButtonElement | undefined;
expect(countPlaceholders).toHaveLength(2);
expect(categoryButtons.every((button) => button.disabled)).toBe(true);
@@ -156,6 +159,26 @@ describe('WorkspaceContextPanelComponent', () => {
expect(router.navigate).not.toHaveBeenCalled();
});
it('keeps local xtream loading states quiet when no import is running', () => {
fixture.componentRef.setInput('section', 'vod');
xtreamIsImporting.set(false);
fixture.detectChanges();
const countPlaceholders = fixture.nativeElement.querySelectorAll(
'.item-count--loading'
);
const categoryButtons = Array.from(
fixture.nativeElement.querySelectorAll('.category-item')
) as HTMLButtonElement[];
const status = fixture.nativeElement.querySelector(
'.context-inline-status'
) as HTMLElement | null;
expect(countPlaceholders).toHaveLength(2);
expect(categoryButtons.every((button) => button.disabled)).toBe(true);
expect(status).toBeNull();
});
it('shows real counts and enables navigation once the selected xtream type is ready', () => {
fixture.componentRef.setInput('section', 'vod');
xtreamSelectedTypeContentState.set('ready');
@@ -174,9 +197,9 @@ describe('WorkspaceContextPanelComponent', () => {
const categoryButtons = Array.from(
fixture.nativeElement.querySelectorAll('.category-item')
) as HTMLButtonElement[];
const manageButton = fixture.nativeElement.querySelector(
'.context-header__action'
) as HTMLButtonElement | null;
const manageButton = Array.from(
fixture.nativeElement.querySelectorAll('.context-header__action')
).at(-1) as HTMLButtonElement | undefined;
expect(countTexts).toEqual(['3', '0']);
expect(categoryButtons.every((button) => !button.disabled)).toBe(true);
@@ -81,6 +81,7 @@ export class WorkspaceContextPanelComponent {
this.xtreamStore.selectedTypeContentReady;
readonly xtreamSelectedTypeCountsReady =
this.xtreamStore.selectedTypeCountsReady;
readonly isXtreamImporting = this.xtreamStore.isImporting;
readonly xtreamImportPhase = this.xtreamStore.currentImportPhase;
readonly isXtreamCategoryLoading = computed(
() =>
@@ -106,6 +107,7 @@ export class WorkspaceContextPanelComponent {
if (
!this.isXtreamCategories() ||
this.isXtreamCategoryLoading() ||
!this.isXtreamImporting() ||
this.xtreamSelectedTypeContentState() !== 'loading'
) {
return '';