mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(m3u-state): reducers and add selectors
Move playlist reducers into a dedicated reducers module and re-export a composed playlistReducer built from smaller reducer arrays (epg, channel, playlist, favorites, filter). This extracts and organizes the previously monolithic reducer implementation into focused reducer files, improving modularity and maintainability. Add simple selectors in the new reducers index for commonly accessed pieces of PlaylistState (playlists, selected playlist id, epg availability, active channel, current EPG program, channels list, playlists loaded flag, current playlist id) so callers can import them from a single place. These changes make reducer logic easier to reason about, enable incremental testing and extension of individual reducer groups, and centralize basic selectors for consistent usage across the app.
This commit is contained in:
1 parent
d391514015
commit
68b87040ad
9 files changed
+413
-434
No files matched your search
@@ -1,140 +1,70 @@
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { createActionGroup, emptyProps, props } from '@ngrx/store';
|
||||
import { Channel, EpgProgram, Playlist, PlaylistMeta } from 'shared-interfaces';
|
||||
|
||||
const STORE_KEY = '[GLOBAL STORE]';
|
||||
const PLAYLISTS_STORE_KEY = '[PLAYLISTS STORE]';
|
||||
export const PlaylistActions = createActionGroup({
|
||||
source: 'Playlists',
|
||||
events: {
|
||||
'Load Playlists': emptyProps(),
|
||||
'Load Playlists Success': props<{ playlists: PlaylistMeta[] }>(),
|
||||
'Add Playlist': props<{ playlist: Playlist }>(),
|
||||
'Add Many Playlists': props<{ playlists: Playlist[] }>(),
|
||||
'Remove Playlist': props<{ playlistId: string }>(),
|
||||
'Update Playlist Meta': props<{ playlist: PlaylistMeta }>(),
|
||||
'Update Playlist': props<{ playlist: Playlist; playlistId: string }>(),
|
||||
'Update Many Playlists': props<{ playlists: Playlist[] }>(),
|
||||
'Parse Playlist': props<{
|
||||
uploadType: 'FILE' | 'URL' | 'TEXT';
|
||||
playlist: string;
|
||||
title: string;
|
||||
path?: string;
|
||||
}>(),
|
||||
'Set Active Playlist': props<{ playlistId: string }>(),
|
||||
'Update Playlist Positions': props<{
|
||||
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;
|
||||
}>(),
|
||||
},
|
||||
});
|
||||
|
||||
export const loadPlaylists = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Load playlists from db`
|
||||
);
|
||||
export const ChannelActions = createActionGroup({
|
||||
source: 'Channels',
|
||||
events: {
|
||||
'Set Channels': props<{ channels: Channel[] }>(),
|
||||
'Set Active Channel': props<{ channel: Channel }>(),
|
||||
'Set Active Channel Success': props<{ channel: Channel }>(),
|
||||
'Reset Active Channel': emptyProps(),
|
||||
'Set Adjacent Channel As Active': props<{
|
||||
direction: 'next' | 'previous';
|
||||
}>(),
|
||||
},
|
||||
});
|
||||
|
||||
export const loadPlaylistsSuccess = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Successfully loaded playlists from db`,
|
||||
props<{ playlists: PlaylistMeta[] }>()
|
||||
);
|
||||
export const EpgActions = createActionGroup({
|
||||
source: 'EPG',
|
||||
events: {
|
||||
'Set Active Epg Program': props<{ program: EpgProgram }>(),
|
||||
'Set Current Epg Program': props<{ program: EpgProgram }>(),
|
||||
'Reset Active Epg Program': emptyProps(),
|
||||
'Set Epg Available Flag': props<{ value: boolean }>(),
|
||||
},
|
||||
});
|
||||
|
||||
export const addPlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Add new playlist`,
|
||||
props<{ playlist: Playlist }>()
|
||||
);
|
||||
export const FavoritesActions = createActionGroup({
|
||||
source: 'Favorites',
|
||||
events: {
|
||||
'Update Favorites': props<{ channel: Channel }>(),
|
||||
'Set Favorites': props<{ channelIds: string[] }>(),
|
||||
},
|
||||
});
|
||||
|
||||
export const addManyPlaylists = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Add many playlists`,
|
||||
props<{ playlists: Playlist[] }>()
|
||||
);
|
||||
|
||||
export const removePlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Remove playlist by id`,
|
||||
props<{ playlistId: string }>()
|
||||
);
|
||||
|
||||
export const updatePlaylistMeta = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} update playlist meta`,
|
||||
props<{ playlist: PlaylistMeta }>()
|
||||
);
|
||||
|
||||
export const updatePlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} update playlist`,
|
||||
props<{ playlist: Playlist; playlistId: string }>()
|
||||
);
|
||||
|
||||
export const updateManyPlaylists = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Update many playlists (auto-update mechanism)`,
|
||||
props<{ playlists: Playlist[] }>()
|
||||
);
|
||||
|
||||
export const parsePlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} parse playlist`,
|
||||
props<{
|
||||
uploadType: 'FILE' | 'URL' | 'TEXT';
|
||||
playlist: string;
|
||||
title: string;
|
||||
path?: string;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const setActivePlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} set active playlist`,
|
||||
props<{ playlistId: string }>()
|
||||
);
|
||||
|
||||
export const updateFavorites = createAction(
|
||||
`${STORE_KEY} Add/remove provided channel to the favorites`,
|
||||
props<{ channel: Channel }>()
|
||||
);
|
||||
|
||||
export const setFavorites = createAction(
|
||||
`${STORE_KEY} Set favorites`,
|
||||
props<{ channelIds: string[] }>()
|
||||
);
|
||||
|
||||
export const setActiveChannel = createAction(
|
||||
`${STORE_KEY} Set active channel`,
|
||||
props<{ channel: Channel }>()
|
||||
);
|
||||
|
||||
export const resetActiveChannel = createAction(
|
||||
`${STORE_KEY} Reset active channel`
|
||||
);
|
||||
|
||||
export const setActiveChannelSuccess = createAction(
|
||||
`${STORE_KEY} Set active channel success`,
|
||||
props<{ channel: Channel }>()
|
||||
);
|
||||
|
||||
export const setActiveEpgProgram = createAction(
|
||||
`${STORE_KEY} Sets the given timestamp for the epg program`,
|
||||
props<{ program: EpgProgram }>()
|
||||
);
|
||||
|
||||
export const setCurrentEpgProgram = createAction(
|
||||
`${STORE_KEY} Updates the active epg program for the active channel`,
|
||||
props<{ program: EpgProgram }>()
|
||||
);
|
||||
|
||||
export const resetActiveEpgProgram = createAction(
|
||||
`${STORE_KEY} Reset active epg program`
|
||||
);
|
||||
|
||||
export const setEpgAvailableFlag = createAction(
|
||||
`${STORE_KEY} Reset active epg program`,
|
||||
props<{ value: boolean }>()
|
||||
);
|
||||
|
||||
export const setChannels = createAction(
|
||||
`${STORE_KEY} Set channels`,
|
||||
props<{ channels: Channel[] }>()
|
||||
);
|
||||
|
||||
export const updatePlaylistPositions = createAction(
|
||||
`${STORE_KEY} Update playlist positions`,
|
||||
props<{
|
||||
positionUpdates: { id: string; changes: { position: number } }[];
|
||||
}>()
|
||||
);
|
||||
|
||||
export const removeAllPlaylists = createAction(
|
||||
`${STORE_KEY} Remove all playlists`
|
||||
);
|
||||
|
||||
export const setAdjacentChannelAsActive = createAction(
|
||||
`${STORE_KEY} Set adjacent channel as active`,
|
||||
props<{
|
||||
direction: 'next' | 'previous';
|
||||
}>()
|
||||
);
|
||||
|
||||
export const setSelectedFilters = createAction(
|
||||
`${STORE_KEY} Set selected filters`,
|
||||
props<{ selectedFilters: string[] }>()
|
||||
);
|
||||
|
||||
export const setCurrentPlaylistId = createAction(
|
||||
`${STORE_KEY} Set current playlist id`,
|
||||
props<{ playlistId: string | undefined }>()
|
||||
);
|
||||
|
||||
export const handleAddingPlaylistByUrl = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Handle adding playlist by URL`,
|
||||
props<{ isTemporary: boolean; playlist: Playlist }>()
|
||||
);
|
||||
export const FilterActions = createActionGroup({
|
||||
source: 'Filters',
|
||||
events: {
|
||||
'Set Selected Filters': props<{ selectedFilters: string[] }>(),
|
||||
},
|
||||
});
|
||||
@@ -24,7 +24,12 @@ import {
|
||||
STORE_KEY,
|
||||
VideoPlayer,
|
||||
} from 'shared-interfaces';
|
||||
import * as PlaylistActions from './actions';
|
||||
import {
|
||||
PlaylistActions,
|
||||
ChannelActions,
|
||||
EpgActions,
|
||||
FavoritesActions,
|
||||
} from './actions';
|
||||
import {
|
||||
selectActive,
|
||||
selectActivePlaylistId,
|
||||
@@ -47,7 +52,7 @@ export class PlaylistEffects {
|
||||
updateFavorites$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.updateFavorites),
|
||||
ofType(FavoritesActions.updateFavorites),
|
||||
combineLatestWith(
|
||||
this.store.select(selectFavorites),
|
||||
this.store.select(selectActivePlaylistId)
|
||||
@@ -70,7 +75,7 @@ export class PlaylistEffects {
|
||||
setFavorites$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.setFavorites),
|
||||
ofType(FavoritesActions.setFavorites),
|
||||
combineLatestWith(this.store.select(selectActivePlaylistId)),
|
||||
filter(
|
||||
([, playlistId]) =>
|
||||
@@ -92,7 +97,7 @@ export class PlaylistEffects {
|
||||
setActiveEpgProgram$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.setActiveEpgProgram),
|
||||
ofType(EpgActions.setActiveEpgProgram),
|
||||
combineLatestWith(this.store.select(selectActive)),
|
||||
map(([, activeChannel]) => {
|
||||
firstValueFrom(this.storage.get(STORE_KEY.Settings)).then(
|
||||
@@ -135,7 +140,7 @@ export class PlaylistEffects {
|
||||
|
||||
setActiveChannel$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.setActiveChannel),
|
||||
ofType(ChannelActions.setActiveChannel),
|
||||
// Skip the effect entirely when channel is falsy
|
||||
filter((action) => !!action.channel),
|
||||
map((action) => {
|
||||
@@ -186,7 +191,7 @@ export class PlaylistEffects {
|
||||
}
|
||||
);
|
||||
|
||||
return PlaylistActions.setActiveChannelSuccess({
|
||||
return ChannelActions.setActiveChannelSuccess({
|
||||
channel: action.channel,
|
||||
});
|
||||
})
|
||||
@@ -400,7 +405,7 @@ export class PlaylistEffects {
|
||||
|
||||
setAdjacentChannelAsActive$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.setAdjacentChannelAsActive),
|
||||
ofType(ChannelActions.setAdjacentChannelAsActive),
|
||||
withLatestFrom(
|
||||
this.store.select(selectChannels),
|
||||
this.store.select(selectActive)
|
||||
@@ -419,7 +424,7 @@ export class PlaylistEffects {
|
||||
adjacentChannel = activeChannel;
|
||||
adjacentChannel = channels[index - 1];
|
||||
}
|
||||
return PlaylistActions.setActiveChannelSuccess({
|
||||
return ChannelActions.setActiveChannelSuccess({
|
||||
channel: adjacentChannel!,
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,292 +1,2 @@
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import moment from 'moment';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import * as PlaylistActions from './actions';
|
||||
import { playlistsAdapter } from './playlists.state';
|
||||
import { PlaylistState, initialState } from './state';
|
||||
|
||||
export const playlistReducer = createReducer(
|
||||
initialState,
|
||||
on(PlaylistActions.setActiveEpgProgram, (state, action): PlaylistState => {
|
||||
const { program } = action;
|
||||
const from = moment(program.start, 'YYYYMMDDHHmm ZZ').unix();
|
||||
const now = moment(Date.now()).unix();
|
||||
const epgParams = `?utc=${from}&lutc=${now}`;
|
||||
return {
|
||||
...state,
|
||||
active: { ...state.active, epgParams } as Channel,
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.resetActiveEpgProgram,
|
||||
(state): PlaylistState => ({
|
||||
...state,
|
||||
active: { ...state.active, epgParams: '' } as Channel,
|
||||
})
|
||||
),
|
||||
on(
|
||||
PlaylistActions.setActiveChannelSuccess,
|
||||
(state, action): PlaylistState => {
|
||||
const { channel } = action;
|
||||
return {
|
||||
...state,
|
||||
active: { ...channel, epgParams: '' } as Channel,
|
||||
};
|
||||
}
|
||||
),
|
||||
on(PlaylistActions.resetActiveChannel, (state): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
active: undefined,
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.setCurrentEpgProgram,
|
||||
(state, action): PlaylistState => ({
|
||||
...state,
|
||||
currentEpgProgram: action.program,
|
||||
})
|
||||
),
|
||||
on(
|
||||
PlaylistActions.setEpgAvailableFlag,
|
||||
(state, action): PlaylistState => ({
|
||||
...state,
|
||||
epgAvailable: action.value,
|
||||
})
|
||||
),
|
||||
on(PlaylistActions.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];
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
favorites,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.setFavorites, (state, action): PlaylistState => {
|
||||
const { channelIds } = action;
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
favorites: channelIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.loadPlaylistsSuccess, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addMany(action.playlists, {
|
||||
...state.playlists,
|
||||
allPlaylistsLoaded: true,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeOne(
|
||||
action.playlistId,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.updatePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateOne(
|
||||
{
|
||||
id: action.playlistId,
|
||||
changes: {
|
||||
...action.playlist,
|
||||
_id: action.playlistId,
|
||||
updateDate: Date.now(),
|
||||
count: action.playlist.playlist.items.length,
|
||||
userAgent: action.playlist.userAgent,
|
||||
favorites:
|
||||
state.playlists.entities[action.playlistId]
|
||||
?.favorites ?? [],
|
||||
},
|
||||
},
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.addPlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addOne(
|
||||
action.playlist,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.addManyPlaylists, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addMany(
|
||||
action.playlists,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.setActivePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
selectedId: action.playlistId,
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.updatePlaylistPositions,
|
||||
(state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateMany(
|
||||
action.positionUpdates,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}
|
||||
),
|
||||
on(PlaylistActions.updatePlaylistMeta, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateOne(
|
||||
{
|
||||
id: action.playlist._id,
|
||||
changes: {
|
||||
title: action.playlist.title,
|
||||
autoRefresh: action.playlist.autoRefresh || false,
|
||||
userAgent: action.playlist.userAgent,
|
||||
...(action.playlist.serverUrl !== null
|
||||
? { serverUrl: action.playlist.serverUrl }
|
||||
: {}),
|
||||
...(action.playlist.username !== null
|
||||
? { username: action.playlist.username }
|
||||
: {}),
|
||||
...(action.playlist.password !== null
|
||||
? { password: action.playlist.password }
|
||||
: {}),
|
||||
...(action.playlist.macAddress !== null
|
||||
? { macAddress: action.playlist.macAddress }
|
||||
: {}),
|
||||
...(action.playlist.portalUrl !== null
|
||||
? { portalUrl: action.playlist.portalUrl }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.setChannels, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
channels: action.channels,
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.updateManyPlaylists, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateMany(
|
||||
action.playlists.map((updatedPlaylist) => ({
|
||||
id: updatedPlaylist._id,
|
||||
changes: {
|
||||
...updatedPlaylist,
|
||||
updateDate: Date.now(),
|
||||
},
|
||||
})),
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removeAllPlaylists, (state): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeAll(state.playlists),
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.setSelectedFilters,
|
||||
(state, { selectedFilters }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
selectedFilters,
|
||||
},
|
||||
};
|
||||
}
|
||||
),
|
||||
on(
|
||||
PlaylistActions.setCurrentPlaylistId,
|
||||
(state, { playlistId }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
currentPlaylistId: playlistId,
|
||||
};
|
||||
}
|
||||
),
|
||||
on(
|
||||
PlaylistActions.handleAddingPlaylistByUrl,
|
||||
(state, action): PlaylistState => {
|
||||
if (action.isTemporary) {
|
||||
return {
|
||||
...state,
|
||||
channels: action.playlist.playlist.items as Channel[],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addOne(
|
||||
action.playlist,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const selectPlaylists = (state: PlaylistState) => state.playlists;
|
||||
export const selectPlaylistId = (state: PlaylistState) =>
|
||||
state.playlists?.selectedId;
|
||||
|
||||
export const selectIsEpgAvailableReducer = (state: PlaylistState) =>
|
||||
state.epgAvailable;
|
||||
export const selectActiveReducer = (state: PlaylistState) => state.active;
|
||||
export const selectCurrentEpgProgramReducer = (state: PlaylistState) =>
|
||||
state.currentEpgProgram;
|
||||
export const selectChannelsReducer = (state: PlaylistState) => state.channels;
|
||||
export const selectPlaylistsLoadingFlagReducer = (state: PlaylistState) =>
|
||||
state.playlists?.allPlaylistsLoaded;
|
||||
export const selectCurrentPlaylistIdReducer = (state: PlaylistState) =>
|
||||
state.currentPlaylistId;
|
||||
// Re-export from the new modular reducers structure
|
||||
export * from './reducers/index';
|
||||
@@ -0,0 +1,29 @@
|
||||
import { on } from '@ngrx/store';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import { ChannelActions } from '../actions';
|
||||
import { PlaylistState } from '../state';
|
||||
|
||||
export const channelReducers = [
|
||||
on(
|
||||
ChannelActions.setActiveChannelSuccess,
|
||||
(state, action): PlaylistState => {
|
||||
const { channel } = action;
|
||||
return {
|
||||
...state,
|
||||
active: { ...channel, epgParams: '' } as Channel,
|
||||
};
|
||||
}
|
||||
),
|
||||
on(ChannelActions.resetActiveChannel, (state): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
active: undefined,
|
||||
};
|
||||
}),
|
||||
on(ChannelActions.setChannels, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
channels: action.channels,
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
import { on } from '@ngrx/store';
|
||||
import moment from 'moment';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import { EpgActions } from '../actions';
|
||||
import { PlaylistState } from '../state';
|
||||
|
||||
export const epgReducers = [
|
||||
on(EpgActions.setActiveEpgProgram, (state, action): PlaylistState => {
|
||||
const { program } = action;
|
||||
const from = moment(program.start, 'YYYYMMDDHHmm ZZ').unix();
|
||||
const now = moment(Date.now()).unix();
|
||||
const epgParams = `?utc=${from}&lutc=${now}`;
|
||||
return {
|
||||
...state,
|
||||
active: { ...state.active, epgParams } as Channel,
|
||||
};
|
||||
}),
|
||||
on(
|
||||
EpgActions.resetActiveEpgProgram,
|
||||
(state): PlaylistState => ({
|
||||
...state,
|
||||
active: { ...state.active, epgParams: '' } as Channel,
|
||||
})
|
||||
),
|
||||
on(
|
||||
EpgActions.setCurrentEpgProgram,
|
||||
(state, action): PlaylistState => ({
|
||||
...state,
|
||||
currentEpgProgram: action.program,
|
||||
})
|
||||
),
|
||||
on(
|
||||
EpgActions.setEpgAvailableFlag,
|
||||
(state, action): PlaylistState => ({
|
||||
...state,
|
||||
epgAvailable: action.value,
|
||||
})
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,50 @@
|
||||
import { on } from '@ngrx/store';
|
||||
import { FavoritesActions } from '../actions';
|
||||
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];
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
favorites,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(FavoritesActions.setFavorites, (state, action): PlaylistState => {
|
||||
const { channelIds } = action;
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
favorites: channelIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
import { on } from '@ngrx/store';
|
||||
import { FilterActions } from '../actions';
|
||||
import { PlaylistState } from '../state';
|
||||
|
||||
export const filterReducers = [
|
||||
on(
|
||||
FilterActions.setSelectedFilters,
|
||||
(state, { selectedFilters }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
selectedFilters,
|
||||
},
|
||||
};
|
||||
}
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createReducer } from '@ngrx/store';
|
||||
import { initialState, PlaylistState } from '../state';
|
||||
import { channelReducers } from './channel.reducers';
|
||||
import { epgReducers } from './epg.reducers';
|
||||
import { favoritesReducers } from './favorites.reducers';
|
||||
import { filterReducers } from './filter.reducers';
|
||||
import { playlistReducers } from './playlist.reducers';
|
||||
|
||||
export const playlistReducer = createReducer(
|
||||
initialState,
|
||||
...epgReducers,
|
||||
...channelReducers,
|
||||
...playlistReducers,
|
||||
...favoritesReducers,
|
||||
...filterReducers
|
||||
);
|
||||
|
||||
// Selectors
|
||||
export const selectPlaylists = (state: PlaylistState) => state.playlists;
|
||||
export const selectPlaylistId = (state: PlaylistState) =>
|
||||
state.playlists?.selectedId;
|
||||
|
||||
export const selectIsEpgAvailableReducer = (state: PlaylistState) =>
|
||||
state.epgAvailable;
|
||||
export const selectActiveReducer = (state: PlaylistState) => state.active;
|
||||
export const selectCurrentEpgProgramReducer = (state: PlaylistState) =>
|
||||
state.currentEpgProgram;
|
||||
export const selectChannelsReducer = (state: PlaylistState) => state.channels;
|
||||
export const selectPlaylistsLoadingFlagReducer = (state: PlaylistState) =>
|
||||
state.playlists?.allPlaylistsLoaded;
|
||||
export const selectCurrentPlaylistIdReducer = (state: PlaylistState) =>
|
||||
state.currentPlaylistId;
|
||||
@@ -0,0 +1,166 @@
|
||||
import { on } from '@ngrx/store';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import { PlaylistActions } from '../actions';
|
||||
import { playlistsAdapter } from '../playlists.state';
|
||||
import { PlaylistState } from '../state';
|
||||
|
||||
export const playlistReducers = [
|
||||
on(PlaylistActions.loadPlaylistsSuccess, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addMany(action.playlists, {
|
||||
...state.playlists,
|
||||
allPlaylistsLoaded: true,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeOne(
|
||||
action.playlistId,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.updatePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateOne(
|
||||
{
|
||||
id: action.playlistId,
|
||||
changes: {
|
||||
...action.playlist,
|
||||
_id: action.playlistId,
|
||||
updateDate: Date.now(),
|
||||
count: action.playlist.playlist.items.length,
|
||||
userAgent: action.playlist.userAgent,
|
||||
favorites:
|
||||
state.playlists.entities[action.playlistId]
|
||||
?.favorites ?? [],
|
||||
},
|
||||
},
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.addPlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addOne(
|
||||
action.playlist,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.addManyPlaylists, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addMany(
|
||||
action.playlists,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.setActivePlaylist, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
selectedId: action.playlistId,
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.updatePlaylistPositions,
|
||||
(state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateMany(
|
||||
action.positionUpdates,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}
|
||||
),
|
||||
on(PlaylistActions.updatePlaylistMeta, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateOne(
|
||||
{
|
||||
id: action.playlist._id,
|
||||
changes: {
|
||||
title: action.playlist.title,
|
||||
autoRefresh: action.playlist.autoRefresh || false,
|
||||
userAgent: action.playlist.userAgent,
|
||||
...(action.playlist.serverUrl !== null
|
||||
? { serverUrl: action.playlist.serverUrl }
|
||||
: {}),
|
||||
...(action.playlist.username !== null
|
||||
? { username: action.playlist.username }
|
||||
: {}),
|
||||
...(action.playlist.password !== null
|
||||
? { password: action.playlist.password }
|
||||
: {}),
|
||||
...(action.playlist.macAddress !== null
|
||||
? { macAddress: action.playlist.macAddress }
|
||||
: {}),
|
||||
...(action.playlist.portalUrl !== null
|
||||
? { portalUrl: action.playlist.portalUrl }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.updateManyPlaylists, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.updateMany(
|
||||
action.playlists.map((updatedPlaylist) => ({
|
||||
id: updatedPlaylist._id,
|
||||
changes: {
|
||||
...updatedPlaylist,
|
||||
updateDate: Date.now(),
|
||||
},
|
||||
})),
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.removeAllPlaylists, (state): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeAll(state.playlists),
|
||||
};
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.setCurrentPlaylistId,
|
||||
(state, { playlistId }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
currentPlaylistId: playlistId,
|
||||
};
|
||||
}
|
||||
),
|
||||
on(
|
||||
PlaylistActions.handleAddingPlaylistByUrl,
|
||||
(state, action): PlaylistState => {
|
||||
if (action.isTemporary) {
|
||||
return {
|
||||
...state,
|
||||
channels: action.playlist.playlist.items as Channel[],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.addOne(
|
||||
action.playlist,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
];
|
||||
Reference in new issue
Block a user