mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(ui): correct imports, guard checks, logging and settings handling
This commit is contained in:
1 parent
516a376dc0
commit
ccbb08cfb4
21 files changed
+240
-216
No files matched your search
@@ -8,6 +8,10 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
updatePlaylistFromFilePath: (filePath: string, title: string) =>
|
||||
ipcRenderer.invoke('update-playlist-from-file-path', filePath, title),
|
||||
openPlaylistFromFile: () => ipcRenderer.invoke('open-playlist-from-file'),
|
||||
saveFileDialog: (defaultPath: string, filters?: { name: string; extensions: string[] }[]) =>
|
||||
ipcRenderer.invoke('save-file-dialog', defaultPath, filters),
|
||||
writeFile: (filePath: string, content: string) =>
|
||||
ipcRenderer.invoke('write-file', filePath, content),
|
||||
setUserAgent: (userAgent: string, referer?: string) =>
|
||||
ipcRenderer.invoke('set-user-agent', userAgent, referer),
|
||||
openInMpv: (
|
||||
|
||||
@@ -7,7 +7,7 @@ import axios from 'axios';
|
||||
import { dialog, ipcMain } from 'electron';
|
||||
import { parse } from 'iptv-playlist-parser';
|
||||
import { createPlaylistObject, getFilenameFromUrl } from 'm3u-utils';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { AUTO_UPDATE_PLAYLISTS } from 'shared-interfaces';
|
||||
|
||||
export default class PlaylistEvents {
|
||||
@@ -102,3 +102,33 @@ ipcMain.handle(AUTO_UPDATE_PLAYLISTS, async (event, playlistUrls) => {
|
||||
console.log(`Auto-updating playlist from ${url}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('save-file-dialog', async (event, defaultPath, filters) => {
|
||||
try {
|
||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||
defaultPath,
|
||||
filters: filters || [
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
});
|
||||
|
||||
if (canceled || !filePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
console.error('Error showing save dialog:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('write-file', async (event, filePath, content) => {
|
||||
try {
|
||||
await writeFile(filePath, content, 'utf-8');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error writing file:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import {
|
||||
MPV_PLAYER_PATH,
|
||||
MPV_REUSE_INSTANCE,
|
||||
store,
|
||||
} from '../services/store.service';
|
||||
@@ -13,5 +12,9 @@ export default class SettingsEvents {
|
||||
|
||||
ipcMain.handle('SETTINGS_UPDATE', (_event, arg) => {
|
||||
console.log('Received SETTINGS_UPDATE with data:', arg);
|
||||
store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance);
|
||||
|
||||
// Only set values that are defined
|
||||
if (arg.mpvReuseInstance !== undefined) {
|
||||
store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance);
|
||||
}
|
||||
});
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Language,
|
||||
OPEN_FILE,
|
||||
Settings,
|
||||
SETTINGS_UPDATE,
|
||||
STORE_KEY,
|
||||
Theme,
|
||||
VIEW_ADD_PLAYLIST,
|
||||
@@ -120,13 +119,12 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
.getValueFromLocalStorage(STORE_KEY.Settings)
|
||||
.subscribe((settings: Settings) => {
|
||||
if (settings && Object.keys(settings).length > 0) {
|
||||
// Send settings to Electron main process
|
||||
if (window.electron) {
|
||||
window.electron.updateSettings(settings);
|
||||
}
|
||||
|
||||
// No need to send settings to Electron on init
|
||||
// Settings are stored in IndexedDB and loaded by the settings store
|
||||
// Only specific Electron settings (MPV/VLC paths) are sent when changed in settings component
|
||||
|
||||
this.translate.use(settings.language ?? this.DEFAULT_LANG);
|
||||
|
||||
|
||||
// Fetch EPG if URLs are configured
|
||||
if (
|
||||
window.electron &&
|
||||
|
||||
@@ -252,16 +252,20 @@ export class SettingsComponent implements OnInit {
|
||||
onSubmit(): void {
|
||||
this.settingsStore.updateSettings(this.settingsForm.value).then(() => {
|
||||
this.applyChangedSettings();
|
||||
|
||||
|
||||
if (window.electron) {
|
||||
window.electron.updateSettings(this.settingsForm.value);
|
||||
|
||||
|
||||
// Set player paths if using external players
|
||||
if (this.settingsForm.value.mpvPlayerPath) {
|
||||
window.electron.setMpvPlayerPath(this.settingsForm.value.mpvPlayerPath);
|
||||
window.electron.setMpvPlayerPath(
|
||||
this.settingsForm.value.mpvPlayerPath
|
||||
);
|
||||
}
|
||||
if (this.settingsForm.value.vlcPlayerPath) {
|
||||
window.electron.setVlcPlayerPath(this.settingsForm.value.vlcPlayerPath);
|
||||
window.electron.setVlcPlayerPath(
|
||||
this.settingsForm.value.vlcPlayerPath
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@if ((activeProcesses$ | async)?.length) {
|
||||
<!-- @if ((activeProcesses$ | async)?.length) {
|
||||
<div class="mpv-player-bar">
|
||||
@for (process of activeProcesses$ | async; track process.id) {
|
||||
<div class="player-item">
|
||||
@@ -24,20 +24,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<!-- <button
|
||||
mat-icon-button
|
||||
(click)="pauseStream(process.id)"
|
||||
title="Pause"
|
||||
>
|
||||
<mat-icon>pause</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="playStream(process.id)"
|
||||
title="Play"
|
||||
>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button> -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="closeStream(process.id)"
|
||||
@@ -50,3 +36,4 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
-->
|
||||
@@ -1,30 +1,19 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
MpvPlayerService,
|
||||
MpvProcess,
|
||||
} from '../../services/mpv-player.service';
|
||||
|
||||
@Component({
|
||||
imports: [CommonModule, MatIconModule, MatButtonModule],
|
||||
/* imports: [MatIcon, MatIconButton], */
|
||||
selector: 'app-mpv-player-bar',
|
||||
templateUrl: './mpv-player-bar.component.html',
|
||||
styleUrls: ['./mpv-player-bar.component.scss']
|
||||
styleUrls: ['./mpv-player-bar.component.scss'],
|
||||
})
|
||||
export class MpvPlayerBarComponent {
|
||||
activeProcesses$: Observable<MpvProcess[]>;
|
||||
|
||||
constructor(private mpvPlayerService: MpvPlayerService) {
|
||||
this.activeProcesses$ = this.mpvPlayerService.activeProcesses$;
|
||||
}
|
||||
/* private mpvPlayerService = inject(MpvPlayerService);
|
||||
|
||||
activeProcesses$ = this.mpvPlayerService.activeProcesses$;
|
||||
|
||||
async closeStream(processId: number): Promise<void> {
|
||||
await this.mpvPlayerService.closeStream(processId);
|
||||
}
|
||||
|
||||
} */
|
||||
/* async playStream(processId: number): Promise<void> {
|
||||
await this.mpvPlayerService.playStream(processId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
/* import { Injectable } from '@angular/core';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
@@ -75,3 +75,4 @@ export class MpvPlayerService {
|
||||
await invoke('close_mpv_process', { processId });
|
||||
}
|
||||
}
|
||||
*/
|
||||
Vendored
+8
@@ -21,6 +21,14 @@ declare global {
|
||||
title: string
|
||||
) => Promise<Playlist>;
|
||||
openPlaylistFromFile: () => Promise<Playlist>;
|
||||
saveFileDialog: (
|
||||
defaultPath: string,
|
||||
filters?: { name: string; extensions: string[] }[]
|
||||
) => Promise<string | null>;
|
||||
writeFile: (
|
||||
filePath: string,
|
||||
content: string
|
||||
) => Promise<{ success: boolean }>;
|
||||
setUserAgent: (userAgent: string, referer?: string) => void;
|
||||
openInMpv: (
|
||||
url: string,
|
||||
|
||||
@@ -125,13 +125,13 @@ export class PlaylistEffects {
|
||||
ofType(PlaylistActions.setActiveChannel),
|
||||
map((action) => {
|
||||
const { channel } = action;
|
||||
|
||||
|
||||
// Use modern EPG service to get channel programs
|
||||
const channelId = channel.tvg?.id || channel.name;
|
||||
if (channelId) {
|
||||
this.epgService.getChannelPrograms(channelId);
|
||||
}
|
||||
|
||||
|
||||
// Set user agent if specified on channel
|
||||
if (channel.http['user-agent']) {
|
||||
window.electron?.setUserAgent(
|
||||
|
||||
@@ -20,10 +20,7 @@ import moment from 'moment';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DataService, EpgService } from 'services';
|
||||
import {
|
||||
EpgChannel,
|
||||
EpgProgram,
|
||||
} from 'shared-interfaces';
|
||||
import { EpgChannel, EpgProgram } from 'shared-interfaces';
|
||||
import { EpgListItemComponent } from './epg-list-item/epg-list-item.component';
|
||||
|
||||
export interface EpgData {
|
||||
@@ -164,12 +161,12 @@ export class EpgListComponent implements OnInit {
|
||||
handleEpgData(programs: EpgProgram[]): void {
|
||||
this.timeNow = new Date().toISOString();
|
||||
this.dateToday = moment().format(DATE_FORMAT);
|
||||
|
||||
|
||||
// Dispatch EPG availability flag
|
||||
this.store.dispatch(
|
||||
setEpgAvailableFlag({ value: programs.length > 0 })
|
||||
);
|
||||
|
||||
|
||||
if (programs.length > 0) {
|
||||
this.setPlayingNow();
|
||||
} else {
|
||||
|
||||
@@ -88,11 +88,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
Hls &&
|
||||
Hls.isSupported()
|
||||
) {
|
||||
console.log(
|
||||
'... switching channel to ',
|
||||
channel.name,
|
||||
url
|
||||
);
|
||||
console.log('... switching channel to ', channel.name, url);
|
||||
this.hls = new Hls();
|
||||
this.hls.attachMedia(this.videoPlayer.nativeElement);
|
||||
this.hls.loadSource(url);
|
||||
|
||||
@@ -33,7 +33,7 @@ export class InfoOverlayComponent implements OnChanges {
|
||||
stop: any | undefined;
|
||||
|
||||
/** Timeout for the overlay visibility */
|
||||
currentTimeout: any;
|
||||
private currentTimeout: any;
|
||||
|
||||
/**
|
||||
* Calculates the necessary information for the visualization in the overview popup
|
||||
|
||||
@@ -159,17 +159,12 @@ export class MultiEpgContainerComponent
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Requesting EPG data:');
|
||||
console.log('- Skip:', this.channelsLowerRange);
|
||||
console.log('- Limit:', this.visibleChannels);
|
||||
|
||||
const response = await window.electron.getEpgChannelsByRange(
|
||||
this.channelsLowerRange,
|
||||
this.visibleChannels
|
||||
);
|
||||
|
||||
if (response && Array.isArray(response)) {
|
||||
console.log('Received channels:', response.length);
|
||||
this.originalEpgData = response;
|
||||
this.channels$.next(this.enrichProgramData());
|
||||
|
||||
|
||||
+11
-13
@@ -82,7 +82,7 @@
|
||||
<input matInput formControlName="count" />
|
||||
</mat-form-field>
|
||||
}
|
||||
@if (isTauri) {
|
||||
@if (isDesktop) {
|
||||
<mat-form-field class="full-width mb-10">
|
||||
<mat-label>{{
|
||||
'HOME.PLAYLISTS.INFO_DIALOG.USER_AGENT' | translate
|
||||
@@ -109,18 +109,16 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
}
|
||||
@if (isTauri && (playlist.url || playlist.filePath)) {
|
||||
<ng-container>
|
||||
<mat-checkbox class="full-width" formControlName="autoRefresh">
|
||||
{{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}
|
||||
</mat-checkbox>
|
||||
<p>
|
||||
{{
|
||||
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION'
|
||||
| translate
|
||||
}}
|
||||
</p>
|
||||
</ng-container>
|
||||
@if (isDesktop && (playlist.url || playlist.filePath)) {
|
||||
<mat-checkbox class="full-width" formControlName="autoRefresh">
|
||||
{{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}
|
||||
</mat-checkbox>
|
||||
<p>
|
||||
{{
|
||||
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION'
|
||||
| translate
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
|
||||
+14
-18
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { CommonModule, DatePipe } from '@angular/common';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
FormControl,
|
||||
@@ -8,16 +7,14 @@ import {
|
||||
UntypedFormGroup,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import { writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import * as PlaylistActions from 'm3u-state';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DatabaseService, PlaylistsService } from 'services';
|
||||
@@ -35,11 +32,10 @@ import { Playlist, PlaylistMeta } from 'shared-interfaces';
|
||||
],
|
||||
providers: [DatePipe],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatButton,
|
||||
MatCheckboxModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatIcon,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
TranslatePipe,
|
||||
@@ -55,7 +51,7 @@ export class PlaylistInfoComponent {
|
||||
private translate = inject(TranslateService);
|
||||
public playlistData = inject<Playlist & { id: string }>(MAT_DIALOG_DATA);
|
||||
|
||||
isTauri = !!window.electron;
|
||||
readonly isDesktop = !!window.electron;
|
||||
|
||||
/** Playlist object */
|
||||
playlist: Playlist & { id: string };
|
||||
@@ -168,20 +164,20 @@ export class PlaylistInfoComponent {
|
||||
this.playlistsService.getRawPlaylistById(this.playlist._id)
|
||||
);
|
||||
|
||||
if (this.isTauri) {
|
||||
if (this.isDesktop) {
|
||||
try {
|
||||
const savePath = await save({
|
||||
filters: [
|
||||
const savePath = await window.electron.saveFileDialog(
|
||||
`${this.playlist.title || 'exported'}.m3u8`,
|
||||
[
|
||||
{
|
||||
name: 'Playlist',
|
||||
extensions: ['m3u8'],
|
||||
extensions: ['m3u8', 'm3u'],
|
||||
},
|
||||
],
|
||||
defaultPath: `${this.playlist.title || 'exported'}.m3u8`,
|
||||
});
|
||||
]
|
||||
);
|
||||
|
||||
if (savePath) {
|
||||
await writeTextFile(savePath, playlistAsString);
|
||||
await window.electron.writeFile(savePath, playlistAsString);
|
||||
this.snackBar.open(
|
||||
this.translate.instant(
|
||||
'HOME.PLAYLISTS.INFO_DIALOG.PLAYLIST_EXPORT_SUCCESS'
|
||||
|
||||
+103
-89
@@ -1,56 +1,60 @@
|
||||
<mat-list-item (click)="playlistClicked.emit(item._id)">
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
*ngIf="item?.url"
|
||||
[matTooltip]="
|
||||
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item?.url
|
||||
"
|
||||
>cloud</mat-icon
|
||||
>
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
*ngIf="!item?.url && !item.serverUrl && !item.macAddress"
|
||||
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
|
||||
>folder</mat-icon
|
||||
>
|
||||
<div class="icon-container" *ngIf="item?.serverUrl" matListItemIcon>
|
||||
@if (item.url) {
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
[matTooltip]="'HOME.PLAYLISTS.XTREAM_PLAYLIST' | translate"
|
||||
>public</mat-icon
|
||||
[matTooltip]="
|
||||
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item.url
|
||||
"
|
||||
>cloud</mat-icon
|
||||
>
|
||||
<span
|
||||
class="status-dot"
|
||||
[class]="getStatusClass()"
|
||||
[matTooltip]="'Portal status: ' + portalStatus"
|
||||
></span>
|
||||
</div>
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
*ngIf="item?.macAddress"
|
||||
[matTooltip]="'HOME.PLAYLISTS.STALKER_PORTAL' | translate"
|
||||
>dashboard</mat-icon
|
||||
>
|
||||
}
|
||||
@if (!item.url && !item.serverUrl && !item.macAddress) {
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
|
||||
>folder</mat-icon
|
||||
>
|
||||
}
|
||||
@if (item.serverUrl) {
|
||||
<div class="icon-container" matListItemIcon>
|
||||
<mat-icon
|
||||
class="upload-type-icon"
|
||||
[matTooltip]="'HOME.PLAYLISTS.XTREAM_PLAYLIST' | translate"
|
||||
>public</mat-icon
|
||||
>
|
||||
<span
|
||||
class="status-dot"
|
||||
[class]="getStatusClass()"
|
||||
[matTooltip]="'Portal status: ' + portalStatus"
|
||||
[attr.aria-label]="'Portal status: ' + portalStatus"
|
||||
></span>
|
||||
</div>
|
||||
}
|
||||
@if (item.macAddress) {
|
||||
<mat-icon
|
||||
matListItemIcon
|
||||
class="upload-type-icon"
|
||||
[matTooltip]="'HOME.PLAYLISTS.STALKER_PORTAL' | translate"
|
||||
>dashboard</mat-icon
|
||||
>
|
||||
}
|
||||
<div matListItemTitle>
|
||||
{{ item.title || item.filename }}
|
||||
</div>
|
||||
<div matListItemLine class="meta">
|
||||
<ng-container
|
||||
*ngIf="!item.serverUrl && !item.portalUrl; else xtreamPlaylist"
|
||||
>
|
||||
@if (!item.serverUrl && !item.portalUrl) {
|
||||
{{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }}
|
||||
<ng-container *ngIf="item.importDate?.length > 3">
|
||||
@if (item.importDate.length > 3) {
|
||||
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
|
||||
{{ item.importDate | date }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="item.updateDate">
|
||||
}
|
||||
@if (item.updateDate) {
|
||||
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
|
||||
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="item.updateState === 2">
|
||||
}
|
||||
@if (item.updateState === 2) {
|
||||
<mat-icon
|
||||
[matTooltip]="
|
||||
'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate
|
||||
@@ -58,64 +62,74 @@
|
||||
[inline]="true"
|
||||
>warning</mat-icon
|
||||
>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #xtreamPlaylist>
|
||||
@if (item?.macAddress) {
|
||||
}
|
||||
} @else {
|
||||
@if (item.macAddress) {
|
||||
Stalker Portal
|
||||
} @else {
|
||||
Xtream Code Playlist
|
||||
}
|
||||
<ng-container *ngIf="item.importDate?.length > 3">
|
||||
@if (item.importDate.length > 3) {
|
||||
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
|
||||
{{ item.importDate | date }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="item.updateDate">
|
||||
}
|
||||
@if (item.updateDate) {
|
||||
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
|
||||
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<span matListItemMeta class="action-buttons" *ngIf="showActions">
|
||||
<button
|
||||
*ngIf="item.url || item.filePath"
|
||||
mat-icon-button
|
||||
class="refresh-btn"
|
||||
color="accent"
|
||||
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="item.serverUrl"
|
||||
mat-icon-button
|
||||
class="refresh-btn"
|
||||
color="accent"
|
||||
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REFRESH_XTREAM' | translate"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
class="edit-btn"
|
||||
(click)="$event.stopPropagation(); editPlaylistClicked.emit(item)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
@if (showActions()) {
|
||||
<span matListItemMeta class="action-buttons">
|
||||
@if (item.url || item.filePath) {
|
||||
<button
|
||||
mat-icon-button
|
||||
class="refresh-btn"
|
||||
color="accent"
|
||||
(click)="
|
||||
$event.stopPropagation(); refreshClicked.emit(item)
|
||||
"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@if (item.serverUrl) {
|
||||
<button
|
||||
mat-icon-button
|
||||
class="refresh-btn"
|
||||
color="accent"
|
||||
(click)="
|
||||
$event.stopPropagation(); refreshClicked.emit(item)
|
||||
"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REFRESH_XTREAM' | translate"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
class="delete-btn"
|
||||
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
class="edit-btn"
|
||||
(click)="
|
||||
$event.stopPropagation(); editPlaylistClicked.emit(item)
|
||||
"
|
||||
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
class="delete-btn"
|
||||
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</mat-list-item>
|
||||
+8
-2
@@ -4,7 +4,7 @@ import { MatListModule } from '@angular/material/list';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MockModule } from 'ng-mocks';
|
||||
import { DataService } from '../../../../../../services/src/lib/data.service';
|
||||
import { DataService } from 'services';
|
||||
import { PlaylistItemComponent } from './playlist-item.component';
|
||||
|
||||
describe('PlaylistItemComponent', () => {
|
||||
@@ -27,7 +27,13 @@ describe('PlaylistItemComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PlaylistItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.item = { title: 'Playlist', id: '1' } as any;
|
||||
component.item = {
|
||||
title: 'Playlist',
|
||||
_id: '1',
|
||||
count: 10,
|
||||
importDate: Date.now().toString(),
|
||||
autoRefresh: false,
|
||||
};
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
||||
+22
-20
@@ -1,9 +1,9 @@
|
||||
import { DatePipe, NgIf } from '@angular/common';
|
||||
import { Component, Input, OnInit, inject, output } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, Input, OnInit, inject, input, output } from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { PortalStatus, PortalStatusService } from 'services';
|
||||
import { PlaylistMeta } from 'shared-interfaces';
|
||||
@@ -14,17 +14,16 @@ import { PlaylistMeta } from 'shared-interfaces';
|
||||
styleUrls: ['./playlist-item.component.scss'],
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatIconButton,
|
||||
MatIcon,
|
||||
MatListModule,
|
||||
MatTooltipModule,
|
||||
NgIf,
|
||||
MatTooltip,
|
||||
TranslatePipe,
|
||||
],
|
||||
})
|
||||
export class PlaylistItemComponent implements OnInit {
|
||||
@Input() item!: PlaylistMeta;
|
||||
@Input() showActions = true;
|
||||
readonly showActions = input(true);
|
||||
|
||||
readonly editPlaylistClicked = output<PlaylistMeta>();
|
||||
readonly playlistClicked = output<string>();
|
||||
@@ -34,21 +33,24 @@ export class PlaylistItemComponent implements OnInit {
|
||||
portalStatus: PortalStatus = 'unavailable';
|
||||
private readonly portalStatusService = inject(PortalStatusService);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
async ngOnInit() {
|
||||
if (this.item?.serverUrl) {
|
||||
await this.checkPortalStatus();
|
||||
}
|
||||
await this.checkPortalStatus();
|
||||
}
|
||||
|
||||
private async checkPortalStatus() {
|
||||
try {
|
||||
this.portalStatus =
|
||||
await this.portalStatusService.checkPortalStatus(
|
||||
this.item.serverUrl,
|
||||
this.item.username,
|
||||
this.item.password
|
||||
);
|
||||
if (
|
||||
this.item.serverUrl &&
|
||||
this.item.username &&
|
||||
this.item.password
|
||||
) {
|
||||
this.portalStatus =
|
||||
await this.portalStatusService.checkPortalStatus(
|
||||
this.item.serverUrl,
|
||||
this.item.username,
|
||||
this.item.password
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking portal status:', error);
|
||||
this.portalStatus = 'unavailable';
|
||||
|
||||
@@ -10,10 +10,8 @@ import {
|
||||
Output,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@@ -47,9 +45,7 @@ import { PlaylistItemComponent } from './playlist-item/playlist-item.component';
|
||||
styleUrls: ['./recent-playlists.component.scss'],
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
MatButtonModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatIcon,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
NgxSkeletonLoaderComponent,
|
||||
@@ -75,7 +71,7 @@ export class RecentPlaylistsComponent implements OnInit {
|
||||
|
||||
readonly ghostElements = new Array(10);
|
||||
|
||||
playlists$ = combineLatest([
|
||||
readonly playlists$ = combineLatest([
|
||||
this.store.select(selectAllPlaylistsMeta),
|
||||
this.searchQuery,
|
||||
this.store.select(selectActiveTypeFilters),
|
||||
|
||||
@@ -13,7 +13,7 @@ export class MomentDatePipe implements PipeTransform {
|
||||
value: string,
|
||||
formatToParse: string,
|
||||
formatToReturn = 'MMMM Do, dddd'
|
||||
): any {
|
||||
): string {
|
||||
return moment(value, formatToParse).format(formatToReturn);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user