mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
Implements the Claude Design "Settings concept" handoff. Navigation: the sidebar is a 20px "Settings" title over App, Library, Devices and Data groups, with About pinned to the footer beside the installed version and an update badge. The active item is a soft fill without a border. Esc leaves settings like the header Back. Reset is no longer a page: its one destructive action is the last card of "Backup & data". Pages: a title and one-line subtitle, then rows grouped into titled cards with one right-aligned control column. Selects are compact without floating labels, checkboxes are switches, segmented controls are pills, and descriptions are capped at 56ch. "Show subtitles" moves to Playback, the Embedded MPV note becomes a callout under the player select (only while it is selected), and the TMDB attribution becomes the About footer. EPG: Add and Refresh all sit in the Sources header, the empty state has its own Add, the format examples are one line, Clear EPG data is its own row and the offset is a stepper. About: version, update state and channel share one card; the nightly warning is a callout shown only while Nightly is selected, with a shortcut to the backup page; the support buttons are neutral with coloured icons. Save model: the save bar stays (design option B) and now floats over the content column, counts the staged changes, answers Cmd/Ctrl+S, and marks pages with staged edits in the sidebar. A saved change that waits for a restart shows a dismissible notice. Rows stack under 600px of pane width (the page is a size container, so the persistent sidebar is accounted for). Page-specific styles moved into the About, EPG, Backup and Remote control section stylesheets to stay within the component style budget. New SETTINGS keys are translated in all 19 locales and NAV_RESET is removed; brand names and loanwords that stay English are in the identical-value baseline. E2E: Material slide toggles report aria-checked after the next render, so both suites toggle through a setSwitch helper; the settings nav is a navigation landmark, so the Dashboard rail link is scoped to the rail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
124 lines
4.3 KiB
TypeScript
124 lines
4.3 KiB
TypeScript
import { CommonModule } from '@angular/common';
|
|
import {
|
|
Component,
|
|
input,
|
|
output,
|
|
ViewEncapsulation,
|
|
ChangeDetectionStrategy,
|
|
} from '@angular/core';
|
|
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { MatInputModule } from '@angular/material/input';
|
|
import { MatSelectModule } from '@angular/material/select';
|
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
import { TranslateModule } from '@ngx-translate/core';
|
|
import {
|
|
StreamFormat,
|
|
VideoPlayer,
|
|
reportsPlaybackFailures,
|
|
} from '@iptvnator/shared/interfaces';
|
|
import { SettingsPlayerOption } from './settings.models';
|
|
import { markSectionForCheckOnFormEvents } from './settings-section-form-render';
|
|
|
|
@Component({
|
|
selector: 'app-settings-playback-section',
|
|
imports: [
|
|
CommonModule,
|
|
MatButtonModule,
|
|
MatFormFieldModule,
|
|
MatIconModule,
|
|
MatInputModule,
|
|
MatSelectModule,
|
|
MatSlideToggleModule,
|
|
MatTooltipModule,
|
|
ReactiveFormsModule,
|
|
TranslateModule,
|
|
],
|
|
templateUrl: './settings-playback-section.component.html',
|
|
encapsulation: ViewEncapsulation.None,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
styles: [':host { display: contents; }'],
|
|
})
|
|
export class SettingsPlaybackSectionComponent {
|
|
readonly mpvPlayerArgumentsPlaceholder = [
|
|
'--ontop',
|
|
'--autofit=640x360',
|
|
'--geometry=+80+80',
|
|
].join('\n');
|
|
readonly vlcPlayerArgumentsPlaceholder = [
|
|
'--video-on-top',
|
|
'--width=640',
|
|
'--height=360',
|
|
].join('\n');
|
|
|
|
readonly form = input.required<FormGroup>();
|
|
|
|
constructor() {
|
|
// Parent patches (Discard, backup import) change the form outside
|
|
// this OnPush section's events.
|
|
markSectionForCheckOnFormEvents(this.form);
|
|
}
|
|
|
|
readonly players = input.required<SettingsPlayerOption[]>();
|
|
readonly streamFormatEnum = input.required<typeof StreamFormat>();
|
|
readonly isDesktop = input(false);
|
|
/** Frame-copy embedded MPV engine is possible on this machine */
|
|
readonly frameCopyAvailable = input(false);
|
|
/** Frame-copy engine is what the current app run actually uses */
|
|
readonly frameCopyActive = input(false);
|
|
readonly supportsManagedExternalPlayers = input(false);
|
|
readonly supportsExternalPlayerPathSettings = input(false);
|
|
/**
|
|
* Cross-playlist movie matching is Electron-only, so the auto-failover
|
|
* toggle would control nothing in the PWA.
|
|
*/
|
|
readonly supportsVodMultiSource = input(false);
|
|
readonly selectRecordingFolder = output<void>();
|
|
|
|
isWebPlayerSelected(): boolean {
|
|
return reportsPlaybackFailures(this.form().value.player);
|
|
}
|
|
|
|
/**
|
|
* The fullscreen channel panel lives inside the fullscreen element the
|
|
* shared controls own (the player view host). The legacy vendor chrome
|
|
* fullscreens the engine's own element and external MPV/VLC own their
|
|
* own window, so in both cases the toggle would control nothing.
|
|
* Embedded MPV always renders the shared controls.
|
|
*/
|
|
supportsFullscreenChannelPanel(): boolean {
|
|
const value = this.form().value;
|
|
return (
|
|
(this.isWebPlayerSelected() &&
|
|
value.webPlayerSharedControls !== false) ||
|
|
value.player === VideoPlayer.EmbeddedMpv
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The Up next card is part of the shared controls. Embedded MPV mounts
|
|
* them only under the frame-copy engine; the native-view engine keeps
|
|
* its own dock, where the toggle would control nothing.
|
|
*/
|
|
supportsUpNextCard(): boolean {
|
|
const value = this.form().value;
|
|
return (
|
|
(this.isWebPlayerSelected() &&
|
|
value.webPlayerSharedControls !== false) ||
|
|
(value.player === VideoPlayer.EmbeddedMpv && this.frameCopyActive())
|
|
);
|
|
}
|
|
|
|
isExternalPlayerSelected(): boolean {
|
|
const player = this.form().value.player;
|
|
return player === VideoPlayer.MPV || player === VideoPlayer.VLC;
|
|
}
|
|
|
|
isEmbeddedMpvSelected(): boolean {
|
|
return this.form().value.player === VideoPlayer.EmbeddedMpv;
|
|
}
|
|
}
|