Files
iptvnator/apps/web/src/app/settings/settings.component.html
T
4gray 5fa4423400 feat(settings): enable desktop remote control and change port
Update default remote control port from 3000 to 8765 in settings
store and component initialization to avoid conflicts with common
development ports and align with the desktop/tauri environment.

Unhide and scope the remote control UI to desktop builds only:
- Restore the remote control block in settings template and guard it
  with @if (isDesktop) so the checkbox and description render for
  desktop users (previously commented out as not ported).
- Keep the remote control settings section conditionally visible
  when the remoteControl form value is true.

These changes ensure desktop users can configure and enable the
remote control feature with a non-conflicting default port.
2025-11-22 11:26:22 +01:00

429 lines
17 KiB
HTML

@if (!isDialog) {
<app-header
[title]="'SETTINGS.TITLE' | translate"
[subtitle]="
('SETTINGS.SUBTITLE' | translate) +
'. ' +
('SETTINGS.DESCRIPTION' | translate)
"
/>
} @else {
<h2 mat-dialog-title>{{ 'SETTINGS.TITLE' | translate }}</h2>
}
<div class="settings-container" data-test-id="settings-container">
<form [formGroup]="settingsForm" (ngSubmit)="onSubmit()" novalidate>
@if (isDesktop) {
<ng-container formArrayName="epgUrl">
<div class="row">
<div class="column">
{{ 'SETTINGS.EPG_URL_LABEL' | translate }}
<p>Add single or multiple URLs as EPG sources</p>
</div>
<div class="column">
@for (_ of epgUrl.controls; track _; let i = $index) {
<div style="display: flex">
<mat-form-field
appearance="outline"
class="full-width"
>
<mat-label>{{
'SETTINGS.EPG_URL_PLACEHOLDER'
| translate
}}</mat-label>
<input
matInput
type="url"
[formControlName]="i"
#epgField
/>
<mat-error>{{
'SETTINGS.EPG_URL_ERROR' | translate
}}</mat-error>
</mat-form-field>
<button
matLine
mat-icon-button
color="accent"
[matTooltip]="
'SETTINGS.REFRESH_EPG' | translate
"
type="button"
[disabled]="epgField.value === ''"
(click)="refreshEpg(epgUrl.value[i])"
>
<mat-icon>refresh</mat-icon>
</button>
<button
mat-icon-button
matLine
color="accent"
[matTooltip]="
'SETTINGS.REFRESH_EPG' | translate
"
type="button"
(click)="removeEpgSource(i)"
>
<mat-icon>remove</mat-icon>
</button>
</div>
}
<button
mat-button
color="accent"
type="button"
(click)="addEpgSource()"
>
{{ 'SETTINGS.ADD_EPG_SOURCE' | translate }}
</button>
</div>
</div>
<mat-divider />
</ng-container>
}
<div class="row">
<div class="column">
{{ 'SETTINGS.VIDEO_PLAYER_LABEL' | translate }}
<p>{{ 'SETTINGS.VIDEO_PLAYER_DESCRIPTION' | translate }}</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label>{{
'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate
}}</mat-label>
<mat-select
formControlName="player"
data-test-id="select-video-player"
>
@for (player of players; track player) {
<mat-option
[value]="player.id"
[attr.data-test-id]="player.id"
>{{ player.label }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
@if (settingsForm.value.player === 'mpv') {
<div class="row">
<div class="column">
{{ 'SETTINGS.MPV_PLAYER_PATH_LABEL' | translate }}
<p>
{{ 'SETTINGS.MPV_PLAYER_PATH_DESCRIPTION' | translate }}
</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label for="mpvPlayerPath">{{
'SETTINGS.MPV_PLAYER_PATH' | translate
}}</mat-label>
<input
matInput
type="text"
id="mpvPlayerPath"
formControlName="mpvPlayerPath"
/>
</mat-form-field>
</div>
</div>
<div class="row">
<div class="column">
{{ 'SETTINGS.MPV_REUSE_INSTANCE_LABEL' | translate }}
<p>
{{
'SETTINGS.MPV_REUSE_INSTANCE_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="column margin-right">
<mat-checkbox
class="column"
formControlName="mpvReuseInstance"
></mat-checkbox>
</div>
</div>
}
@if (settingsForm.value.player === 'vlc') {
<div class="row">
<div class="column">
{{ 'SETTINGS.VLC_PLAYER_PATH_LABEL' | translate }}
<p>
{{ 'SETTINGS.VLC_PLAYER_PATH_DESCRIPTION' | translate }}
</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label for="vlcPlayerPath">{{
'SETTINGS.VLC_PLAYER_PATH' | translate
}}</mat-label>
<input
matInput
type="text"
id="vlcPlayerPath"
formControlName="vlcPlayerPath"
/>
</mat-form-field>
</div>
</div>
}
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.STREAM_FORMAT' | translate }}
<p>{{ 'SETTINGS.STREAM_FORMAT_DESCRIPTION' | translate }}</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label>{{
'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate
}}</mat-label>
<mat-select
formControlName="streamFormat"
data-test-id="select-stream-format"
>
@for (
streamFormat of streamFormatEnum | keyvalue;
track streamFormat
) {
<mat-option
[value]="streamFormat.value"
[attr.data-test-id]="streamFormat.value"
>{{ streamFormat.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.LANGUAGE' | translate }}
<p>{{ 'SETTINGS.LANGUAGE_DESCRIPTION' | translate }}</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label>{{
'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate
}}</mat-label>
<mat-select
formControlName="language"
data-test-id="select-language"
>
@for (
language of languageEnum | keyvalue;
track language
) {
<mat-option
[value]="language.value"
[attr.data-test-id]="language.value"
>{{
'LANGUAGES.' + language.key | translate
}}</mat-option
>
}
</mat-select>
</mat-form-field>
</div>
</div>
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.THEME' | translate }}
<p>{{ 'SETTINGS.THEME_DESCRIPTION' | translate }}</p>
</div>
<div class="column">
<mat-form-field appearance="outline">
<mat-label>{{
'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate
}}</mat-label>
<mat-select
formControlName="theme"
data-test-id="select-theme"
>
@for (theme of themeEnum | keyvalue; track theme) {
<mat-option
[value]="theme.value"
[attr.data-test-id]="theme.value"
>{{
'THEMES.' + theme.value | translate
}}</mat-option
>
}
</mat-select>
</mat-form-field>
</div>
</div>
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.SHOW_CAPTIONS' | translate }}
<p>{{ 'SETTINGS.SHOW_CAPTIONS_DESCRIPTION' | translate }}</p>
</div>
<div class="column margin-right">
<mat-checkbox
class="column"
formControlName="showCaptions"
></mat-checkbox>
</div>
</div>
@if (isDesktop) {
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.REMOTE_CONTROL' | translate }} (m3u)
<p>
{{ 'SETTINGS.REMOTE_CONTROL_DESCRIPTION' | translate }}
</p>
</div>
<div class="column margin-right">
<mat-checkbox
class="column"
formControlName="remoteControl"
></mat-checkbox>
</div>
</div>
}
@if (settingsForm.value.remoteControl === true) {
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.REMOTE_CONTROL_PORT' | translate }}
<p>
{{
'SETTINGS.REMOTE_CONTROL_PORT_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="column margin-right">
<mat-form-field appearance="outline">
<mat-label>Port (1-65535)</mat-label>
<input
matInput
type="number"
id="remoteControlPort"
formControlName="remoteControlPort"
min="1"
max="65535"
step="1"
/>
@if (settingsForm.get('remoteControlPort')?.hasError('required')) {
<mat-error>{{ 'SETTINGS.PORT_REQUIRED' | translate }}</mat-error>
}
@if (settingsForm.get('remoteControlPort')?.hasError('min')) {
<mat-error>{{ 'SETTINGS.PORT_MIN' | translate }}</mat-error>
}
@if (settingsForm.get('remoteControlPort')?.hasError('max')) {
<mat-error>{{ 'SETTINGS.PORT_MAX' | translate }}</mat-error>
}
@if (settingsForm.get('remoteControlPort')?.hasError('pattern')) {
<mat-error>{{ 'SETTINGS.PORT_PATTERN' | translate }}</mat-error>
}
</mat-form-field>
</div>
</div>
}
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.VERSION' | translate }}
<p>{{ 'SETTINGS.VERSION_DESCRIPTION' | translate }}</p>
</div>
<div class="column">
{{ version }} <br />
{{ updateMessage }}
</div>
</div>
<mat-divider />
<div class="row">
<div class="column">
{{ 'SETTINGS.IMPORT_EXPORT_DATA' | translate }}
<p>
{{ 'SETTINGS.IMPORT_EXPORT_DATA_DESCRIPTION' | translate }}
</p>
</div>
<div class="column">
<button
mat-button
(click)="
$event.preventDefault();
$event.stopPropagation();
importData()
"
>
{{ 'SETTINGS.IMPORT_DATA' | translate }}
</button>
<button
mat-button
(click)="
$event.preventDefault();
$event.stopPropagation();
exportData()
"
>
{{ 'SETTINGS.EXPORT_DATA' | translate }}
</button>
</div>
</div>
<div class="row">
<div class="column">
{{ 'SETTINGS.REMOVE_ALL' | translate }}
<p>
{{ 'SETTINGS.REMOVE_ALL_DESCRIPTION' | translate }}
</p>
</div>
<div class="column">
<button
mat-button
(click)="
$event.preventDefault();
$event.stopPropagation();
removeAll()
"
color="warn"
>
{{ 'SETTINGS.REMOVE_ALL_BUTTON' | translate }}
</button>
</div>
</div>
@if (isPwa) {
<div class="row">
{{ 'SETTINGS.EPG_NOTE' | translate }}
&nbsp;<a href="https://github.com/4gray/iptvnator/releases">{{
'SETTINGS.EPG_NOTE_URL_TEXT' | translate
}}</a>
</div>
}
<mat-divider />
<div class="action-buttons" align="end">
<button
mat-button
color="accent"
type="reset"
(click)="backToHome()"
data-test-id="back-to-home"
>
<mat-icon>chevron_left</mat-icon>
{{ 'SETTINGS.BACK_TO_HOME' | translate }}
</button>
<button
mat-button
color="accent"
type="submit"
[disabled]="settingsForm.pristine || !settingsForm.valid"
data-test-id="save-settings"
>
<mat-icon>save</mat-icon>
{{ 'SETTINGS.SAVE_CHANGES' | translate }}
</button>
</div>
</form>
</div>