mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: streamline playlist management and navigation logic
This commit is contained in:
1 parent
2bfa557438
commit
0c9fd18677
9 files changed
+111
-125
No files matched your search
@@ -23,7 +23,6 @@ export const PlaylistActions = createActionGroup({
|
||||
positionUpdates: { id: string; changes: { position: number } }[];
|
||||
}>(),
|
||||
'Remove All Playlists': emptyProps(),
|
||||
'Set Current Playlist Id': props<{ playlistId: string | undefined }>(),
|
||||
'Handle Adding Playlist By Url': props<{
|
||||
isTemporary: boolean;
|
||||
playlist: Playlist;
|
||||
|
||||
@@ -268,30 +268,18 @@ export class PlaylistEffects {
|
||||
PlaylistActions.addPlaylist,
|
||||
PlaylistActions.handleAddingPlaylistByUrl
|
||||
),
|
||||
tap((action) => {
|
||||
if ('isTemporary' in action && action.isTemporary) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.navigateToPlaylist(action.playlist);
|
||||
}),
|
||||
switchMap((action) => {
|
||||
if ('isTemporary' in action && action.isTemporary) {
|
||||
return EMPTY;
|
||||
}
|
||||
return this.playlistsService.addPlaylist(action.playlist);
|
||||
}),
|
||||
map((playlist: Playlist) => {
|
||||
if (playlist.serverUrl) {
|
||||
this.router.navigate([
|
||||
'/workspace/xtreams/',
|
||||
playlist._id,
|
||||
]);
|
||||
} else if (playlist.macAddress) {
|
||||
this.router.navigate([
|
||||
'/workspace/stalker/',
|
||||
playlist._id,
|
||||
]);
|
||||
} else {
|
||||
this.router.navigate([
|
||||
'/workspace/playlists/',
|
||||
playlist._id,
|
||||
]);
|
||||
}
|
||||
return playlist;
|
||||
})
|
||||
);
|
||||
},
|
||||
@@ -375,4 +363,18 @@ export class PlaylistEffects {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
private navigateToPlaylist(playlist: Playlist): void {
|
||||
if (playlist.serverUrl) {
|
||||
void this.router.navigate(['/workspace', 'xtreams', playlist._id]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playlist.macAddress) {
|
||||
void this.router.navigate(['/workspace', 'stalker', playlist._id]);
|
||||
return;
|
||||
}
|
||||
|
||||
void this.router.navigate(['/workspace', 'playlists', playlist._id]);
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,28 @@ import { PlaylistState } from '../state';
|
||||
|
||||
export const favoritesReducers = [
|
||||
on(FavoritesActions.updateFavorites, (state, action): PlaylistState => {
|
||||
let favorites;
|
||||
const { channel } = action;
|
||||
const playlistFavorites =
|
||||
state.playlists.entities[state.playlists.selectedId]?.favorites;
|
||||
if (playlistFavorites?.includes(channel.url)) {
|
||||
favorites = [
|
||||
...(playlistFavorites ?? []).filter(
|
||||
(url: string) => url !== channel.url
|
||||
),
|
||||
];
|
||||
} else {
|
||||
favorites = [...(playlistFavorites ?? []), channel.url];
|
||||
const selectedId = state.playlists.selectedId;
|
||||
const playlist = state.playlists.entities[selectedId];
|
||||
if (!selectedId || !playlist) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const { channel } = action;
|
||||
const playlistFavorites = (playlist.favorites ?? []).filter(
|
||||
(favorite): favorite is string => typeof favorite === 'string'
|
||||
);
|
||||
const favorites = playlistFavorites.includes(channel.url)
|
||||
? playlistFavorites.filter((url) => url !== channel.url)
|
||||
: [...playlistFavorites, channel.url];
|
||||
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
[selectedId]: {
|
||||
...playlist,
|
||||
favorites,
|
||||
},
|
||||
},
|
||||
@@ -32,6 +33,12 @@ export const favoritesReducers = [
|
||||
};
|
||||
}),
|
||||
on(FavoritesActions.setFavorites, (state, action): PlaylistState => {
|
||||
const selectedId = state.playlists.selectedId;
|
||||
const playlist = state.playlists.entities[selectedId];
|
||||
if (!selectedId || !playlist) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const { channelIds } = action;
|
||||
return {
|
||||
...state,
|
||||
@@ -39,8 +46,8 @@ export const favoritesReducers = [
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
[selectedId]: {
|
||||
...playlist,
|
||||
favorites: channelIds,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,5 +28,3 @@ export const selectCurrentEpgProgramReducer = (state: PlaylistState) =>
|
||||
export const selectChannelsReducer = (state: PlaylistState) => state.channels;
|
||||
export const selectPlaylistsLoadingFlagReducer = (state: PlaylistState) =>
|
||||
state.playlists?.allPlaylistsLoaded;
|
||||
export const selectCurrentPlaylistIdReducer = (state: PlaylistState) =>
|
||||
state.currentPlaylistId;
|
||||
@@ -15,12 +15,19 @@ export const playlistReducers = [
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removePlaylist, (state, action): PlaylistState => {
|
||||
const playlists = playlistsAdapter.removeOne(
|
||||
action.playlistId,
|
||||
state.playlists
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeOne(
|
||||
action.playlistId,
|
||||
state.playlists
|
||||
),
|
||||
playlists: {
|
||||
...playlists,
|
||||
selectedId:
|
||||
state.playlists.selectedId === action.playlistId
|
||||
? ''
|
||||
: playlists.selectedId,
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.updatePlaylist, (state, action): PlaylistState => {
|
||||
@@ -137,20 +144,15 @@ export const playlistReducers = [
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removeAllPlaylists, (state): PlaylistState => {
|
||||
const playlists = playlistsAdapter.removeAll(state.playlists);
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeAll(state.playlists),
|
||||
playlists: {
|
||||
...playlists,
|
||||
selectedId: '',
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.setCurrentPlaylistId,
|
||||
(state, { playlistId }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
currentPlaylistId: playlistId,
|
||||
};
|
||||
}
|
||||
),
|
||||
on(
|
||||
PlaylistActions.handleAddingPlaylistByUrl,
|
||||
(state, action): PlaylistState => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { PlaylistMeta } from 'shared-interfaces';
|
||||
import { selectActivePlaylist } from './selectors';
|
||||
|
||||
describe('selectors', () => {
|
||||
const playlistOne = {
|
||||
_id: 'playlist-1',
|
||||
title: 'Playlist One',
|
||||
} as PlaylistMeta;
|
||||
const playlistTwo = {
|
||||
_id: 'playlist-2',
|
||||
title: 'Playlist Two',
|
||||
} as PlaylistMeta;
|
||||
const entities = {
|
||||
'playlist-1': playlistOne,
|
||||
'playlist-2': playlistTwo,
|
||||
};
|
||||
|
||||
describe('selectActivePlaylist', () => {
|
||||
it('resolves the active playlist from entity state and selected id', () => {
|
||||
expect(selectActivePlaylist.projector(entities, 'playlist-2')).toBe(
|
||||
playlistTwo
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null when there is no active playlist id', () => {
|
||||
expect(selectActivePlaylist.projector(entities, '')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,7 @@
|
||||
import { EntityState } from '@ngrx/entity';
|
||||
import { createFeatureSelector, createSelector, Selector } from '@ngrx/store';
|
||||
import { Playlist, PlaylistMeta } from 'shared-interfaces';
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { Playlist } from 'shared-interfaces';
|
||||
import * as fromPlaylistMetaState from './playlists.state';
|
||||
import * as fromPlaylistState from './reducers';
|
||||
import { selectRouteParam } from './router.selectors';
|
||||
import { PlaylistState } from './state';
|
||||
|
||||
export const selectPlaylistState =
|
||||
@@ -24,11 +22,6 @@ export const selectCurrentEpgProgram = createSelector(
|
||||
fromPlaylistState.selectCurrentEpgProgramReducer
|
||||
);
|
||||
|
||||
export const selectCurrentPlaylistId = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectCurrentPlaylistIdReducer
|
||||
);
|
||||
|
||||
export const selectChannels = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectChannelsReducer
|
||||
@@ -52,41 +45,15 @@ export const selectAllPlaylistsMeta = createSelector(
|
||||
|
||||
export const selectActiveTypeFilters = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(state) => state.selectedFilters
|
||||
);
|
||||
|
||||
export const selectPlaylistEntity = (id: string) =>
|
||||
createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(data) => {
|
||||
return data.entities[id];
|
||||
}
|
||||
);
|
||||
createSelector(selectPlaylistEntities, (entities) => entities[id]);
|
||||
|
||||
export const selectActivePlaylistId = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(data) => data.selectedId
|
||||
);
|
||||
|
||||
export const selectPlaylistTitle = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
selectCurrentPlaylistId,
|
||||
(data) => {
|
||||
if (
|
||||
data.entities &&
|
||||
data.selectedId &&
|
||||
data.entities[data.selectedId]
|
||||
) {
|
||||
return (
|
||||
data.entities[data.selectedId]?.title ||
|
||||
data.entities[data.selectedId]?.filename
|
||||
);
|
||||
} else return 'Untitled playlist';
|
||||
}
|
||||
(state) => state.selectedId
|
||||
);
|
||||
|
||||
export const selectPlaylistEntities = createSelector(
|
||||
@@ -94,18 +61,23 @@ export const selectPlaylistEntities = createSelector(
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities
|
||||
);
|
||||
|
||||
export const selectCurrentPlaylist = createSelector(
|
||||
export const selectActivePlaylist = createSelector(
|
||||
selectPlaylistEntities,
|
||||
selectRouteParam('id'),
|
||||
selectCurrentPlaylistId,
|
||||
(entities, id, currentPlaylistId) => {
|
||||
if (entities) {
|
||||
return entities[id!] || entities[currentPlaylistId!];
|
||||
selectActivePlaylistId,
|
||||
(entities, activePlaylistId) => {
|
||||
if (!entities || !activePlaylistId) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
return entities[activePlaylistId] ?? null;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectPlaylistTitle = createSelector(
|
||||
selectActivePlaylist,
|
||||
(playlist) => playlist?.title || playlist?.filename || 'Untitled playlist'
|
||||
);
|
||||
|
||||
export const selectPlaylistById = (id: string) =>
|
||||
createSelector(selectPlaylistEntities, (entities) => {
|
||||
if (entities) {
|
||||
@@ -114,32 +86,10 @@ export const selectPlaylistById = (id: string) =>
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectActivePlaylist = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
(state) => {
|
||||
if (state.entities && state.selectedId !== '') {
|
||||
return state.entities[state.selectedId] as Playlist;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectFavorites = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
fromPlaylistState.selectPlaylistId as unknown as Selector<
|
||||
EntityState<PlaylistMeta>,
|
||||
string
|
||||
>,
|
||||
(data) => {
|
||||
if (
|
||||
data.entities &&
|
||||
data.selectedId &&
|
||||
data.entities[data.selectedId]
|
||||
) {
|
||||
return (data.entities[data.selectedId]?.favorites || []).filter(
|
||||
(f): f is string => typeof f === 'string'
|
||||
);
|
||||
} else return [];
|
||||
}
|
||||
selectActivePlaylist,
|
||||
(playlist) =>
|
||||
(playlist?.favorites || []).filter(
|
||||
(favorite): favorite is string => typeof favorite === 'string'
|
||||
)
|
||||
);
|
||||
@@ -9,7 +9,6 @@ export interface PlaylistState {
|
||||
epgAvailable: boolean;
|
||||
channels: Channel[]; // TODO: use entity store
|
||||
playlists: PlaylistMetaState;
|
||||
currentPlaylistId: string | undefined;
|
||||
}
|
||||
|
||||
export const initialState: PlaylistState = {
|
||||
@@ -18,5 +17,4 @@ export const initialState: PlaylistState = {
|
||||
epgAvailable: false,
|
||||
channels: [],
|
||||
playlists: initialPlaylistMetaState,
|
||||
currentPlaylistId: undefined,
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './lib/m3u-collection-route/m3u-collection-route.component';
|
||||
export * from './lib/m3u-workspace-route-session.service';
|
||||
export * from './lib/video-player/video-player.component';
|
||||
Reference in new issue
Block a user