- @if (stalkerStore.selectedCategoryId() && isSidebarCollapsed()) {
+ @if (isSidebarCollapsed()) {
this.liveEpgPanelState() === 'collapsed'
);
- readonly liveSidebarState = signal(
- restoreLiveSidebarState()
- );
- readonly isSidebarCollapsed = computed(
- () => this.liveSidebarState() === 'collapsed'
- );
+ readonly isSidebarCollapsed = this.liveSidebarStateService.isCollapsed;
readonly liveEpgPanelSummary = computed(() =>
this.toLiveEpgPanelSummary(this.currentProgram())
);
@@ -398,11 +394,7 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
}
toggleSidebar(): void {
- const next: LiveSidebarState = this.isSidebarCollapsed()
- ? 'expanded'
- : 'collapsed';
- this.liveSidebarState.set(next);
- persistLiveSidebarState(next);
+ this.liveSidebarStateService.toggle();
}
@HostListener('document:keydown', ['$event'])
diff --git a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
index 88376267a..fcdfe651f 100644
--- a/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
+++ b/libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.html
@@ -78,7 +78,7 @@
}
- @if (selectedCategoryId() && isSidebarCollapsed()) {
+ @if (isSidebarCollapsed()) {
{
jest.setSystemTime(fixedNow);
localStorage.removeItem(LIVE_CHANNEL_SORT_STORAGE_KEY);
localStorage.removeItem(LIVE_EPG_PANEL_STATE_STORAGE_KEY);
+ localStorage.removeItem(LIVE_SIDEBAR_STATE_STORAGE_KEY);
window.electron = {
updateRemoteControlStatus: jest.fn(),
@@ -243,13 +246,17 @@ describe('LiveStreamLayoutComponent', () => {
fixture = TestBed.createComponent(LiveStreamLayoutComponent);
component = fixture.componentInstance;
+
+ TestBed.inject(LiveLayoutSidebarStateService).setState('expanded');
});
afterEach(() => {
+ TestBed.inject(LiveLayoutSidebarStateService).setState('expanded');
fixture.destroy();
jest.useRealTimers();
localStorage.removeItem(LIVE_CHANNEL_SORT_STORAGE_KEY);
localStorage.removeItem(LIVE_EPG_PANEL_STATE_STORAGE_KEY);
+ localStorage.removeItem(LIVE_SIDEBAR_STATE_STORAGE_KEY);
window.electron = originalElectron;
});
@@ -472,6 +479,26 @@ describe('LiveStreamLayoutComponent', () => {
).toBeNull();
});
+ it('shows the floating restore button when the sidebar is collapsed even without a selected category', () => {
+ selectedCategoryId.set(null);
+ TestBed.inject(LiveLayoutSidebarStateService).setState('collapsed');
+ fixture.detectChanges();
+
+ expect(
+ fixture.nativeElement.querySelector('.sidebar-restore')
+ ).not.toBeNull();
+ });
+
+ it('hides the floating restore button when the sidebar is expanded', () => {
+ selectedCategoryId.set(1);
+ TestBed.inject(LiveLayoutSidebarStateService).setState('expanded');
+ fixture.detectChanges();
+
+ expect(
+ fixture.nativeElement.querySelector('.sidebar-restore')
+ ).toBeNull();
+ });
+
it('hides the archive-unavailable notice when there are no past programs yet', () => {
const nonArchiveChannel = {
...sampleChannel,
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 407fac226..df7bcb950 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
@@ -18,6 +18,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { ResizableDirective } from 'components';
import { PortalEmptyStateComponent } from '@iptvnator/portal/shared/ui';
import {
+ LiveLayoutSidebarStateService,
PORTAL_PLAYER,
PortalChannelSortMode,
getPortalChannelSortModeLabel,
@@ -26,13 +27,10 @@ import {
isTypingInInput,
isWorkspaceLayoutRoute,
LiveEpgPanelState,
- LiveSidebarState,
persistLiveEpgPanelState,
- persistLiveSidebarState,
persistPortalChannelSortMode,
queryParamSignal,
restoreLiveEpgPanelState,
- restoreLiveSidebarState,
restorePortalChannelSortMode,
} from '@iptvnator/portal/shared/util';
import {
@@ -103,6 +101,9 @@ export class LiveStreamLayoutComponent implements OnInit, OnDestroy {
private readonly xtreamStore = inject(XtreamStore);
private readonly xtreamUrlService = inject(XtreamUrlService);
private readonly portalPlayer = inject(PORTAL_PLAYER);
+ private readonly liveSidebarStateService = inject(
+ LiveLayoutSidebarStateService
+ );
readonly categories = this.xtreamStore.getCategoriesBySelectedType;
readonly categoryItemCounts = this.xtreamStore.getCategoryItemCounts;
@@ -177,12 +178,7 @@ export class LiveStreamLayoutComponent implements OnInit, OnDestroy {
readonly isLiveEpgPanelCollapsed = computed(
() => this.liveEpgPanelState() === 'collapsed'
);
- readonly liveSidebarState = signal(
- restoreLiveSidebarState()
- );
- readonly isSidebarCollapsed = computed(
- () => this.liveSidebarState() === 'collapsed'
- );
+ readonly isSidebarCollapsed = this.liveSidebarStateService.isCollapsed;
readonly liveEpgPanelSummary = computed(() =>
this.toLiveEpgPanelSummary(this.currentEpgItem())
);
@@ -379,11 +375,7 @@ export class LiveStreamLayoutComponent implements OnInit, OnDestroy {
}
toggleSidebar(): void {
- const next: LiveSidebarState = this.isSidebarCollapsed()
- ? 'expanded'
- : 'collapsed';
- this.liveSidebarState.set(next);
- persistLiveSidebarState(next);
+ this.liveSidebarStateService.toggle();
}
@HostListener('document:keydown', ['$event'])
diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.html b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.html
index eb2745632..2227a0916 100644
--- a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.html
+++ b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.html
@@ -14,6 +14,7 @@
@if (section(); as section) {
{
let fixture: ComponentFixture;
+ let liveSidebarService: LiveLayoutSidebarStateService;
beforeEach(async () => {
+ localStorage.removeItem('live-sidebar-state');
+
await TestBed.configureTestingModule({
imports: [WorkspaceShellContextSidebarComponent],
})
@@ -69,6 +73,8 @@ describe('WorkspaceShellContextSidebarComponent', () => {
.compileComponents();
fixture = TestBed.createComponent(WorkspaceShellContextSidebarComponent);
+ liveSidebarService = TestBed.inject(LiveLayoutSidebarStateService);
+ liveSidebarService.setState('expanded');
});
it('renders the settings panel for the settings variant', () => {
@@ -106,4 +112,69 @@ describe('WorkspaceShellContextSidebarComponent', () => {
)
).not.toBeNull();
});
+
+ describe('live sidebar collapse', () => {
+ function setupLiveCategory(section: 'live' | 'itv' | 'vod'): void {
+ fixture.componentRef.setInput('variant', 'category');
+ fixture.componentRef.setInput('context', {
+ provider: 'xtreams',
+ playlistId: 'pl-1',
+ });
+ fixture.componentRef.setInput('section', section);
+ fixture.detectChanges();
+ }
+
+ it('collapses the categories rail when the shared service is collapsed on a live route', () => {
+ setupLiveCategory('live');
+
+ liveSidebarService.setState('collapsed');
+ fixture.detectChanges();
+
+ const aside = fixture.nativeElement.querySelector(
+ 'aside.context-panel--route'
+ );
+ expect(aside.classList.contains('context-panel--collapsed')).toBe(
+ true
+ );
+ });
+
+ it('also collapses on the Stalker itv section', () => {
+ setupLiveCategory('itv');
+
+ liveSidebarService.setState('collapsed');
+ fixture.detectChanges();
+
+ const aside = fixture.nativeElement.querySelector(
+ 'aside.context-panel--route'
+ );
+ expect(aside.classList.contains('context-panel--collapsed')).toBe(
+ true
+ );
+ });
+
+ it('does not collapse the categories rail on non-live sections', () => {
+ setupLiveCategory('vod');
+
+ liveSidebarService.setState('collapsed');
+ fixture.detectChanges();
+
+ const aside = fixture.nativeElement.querySelector(
+ 'aside.context-panel--route'
+ );
+ expect(aside.classList.contains('context-panel--collapsed')).toBe(
+ false
+ );
+ });
+
+ it('keeps the categories rail expanded when the service is expanded', () => {
+ setupLiveCategory('live');
+
+ const aside = fixture.nativeElement.querySelector(
+ 'aside.context-panel--route'
+ );
+ expect(aside.classList.contains('context-panel--collapsed')).toBe(
+ false
+ );
+ });
+ });
});
diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.ts b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.ts
index 087b4ea4e..862426f71 100644
--- a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.ts
+++ b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-context-sidebar/workspace-shell-context-sidebar.component.ts
@@ -1,10 +1,15 @@
import {
ChangeDetectionStrategy,
Component,
+ computed,
+ inject,
input,
} from '@angular/core';
import { ResizableDirective } from 'components';
-import { PortalRailSection } from '@iptvnator/portal/shared/util';
+import {
+ LiveLayoutSidebarStateService,
+ PortalRailSection,
+} from '@iptvnator/portal/shared/util';
import {
WorkspacePortalContext,
WorkspaceShellContextPanel,
@@ -14,6 +19,8 @@ import { WorkspaceContextPanelComponent } from '../../../workspace-context-panel
import { WorkspaceSettingsContextPanelComponent } from '../../../workspace-context-panel/workspace-settings-context-panel.component';
import { WorkspaceSourcesFiltersPanelComponent } from '../../../workspace-sources-filters-panel/workspace-sources-filters-panel.component';
+const LIVE_SECTIONS: ReadonlySet = new Set(['live', 'itv']);
+
@Component({
selector: 'app-workspace-shell-context-sidebar',
imports: [
@@ -28,8 +35,26 @@ import { WorkspaceSourcesFiltersPanelComponent } from '../../../workspace-source
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WorkspaceShellContextSidebarComponent {
+ private readonly liveSidebarStateService = inject(
+ LiveLayoutSidebarStateService
+ );
+
readonly variant = input.required();
readonly context = input(null);
readonly section = input(null);
readonly hasPlaylists = input(true);
+
+ readonly isLiveCategoryRoute = computed(() => {
+ const section = this.section();
+ return (
+ this.variant() === 'category' &&
+ section !== null &&
+ LIVE_SECTIONS.has(section)
+ );
+ });
+ readonly isContextPanelCollapsed = computed(
+ () =>
+ this.isLiveCategoryRoute() &&
+ this.liveSidebarStateService.isCollapsed()
+ );
}