feat(sidebar): implement shared collapse state for live-TV sidebar across components

Entire-Checkpoint: f957cd9849e0
This commit is contained in:
4gray committed 2026-05-03 23:26:52 +02:00
1 parent bbbe7b4036
commit e36c953865
13 files changed
+214 -54

No files matched your search

@@ -14,6 +14,7 @@
@if (section(); as section) {
<aside
class="context-panel context-panel--route"
[class.context-panel--collapsed]="isContextPanelCollapsed()"
appResizable
[minWidth]="240"
[maxWidth]="560"
@@ -16,6 +16,22 @@
min-width: 0;
min-height: 0;
overflow: hidden;
transition:
width 180ms ease,
padding 180ms ease,
border-right-color 180ms ease;
}
.context-panel--collapsed {
width: 0 !important;
min-width: 0 !important;
padding-left: 0;
padding-right: 0;
border-right-color: transparent;
.resize-handle {
display: none;
}
}
.context-panel--route {
@@ -4,6 +4,7 @@ import {
input,
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LiveLayoutSidebarStateService } from '@iptvnator/portal/shared/util';
import { WorkspaceShellContextSidebarComponent } from './workspace-shell-context-sidebar.component';
@Directive({
@@ -50,8 +51,11 @@ class MockWorkspaceSourcesFiltersPanelComponent {}
describe('WorkspaceShellContextSidebarComponent', () => {
let fixture: ComponentFixture<WorkspaceShellContextSidebarComponent>;
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
);
});
});
});
@@ -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<PortalRailSection> = 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<WorkspaceShellContextPanel>();
readonly context = input<WorkspacePortalContext | null>(null);
readonly section = input<PortalRailSection | null>(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()
);
}