mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
feat(sidebar): implement shared collapse state for live-TV sidebar across components
Entire-Checkpoint: f957cd9849e0
This commit is contained in:
1 parent
bbbe7b4036
commit
e36c953865
13 files changed
+214
-54
No files matched your search
@@ -147,20 +147,32 @@ The shared row should be reused instead of rebuilding channel markup per view.
|
||||
|
||||
- M3U, Xtream, and Stalker live layouts share a single sidebar collapse toggle
|
||||
that hides the channels rail to give the player and EPG full width.
|
||||
- Collapsed state is persisted in `live-sidebar-state` (helpers in
|
||||
`@iptvnator/portal/shared/util`); missing or invalid values restore to
|
||||
expanded. The state is global across all three modules so the user's choice
|
||||
carries across portals and reloads.
|
||||
- In Xtream and Stalker live TV, the same toggle also collapses the workspace
|
||||
shell context sidebar (the "Live Categories" rail rendered by
|
||||
`WorkspaceShellContextSidebarComponent`), matching M3U's "everything quiets"
|
||||
behaviour. The shell categories rail only collapses when the active section
|
||||
is `live` (Xtream) or `itv` (Stalker); movies, series, favorites, and recent
|
||||
routes leave it untouched.
|
||||
- Collapsed state is owned by `LiveLayoutSidebarStateService`
|
||||
(`providedIn: 'root'`) in `@iptvnator/portal/shared/util`. Every surface that
|
||||
participates injects the service and reads `isCollapsed`; any toggle calls
|
||||
`service.toggle()`. Persistence delegates to the existing
|
||||
`live-sidebar-state` helpers, so the localStorage key stays unchanged and
|
||||
missing/invalid values restore to expanded.
|
||||
- A `mat-icon-button` with `chevron_left` lives in the sidebar header and
|
||||
toggles state. While collapsed, a floating `chevron_right` mini-fab appears
|
||||
at the left edge of `.content-container` to restore the rail.
|
||||
at the left edge of `.content-container` to restore the rail (and the
|
||||
categories rail, in Xtream/Stalker live).
|
||||
- Keyboard shortcut: `Cmd/Ctrl+B`. The handler ignores events that originate
|
||||
inside `<input>`, `<textarea>`, `<select>`, or content-editable elements via
|
||||
the shared `isTypingInInput` helper.
|
||||
- The CSS class `.sidebar-collapsed` overrides the inline width set by the
|
||||
`appResizable` directive with `width: 0 !important; min-width: 0 !important`.
|
||||
The directive's persisted width is preserved so uncollapsing restores the
|
||||
user's previous resized width.
|
||||
- The CSS class `.sidebar-collapsed` (channels rail) and
|
||||
`.context-panel--collapsed` (workspace shell categories rail) both override
|
||||
the inline width set by the `appResizable` directive with
|
||||
`width: 0 !important; min-width: 0 !important`. The directive's persisted
|
||||
width is preserved so uncollapsing restores the user's previous resized
|
||||
width. Both rails share the same 180 ms width transition so motion stays in
|
||||
lockstep.
|
||||
- Below 600 px viewport, the M3U layout's mobile bottom-drawer rule overrides
|
||||
the desktop collapse to `height: 0` instead of `width: 0`, and the floating
|
||||
restore handle is hidden.
|
||||
|
||||
+6
-14
@@ -38,14 +38,12 @@ import {
|
||||
getOpenLiveCollectionItemState,
|
||||
getUnifiedCollectionNavigation,
|
||||
isWorkspaceLayoutRoute,
|
||||
LiveSidebarState,
|
||||
LiveLayoutSidebarStateService,
|
||||
OPEN_COLLECTION_DETAIL_STATE_KEY,
|
||||
OPEN_LIVE_COLLECTION_ITEM_STATE_KEY,
|
||||
persistFavoritesChannelSortMode,
|
||||
persistLiveSidebarState,
|
||||
queryParamSignal,
|
||||
restoreFavoritesChannelSortMode,
|
||||
restoreLiveSidebarState,
|
||||
routeParamSignal,
|
||||
ScopeToggleService,
|
||||
STALKER_RETURN_TO_STATE_KEY,
|
||||
@@ -100,6 +98,9 @@ export class UnifiedCollectionPageComponent implements AfterContentInit {
|
||||
private readonly dialogService = inject(DialogService);
|
||||
private readonly translate = inject(TranslateService);
|
||||
private readonly workspaceViewCommands = inject(WorkspaceViewCommandService);
|
||||
private readonly liveSidebarStateService = inject(
|
||||
LiveLayoutSidebarStateService
|
||||
);
|
||||
readonly detailTemplate = contentChild(UnifiedCollectionDetailDirective);
|
||||
private readonly playlists = this.store.selectSignal(
|
||||
selectAllPlaylistsMeta
|
||||
@@ -265,12 +266,7 @@ export class UnifiedCollectionPageComponent implements AfterContentInit {
|
||||
this.selectedContentType() === 'live' &&
|
||||
this.hasLive()
|
||||
);
|
||||
readonly liveSidebarState = signal<LiveSidebarState>(
|
||||
restoreLiveSidebarState()
|
||||
);
|
||||
readonly isSidebarCollapsed = computed(
|
||||
() => this.liveSidebarState() === 'collapsed'
|
||||
);
|
||||
readonly isSidebarCollapsed = this.liveSidebarStateService.isCollapsed;
|
||||
readonly showSidebarToggle = computed(
|
||||
() => this.selectedContentType() === 'live' && this.hasLive()
|
||||
);
|
||||
@@ -460,11 +456,7 @@ export class UnifiedCollectionPageComponent implements AfterContentInit {
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
const next: LiveSidebarState = this.isSidebarCollapsed()
|
||||
? 'expanded'
|
||||
: 'collapsed';
|
||||
this.liveSidebarState.set(next);
|
||||
persistLiveSidebarState(next);
|
||||
this.liveSidebarStateService.toggle();
|
||||
}
|
||||
|
||||
setFavSortMode(mode: FavoritesChannelSortMode): void {
|
||||
|
||||
@@ -13,6 +13,7 @@ export * from './lib/portal-collection-context.service';
|
||||
export * from './lib/portal-collection-items';
|
||||
export * from './lib/live-epg-panel-state';
|
||||
export * from './lib/live-sidebar-state';
|
||||
export * from './lib/live-layout-sidebar-state.service';
|
||||
export * from './lib/keyboard';
|
||||
export * from './lib/remote-channel-navigation';
|
||||
export * from './lib/workspace-header-context.service';
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { computed, Injectable, signal } from '@angular/core';
|
||||
import {
|
||||
LiveSidebarState,
|
||||
persistLiveSidebarState,
|
||||
restoreLiveSidebarState,
|
||||
} from './live-sidebar-state';
|
||||
|
||||
/**
|
||||
* Shared collapse state for the live-TV sidebar across the workspace shell
|
||||
* categories rail (Xtream/Stalker), the inline channels rail, and the
|
||||
* unified-collection live tab. A single signal keeps all surfaces in sync
|
||||
* within a session; localStorage persistence is delegated to the existing
|
||||
* `live-sidebar-state` helpers so the storage key stays unchanged.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LiveLayoutSidebarStateService {
|
||||
private readonly _state = signal<LiveSidebarState>(
|
||||
restoreLiveSidebarState()
|
||||
);
|
||||
readonly state = this._state.asReadonly();
|
||||
readonly isCollapsed = computed(() => this._state() === 'collapsed');
|
||||
|
||||
toggle(): void {
|
||||
this.setState(this.isCollapsed() ? 'expanded' : 'collapsed');
|
||||
}
|
||||
|
||||
setState(state: LiveSidebarState): void {
|
||||
this._state.set(state);
|
||||
persistLiveSidebarState(state);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -88,7 +88,7 @@
|
||||
}
|
||||
|
||||
<div class="content-container">
|
||||
@if (stalkerStore.selectedCategoryId() && isSidebarCollapsed()) {
|
||||
@if (isSidebarCollapsed()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
|
||||
+6
-14
@@ -44,17 +44,15 @@ import {
|
||||
WebPlayerViewComponent,
|
||||
} from 'shared-portals';
|
||||
import {
|
||||
LiveLayoutSidebarStateService,
|
||||
PORTAL_PLAYER,
|
||||
createLogger,
|
||||
getAdjacentChannelItem,
|
||||
getChannelItemByNumber,
|
||||
isTypingInInput,
|
||||
LiveEpgPanelState,
|
||||
LiveSidebarState,
|
||||
persistLiveEpgPanelState,
|
||||
persistLiveSidebarState,
|
||||
restoreLiveEpgPanelState,
|
||||
restoreLiveSidebarState,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
import { PortalEmptyStateComponent } from '@iptvnator/portal/shared/ui';
|
||||
import {
|
||||
@@ -91,6 +89,9 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
|
||||
private readonly portalPlayer = inject(PORTAL_PLAYER);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private readonly translate = inject(TranslateService);
|
||||
private readonly liveSidebarStateService = inject(
|
||||
LiveLayoutSidebarStateService
|
||||
);
|
||||
private readonly logger = createLogger('StalkerLiveStream');
|
||||
readonly selectedCategoryTitle = this.stalkerStore.getSelectedCategoryName;
|
||||
|
||||
@@ -151,12 +152,7 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
|
||||
readonly isLiveEpgPanelCollapsed = computed(
|
||||
() => this.liveEpgPanelState() === 'collapsed'
|
||||
);
|
||||
readonly liveSidebarState = signal<LiveSidebarState>(
|
||||
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'])
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
}
|
||||
<div class="content-container">
|
||||
@if (selectedCategoryId() && isSidebarCollapsed()) {
|
||||
@if (isSidebarCollapsed()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
|
||||
+27
@@ -7,6 +7,8 @@ import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import {
|
||||
LIVE_EPG_PANEL_STATE_STORAGE_KEY,
|
||||
LIVE_SIDEBAR_STATE_STORAGE_KEY,
|
||||
LiveLayoutSidebarStateService,
|
||||
PORTAL_PLAYER,
|
||||
ResizableDirective,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
@@ -162,6 +164,7 @@ describe('LiveStreamLayoutComponent', () => {
|
||||
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,
|
||||
|
||||
+6
-14
@@ -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<LiveSidebarState>(
|
||||
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'])
|
||||
|
||||
+1
@@ -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
@@ -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 {
|
||||
|
||||
+71
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+26
-1
@@ -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()
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user