mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 11:06:16 -08:00
feat: general development, update core application components, and enhance build scripts.
This commit is contained in:
1 parent
9a57eaff92
commit
992b996ecd
37 files changed
+500
-341
No files matched your search
@@ -49,6 +49,21 @@ export const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'playlists/:id',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'playlists/:id/all',
|
||||
},
|
||||
{
|
||||
path: 'playlists/:id/:view',
|
||||
data: {
|
||||
layout: 'workspace',
|
||||
},
|
||||
loadComponent: () =>
|
||||
import('./home/video-player/video-player.component').then(
|
||||
(c) => c.VideoPlayerComponent
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'global-favorites',
|
||||
data: {
|
||||
layout: 'workspace',
|
||||
},
|
||||
@@ -90,6 +105,11 @@ export const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'playlists/:id',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'playlists/:id/all',
|
||||
},
|
||||
{
|
||||
path: 'playlists/:id/:view',
|
||||
loadComponent: () =>
|
||||
import('./home/video-player/video-player.component').then(
|
||||
(c) => c.VideoPlayerComponent
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<app-sidebar
|
||||
[channels]="(channels$ | async)!"
|
||||
[showPlaylistHeader]="!isWorkspaceLayout"
|
||||
[activeView]="activeView()"
|
||||
/>
|
||||
} @loading {
|
||||
<p>Loading...</p>
|
||||
@@ -29,7 +30,7 @@
|
||||
}
|
||||
</mat-drawer>
|
||||
<mat-drawer-content class="drawer-content">
|
||||
@if (activeChannel$ | async; as activeChannel) {
|
||||
@if (activeChannel(); as activeChannel) {
|
||||
<!-- toolbar with drawer icon -->
|
||||
<app-toolbar
|
||||
(toggleLeftDrawerClicked)="drawer.toggle()"
|
||||
@@ -117,7 +118,7 @@
|
||||
<!-- channel overlay -->
|
||||
<app-info-overlay
|
||||
[channel]="activeChannel"
|
||||
[epgProgram]="(epgProgram$ | async)!"
|
||||
[epgProgram]="epgProgram()"
|
||||
/>
|
||||
|
||||
<!-- channel number overlay -->
|
||||
@@ -129,6 +130,20 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
<!-- Minimal toolbar when no channel is active so drawer works -->
|
||||
<app-toolbar
|
||||
(toggleLeftDrawerClicked)="drawer.toggle()"
|
||||
(toggleRightDrawerClicked)="drawerRight.toggle()"
|
||||
[isLeftDrawerOpened]="drawer.opened"
|
||||
(settingsClicked)="openSettings()"
|
||||
/>
|
||||
<div class="empty-state-container">
|
||||
<mat-icon class="empty-state-icon">live_tv</mat-icon>
|
||||
<h2 class="empty-state-title">
|
||||
{{ 'CHANNELS.SELECT_CHANNEL_PLAYBACK' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
}
|
||||
</mat-drawer-content>
|
||||
<!-- right sidebar content -->
|
||||
|
||||
@@ -126,3 +126,29 @@
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: calc(100% - 64px);
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 72px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin-bottom: 24px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
margin: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,14 @@ import {
|
||||
inject,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
ArtPlayerComponent,
|
||||
AudioPlayerComponent,
|
||||
@@ -29,8 +32,10 @@ import {
|
||||
ToolbarComponent,
|
||||
VjsPlayerComponent,
|
||||
} from 'components';
|
||||
import { PlaylistActions, ChannelActions, FavoritesActions } from 'm3u-state';
|
||||
import {
|
||||
ChannelActions,
|
||||
FavoritesActions,
|
||||
PlaylistActions,
|
||||
selectActive,
|
||||
selectChannels,
|
||||
selectCurrentEpgProgram,
|
||||
@@ -40,6 +45,7 @@ import {
|
||||
Subscription,
|
||||
combineLatest,
|
||||
combineLatestWith,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
startWith,
|
||||
@@ -56,13 +62,14 @@ import {
|
||||
VideoPlayer,
|
||||
} from 'shared-interfaces';
|
||||
import { SettingsStore } from '../../services/settings-store.service';
|
||||
import { SettingsComponent } from '../../settings/settings.component';
|
||||
import {
|
||||
getAdjacentChannelItem,
|
||||
getChannelItemByNumber,
|
||||
} from '../../shared/services/remote-channel-navigation.util';
|
||||
import { SettingsComponent } from '../../settings/settings.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-player',
|
||||
imports: [
|
||||
ArtPlayerComponent,
|
||||
AsyncPipe,
|
||||
@@ -71,11 +78,13 @@ import { SettingsComponent } from '../../settings/settings.component';
|
||||
EpgListComponent,
|
||||
HtmlVideoPlayerComponent,
|
||||
InfoOverlayComponent,
|
||||
MatIcon,
|
||||
MatSidenavModule,
|
||||
ResizableDirective,
|
||||
RouterLink,
|
||||
SidebarComponent,
|
||||
ToolbarComponent,
|
||||
TranslatePipe,
|
||||
VjsPlayerComponent,
|
||||
],
|
||||
templateUrl: './video-player.component.html',
|
||||
@@ -93,13 +102,34 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
private readonly store = inject(Store);
|
||||
|
||||
/** Active selected channel */
|
||||
readonly activeChannel$ = this.store.select(selectActive);
|
||||
readonly activeChannel = this.store.selectSignal(selectActive);
|
||||
|
||||
/** Channels list */
|
||||
channels$!: Observable<Channel[]>;
|
||||
readonly channels$: Observable<Channel[]> = this.store.select(
|
||||
selectChannels
|
||||
) as Observable<Channel[]>;
|
||||
|
||||
/** Current epg program */
|
||||
readonly epgProgram$ = this.store.select(selectCurrentEpgProgram);
|
||||
readonly epgProgram = this.store.selectSignal(selectCurrentEpgProgram);
|
||||
|
||||
/** Active M3U view (all, groups, favorites) */
|
||||
readonly activeView = toSignal(
|
||||
this.activatedRoute.params.pipe(
|
||||
map((params) => {
|
||||
if (this.router.url.includes('/workspace/global-favorites')) {
|
||||
return 'favorites';
|
||||
}
|
||||
return params['view'] || 'all';
|
||||
})
|
||||
),
|
||||
{
|
||||
initialValue: this.router.url.includes(
|
||||
'/workspace/global-favorites'
|
||||
)
|
||||
? 'favorites'
|
||||
: 'all',
|
||||
}
|
||||
);
|
||||
|
||||
/** Selected video player options */
|
||||
playerSettings: Partial<Settings> = {
|
||||
@@ -118,6 +148,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
private unsubscribeRemoteChannelChange?: () => void;
|
||||
private unsubscribeRemoteCommand?: () => void;
|
||||
private statusSubscription?: Subscription;
|
||||
private routeSubscription?: Subscription;
|
||||
private lastKnownVolume = 1;
|
||||
|
||||
/** Info overlay component reference for manual triggering */
|
||||
@@ -155,11 +186,11 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Setup remote control channel change listener (Electron only)
|
||||
if (this.isDesktop && window.electron?.onChannelChange) {
|
||||
const unsubscribe = window.electron.onChannelChange((data: {
|
||||
direction: 'up' | 'down';
|
||||
}) => {
|
||||
this.handleRemoteChannelChange(data.direction);
|
||||
});
|
||||
const unsubscribe = window.electron.onChannelChange(
|
||||
(data: { direction: 'up' | 'down' }) => {
|
||||
this.handleRemoteChannelChange(data.direction);
|
||||
}
|
||||
);
|
||||
if (typeof unsubscribe === 'function') {
|
||||
this.unsubscribeRemoteChannelChange = unsubscribe;
|
||||
}
|
||||
@@ -175,66 +206,109 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
this.channels$ = this.activatedRoute.params.pipe(
|
||||
combineLatestWith(this.activatedRoute.queryParams),
|
||||
switchMap(([params, queryParams]) => {
|
||||
if (params['id']) {
|
||||
this.store.dispatch(ChannelActions.resetActiveChannel());
|
||||
this.store.dispatch(
|
||||
PlaylistActions.setActivePlaylist({
|
||||
playlistId: params['id'],
|
||||
})
|
||||
this.routeSubscription = this.activatedRoute.params
|
||||
.pipe(
|
||||
distinctUntilChanged((prev, curr) => prev['id'] === curr['id']),
|
||||
combineLatestWith(this.activatedRoute.queryParams),
|
||||
switchMap(([params, queryParams]) => {
|
||||
const isGlobalFavorites = this.router.url.includes(
|
||||
'/workspace/global-favorites'
|
||||
);
|
||||
return this.playlistsService.getPlaylist(params['id']).pipe(
|
||||
map((playlist) => {
|
||||
// Set user agent if specified on playlist level
|
||||
if (playlist.userAgent) {
|
||||
window.electron?.setUserAgent(
|
||||
playlist.userAgent,
|
||||
'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
ChannelActions.setChannels({
|
||||
channels: playlist.playlist.items,
|
||||
if (isGlobalFavorites) {
|
||||
this.store.dispatch(
|
||||
ChannelActions.resetActiveChannel()
|
||||
);
|
||||
this.store.dispatch(
|
||||
PlaylistActions.setActivePlaylist({
|
||||
playlistId: '',
|
||||
})
|
||||
);
|
||||
return this.playlistsService
|
||||
.getPlaylistWithGlobalFavorites()
|
||||
.pipe(
|
||||
map((playlist) => {
|
||||
this.store.dispatch(
|
||||
ChannelActions.setChannels({
|
||||
channels: playlist.playlist.items,
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
playlist.favorites &&
|
||||
playlist.favorites.length > 0
|
||||
) {
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: playlist.favorites,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: [],
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else if (params['id']) {
|
||||
this.store.dispatch(
|
||||
ChannelActions.resetActiveChannel()
|
||||
);
|
||||
this.store.dispatch(
|
||||
PlaylistActions.setActivePlaylist({
|
||||
playlistId: params['id'],
|
||||
})
|
||||
);
|
||||
return this.playlistsService
|
||||
.getPlaylist(params['id'])
|
||||
.pipe(
|
||||
map((playlist) => {
|
||||
// Set user agent if specified on playlist level
|
||||
if (playlist.userAgent) {
|
||||
window.electron?.setUserAgent(
|
||||
playlist.userAgent,
|
||||
'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
// Load favorites from the playlist
|
||||
if (
|
||||
playlist.favorites &&
|
||||
playlist.favorites.length > 0
|
||||
) {
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: playlist.favorites,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Clear favorites if playlist has none
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: [],
|
||||
})
|
||||
);
|
||||
}
|
||||
this.store.dispatch(
|
||||
ChannelActions.setChannels({
|
||||
channels: playlist.playlist.items,
|
||||
})
|
||||
);
|
||||
|
||||
return playlist.playlist.items as Channel[];
|
||||
})
|
||||
);
|
||||
} else if (queryParams['url']) {
|
||||
return this.store.select(selectChannels) as Observable<
|
||||
Channel[]
|
||||
>;
|
||||
}
|
||||
})
|
||||
);
|
||||
// Load favorites from the playlist
|
||||
if (
|
||||
playlist.favorites &&
|
||||
playlist.favorites.length > 0
|
||||
) {
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: playlist.favorites,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Clear favorites if playlist has none
|
||||
this.store.dispatch(
|
||||
FavoritesActions.setFavorites({
|
||||
channelIds: [],
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.statusSubscription = combineLatest([
|
||||
this.channels$,
|
||||
this.activeChannel$,
|
||||
this.epgProgram$.pipe(startWith(null)),
|
||||
this.store.select(selectActive),
|
||||
this.store.select(selectCurrentEpgProgram).pipe(startWith(null)),
|
||||
]).subscribe(([channels, activeChannel, epgProgram]) => {
|
||||
if (!window.electron?.updateRemoteControlStatus || !activeChannel) {
|
||||
return;
|
||||
@@ -247,9 +321,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
window.electron.updateRemoteControlStatus({
|
||||
portal: 'm3u',
|
||||
isLiveView: true,
|
||||
channelName:
|
||||
activeChannel.name ??
|
||||
activeChannel.tvg?.name,
|
||||
channelName: activeChannel.name ?? activeChannel.tvg?.name,
|
||||
channelNumber: currentIndex >= 0 ? currentIndex + 1 : undefined,
|
||||
epgTitle: (epgProgram as any)?.title,
|
||||
epgStart: (epgProgram as any)?.start,
|
||||
@@ -268,7 +340,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
console.log(`Remote control: changing channel ${direction}`);
|
||||
|
||||
// Use combineLatest to get both values and take only the first emission
|
||||
combineLatest([this.channels$, this.activeChannel$])
|
||||
combineLatest([this.channels$, this.store.select(selectActive)])
|
||||
.pipe(
|
||||
filter(([channels, activeChannel]) => {
|
||||
return channels.length > 0 && !!activeChannel;
|
||||
@@ -310,6 +382,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
this.unsubscribeRemoteChannelChange?.();
|
||||
this.unsubscribeRemoteCommand?.();
|
||||
this.statusSubscription?.unsubscribe();
|
||||
this.routeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,11 +455,11 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
) as Observable<Channel[]>;
|
||||
|
||||
// Pass the active channel's tvg.id for highlighting
|
||||
this.activeChannel$.pipe(take(1)).subscribe((channel) => {
|
||||
if (channel) {
|
||||
componentRef.instance.activeChannelId = (channel as Channel).tvg?.id || null;
|
||||
}
|
||||
});
|
||||
const currentChannel = this.activeChannel();
|
||||
if (currentChannel) {
|
||||
componentRef.instance.activeChannelId =
|
||||
currentChannel.tvg?.id || null;
|
||||
}
|
||||
|
||||
this.overlayRef.backdropClick().subscribe(() => {
|
||||
this.overlayRef.dispose();
|
||||
@@ -479,7 +552,9 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
|
||||
this.channels$
|
||||
.pipe(
|
||||
take(1),
|
||||
map((channels) => getChannelItemByNumber(channels, channelNumber))
|
||||
map((channels) =>
|
||||
getChannelItemByNumber(channels, channelNumber)
|
||||
)
|
||||
)
|
||||
.subscribe((channel) => {
|
||||
if (channel) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DataService } from 'services';
|
||||
import { GLOBAL_FAVORITES_PLAYLIST_ID } from 'shared-interfaces';
|
||||
//import { shell } from 'electron';
|
||||
import { AddPlaylistMenuComponent, PlaylistType } from 'components';
|
||||
import { HomeComponent } from '../../../home/home.component';
|
||||
@@ -85,7 +84,7 @@ export class HeaderComponent implements OnInit {
|
||||
* Navigates to the global favorites view
|
||||
*/
|
||||
navigateToGlobalFavorites(): void {
|
||||
this.router.navigate(['playlists', GLOBAL_FAVORITES_PLAYLIST_ID]);
|
||||
this.router.navigate(['/workspace', 'global-favorites']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { GLOBAL_FAVORITES_PLAYLIST_ID } from 'shared-interfaces';
|
||||
|
||||
export type PortalProvider = 'xtreams' | 'stalker' | 'playlists';
|
||||
|
||||
export interface PortalRailLink {
|
||||
@@ -160,29 +158,36 @@ export function buildPortalRailLinks(
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
return {
|
||||
primary: [
|
||||
if (provider === 'playlists') {
|
||||
const primary: PortalRailLink[] = [
|
||||
{
|
||||
icon: 'play_circle',
|
||||
tooltip: 'Player',
|
||||
path: root,
|
||||
icon: 'list',
|
||||
tooltip: 'All Channels',
|
||||
path: [...root, 'all'],
|
||||
exact: true,
|
||||
section: 'player',
|
||||
section: 'all',
|
||||
},
|
||||
],
|
||||
secondary: workspace
|
||||
? [
|
||||
{
|
||||
icon: 'favorite',
|
||||
tooltip: 'Global favorites',
|
||||
path: [
|
||||
'/workspace',
|
||||
'playlists',
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
],
|
||||
section: 'favorites',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
{
|
||||
icon: 'folder',
|
||||
tooltip: 'Groups',
|
||||
path: [...root, 'groups'],
|
||||
exact: true,
|
||||
section: 'groups',
|
||||
},
|
||||
{
|
||||
icon: 'star',
|
||||
tooltip: 'Favorites',
|
||||
path: [...root, 'favorites'],
|
||||
exact: true,
|
||||
section: 'favorites',
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
primary,
|
||||
secondary: [],
|
||||
};
|
||||
}
|
||||
|
||||
return { primary: [], secondary: [] };
|
||||
}
|
||||
@@ -131,18 +131,16 @@
|
||||
<mat-icon>{{ action.icon }}</mat-icon>
|
||||
</button>
|
||||
}
|
||||
<!-- <button
|
||||
type="button"
|
||||
<a
|
||||
class="header-shortcut"
|
||||
mat-icon-button
|
||||
[class.is-active]="isFavoritesView()"
|
||||
(click)="toggleFavoritesShortcut()"
|
||||
[matTooltip]="'WORKSPACE.FAVORITES_ONLY' | translate"
|
||||
[disabled]="!currentContext()"
|
||||
aria-label="Toggle favorites only"
|
||||
routerLink="/workspace/global-favorites"
|
||||
[class.is-active]="isGlobalFavoritesRoute()"
|
||||
[matTooltip]="'HOME.PLAYLISTS.GLOBAL_FAVORITES' | translate"
|
||||
aria-label="Open global favorites"
|
||||
>
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button> -->
|
||||
<mat-icon>star</mat-icon>
|
||||
</a>
|
||||
@if (canSort()) {
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -234,7 +234,8 @@
|
||||
gap: 2px;
|
||||
app-region: no-drag;
|
||||
|
||||
button {
|
||||
button,
|
||||
a {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
} from 'm3u-state';
|
||||
import { filter, firstValueFrom } from 'rxjs';
|
||||
import { PlaylistsService } from 'services';
|
||||
import { GLOBAL_FAVORITES_PLAYLIST_ID } from 'shared-interfaces';
|
||||
import { DownloadsService } from '../services/downloads.service';
|
||||
import { AddPlaylistDialogComponent } from '../shared/components/add-playlist/add-playlist-dialog.component';
|
||||
import {
|
||||
@@ -146,12 +145,13 @@ export class WorkspaceShellComponent {
|
||||
return routeContext;
|
||||
}
|
||||
|
||||
// On dashboard, sources, and settings we still want provider-specific rail links
|
||||
// On dashboard, sources, settings, and global favorites we still want provider-specific rail links
|
||||
// for the currently active playlist selected in the switcher.
|
||||
if (
|
||||
!this.isDashboardRoute() &&
|
||||
!this.isSourcesRoute() &&
|
||||
!this.isSettingsRoute()
|
||||
!this.isSettingsRoute() &&
|
||||
!this.isGlobalFavoritesRoute()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -175,6 +175,11 @@ export class WorkspaceShellComponent {
|
||||
readonly isSettingsRoute = computed(() =>
|
||||
/^\/workspace\/settings(?:\/)?(?:\?.*)?$/.test(this.currentUrl())
|
||||
);
|
||||
readonly isGlobalFavoritesRoute = computed(() =>
|
||||
/^\/workspace\/global-favorites(?:\/)?(?:\?.*)?$/.test(
|
||||
this.currentUrl()
|
||||
)
|
||||
);
|
||||
readonly dashboardXtreamContext = computed<WorkspaceContext | null>(() => {
|
||||
if (!this.isDashboardRoute()) {
|
||||
return null;
|
||||
@@ -221,17 +226,6 @@ export class WorkspaceShellComponent {
|
||||
this.isCategoryContextRoute() ||
|
||||
this.isFavoritesContextRoute()
|
||||
);
|
||||
readonly isFavoritesView = computed(() => {
|
||||
const context = this.currentContext();
|
||||
if (
|
||||
context?.provider === 'playlists' &&
|
||||
context.playlistId === GLOBAL_FAVORITES_PLAYLIST_ID
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.currentSection() === 'favorites';
|
||||
});
|
||||
readonly canUseSearch = computed(() => {
|
||||
if (this.isSettingsRoute()) {
|
||||
return false;
|
||||
@@ -504,9 +498,7 @@ export class WorkspaceShellComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.playlistId !== GLOBAL_FAVORITES_PLAYLIST_ID) {
|
||||
this.lastM3uPlaylistId.set(context.playlistId);
|
||||
}
|
||||
this.lastM3uPlaylistId.set(context.playlistId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -600,55 +592,6 @@ export class WorkspaceShellComponent {
|
||||
}
|
||||
}
|
||||
|
||||
toggleFavoritesShortcut(): void {
|
||||
const context = this.currentContext();
|
||||
if (!context) return;
|
||||
|
||||
if (context.provider === 'xtreams') {
|
||||
if (!this.isElectron) return;
|
||||
|
||||
const base = ['/workspace', 'xtreams', context.playlistId];
|
||||
this.router.navigate(
|
||||
this.isFavoritesView()
|
||||
? [...base, 'vod']
|
||||
: [...base, 'favorites']
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.provider === 'stalker') {
|
||||
const base = ['/workspace', 'stalker', context.playlistId];
|
||||
this.router.navigate(
|
||||
this.isFavoritesView()
|
||||
? [...base, 'vod']
|
||||
: [...base, 'favorites']
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.provider === 'playlists') {
|
||||
if (context.playlistId === GLOBAL_FAVORITES_PLAYLIST_ID) {
|
||||
const fallbackPlaylist = this.lastM3uPlaylistId();
|
||||
if (fallbackPlaylist) {
|
||||
this.router.navigate([
|
||||
'/workspace',
|
||||
'playlists',
|
||||
fallbackPlaylist,
|
||||
]);
|
||||
} else {
|
||||
this.router.navigate(['/workspace', 'dashboard']);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate([
|
||||
'/workspace',
|
||||
'playlists',
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
openDownloadsShortcut(): void {
|
||||
const context = this.downloadsContext();
|
||||
if (!context) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"UPLOAD_OR_SELECT_OTHER_PLAYLIST": "Neue Playlist hochladen oder eine andere aussuchen",
|
||||
"USE_STAR_TO_FAVORITE": "Nutzen Sie das Star-Icon um Sender als Favoriten zu markieren",
|
||||
"FAVORITES_UPDATED": "Favoriten wurden aktualisiert!",
|
||||
"SELECT_CHANNEL_PLAYBACK": "Bitte wählen Sie einen Sender aus, um die Wiedergabe zu starten",
|
||||
"SORT_BY": "Sortieren nach",
|
||||
"SORT_BY_DATE": "Nach Datum sortieren (Neueste zuerst)",
|
||||
"SORT_BY_RATING": "Nach Bewertung sortieren",
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
"REMOVE_FAVORITE": "Remove from favorites",
|
||||
"ADD_FAVORITE": "Add to favorites",
|
||||
"FAVORITES_UPDATED": "Favorites were updated!",
|
||||
"SELECT_CHANNEL_PLAYBACK": "Please select a channel to start playback",
|
||||
"SORT_BY": "Sort by",
|
||||
"SORT_BY_DATE": "Sort by Date Added (Newest First)",
|
||||
"SORT_BY_RATING": "Sort by Rating",
|
||||
|
||||
@@ -203,6 +203,7 @@
|
||||
"USE_STAR_TO_FAVORITE": "Используйте иконку звезды, чтобы добавлять каналы в избранное",
|
||||
"REMOVE_FAVORITE": "Удалить из списка фаворитов",
|
||||
"FAVORITES_UPDATED": "Список фаворитов был обновлен!",
|
||||
"SELECT_CHANNEL_PLAYBACK": "Пожалуйста, выберите канал для начала воспроизведения",
|
||||
"SORT_BY": "Сортировать по",
|
||||
"SORT_BY_DATE": "Сортировать по дате добавления (сначала новые)",
|
||||
"SORT_BY_RATING": "Сортировать по рейтингу",
|
||||
|
||||
Reference in new issue
Block a user