Files
KiraTV---App-Linux/apps/web/src/app/app.component.ts
T
2026-05-03 23:43:09 +02:00

228 lines
8.5 KiB
TypeScript

import { Component, HostBinding, inject, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router, RouterOutlet } from '@angular/router';
import { Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { EpgService } from '@iptvnator/epg/data-access';
import { WORKSPACE_SHELL_ACTIONS } from '@iptvnator/workspace/shell/util';
import { EpgProgressPanelComponent } from '@iptvnator/ui/epg/progress-panel';
import { PlaylistActions, selectAllPlaylistsMeta } from 'm3u-state';
import { filter, take } from 'rxjs';
import { DataService } from 'services';
import { AppConfig } from '../environments/environment';
import {
AUTO_UPDATE_PLAYLISTS,
Language,
OPEN_FILE,
Settings,
STORE_KEY,
Theme,
} from 'shared-interfaces';
import { SettingsService } from './services/settings.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
imports: [EpgProgressPanelComponent, RouterOutlet],
})
export class AppComponent implements OnInit {
@HostBinding('class.macos-platform') get isMacOS() {
return (
window.electron && navigator.platform.toLowerCase().includes('mac')
);
}
private actions$ = inject(Actions);
private dataService = inject(DataService);
private epgService = inject(EpgService);
private snackBar = inject(MatSnackBar);
private router = inject(Router);
private store = inject(Store);
private translate = inject(TranslateService);
private settingsService = inject(SettingsService);
private readonly workspaceShellActions = inject(WORKSPACE_SHELL_ACTIONS);
private readonly defaultUiLang: Language =
AppConfig.product?.defaultLanguage ??
(AppConfig.defaultTranslateLang === 'es'
? Language.SPANISH
: Language.ENGLISH);
constructor() {
if (
((this.dataService.isElectron &&
this.dataService?.remote?.process.platform === 'linux') ||
this.dataService?.remote?.process.platform === 'win32') &&
this.dataService.remote.process.argv.length > 2
) {
const filePath = this.dataService.remote.process.argv.find(
(filepath) =>
filepath.endsWith('.m3u') || filepath.endsWith('.m3u8')
);
if (filePath) {
const filePathsArray = filePath.split('/');
const fileName = filePathsArray[filePathsArray.length - 1];
this.dataService.sendIpcEvent(OPEN_FILE, {
filePath,
fileName,
});
}
}
if (window.electron) {
document.addEventListener('keydown', (event) => {
if (event.ctrlKey || event.metaKey) {
if (event.key === 'f') {
event.preventDefault();
this.workspaceShellActions.openGlobalSearch();
} else if (event.key === 'r') {
event.preventDefault();
this.workspaceShellActions.openGlobalRecent();
}
}
});
}
if (AppConfig.product?.indexHtmlLang) {
document.documentElement.lang = AppConfig.product.indexHtmlLang;
}
if (AppConfig.product?.appDisplayName) {
document.title = AppConfig.product.appDisplayName;
}
}
ngOnInit() {
this.store.dispatch(PlaylistActions.loadPlaylists());
/** English is the translation fallback (see TranslateModule defaultLanguage / useDefaultLang). */
this.translate.setDefaultLang(Language.ENGLISH);
this.initSettings();
this.triggerAutoUpdatePlaylists();
}
/**
* Reads the settings object from local storage and initializes the
* application based on them
*/
initSettings(): void {
this.settingsService
.getValueFromLocalStorage(STORE_KEY.Settings)
.subscribe((settings: Settings) => {
if (settings && Object.keys(settings).length > 0) {
// 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.defaultUiLang
);
// Fetch EPG if URLs are configured (only fetch stale data)
if (
window.electron &&
settings.epgUrl?.length > 0 &&
settings.epgUrl?.some((u) => u !== '')
) {
this.fetchStaleEpgData(settings.epgUrl);
}
if (settings.theme) {
this.settingsService.changeTheme(settings.theme);
} else {
this.detectDarkMode();
}
} else {
this.translate.use(this.defaultUiLang);
this.detectDarkMode();
}
});
}
/**
* Applies the operating system color scheme when no explicit theme is set
*/
detectDarkMode(): void {
this.settingsService.changeTheme(Theme.SystemTheme);
}
/**
* Navigate to the specified route
* @param route route to navigate to
*/
navigateToRoute(route: string) {
this.router.navigateByUrl(route);
}
/**
* Fetches EPG data only for URLs that have stale or missing data.
* Data is considered fresh if updated within the last 12 hours.
*/
private async fetchStaleEpgData(urls: string[]): Promise<void> {
try {
const result = await window.electron.checkEpgFreshness(urls, 12);
if (result.freshUrls.length > 0) {
console.log(
`EPG: ${result.freshUrls.length} source(s) already fresh, skipping fetch`
);
// Show snackbar if all EPG sources are fresh (no stale URLs)
if (result.staleUrls.length === 0) {
this.snackBar.open(
this.translate.instant('EPG.UP_TO_DATE'),
this.translate.instant('CLOSE'),
{ duration: 3000 }
);
}
}
if (result.staleUrls.length > 0) {
console.log(
`EPG: Fetching ${result.staleUrls.length} stale source(s)`
);
this.epgService.fetchEpg(result.staleUrls);
}
} catch (error) {
console.error('Error checking EPG freshness, fetching all:', error);
// Fallback: fetch all URLs if freshness check fails
this.epgService.fetchEpg(urls);
}
}
/**
* Triggers auto-update for playlists that have autoRefresh enabled
*/
private triggerAutoUpdatePlaylists(): void {
// Wait for playlists to be loaded successfully
this.actions$
.pipe(
ofType(PlaylistActions.loadPlaylistsSuccess),
take(1) // Only trigger once on app startup
)
.subscribe(() => {
// Get all playlists from store
this.store
.select(selectAllPlaylistsMeta)
.pipe(
take(1),
filter((playlists) => playlists.length > 0)
)
.subscribe((playlists) => {
// Filter playlists with autoRefresh enabled
const playlistsToUpdate = playlists.filter(
(playlist) => playlist.autoRefresh === true
);
// Trigger auto-update if there are playlists to update
if (playlistsToUpdate.length > 0) {
console.log(
`Auto-updating ${playlistsToUpdate.length} playlist(s) on startup`
);
this.dataService.sendIpcEvent(
AUTO_UPDATE_PLAYLISTS,
playlistsToUpdate
);
}
});
});
}
}