mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
refactor: refactor state management
This commit is contained in:
1 parent
96e13d0afb
commit
c4e69adef2
6 files changed
+388
-48
No files matched your search
@@ -2,8 +2,59 @@ import { createAction, props } from '@ngrx/store';
|
||||
import { Channel } from '../../../shared/channel.interface';
|
||||
import { Playlist } from '../../../shared/playlist.interface';
|
||||
import { EpgProgram } from '../player/models/epg-program.model';
|
||||
import { PlaylistMeta } from '../shared/playlist-meta.type';
|
||||
|
||||
const STORE_KEY = '[PLAYLIST STORE]';
|
||||
const STORE_KEY = '[GLOBAL STORE]';
|
||||
const PLAYLISTS_STORE_KEY = '[PLAYLISTS STORE]';
|
||||
|
||||
export const loadPlaylists = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Load playlists from db`
|
||||
);
|
||||
|
||||
export const loadPlaylistsSuccess = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Successfully loaded playlists from db`,
|
||||
props<{ playlists: PlaylistMeta[] }>()
|
||||
);
|
||||
|
||||
export const addPlaylist = createAction(
|
||||
`${PLAYLISTS_STORE_KEY} Add new playlist`,
|
||||
props<{ playlist: Playlist }>()
|
||||
);
|
||||
|
||||
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 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`,
|
||||
@@ -43,3 +94,10 @@ export const setPlaylist = createAction(
|
||||
`${STORE_KEY} Set playlist as active`,
|
||||
props<{ playlist: Playlist }>()
|
||||
);
|
||||
|
||||
export const updatePlaylistPositions = createAction(
|
||||
`${STORE_KEY} Update playlist positions`,
|
||||
props<{
|
||||
positionUpdates: { id: string; changes: { position: number } }[];
|
||||
}>()
|
||||
);
|
||||
+125
-11
@@ -1,19 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { combineLatestWith, map } from 'rxjs/operators';
|
||||
import { combineLatestWith, map, switchMap, tap } from 'rxjs/operators';
|
||||
import {
|
||||
CHANNEL_SET_USER_AGENT,
|
||||
EPG_GET_PROGRAM,
|
||||
PLAYLIST_UPDATE_FAVORITES,
|
||||
} from '../../../shared/ipc-commands';
|
||||
import { DataService } from '../services/data.service';
|
||||
import { PlaylistsService } from '../services/playlists.service';
|
||||
import * as PlaylistActions from './actions';
|
||||
import {
|
||||
selectActivePlaylistId,
|
||||
selectFavorites,
|
||||
selectIsEpgAvailable,
|
||||
selectPlaylistId,
|
||||
} from './selectors';
|
||||
|
||||
@Injectable({ providedIn: 'any' })
|
||||
@@ -24,14 +25,11 @@ export class PlaylistEffects {
|
||||
ofType(PlaylistActions.updateFavorites),
|
||||
combineLatestWith(
|
||||
this.store.select(selectFavorites),
|
||||
this.store.select(selectPlaylistId)
|
||||
this.store.select(selectActivePlaylistId)
|
||||
),
|
||||
map(([action, favorites, playlistId]) => {
|
||||
this.dataService.sendIpcEvent(PLAYLIST_UPDATE_FAVORITES, {
|
||||
id: playlistId,
|
||||
favorites,
|
||||
});
|
||||
})
|
||||
switchMap(([action, favorites, playlistId]) =>
|
||||
this.playlistsService.updateFavorites(playlistId, favorites)
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
@@ -61,9 +59,125 @@ export class PlaylistEffects {
|
||||
);
|
||||
});
|
||||
|
||||
loadPlaylists$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.loadPlaylists),
|
||||
switchMap(() =>
|
||||
this.playlistsService.getAllPlaylists().pipe(
|
||||
map((playlists) =>
|
||||
PlaylistActions.loadPlaylistsSuccess({
|
||||
playlists,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
removePlaylist$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.removePlaylist),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.deletePlaylist(action.playlistId)
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
updatePlaylist$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.updatePlaylist),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.updatePlaylist(action.playlistId, {
|
||||
...action.playlist,
|
||||
_id: action.playlistId,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
parsePlaylist$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.parsePlaylist),
|
||||
map((action) =>
|
||||
this.playlistsService.handlePlaylistParsing(
|
||||
action.uploadType,
|
||||
action.playlist,
|
||||
action.title,
|
||||
action.path
|
||||
)
|
||||
),
|
||||
map((playlist) =>
|
||||
PlaylistActions.addPlaylist({
|
||||
playlist,
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
addPlaylist$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.addPlaylist),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.addPlaylist(action.playlist)
|
||||
),
|
||||
tap((playlist) => {
|
||||
this.router.navigate(['/playlists/', playlist._id]);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
updatePlaylistMeta$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.updatePlaylistMeta),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.updatePlaylistMeta(action.playlist)
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
updatePlaylistPositions$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.updatePlaylistPositions),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.updatePlaylistPositions(
|
||||
action.positionUpdates
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
addManyPlaylists$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(PlaylistActions.addManyPlaylists),
|
||||
switchMap((action) =>
|
||||
this.playlistsService.addManyPlaylists(action.playlists)
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private playlistsService: PlaylistsService,
|
||||
private dataService: DataService,
|
||||
private store: Store
|
||||
private store: Store,
|
||||
private router: Router
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
|
||||
import { Playlist } from '../../../shared/playlist.interface';
|
||||
import { PlaylistMeta } from '../shared/playlist-meta.type';
|
||||
|
||||
export const playlistsAdapter: EntityAdapter<PlaylistMeta> =
|
||||
createEntityAdapter<PlaylistMeta>({
|
||||
selectId: (item: Playlist) => item._id,
|
||||
});
|
||||
|
||||
export interface PlaylistMetaState extends EntityState<PlaylistMeta> {
|
||||
selectedId: string;
|
||||
}
|
||||
|
||||
export const initialPlaylistMetaState: PlaylistMetaState =
|
||||
playlistsAdapter.getInitialState({
|
||||
selectedId: '',
|
||||
});
|
||||
|
||||
export const {
|
||||
selectIds,
|
||||
selectEntities: getPlaylistMetaEntities,
|
||||
selectAll: getAllPlaylistsMeta,
|
||||
selectTotal,
|
||||
} = playlistsAdapter.getSelectors();
|
||||
+111
-15
@@ -1,7 +1,7 @@
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import * as moment from 'moment';
|
||||
import { createChannel } from '../shared/channel.model';
|
||||
import * as PlaylistActions from './actions';
|
||||
import { playlistsAdapter } from './playlists.state';
|
||||
import { initialState, PlaylistState } from './state';
|
||||
|
||||
export const playlistReducer = createReducer(
|
||||
@@ -47,39 +47,135 @@ export const playlistReducer = createReducer(
|
||||
epgAvailable: action.value,
|
||||
})
|
||||
),
|
||||
on(PlaylistActions.setPlaylist, (state, action): PlaylistState => {
|
||||
const { playlist } = action;
|
||||
|
||||
/* on(PlaylistActions.setPlaylist, (state, action): PlaylistState => {
|
||||
const { playlist } = action;
|
||||
return {
|
||||
...state,
|
||||
channels: playlist?.playlist.items.map((element) =>
|
||||
createChannel(element)
|
||||
),
|
||||
favorites: playlist?.favorites || [],
|
||||
active: undefined,
|
||||
playlistId: playlist._id,
|
||||
playlistFilename: playlist.title || playlist.filename,
|
||||
};
|
||||
}),
|
||||
}), */
|
||||
on(PlaylistActions.updateFavorites, (state, action): PlaylistState => {
|
||||
let favorites;
|
||||
const { channel } = action;
|
||||
if (state.favorites.includes(channel.id)) {
|
||||
favorites = [...state.favorites.filter((id) => id !== channel.id)];
|
||||
const playlistFavorites =
|
||||
state.playlists.entities[state.playlists.selectedId].favorites;
|
||||
if (playlistFavorites.includes(channel.id)) {
|
||||
favorites = [
|
||||
...playlistFavorites.filter((id) => id !== channel.id),
|
||||
];
|
||||
} else {
|
||||
favorites = [...state.favorites, channel.id];
|
||||
favorites = [...playlistFavorites, channel.id];
|
||||
}
|
||||
return { ...state, favorites };
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
entities: {
|
||||
...state.playlists.entities,
|
||||
[state.playlists.selectedId]: {
|
||||
...state.playlists.entities[state.playlists.selectedId],
|
||||
favorites,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
on(PlaylistActions.loadPlaylistsSuccess, (state, action): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: playlistsAdapter.upsertMany(
|
||||
action.playlists,
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
}),
|
||||
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 },
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
state.playlists
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const selectFavorites = (state: PlaylistState) => state.favorites;
|
||||
export const selectPlaylistId = (state: PlaylistState) => state.playlistId;
|
||||
export const selectPlaylistFilename = (state: PlaylistState) =>
|
||||
state.playlistFilename;
|
||||
export const selectIsEpgAvailable = (state: PlaylistState) =>
|
||||
state.epgAvailable;
|
||||
export const selectActive = (state: PlaylistState) => state.active;
|
||||
export const selectCurrentEpgProgram = (state: PlaylistState) =>
|
||||
state.currentEpgProgram;
|
||||
export const selectChannels = (state: PlaylistState) => state.channels;
|
||||
export const selectPlaylists = (state: PlaylistState) => state.playlists;
|
||||
export const selectPlaylistId = (state: PlaylistState) =>
|
||||
state.playlists?.selectedId;
|
||||
+65
-15
@@ -1,30 +1,16 @@
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import * as fromPlaylistMetaState from './playlists.state';
|
||||
import * as fromPlaylistState from './reducers';
|
||||
import { PlaylistState } from './state';
|
||||
|
||||
export const selectPlaylistState =
|
||||
createFeatureSelector<PlaylistState>('playlistState');
|
||||
|
||||
export const selectFavorites = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectFavorites
|
||||
);
|
||||
|
||||
export const selectPlaylistId = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectPlaylistId
|
||||
);
|
||||
|
||||
export const selectIsEpgAvailable = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectIsEpgAvailable
|
||||
);
|
||||
|
||||
export const selectPlaylistFilename = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectPlaylistFilename
|
||||
);
|
||||
|
||||
export const selectActive = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectActive
|
||||
@@ -39,3 +25,67 @@ export const selectChannels = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectChannels
|
||||
);
|
||||
|
||||
/** Playlist entity store selectors */
|
||||
export const selectPlaylistsMetaState = createSelector(
|
||||
selectPlaylistState,
|
||||
fromPlaylistState.selectPlaylists
|
||||
);
|
||||
|
||||
export const selectAllPlaylistsMeta = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getAllPlaylistsMeta
|
||||
);
|
||||
|
||||
export const selectPlaylistEntity = (id: string) =>
|
||||
createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(data) => {
|
||||
return data.entities[id];
|
||||
}
|
||||
);
|
||||
|
||||
export const selectActivePlaylistId = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(data) => data.selectedId
|
||||
);
|
||||
|
||||
export const selectPlaylistTitle = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
fromPlaylistState.selectPlaylistId,
|
||||
(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';
|
||||
}
|
||||
);
|
||||
|
||||
export const selectPlaylistEntities = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities
|
||||
);
|
||||
|
||||
export const selectFavorites = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
fromPlaylistState.selectPlaylistId,
|
||||
(data) => {
|
||||
if (
|
||||
data.entities &&
|
||||
data.selectedId &&
|
||||
data.entities[data.selectedId]
|
||||
) {
|
||||
return data.entities[data.selectedId].favorites || [];
|
||||
} else return [];
|
||||
}
|
||||
);
|
||||
@@ -1,22 +1,20 @@
|
||||
import { Channel } from '../../../shared/channel.interface';
|
||||
import { initialPlaylistMetaState, PlaylistMetaState } from './playlists.state';
|
||||
|
||||
import { EpgProgram } from '../player/models/epg-program.model';
|
||||
|
||||
export interface PlaylistState {
|
||||
active: Channel | undefined;
|
||||
currentEpgProgram: EpgProgram | undefined;
|
||||
epgAvailable: boolean;
|
||||
favorites: string[];
|
||||
playlistId: string;
|
||||
playlistFilename: string;
|
||||
channels: Channel[]; // TODO: use entity store
|
||||
playlists: PlaylistMetaState;
|
||||
}
|
||||
|
||||
export const initialState: PlaylistState = {
|
||||
active: undefined,
|
||||
currentEpgProgram: undefined,
|
||||
epgAvailable: false,
|
||||
favorites: [],
|
||||
playlistId: '',
|
||||
playlistFilename: '',
|
||||
channels: [],
|
||||
playlists: initialPlaylistMetaState,
|
||||
};
|
||||
Reference in new issue
Block a user