Add playlists
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
||||
import { Album, AlbumTrack } from './types';
|
||||
import { Album, AlbumTrack, Playlist } from './types';
|
||||
import { AsyncThunkAPI } from '..';
|
||||
import { retrieveAllAlbums, retrieveAlbumTracks, retrieveRecentAlbums, searchItem, retrieveAlbum } from 'utility/JellyfinApi';
|
||||
import { retrieveAllAlbums, retrieveAlbumTracks, retrieveRecentAlbums, searchItem, retrieveAlbum, retrieveAllPlaylists, retrievePlaylistTracks } from 'utility/JellyfinApi';
|
||||
|
||||
export const albumAdapter = createEntityAdapter<Album>({
|
||||
selectId: album => album.Id,
|
||||
@@ -76,4 +76,31 @@ AsyncThunkAPI
|
||||
results
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const playlistAdapter = createEntityAdapter<Playlist>({
|
||||
selectId: (playlist) => playlist.Id,
|
||||
sortComparer: (a, b) => a.Name.localeCompare(b.Name),
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch all playlists available
|
||||
*/
|
||||
export const fetchAllPlaylists = createAsyncThunk<Playlist[], undefined, AsyncThunkAPI>(
|
||||
'/playlists/all',
|
||||
async (empty, thunkAPI) => {
|
||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
||||
return retrieveAllPlaylists(credentials) as Promise<Playlist[]>;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieve all tracks from a particular playlist
|
||||
*/
|
||||
export const fetchTracksByPlaylist = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
||||
'/tracks/byPlaylist',
|
||||
async (ItemId, thunkAPI) => {
|
||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
||||
return retrievePlaylistTracks(ItemId, credentials) as Promise<AlbumTrack[]>;
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,16 @@
|
||||
import { fetchAllAlbums, albumAdapter, fetchTracksByAlbum, trackAdapter, fetchRecentAlbums, searchAndFetchAlbums } from './actions';
|
||||
import {
|
||||
fetchAllAlbums,
|
||||
albumAdapter,
|
||||
fetchTracksByAlbum,
|
||||
trackAdapter,
|
||||
fetchRecentAlbums,
|
||||
searchAndFetchAlbums,
|
||||
playlistAdapter,
|
||||
fetchAllPlaylists,
|
||||
fetchTracksByPlaylist
|
||||
} from './actions';
|
||||
import { createSlice, Dictionary, EntityId } from '@reduxjs/toolkit';
|
||||
import { Album, AlbumTrack } from './types';
|
||||
import { Album, AlbumTrack, Playlist } from './types';
|
||||
import { setJellyfinCredentials } from 'store/settings/actions';
|
||||
|
||||
export interface State {
|
||||
@@ -8,13 +18,21 @@ export interface State {
|
||||
isLoading: boolean;
|
||||
entities: Dictionary<Album>;
|
||||
ids: EntityId[];
|
||||
lastRefreshed?: number,
|
||||
},
|
||||
tracks: {
|
||||
isLoading: boolean;
|
||||
entities: Dictionary<AlbumTrack>;
|
||||
ids: EntityId[];
|
||||
byAlbum: Dictionary<EntityId[]>;
|
||||
byPlaylist: Dictionary<EntityId[]>;
|
||||
},
|
||||
lastRefreshed?: number,
|
||||
playlists: {
|
||||
isLoading: boolean;
|
||||
entities: Dictionary<Playlist>;
|
||||
ids: EntityId[];
|
||||
lastRefreshed?: number,
|
||||
}
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
@@ -25,7 +43,13 @@ const initialState: State = {
|
||||
tracks: {
|
||||
...trackAdapter.getInitialState(),
|
||||
isLoading: false,
|
||||
byAlbum: {},
|
||||
byPlaylist: {},
|
||||
},
|
||||
playlists: {
|
||||
...playlistAdapter.getInitialState(),
|
||||
isLoading: false,
|
||||
}
|
||||
};
|
||||
|
||||
const music = createSlice({
|
||||
@@ -41,7 +65,7 @@ const music = createSlice({
|
||||
builder.addCase(fetchAllAlbums.fulfilled, (state, { payload }) => {
|
||||
albumAdapter.setAll(state.albums, payload);
|
||||
state.albums.isLoading = false;
|
||||
state.lastRefreshed = new Date().getTime();
|
||||
state.albums.lastRefreshed = new Date().getTime();
|
||||
});
|
||||
builder.addCase(fetchAllAlbums.pending, (state) => { state.albums.isLoading = true; });
|
||||
builder.addCase(fetchAllAlbums.rejected, (state) => { state.albums.isLoading = false; });
|
||||
@@ -59,7 +83,7 @@ const music = createSlice({
|
||||
/**
|
||||
* Fetch tracks by album
|
||||
*/
|
||||
builder.addCase(fetchTracksByAlbum.fulfilled, (state, { payload }) => {
|
||||
builder.addCase(fetchTracksByAlbum.fulfilled, (state, { payload, meta }) => {
|
||||
if (!payload.length) {
|
||||
return;
|
||||
}
|
||||
@@ -67,9 +91,9 @@ const music = createSlice({
|
||||
trackAdapter.upsertMany(state.tracks, payload);
|
||||
|
||||
// Also store all the track ids in the album
|
||||
const album = state.albums.entities[payload[0].AlbumId];
|
||||
state.tracks.byAlbum[meta.arg] = payload.map(d => d.Id);
|
||||
const album = state.albums.entities[meta.arg];
|
||||
if (album) {
|
||||
album.Tracks = payload.map(d => d.Id);
|
||||
album.lastRefreshed = new Date().getTime();
|
||||
}
|
||||
state.tracks.isLoading = false;
|
||||
@@ -82,6 +106,40 @@ const music = createSlice({
|
||||
albumAdapter.upsertMany(state.albums, payload.albums);
|
||||
state.albums.isLoading = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch all playlists
|
||||
*/
|
||||
builder.addCase(fetchAllPlaylists.fulfilled, (state, { payload }) => {
|
||||
playlistAdapter.setAll(state.playlists, payload);
|
||||
state.playlists.isLoading = false;
|
||||
state.playlists.lastRefreshed = new Date().getTime();
|
||||
});
|
||||
builder.addCase(fetchAllPlaylists.pending, (state) => { state.playlists.isLoading = true; });
|
||||
builder.addCase(fetchAllPlaylists.rejected, (state) => { state.playlists.isLoading = false; });
|
||||
|
||||
/**
|
||||
* Fetch tracks by playlist
|
||||
*/
|
||||
builder.addCase(fetchTracksByPlaylist.fulfilled, (state, { payload, meta }) => {
|
||||
if (!payload.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Upsert the retrieved tracks
|
||||
trackAdapter.upsertMany(state.tracks, payload);
|
||||
|
||||
// Also store all the track ids in the playlist
|
||||
state.tracks.byPlaylist[meta.arg] = payload.map(d => d.Id);
|
||||
state.tracks.isLoading = false;
|
||||
|
||||
const playlist = state.playlists.entities[meta.arg];
|
||||
if (playlist) {
|
||||
playlist.lastRefreshed = new Date().getTime();
|
||||
}
|
||||
});
|
||||
builder.addCase(fetchTracksByPlaylist.pending, (state) => { state.tracks.isLoading = true; });
|
||||
builder.addCase(fetchTracksByPlaylist.rejected, (state) => { state.tracks.isLoading = false; });
|
||||
|
||||
// Reset any caches we have when a new server is set
|
||||
builder.addCase(setJellyfinCredentials, () => initialState);
|
||||
|
||||
@@ -55,7 +55,7 @@ export type SectionedId = SectionListData<EntityId>;
|
||||
/**
|
||||
* Splits a set of albums into a list that is split by alphabet letters
|
||||
*/
|
||||
function splitByAlphabet(state: AppState['music']['albums']): SectionedId[] {
|
||||
function splitAlbumsByAlphabet(state: AppState['music']['albums']): SectionedId[] {
|
||||
const { entities: albums } = state;
|
||||
const albumIds = albumsByArtist(state);
|
||||
const sections: SectionedId[] = ALPHABET_LETTERS.split('').map((l) => ({ label: l, data: [] }));
|
||||
@@ -75,5 +75,5 @@ function splitByAlphabet(state: AppState['music']['albums']): SectionedId[] {
|
||||
*/
|
||||
export const selectAlbumsByAlphabet = createSelector(
|
||||
(state: AppState) => state.music.albums,
|
||||
splitByAlphabet,
|
||||
);
|
||||
splitAlbumsByAlphabet,
|
||||
);
|
||||
|
||||
@@ -74,4 +74,24 @@ export interface State {
|
||||
entities: Dictionary<Album>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
Name: string;
|
||||
ServerId: string;
|
||||
Id: string;
|
||||
CanDelete: boolean;
|
||||
SortName: string;
|
||||
ChannelId?: any;
|
||||
RunTimeTicks: number;
|
||||
IsFolder: boolean;
|
||||
Type: string;
|
||||
UserData: UserData;
|
||||
PrimaryImageAspectRatio: number;
|
||||
ImageTags: ImageTags;
|
||||
BackdropImageTags: any[];
|
||||
LocationType: string;
|
||||
MediaType: string;
|
||||
Tracks?: string[];
|
||||
lastRefreshed?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user