mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
Merge remote-tracking branch 'origin/master' into wt/pr-1830
This commit is contained in:
commit
e7e61da5f3
116 files changed
+4519
-554
No files matched your search
+1
-1
@@ -45,7 +45,7 @@ interface WorkspaceCommandGroupSection {
|
||||
selector: 'app-workspace-command-palette',
|
||||
imports: [MatDialogModule, MatIconModule, TranslatePipe],
|
||||
templateUrl: './workspace-command-palette.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-command-palette.component.scss',
|
||||
})
|
||||
export class WorkspaceCommandPaletteComponent implements AfterViewInit {
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ import { WorkspaceShellContextDrawerService } from '@iptvnator/workspace/shell/u
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-context-panel.component.scss',
|
||||
})
|
||||
export class WorkspaceCollectionContextPanelComponent {
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ interface WorkspaceCategoryLike {
|
||||
WorkspaceContextErrorViewComponent,
|
||||
],
|
||||
templateUrl: './workspace-context-panel.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-context-panel.component.scss',
|
||||
})
|
||||
export class WorkspaceContextPanelComponent {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings
|
||||
selector: 'app-workspace-settings-context-panel',
|
||||
imports: [MatIconModule, RouterLink, RouterLinkActive, TranslateModule],
|
||||
styleUrls: ['./workspace-settings-context-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<h2 class="panel-title">{{ 'SETTINGS.TITLE' | translate }}</h2>
|
||||
<div class="settings-panel-body">
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export interface WorkspaceKeyboardShortcutsDialogData {
|
||||
selector: 'app-workspace-keyboard-shortcuts-dialog',
|
||||
imports: [MatButtonModule, MatDialogModule, MatIconModule, TranslatePipe],
|
||||
templateUrl: './workspace-keyboard-shortcuts-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-keyboard-shortcuts-dialog.component.scss',
|
||||
})
|
||||
export class WorkspaceKeyboardShortcutsDialogComponent {
|
||||
|
||||
+9
-1
@@ -7,8 +7,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 56px;
|
||||
// The shell's header band: 56px, taller on macOS when zoomed out.
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
// macOS: app zoom does not scale the traffic lights, so zoomed out the
|
||||
// leading controls keep their 100 % distance from them (see the shell).
|
||||
padding-inline-start: max(24px, var(--workspace-header-lights-inset, 0px));
|
||||
border-bottom: 1px solid var(--mat-sys-outline-variant);
|
||||
background: var(--app-header-bg, var(--mat-sys-surface-container-low));
|
||||
app-region: drag;
|
||||
@@ -318,6 +322,10 @@
|
||||
@media (max-width: 780px) {
|
||||
.workspace-header {
|
||||
padding-inline: 10px;
|
||||
padding-inline-start: max(
|
||||
10px,
|
||||
var(--workspace-header-lights-inset, 0px)
|
||||
);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -1,10 +1,6 @@
|
||||
<!-- No brand mark: it linked to the page the first workspace link opens.
|
||||
The page's Back lives in the header's leading slot. -->
|
||||
<aside
|
||||
class="app-rail"
|
||||
[class.is-macos]="isMacOS()"
|
||||
[style.--rail-zoom-factor]="isMacOS() ? zoomFactor() : null"
|
||||
>
|
||||
<aside class="app-rail" [class.is-macos]="isMacOS()">
|
||||
<app-workspace-shell-rail-links [links]="workspaceLinks()" />
|
||||
|
||||
@if (primaryContextLinks().length > 0) {
|
||||
|
||||
+4
-5
@@ -29,13 +29,12 @@
|
||||
--rail-link-active-border: var(--app-selection-border);
|
||||
--rail-link-active-glow: var(--app-selection-glow);
|
||||
|
||||
// The traffic lights sit in the header band (56px, see the shell grid).
|
||||
// The first link starts below that band, where the content area and the
|
||||
// The traffic lights sit in the header band (see the shell grid). The
|
||||
// first link starts below that band, where the content area and the
|
||||
// dashboard hero start, so its hover surface never touches the lights.
|
||||
// App zoom scales CSS pixels but not the lights: zoomed out, the inset
|
||||
// keeps 48 window pixels (lights' bottom plus a gap) instead.
|
||||
// Zoomed out, the band grows to keep the lights inside it.
|
||||
&.is-macos {
|
||||
padding-top: max(56px, calc(48px / var(--rail-zoom-factor, 1)));
|
||||
padding-top: var(--workspace-header-band, 56px);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
-42
@@ -84,48 +84,6 @@ describe('WorkspaceShellRailComponent', () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('macOS zoom inset', () => {
|
||||
const setWindowWidths = (outer: number, inner: number) => {
|
||||
Object.defineProperty(window, 'outerWidth', {
|
||||
configurable: true,
|
||||
value: outer,
|
||||
});
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: inner,
|
||||
});
|
||||
};
|
||||
const zoomVar = () =>
|
||||
(
|
||||
fixture.nativeElement.querySelector('.app-rail') as HTMLElement
|
||||
).style.getPropertyValue('--rail-zoom-factor');
|
||||
|
||||
afterEach(() => setWindowWidths(1024, 1024));
|
||||
|
||||
it('publishes the page zoom factor on macOS and follows resize', () => {
|
||||
setWindowWidths(1200, 1200);
|
||||
fixture.componentRef.setInput('isMacOS', true);
|
||||
fixture.detectChanges();
|
||||
TestBed.tick();
|
||||
fixture.detectChanges();
|
||||
expect(zoomVar()).toBe('1');
|
||||
|
||||
// Zoomed out to 50%: the viewport holds twice the CSS pixels.
|
||||
setWindowWidths(1200, 2400);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
fixture.detectChanges();
|
||||
expect(zoomVar()).toBe('0.5');
|
||||
});
|
||||
|
||||
it('leaves the inset alone off macOS', () => {
|
||||
setWindowWidths(1200, 2400);
|
||||
fixture.detectChanges();
|
||||
TestBed.tick();
|
||||
fixture.detectChanges();
|
||||
expect(zoomVar()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('has no brand mark duplicating the first workspace link', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
+1
-34
@@ -1,12 +1,4 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { RouterLink } from '@angular/router';
|
||||
@@ -40,29 +32,4 @@ export class WorkspaceShellRailComponent {
|
||||
>(null);
|
||||
readonly railProviderClass = input('rail-context-region');
|
||||
readonly isSettingsRoute = input(false);
|
||||
|
||||
/**
|
||||
* The page zoom factor. App zoom (`webFrame.setZoomLevel`) scales CSS
|
||||
* pixels but not the native traffic lights, so the macOS top inset is
|
||||
* kept in window pixels when zoomed out. Chromium reports the window in
|
||||
* window pixels and the viewport in CSS pixels, and fires `resize` when
|
||||
* the zoom changes.
|
||||
*/
|
||||
protected readonly zoomFactor = signal(1);
|
||||
|
||||
constructor() {
|
||||
const view = inject(DOCUMENT).defaultView;
|
||||
effect((onCleanup) => {
|
||||
if (!this.isMacOS() || !view) return;
|
||||
const update = () =>
|
||||
this.zoomFactor.set(
|
||||
view.innerWidth > 0 && view.outerWidth > 0
|
||||
? view.outerWidth / view.innerWidth
|
||||
: 1
|
||||
);
|
||||
update();
|
||||
view.addEventListener('resize', update);
|
||||
onCleanup(() => view.removeEventListener('resize', update));
|
||||
});
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
ZOOM_LEVEL_MAX,
|
||||
ZOOM_LEVEL_MIN,
|
||||
ZOOM_LEVEL_STEP,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
pageZoomFactor,
|
||||
TrafficLightsClearanceDirective,
|
||||
trafficLightsClearance,
|
||||
} from './traffic-lights-clearance.directive';
|
||||
|
||||
@Component({
|
||||
template: `<div [appTrafficLightsClearance]="enabled()"></div>`,
|
||||
imports: [TrafficLightsClearanceDirective],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class HostComponent {
|
||||
readonly enabled = signal(true);
|
||||
}
|
||||
|
||||
function setWindowWidths(outer: number, inner: number): void {
|
||||
Object.defineProperty(window, 'outerWidth', {
|
||||
configurable: true,
|
||||
value: outer,
|
||||
});
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: inner,
|
||||
});
|
||||
}
|
||||
|
||||
describe('trafficLightsClearance', () => {
|
||||
it('matches the default layout at 100 %', () => {
|
||||
// x: the rail (60px) plus the header's padding (24px), where the
|
||||
// header content already starts; y: inside the 56px header band.
|
||||
expect(trafficLightsClearance(1)).toEqual({ x: 84, y: 48 });
|
||||
});
|
||||
|
||||
it('keeps the same window pixels at every supported zoom', () => {
|
||||
for (
|
||||
let level = ZOOM_LEVEL_MIN;
|
||||
level <= ZOOM_LEVEL_MAX;
|
||||
level += ZOOM_LEVEL_STEP
|
||||
) {
|
||||
const factor = 1.2 ** level;
|
||||
const clearance = trafficLightsClearance(factor);
|
||||
expect(clearance.x * factor).toBeCloseTo(84);
|
||||
expect(clearance.y * factor).toBeCloseTo(48);
|
||||
}
|
||||
});
|
||||
|
||||
it('outgrows the default inset and band when zoomed out', () => {
|
||||
const clearance = trafficLightsClearance(1.2 ** ZOOM_LEVEL_MIN);
|
||||
// Past the 60px rail: wider than the header's 24px padding.
|
||||
expect(clearance.x - 60).toBeGreaterThan(24);
|
||||
expect(clearance.y).toBeGreaterThan(56);
|
||||
});
|
||||
|
||||
it('treats an unusable factor as 100 %', () => {
|
||||
for (const factor of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
|
||||
expect(trafficLightsClearance(factor)).toEqual({ x: 84, y: 48 });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageZoomFactor', () => {
|
||||
it('divides window pixels by CSS pixels', () => {
|
||||
expect(pageZoomFactor({ outerWidth: 1200, innerWidth: 2400 })).toBe(
|
||||
0.5
|
||||
);
|
||||
expect(pageZoomFactor({ outerWidth: 1200, innerWidth: 1000 })).toBe(
|
||||
1.2
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to 100 % without a measured window', () => {
|
||||
expect(pageZoomFactor({ outerWidth: 0, innerWidth: 1200 })).toBe(1);
|
||||
expect(pageZoomFactor({ outerWidth: 1200, innerWidth: 0 })).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TrafficLightsClearanceDirective', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
||||
const clearance = () => {
|
||||
const style = (
|
||||
fixture.nativeElement.querySelector('div') as HTMLElement
|
||||
).style;
|
||||
return {
|
||||
x: style.getPropertyValue('--traffic-lights-clear-x'),
|
||||
y: style.getPropertyValue('--traffic-lights-clear-y'),
|
||||
};
|
||||
};
|
||||
const render = () => {
|
||||
fixture.detectChanges();
|
||||
TestBed.tick();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setWindowWidths(1200, 1200);
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
});
|
||||
|
||||
afterEach(() => setWindowWidths(1024, 1024));
|
||||
|
||||
it('publishes the clearance and follows zoom changes', () => {
|
||||
render();
|
||||
expect(clearance()).toEqual({ x: '84px', y: '48px' });
|
||||
|
||||
// Zoomed out to 50%: the viewport holds twice the CSS pixels.
|
||||
setWindowWidths(1200, 2400);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
fixture.detectChanges();
|
||||
expect(clearance()).toEqual({ x: '168px', y: '96px' });
|
||||
});
|
||||
|
||||
it('measures the zoom before the first render', () => {
|
||||
setWindowWidths(1200, 2400);
|
||||
fixture.detectChanges();
|
||||
expect(clearance()).toEqual({ x: '168px', y: '96px' });
|
||||
});
|
||||
|
||||
it('publishes nothing off macOS and stops listening when disabled', () => {
|
||||
render();
|
||||
const removeListener = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
fixture.componentInstance.enabled.set(false);
|
||||
render();
|
||||
expect(clearance()).toEqual({ x: '', y: '' });
|
||||
expect(removeListener).toHaveBeenCalledWith(
|
||||
'resize',
|
||||
expect.any(Function)
|
||||
);
|
||||
|
||||
setWindowWidths(1200, 2400);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
fixture.componentInstance.enabled.set(true);
|
||||
render();
|
||||
// Re-enabled, it measures the current zoom.
|
||||
expect(clearance()).toEqual({ x: '168px', y: '96px' });
|
||||
removeListener.mockRestore();
|
||||
});
|
||||
|
||||
it('stops listening when destroyed', () => {
|
||||
render();
|
||||
const removeListener = jest.spyOn(window, 'removeEventListener');
|
||||
fixture.destroy();
|
||||
expect(removeListener).toHaveBeenCalledWith(
|
||||
'resize',
|
||||
expect.any(Function)
|
||||
);
|
||||
removeListener.mockRestore();
|
||||
});
|
||||
});
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import {
|
||||
computed,
|
||||
Directive,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { MACOS_TRAFFIC_LIGHTS_POSITION } from '@iptvnator/shared/interfaces';
|
||||
|
||||
/**
|
||||
* The three buttons are 14 pt circles. macOS 26 spaces them 23 pt apart
|
||||
* (60 pt in all); earlier releases 20 pt (52 pt).
|
||||
*/
|
||||
const LIGHTS_WIDTH = 60;
|
||||
const LIGHTS_HEIGHT = 14;
|
||||
/**
|
||||
* Window points from the window's left edge to where header content may
|
||||
* start: the lights plus a gap. 84 is also where the header's content starts
|
||||
* at 100 % (the 60 px rail plus 24 px padding), so default zoom is unchanged.
|
||||
*/
|
||||
const CLEAR_X = MACOS_TRAFFIC_LIGHTS_POSITION.x + LIGHTS_WIDTH + 8;
|
||||
/** Window points from the top to where the content area may start. */
|
||||
const CLEAR_Y = MACOS_TRAFFIC_LIGHTS_POSITION.y + LIGHTS_HEIGHT + 14;
|
||||
|
||||
/** CSS pixels from the window's top-left corner that clear the lights. */
|
||||
export interface TrafficLightsClearance {
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The page zoom factor. Chromium reports the window in window pixels and the
|
||||
* viewport in CSS pixels, so their ratio is the app zoom.
|
||||
*/
|
||||
export function pageZoomFactor(
|
||||
view: Pick<Window, 'innerWidth' | 'outerWidth'>
|
||||
): number {
|
||||
return view.innerWidth > 0 && view.outerWidth > 0
|
||||
? view.outerWidth / view.innerWidth
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The clearance at `zoomFactor`. App zoom (`webFrame.setZoomLevel`) scales
|
||||
* CSS pixels but not the native buttons: zoomed out, the same window points
|
||||
* hold more CSS pixels.
|
||||
*/
|
||||
export function trafficLightsClearance(
|
||||
zoomFactor: number
|
||||
): TrafficLightsClearance {
|
||||
const factor =
|
||||
Number.isFinite(zoomFactor) && zoomFactor > 0 ? zoomFactor : 1;
|
||||
return { x: CLEAR_X / factor, y: CLEAR_Y / factor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the macOS traffic-light clearance on its host as
|
||||
* `--traffic-lights-clear-x` and `--traffic-lights-clear-y`, refreshed on
|
||||
* `resize` (Chromium fires it when the zoom changes). The shell grid, the
|
||||
* rail and the header read them; without them (off macOS) they fall back to
|
||||
* their default layout.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[appTrafficLightsClearance]',
|
||||
host: {
|
||||
'[style.--traffic-lights-clear-x]': 'clearX()',
|
||||
'[style.--traffic-lights-clear-y]': 'clearY()',
|
||||
},
|
||||
})
|
||||
export class TrafficLightsClearanceDirective {
|
||||
/** True where the native traffic lights overlay the window (macOS). */
|
||||
readonly enabled = input(false, { alias: 'appTrafficLightsClearance' });
|
||||
|
||||
private readonly view = inject(DOCUMENT).defaultView;
|
||||
private readonly zoomFactor = signal(
|
||||
this.view ? pageZoomFactor(this.view) : 1
|
||||
);
|
||||
private readonly clearance = computed(() =>
|
||||
this.enabled() ? trafficLightsClearance(this.zoomFactor()) : null
|
||||
);
|
||||
protected readonly clearX = computed(() => toPx(this.clearance()?.x));
|
||||
protected readonly clearY = computed(() => toPx(this.clearance()?.y));
|
||||
|
||||
constructor() {
|
||||
effect((onCleanup) => {
|
||||
const view = this.view;
|
||||
if (!this.enabled() || !view) return;
|
||||
const update = () => this.zoomFactor.set(pageZoomFactor(view));
|
||||
update();
|
||||
view.addEventListener('resize', update);
|
||||
onCleanup(() => view.removeEventListener('resize', update));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toPx(value: number | undefined): string | null {
|
||||
return value === undefined ? null : `${value}px`;
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
<div class="workspace-shell" appPlaylistDropZone #dropZone="playlistDropZone">
|
||||
<div
|
||||
class="workspace-shell"
|
||||
appPlaylistDropZone
|
||||
#dropZone="playlistDropZone"
|
||||
[appTrafficLightsClearance]="facade.isMacOS"
|
||||
>
|
||||
<app-playlist-drop-overlay [state]="dropZone.overlayState()" />
|
||||
@if (facade.showXtreamImportOverlay()) {
|
||||
<app-workspace-shell-import-overlay />
|
||||
|
||||
+21
-3
@@ -6,9 +6,14 @@
|
||||
}
|
||||
|
||||
.workspace-shell {
|
||||
// The header band. On macOS it also holds the traffic lights, which app
|
||||
// zoom does not scale: zoomed out, it stays tall enough to contain them
|
||||
// (`TrafficLightsClearanceDirective` publishes the clearance), so no
|
||||
// content starts beside them. The macOS rail starts its first link here.
|
||||
--workspace-header-band: max(56px, var(--traffic-lights-clear-y, 0px));
|
||||
display: grid;
|
||||
grid-template-columns: 60px 1fr;
|
||||
grid-template-rows: 56px minmax(0, 1fr) auto;
|
||||
grid-template-rows: var(--workspace-header-band) minmax(0, 1fr) auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
@@ -27,6 +32,12 @@ app-workspace-shell-header {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
min-width: 0;
|
||||
// The leading inset that keeps the header's controls clear of the macOS
|
||||
// traffic lights: their clearance minus the rail column. It equals the
|
||||
// header's 24px padding at 100 % zoom and is negative off macOS.
|
||||
--workspace-header-lights-inset: calc(
|
||||
var(--traffic-lights-clear-x, 0px) - 60px
|
||||
);
|
||||
}
|
||||
|
||||
.workspace-body {
|
||||
@@ -72,11 +83,16 @@ app-workspace-shell-context-sidebar {
|
||||
box-shadow: -4px 8px 20px var(--app-content-shadow, rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 780px) {
|
||||
.workspace-shell {
|
||||
grid-template-columns: 56px 1fr;
|
||||
grid-template-rows: 56px 1fr auto;
|
||||
grid-template-rows: var(--workspace-header-band) 1fr auto;
|
||||
}
|
||||
|
||||
app-workspace-shell-header {
|
||||
--workspace-header-lights-inset: calc(
|
||||
var(--traffic-lights-clear-x, 0px) - 56px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +107,11 @@ app-workspace-shell-context-sidebar {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
// Below the rail's row, the header never meets the traffic lights.
|
||||
app-workspace-shell-header {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
--workspace-header-lights-inset: 0px;
|
||||
}
|
||||
|
||||
.workspace-body {
|
||||
|
||||
+14
@@ -18,6 +18,7 @@ import {
|
||||
WorkspacePortalContext,
|
||||
WorkspaceShellContextPanel,
|
||||
} from '@iptvnator/workspace/shell/util';
|
||||
import { TrafficLightsClearanceDirective } from './traffic-lights-clearance.directive';
|
||||
import { WorkspaceShellComponent } from './workspace-shell.component';
|
||||
import {
|
||||
WorkspaceHeaderBulkAction,
|
||||
@@ -270,6 +271,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
TrafficLightsClearanceDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -307,6 +309,14 @@ describe('WorkspaceShellComponent', () => {
|
||||
By.directive(MockWorkspaceShellHeaderComponent)
|
||||
).componentInstance as MockWorkspaceShellHeaderComponent;
|
||||
expect(header.activeDownloadsCount()).toBe(3);
|
||||
// macOS: the rail and header read the traffic-light clearance.
|
||||
expect(
|
||||
(
|
||||
fixture.nativeElement.querySelector(
|
||||
'.workspace-shell'
|
||||
) as HTMLElement
|
||||
).style.getPropertyValue('--traffic-lights-clear-x')
|
||||
).toMatch(/px$/);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'app-workspace-shell-context-sidebar'
|
||||
@@ -357,6 +367,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
TrafficLightsClearanceDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -434,6 +445,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
TrafficLightsClearanceDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -507,6 +519,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
TrafficLightsClearanceDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -606,6 +619,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
TrafficLightsClearanceDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ import { WorkspaceShellContextSidebarComponent } from './components/workspace-sh
|
||||
import { WorkspaceShellHeaderComponent } from './components/workspace-shell-header/workspace-shell-header.component';
|
||||
import { WorkspaceShellImportOverlayComponent } from './components/workspace-shell-import-overlay/workspace-shell-import-overlay.component';
|
||||
import { WorkspaceShellRailComponent } from './components/workspace-shell-rail/workspace-shell-rail.component';
|
||||
import { TrafficLightsClearanceDirective } from './traffic-lights-clearance.directive';
|
||||
import { WorkspaceShellFacade } from './services/workspace-shell.facade';
|
||||
import { WorkspaceShellXtreamImportService } from './services/workspace-shell-xtream-import.service';
|
||||
import { WorkspaceShellCommandPaletteService } from './services/workspace-shell-command-palette.service';
|
||||
@@ -42,6 +43,7 @@ import { LIVE_CATEGORIES_POPOVER } from '@iptvnator/portal/shared/util';
|
||||
PlaylistDropOverlayComponent,
|
||||
PlaylistDropZoneDirective,
|
||||
RouterOutlet,
|
||||
TrafficLightsClearanceDirective,
|
||||
WorkspaceShellContextSidebarComponent,
|
||||
WorkspaceShellHeaderComponent,
|
||||
WorkspaceShellImportOverlayComponent,
|
||||
@@ -49,7 +51,7 @@ import { LIVE_CATEGORIES_POPOVER } from '@iptvnator/portal/shared/util';
|
||||
],
|
||||
templateUrl: './workspace-shell.component.html',
|
||||
styleUrl: './workspace-shell.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
WorkspaceShellFacade,
|
||||
WorkspaceShellRouteStateService,
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ interface SortOption {
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './workspace-sources.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-sources.component.scss',
|
||||
})
|
||||
export class WorkspaceSourcesComponent {
|
||||
|
||||
Reference in new issue
Block a user