mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
progress on subsonic api
This commit is contained in:
@@ -73,7 +73,7 @@ function createLoggedFunction<TRequest, TResponse>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return async (request: TRequest, server: ServerListItem, options?: any) => {
|
||||
return async (request: TRequest, requestOptions?: any) => {
|
||||
const startTime = Date.now();
|
||||
const requestId = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
@@ -96,13 +96,11 @@ function createLoggedFunction<TRequest, TResponse>(
|
||||
logger.info(`[API] ${functionName} called`, {
|
||||
request: truncatedRequest,
|
||||
requestId,
|
||||
serverId: server.id,
|
||||
serverType: server.type,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await originalFn(request, server, options);
|
||||
const result = await originalFn(request, requestOptions);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (result[0]) {
|
||||
|
||||
@@ -1,47 +1,75 @@
|
||||
import { createLoggedApiController } from '/@/renderer/api/api-controller-logger';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
import {
|
||||
apiClient as subsonicApiClient,
|
||||
controller as subsonicBaseAdapter,
|
||||
createApiClient as subsonicApiClient,
|
||||
authenticate as subsonicAuthenticate,
|
||||
controller as subsonicController,
|
||||
middleware as subsonicMiddleware,
|
||||
} from '/@/shared/api/subsonic/subsonic-controller';
|
||||
import { ApiController } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
|
||||
export const serverApi = {
|
||||
export const serverApiMap = {
|
||||
[ServerType.JELLYFIN]: {
|
||||
apiClient: null,
|
||||
authenticate: null,
|
||||
controller: {},
|
||||
middleware: null,
|
||||
},
|
||||
[ServerType.NAVIDROME]: {
|
||||
apiClient: null,
|
||||
authenticate: null,
|
||||
controller: {},
|
||||
middleware: null,
|
||||
},
|
||||
[ServerType.SUBSONIC]: {
|
||||
apiClient: subsonicApiClient,
|
||||
controller: createLoggedApiController(subsonicBaseAdapter),
|
||||
authenticate: subsonicAuthenticate,
|
||||
controller: subsonicController,
|
||||
middleware: subsonicMiddleware,
|
||||
},
|
||||
};
|
||||
|
||||
export const api = (serverId: string): ApiController => {
|
||||
const server = getServerById(serverId);
|
||||
const getApiByServer = (serverId: string): ApiController => {
|
||||
const servers = useAuthStore.getState().serverList;
|
||||
const server = servers[serverId];
|
||||
|
||||
if (!server) {
|
||||
throw new Error('No server or api client selected');
|
||||
}
|
||||
|
||||
const { apiClient, controller, middleware } = serverApi[server.type];
|
||||
|
||||
if (middleware) {
|
||||
apiClient.use(middleware(server));
|
||||
}
|
||||
const { apiClient, controller, middleware } = serverApiMap[server.type];
|
||||
|
||||
if (!apiClient) {
|
||||
throw new Error('No api client found');
|
||||
}
|
||||
|
||||
return controller as ApiController;
|
||||
const client = apiClient(server, middleware);
|
||||
|
||||
return createLoggedApiController(controller(client, server));
|
||||
};
|
||||
|
||||
const getAppApi = () => {
|
||||
const servers = useAuthStore.getState().serverList;
|
||||
|
||||
return Object.entries(servers).reduce(
|
||||
(acc, [id]) => {
|
||||
acc[id] = getApiByServer(id);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ApiController>,
|
||||
);
|
||||
};
|
||||
|
||||
export const api = {
|
||||
authenticate: (serverType: ServerType) => {
|
||||
const { authenticate } = serverApiMap[serverType];
|
||||
|
||||
if (!serverType || !authenticate) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return authenticate;
|
||||
},
|
||||
controller: getAppApi(),
|
||||
};
|
||||
|
||||
@@ -322,7 +322,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: albumListSortMap.jellyfin[query.sortBy] || 'SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
@@ -334,14 +334,14 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.album(item, apiClientProps.server)),
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
},
|
||||
getAlbumListCount: async ({ apiClientProps, query }) =>
|
||||
JellyfinController.getAlbumList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getArtistList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
@@ -356,7 +356,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: albumArtistListSortMap.jellyfin[query.sortBy] || 'SortName,Name',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
UserId: apiClientProps.server?.userId || undefined,
|
||||
},
|
||||
});
|
||||
@@ -369,14 +369,14 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
items: res.body.Items.map((item) =>
|
||||
jfNormalize.albumArtist(item, apiClientProps.server),
|
||||
),
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
},
|
||||
getArtistListCount: async ({ apiClientProps, query }) =>
|
||||
JellyfinController.getArtistList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getDownloadUrl: (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
@@ -398,7 +398,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query?.searchTerm,
|
||||
SortBy: genreListSortMap.jellyfin[query.sortBy] || 'SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
UserId: apiClientProps.server?.userId,
|
||||
},
|
||||
});
|
||||
@@ -409,7 +409,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.genre(item, apiClientProps.server)),
|
||||
startIndex: query.startIndex || 0,
|
||||
offset: query.offset || 0,
|
||||
totalRecordCount: res.body?.TotalRecordCount || 0,
|
||||
};
|
||||
},
|
||||
@@ -458,7 +458,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: musicFolders.map(jfNormalize.musicFolder),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: musicFolders?.length || 0,
|
||||
};
|
||||
},
|
||||
@@ -505,7 +505,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: playlistListSortMap.jellyfin[query.sortBy],
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -515,14 +515,14 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.playlist(item, apiClientProps.server)),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
},
|
||||
getPlaylistListCount: async ({ apiClientProps, query }) =>
|
||||
JellyfinController.getPlaylistList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getPlaylistSongList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
@@ -552,7 +552,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||
startIndex: query.startIndex,
|
||||
offset: query.startIndex,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
},
|
||||
@@ -602,7 +602,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.Items.length || 0,
|
||||
};
|
||||
},
|
||||
@@ -742,7 +742,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
@@ -777,7 +777,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
StartIndex: query.offset,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
@@ -806,14 +806,14 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
items: items.map((item) =>
|
||||
jfNormalize.song(item, apiClientProps.server, '', query.imageSize),
|
||||
),
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount,
|
||||
};
|
||||
},
|
||||
getSongListCount: async ({ apiClientProps, query }) =>
|
||||
JellyfinController.getSongList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getTags: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
@@ -869,7 +869,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -273,10 +273,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_end: query.offset + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: albumListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
artist_id: query.artistIds?.[0],
|
||||
compilation: query.compilation,
|
||||
genre_id: query.genres?.[0],
|
||||
@@ -293,24 +293,24 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
|
||||
startIndex: query?.startIndex || 0,
|
||||
offset: query?.offset || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
getAlbumListCount: async ({ apiClientProps, query }) =>
|
||||
NavidromeController.getAlbumList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getArtistList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getAlbumArtistList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_end: query.offset + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
name: query.searchTerm,
|
||||
...query._custom?.navidrome,
|
||||
role: query.role || undefined,
|
||||
@@ -335,14 +335,14 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
apiClientProps.server,
|
||||
),
|
||||
),
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
getArtistListCount: async ({ apiClientProps, query }) =>
|
||||
NavidromeController.getArtistList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getDownloadUrl: SubsonicController.getDownloadUrl,
|
||||
getGenreList: async (args) => {
|
||||
@@ -350,10 +350,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getGenreList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_end: query.offset + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: genreListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
name: query.searchTerm,
|
||||
},
|
||||
});
|
||||
@@ -364,7 +364,7 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.data.map((genre) => ndNormalize.genre(genre)),
|
||||
startIndex: query.startIndex || 0,
|
||||
offset: query.offset || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
@@ -400,10 +400,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getPlaylistList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_end: query.offset + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
q: query.searchTerm,
|
||||
...customQuery,
|
||||
},
|
||||
@@ -415,14 +415,14 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.data.map((item) => ndNormalize.playlist(item, apiClientProps.server)),
|
||||
startIndex: query?.startIndex || 0,
|
||||
offset: query?.offset || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
getPlaylistListCount: async ({ apiClientProps, query }) =>
|
||||
NavidromeController.getPlaylistList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getPlaylistSongList: async (
|
||||
args: PlaylistSongListRequest,
|
||||
@@ -450,7 +450,7 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
|
||||
startIndex: query?.startIndex || 0,
|
||||
offset: query?.startIndex || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
@@ -576,10 +576,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getSongList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || -1),
|
||||
_end: query.offset + (query.limit || -1),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: songListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
album_artist_id: query.albumArtistIds,
|
||||
album_id: query.albumIds,
|
||||
artist_id: query.artistIds,
|
||||
@@ -599,14 +599,14 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
items: res.body.data.map((song) =>
|
||||
ndNormalize.song(song, apiClientProps.server, query.imageSize),
|
||||
),
|
||||
startIndex: query?.startIndex || 0,
|
||||
offset: query?.offset || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
getSongListCount: async ({ apiClientProps, query }) =>
|
||||
NavidromeController.getSongList({
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
query: { ...query, limit: 1, offset: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getStructuredLyrics: SubsonicController.getStructuredLyrics,
|
||||
getTags: async (args) => {
|
||||
@@ -655,10 +655,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getUserList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_end: query.offset + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: userListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
_start: query.offset,
|
||||
...query._custom?.navidrome,
|
||||
},
|
||||
});
|
||||
@@ -669,7 +669,7 @@ export const NavidromeController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: res.body.data.map((user) => ndNormalize.user(user)),
|
||||
startIndex: query?.startIndex || 0,
|
||||
offset: query?.offset || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -269,7 +269,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
const res = await ssApiClient(apiClientProps).search3({
|
||||
query: {
|
||||
albumCount: query.limit,
|
||||
albumOffset: query.startIndex,
|
||||
albumOffset: query.offset,
|
||||
artistCount: 0,
|
||||
artistOffset: 0,
|
||||
query: query.searchTerm || '',
|
||||
@@ -289,7 +289,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results,
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: null,
|
||||
};
|
||||
}
|
||||
@@ -323,7 +323,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: sortAlbumList(items, query.sortBy, query.sortOrder),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: albums.length,
|
||||
};
|
||||
}
|
||||
@@ -346,7 +346,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: sortAlbumList(results, query.sortBy, query.sortOrder),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.starred?.album?.length || 0,
|
||||
};
|
||||
}
|
||||
@@ -390,7 +390,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
fromYear,
|
||||
genre: query.genres?.length ? query.genres[0] : undefined,
|
||||
musicFolderId: query.musicFolderId,
|
||||
offset: query.startIndex,
|
||||
offset: query.offset,
|
||||
size: query.limit,
|
||||
toYear,
|
||||
type,
|
||||
@@ -406,7 +406,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
res.body.albumList2.album?.map((album) =>
|
||||
normalize.album(album, apiClientProps.server, 300),
|
||||
) || [],
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: null,
|
||||
};
|
||||
},
|
||||
@@ -591,7 +591,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results,
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: results?.length || 0,
|
||||
};
|
||||
},
|
||||
@@ -639,7 +639,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: genres,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: genres.length,
|
||||
};
|
||||
},
|
||||
@@ -657,7 +657,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
id: folder.id.toString(),
|
||||
name: folder.name,
|
||||
})),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.musicFolders.musicFolder.length,
|
||||
};
|
||||
},
|
||||
@@ -720,7 +720,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results.map((playlist) => normalize.playlist(playlist, apiClientProps.server)),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: results.length,
|
||||
};
|
||||
},
|
||||
@@ -764,7 +764,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: results?.length || 0,
|
||||
};
|
||||
},
|
||||
@@ -789,7 +789,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results.map((song) => normalize.song(song, apiClientProps.server)),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.randomSongs?.song?.length || 0,
|
||||
};
|
||||
},
|
||||
@@ -907,7 +907,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
artistOffset: 0,
|
||||
query: query.searchTerm || '',
|
||||
songCount: query.limit,
|
||||
songOffset: query.startIndex,
|
||||
songOffset: query.offset,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -920,7 +920,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
res.body.searchResult3?.song?.map((song) =>
|
||||
normalize.song(song, apiClientProps.server),
|
||||
) || [],
|
||||
startIndex: query.startIndex,
|
||||
offset: query.offset,
|
||||
totalRecordCount: null,
|
||||
};
|
||||
}
|
||||
@@ -931,7 +931,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
count: query.limit,
|
||||
genre: query.genreIds[0],
|
||||
musicFolderId: query.musicFolderId,
|
||||
offset: query.startIndex,
|
||||
offset: query.offset,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -943,7 +943,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results.map((song) => normalize.song(song, apiClientProps.server)) || [],
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: null,
|
||||
};
|
||||
}
|
||||
@@ -966,7 +966,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: sortSongList(results, query.sortBy, query.sortOrder),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: (res.body.starred?.song || []).length || 0,
|
||||
};
|
||||
}
|
||||
@@ -1036,7 +1036,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
items: results.map((song) => normalize.song(song, apiClientProps.server)),
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: results.length,
|
||||
};
|
||||
}
|
||||
@@ -1049,7 +1049,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
artistOffset: 0,
|
||||
query: query.searchTerm || '',
|
||||
songCount: query.limit,
|
||||
songOffset: query.startIndex,
|
||||
songOffset: query.offset,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1062,7 +1062,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
res.body.searchResult3?.song?.map((song) =>
|
||||
normalize.song(song, apiClientProps.server),
|
||||
) || [],
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: null,
|
||||
};
|
||||
},
|
||||
@@ -1299,7 +1299,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
res.body.topSongs?.song?.map((song) =>
|
||||
normalize.song(song, apiClientProps.server),
|
||||
) || [],
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -23,7 +23,10 @@ import { SetContextMenuItems, useHandleTableContextMenu } from '/@/renderer/feat
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { PersistedTableColumn, useListStoreActions } from '/@/renderer/store';
|
||||
import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import {
|
||||
BasePaginatedResponse,
|
||||
BasePaginatedQuery,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { ListDisplayType, TablePagination } from '/@/shared/types/types';
|
||||
@@ -49,7 +52,7 @@ interface UseAgGridProps<TFilter> {
|
||||
|
||||
const BLOCK_SIZE = 500;
|
||||
|
||||
export const useVirtualTable = <TFilter extends BaseQuery<any>>({
|
||||
export const useVirtualTable = <TFilter extends BasePaginatedQuery<any>>({
|
||||
columnType,
|
||||
contextMenu,
|
||||
customFilters,
|
||||
|
||||
@@ -150,8 +150,8 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||
|
||||
const artistQuery = useAlbumList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
|
||||
gcTime: 1000 * 60,
|
||||
keepPreviousData: true,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
@@ -165,9 +165,9 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||
? [detailQuery?.data?.albumArtists[0].id]
|
||||
: undefined,
|
||||
limit: 15,
|
||||
offset: 0,
|
||||
sortBy: AlbumListSort.YEAR,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -175,15 +175,15 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||
const relatedAlbumGenresRequest: AlbumListQuery = {
|
||||
genres: detailQuery.data?.genres.length ? [detailQuery.data.genres[0].id] : undefined,
|
||||
limit: 15,
|
||||
offset: 0,
|
||||
sortBy: AlbumListSort.RANDOM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
};
|
||||
|
||||
const relatedAlbumGenresQuery = useAlbumList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
enabled: !!detailQuery?.data?.genres?.[0],
|
||||
gcTime: 1000 * 60,
|
||||
queryKey: queryKeys.albums.related(
|
||||
server?.id || '',
|
||||
albumId,
|
||||
|
||||
@@ -137,15 +137,11 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
||||
const itemData: Album[] = [];
|
||||
|
||||
for (const [, data] of queriesFromCache) {
|
||||
const { items, startIndex } = data || {};
|
||||
const { items, offset } = data || {};
|
||||
|
||||
if (items && items.length !== 1 && startIndex !== undefined) {
|
||||
if (items && items.length !== 1 && offset !== undefined) {
|
||||
let itemIndex = 0;
|
||||
for (
|
||||
let rowIndex = startIndex;
|
||||
rowIndex < startIndex + items.length;
|
||||
rowIndex += 1
|
||||
) {
|
||||
for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
|
||||
itemData[rowIndex] = items[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
@@ -165,7 +161,7 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
||||
limit: take,
|
||||
...filter,
|
||||
...customFilters,
|
||||
startIndex: skip,
|
||||
offset: skip,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.albums.list(server?.id || '', query, id);
|
||||
|
||||
@@ -41,14 +41,14 @@ export const JellyfinAlbumFilters = ({
|
||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
||||
const genreListQuery = useGenreList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
musicFolderId: filter?.musicFolderId,
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
@@ -63,7 +63,7 @@ export const JellyfinAlbumFilters = ({
|
||||
|
||||
const tagsQuery = useTagList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
@@ -170,7 +170,7 @@ export const JellyfinAlbumFilters = ({
|
||||
|
||||
const albumArtistListQuery = useAlbumArtistList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
|
||||
@@ -42,13 +42,13 @@ export const NavidromeAlbumFilters = ({
|
||||
|
||||
const genreListQuery = useGenreList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export const NavidromeAlbumFilters = ({
|
||||
|
||||
const tagsQuery = useTagList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
@@ -185,7 +185,7 @@ export const NavidromeAlbumFilters = ({
|
||||
|
||||
const albumArtistListQuery = useAlbumArtistList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
|
||||
@@ -39,7 +39,7 @@ export const SubsonicAlbumFilters = ({
|
||||
|
||||
const albumArtistListQuery = useAlbumArtistList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
@@ -72,13 +72,13 @@ export const SubsonicAlbumFilters = ({
|
||||
|
||||
const genreListQuery = useGenreList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumDetailQuery } from '/@/shared/types/domain/album-domain-types';
|
||||
|
||||
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
queryFn: ({ signal }) => {
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
|
||||
|
||||
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -5,12 +5,12 @@ import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain/album-domain-types';
|
||||
|
||||
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
@@ -35,7 +35,7 @@ export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
|
||||
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useInfiniteQuery({
|
||||
enabled: !!serverId,
|
||||
@@ -57,7 +57,7 @@ export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
query: {
|
||||
...query,
|
||||
limit: query.limit || 50,
|
||||
startIndex: pageParam * (query.limit || 50),
|
||||
offset: pageParam * (query.limit || 50),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -52,13 +52,13 @@ const AlbumListRoute = () => {
|
||||
|
||||
const genreList = useGenreList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 60,
|
||||
gcTime: 1000 * 60 * 60,
|
||||
enabled: !!genreId,
|
||||
},
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -74,7 +74,7 @@ const AlbumListRoute = () => {
|
||||
|
||||
const itemCountCheck = useAlbumListCount({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
gcTime: 1000 * 60,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
query: {
|
||||
|
||||
@@ -102,7 +102,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||
limit: 15,
|
||||
sortBy: AlbumListSort.RELEASE_DATE,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -117,7 +117,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||
limit: 15,
|
||||
sortBy: AlbumListSort.RELEASE_DATE,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
@@ -55,15 +55,11 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
|
||||
const itemData: AlbumArtist[] = [];
|
||||
|
||||
for (const [, data] of queriesFromCache) {
|
||||
const { items, startIndex } = data || {};
|
||||
const { items, offset } = data || {};
|
||||
|
||||
if (items && items.length !== 1 && startIndex !== undefined) {
|
||||
if (items && items.length !== 1 && offset !== undefined) {
|
||||
let itemIndex = 0;
|
||||
for (
|
||||
let rowIndex = startIndex;
|
||||
rowIndex < startIndex + items.length;
|
||||
rowIndex += 1
|
||||
) {
|
||||
for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
|
||||
itemData[rowIndex] = items[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
@@ -78,7 +74,7 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
|
||||
const query: ArtistListQuery = {
|
||||
...filter,
|
||||
limit,
|
||||
startIndex,
|
||||
offset,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.artists.list(server?.id || '', query);
|
||||
|
||||
@@ -140,7 +140,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||
const cq = useContainerQuery();
|
||||
const roles = useRoles({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 60 * 2,
|
||||
},
|
||||
query: {},
|
||||
@@ -188,7 +188,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||
},
|
||||
query: {
|
||||
limit,
|
||||
startIndex,
|
||||
offset,
|
||||
...filters,
|
||||
},
|
||||
}),
|
||||
@@ -224,7 +224,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||
},
|
||||
query: {
|
||||
limit,
|
||||
startIndex,
|
||||
offset,
|
||||
...filters,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
|
||||
|
||||
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id && !!query.id,
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
||||
|
||||
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
||||
|
||||
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
|
||||
|
||||
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id && !!query.id,
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
|
||||
|
||||
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -3,11 +3,11 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
|
||||
export const useRoles = (args: QueryHookArgs<object>) => {
|
||||
const { options, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { TopSongListQuery } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
|
||||
@@ -23,7 +23,7 @@ const AlbumArtistListRoute = () => {
|
||||
|
||||
const itemCountCheck = useAlbumArtistListCount({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
gcTime: 1000 * 60,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
query: albumArtistListFilter,
|
||||
|
||||
@@ -23,7 +23,7 @@ const ArtistListRoute = () => {
|
||||
|
||||
const itemCountCheck = useArtistListCount({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
gcTime: 1000 * 60,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
query: artistListFilter,
|
||||
|
||||
@@ -37,7 +37,7 @@ import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/
|
||||
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
getServerById,
|
||||
useServerById,
|
||||
useAuthStore,
|
||||
useCurrentServer,
|
||||
usePlayerStore,
|
||||
@@ -712,7 +712,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
const item = ctx.data[0];
|
||||
const songs = await controller.getSimilarSongs({
|
||||
apiClientProps: {
|
||||
server: getServerById(item.serverId),
|
||||
server: useServerById(item.serverId),
|
||||
signal: undefined,
|
||||
},
|
||||
query: { albumArtistIds: item.albumArtistIds, songId: item.id },
|
||||
|
||||
@@ -5,15 +5,15 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import {
|
||||
DiscordDisplayType,
|
||||
getServerById,
|
||||
useAppStore,
|
||||
useDiscordSettings,
|
||||
useServerById,
|
||||
useGeneralSettings,
|
||||
usePlayerStore,
|
||||
} from '/@/renderer/store';
|
||||
import { QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { PlayerStatus, PlayerStatus } from '/@/shared/types/types';
|
||||
import { PlayerStatus } from '/@/shared/types/types';
|
||||
|
||||
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
||||
|
||||
@@ -93,7 +93,7 @@ export const useDiscordRpc = () => {
|
||||
if (song.serverType === ServerType.JELLYFIN && song.imageUrl) {
|
||||
activity.largeImageKey = song.imageUrl;
|
||||
} else if (song.serverType === ServerType.NAVIDROME) {
|
||||
const server = getServerById(song.serverId);
|
||||
const server = useServerById(song.serverId);
|
||||
|
||||
try {
|
||||
const info = await controller.getAlbumInfo({
|
||||
|
||||
@@ -74,15 +74,11 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
|
||||
const itemData: Genre[] = [];
|
||||
|
||||
for (const [, data] of queriesFromCache) {
|
||||
const { items, startIndex } = data || {};
|
||||
const { items, offset } = data || {};
|
||||
|
||||
if (items && items.length !== 1 && startIndex !== undefined) {
|
||||
if (items && items.length !== 1 && offset !== undefined) {
|
||||
let itemIndex = 0;
|
||||
for (
|
||||
let rowIndex = startIndex;
|
||||
rowIndex < startIndex + items.length;
|
||||
rowIndex += 1
|
||||
) {
|
||||
for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
|
||||
itemData[rowIndex] = items[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
@@ -101,7 +97,7 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
|
||||
const query: GenreListQuery = {
|
||||
...filter,
|
||||
limit: take,
|
||||
startIndex: skip,
|
||||
offset: skip,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.albums.list(server?.id || '', query);
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
|
||||
|
||||
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
|
||||
@@ -23,7 +23,7 @@ const GenreListRoute = () => {
|
||||
query: {
|
||||
...filter,
|
||||
limit: 1,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
@@ -3,19 +3,19 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
|
||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
const requestQuery: AlbumListQuery = {
|
||||
limit: 5,
|
||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const HomeRoute = () => {
|
||||
|
||||
const feature = useAlbumList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
gcTime: 1000 * 60,
|
||||
enabled: homeFeature,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
@@ -48,7 +48,7 @@ const HomeRoute = () => {
|
||||
limit: 20,
|
||||
sortBy: AlbumListSort.RANDOM,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -65,7 +65,7 @@ const HomeRoute = () => {
|
||||
limit: itemsPerPage,
|
||||
sortBy: AlbumListSort.RANDOM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -78,7 +78,7 @@ const HomeRoute = () => {
|
||||
limit: itemsPerPage,
|
||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -91,7 +91,7 @@ const HomeRoute = () => {
|
||||
limit: itemsPerPage,
|
||||
sortBy: AlbumListSort.RECENTLY_ADDED,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -105,7 +105,7 @@ const HomeRoute = () => {
|
||||
limit: itemsPerPage,
|
||||
sortBy: AlbumListSort.PLAY_COUNT,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -120,7 +120,7 @@ const HomeRoute = () => {
|
||||
limit: itemsPerPage,
|
||||
sortBy: SongListSort.PLAY_COUNT,
|
||||
sortOrder: ListSortOrder.DESC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import isElectron from 'is-electron';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
||||
import { useServerById, useLyricsSettings } from '/@/renderer/store';
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import {
|
||||
FullLyricsMetadata,
|
||||
@@ -64,7 +64,7 @@ export const useServerLyrics = (
|
||||
args: QueryHookArgs<LyricsQuery>,
|
||||
): UseQueryResult<null | string> => {
|
||||
const { query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
// Note: This currently fetches for every song, even if it shouldn't have
|
||||
@@ -86,7 +86,7 @@ export const useSongLyricsBySong = (
|
||||
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
|
||||
const { query } = args;
|
||||
const { fetch, preferLocalLyrics } = useLyricsSettings();
|
||||
const server = getServerById(song?.serverId);
|
||||
const server = useServerById(song?.serverId);
|
||||
|
||||
return useQuery({
|
||||
cacheTime: Infinity,
|
||||
|
||||
@@ -258,7 +258,7 @@ export const openShuffleAllModal = async (
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
}),
|
||||
queryKey: queryKeys.genres.list(server?.id),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { MutationOptions } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useIncrementQueuePlayCount } from '/@/renderer/store';
|
||||
import { useServerById, useIncrementQueuePlayCount } from '/@/renderer/store';
|
||||
import { usePlayEvent } from '/@/renderer/store/event.store';
|
||||
import { ScrobbleRequest, ScrobbleResponse } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useSendScrobble = (options?: MutationOptions) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.scrobble({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@ export const getAlbumSongsById = async (args: {
|
||||
albumIds: id,
|
||||
sortBy: SongListSort.ALBUM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ export const getGenreSongsById = async (args: {
|
||||
|
||||
const data: SongListResponse = {
|
||||
items: [],
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: 0,
|
||||
};
|
||||
for (const genreId of id) {
|
||||
@@ -106,7 +106,7 @@ export const getGenreSongsById = async (args: {
|
||||
genreIds: [genreId],
|
||||
sortBy: SongListSort.GENRE,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ export const getAlbumArtistSongsById = async (args: {
|
||||
albumArtistIds: id || [],
|
||||
sortBy: SongListSort.ALBUM_ARTIST,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
@@ -187,7 +187,7 @@ export const getArtistSongsById = async (args: {
|
||||
artistIds: id,
|
||||
sortBy: SongListSort.ALBUM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
@@ -222,7 +222,7 @@ export const getSongsByQuery = async (args: {
|
||||
const queryFilter: SongListQuery = {
|
||||
sortBy: SongListSort.ALBUM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
@@ -279,7 +279,7 @@ export const getSongById = async (args: {
|
||||
|
||||
return {
|
||||
items: [res],
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
totalRecordCount: 1,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export const AddToPlaylistContextModal = ({
|
||||
},
|
||||
sortBy: PlaylistListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -72,7 +72,7 @@ export const AddToPlaylistContextModal = ({
|
||||
albumIds: [albumId],
|
||||
sortBy: SongListSort.ALBUM,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||
@@ -90,7 +90,7 @@ export const AddToPlaylistContextModal = ({
|
||||
artistIds: [artistId],
|
||||
sortBy: SongListSort.ARTIST,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||
|
||||
@@ -88,15 +88,11 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
||||
const itemData: Playlist[] = [];
|
||||
|
||||
for (const [, data] of queriesFromCache) {
|
||||
const { items, startIndex } = data || {};
|
||||
const { items, offset } = data || {};
|
||||
|
||||
if (items && items.length !== 1 && startIndex !== undefined) {
|
||||
if (items && items.length !== 1 && offset !== undefined) {
|
||||
let itemIndex = 0;
|
||||
for (
|
||||
let rowIndex = startIndex;
|
||||
rowIndex < startIndex + items.length;
|
||||
rowIndex += 1
|
||||
) {
|
||||
for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
|
||||
itemData[rowIndex] = items[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
@@ -116,7 +112,7 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
||||
limit: take,
|
||||
...filter,
|
||||
_custom: {},
|
||||
startIndex: skip,
|
||||
offset: skip,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.playlists.list(server?.id || '', query);
|
||||
|
||||
@@ -160,7 +160,7 @@ export const PlaylistListHeaderFilters = ({
|
||||
},
|
||||
},
|
||||
limit: take,
|
||||
startIndex: skip,
|
||||
offset: skip,
|
||||
...filters,
|
||||
};
|
||||
|
||||
@@ -213,7 +213,7 @@ export const PlaylistListHeaderFilters = ({
|
||||
},
|
||||
query: {
|
||||
limit,
|
||||
startIndex,
|
||||
offset,
|
||||
...pageFilters,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -111,7 +111,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
||||
);
|
||||
|
||||
const { data: playlists } = usePlaylistList({
|
||||
query: { sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC, startIndex: 0 },
|
||||
query: { sortBy: PlaylistListSort.NAME, sortOrder: ListSortOrder.ASC, offset: 0 },
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ export const openUpdatePlaylistModal = async (args: {
|
||||
const query: UserListQuery = {
|
||||
sortBy: UserListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
if (!server) return;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import {
|
||||
AddToPlaylistArgs,
|
||||
AddToPlaylistResponse,
|
||||
@@ -21,7 +21,7 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.addToPlaylist({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { CreatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const useCreatePlaylist = (args: MutationHookArgs) => {
|
||||
@@ -18,12 +18,12 @@ export const useCreatePlaylist = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.createPlaylist({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
onSuccess: (_args, variables) => {
|
||||
const server = getServerById(variables.serverId);
|
||||
const server = useServerById(variables.serverId);
|
||||
if (server) {
|
||||
queryClient.invalidateQueries(queryKeys.playlists.list(server.id));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useCurrentServer } from '/@/renderer/store';
|
||||
import { useServerById, useCurrentServer } from '/@/renderer/store';
|
||||
import { DeletePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const useDeletePlaylist = (args: MutationHookArgs) => {
|
||||
@@ -19,7 +19,7 @@ export const useDeletePlaylist = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.deletePlaylist({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError, AxiosError } from 'axios';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationOptions } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { RemoveFromPlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
||||
@@ -17,7 +17,7 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.removeFromPlaylist({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { UpdatePlaylistResponse } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
||||
@@ -18,7 +18,7 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.updatePlaylist({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { PlaylistDetailQuery } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { PlaylistListQuery } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const usePlaylistList = (args: {
|
||||
@@ -13,7 +13,7 @@ export const usePlaylistList = (args: {
|
||||
serverId?: string;
|
||||
}) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
cacheTime: 1000 * 60 * 60,
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { PlaylistSongListQuery } from '/@/shared/types/domain/playlist-domain-types';
|
||||
|
||||
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
|
||||
@@ -26,7 +26,7 @@ const PlaylistListRoute = () => {
|
||||
|
||||
const itemCountCheck = usePlaylistList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 60 * 2,
|
||||
},
|
||||
query: {
|
||||
@@ -34,7 +34,7 @@ const PlaylistListRoute = () => {
|
||||
limit: 1,
|
||||
sortBy: PlaylistListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { SearchQuery } from '/@/shared/types/domain/search-domain-types';
|
||||
|
||||
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -6,7 +6,8 @@ import { nanoid } from 'nanoid/non-secure';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { useSignIn } from '../mutations/sign-in-mutation';
|
||||
|
||||
import JellyfinIcon from '/@/renderer/features/servers/assets/jellyfin.png';
|
||||
import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png';
|
||||
import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png';
|
||||
@@ -20,7 +21,6 @@ import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { toServerType } from '/@/shared/types/types';
|
||||
|
||||
@@ -59,6 +59,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
||||
const focusTrapRef = useFocusTrap(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { addServer, setCurrentServer } = useAuthStoreActions();
|
||||
const { isPending, mutateAsync: signIn } = useSignIn();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
@@ -87,7 +88,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
||||
const isSubmitDisabled = !form.values.name || !form.values.url || !form.values.username;
|
||||
|
||||
const handleSubmit = form.onSubmit(async (values) => {
|
||||
const authFunction = api.controller.authenticate;
|
||||
const authFunction = signIn;
|
||||
|
||||
if (!authFunction) {
|
||||
return toast.error({
|
||||
@@ -97,35 +98,33 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data: AuthenticationResponse | undefined = await authFunction(
|
||||
values.url,
|
||||
{
|
||||
legacy: values.legacyAuth,
|
||||
password: values.password,
|
||||
username: values.username,
|
||||
const [err, response] = await signIn({
|
||||
request: {
|
||||
body: {
|
||||
password: values.password,
|
||||
username: values.username,
|
||||
},
|
||||
url: values.url,
|
||||
},
|
||||
values.type as ServerType,
|
||||
);
|
||||
serverType: values.type as ServerType,
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
if (err || !response) {
|
||||
return toast.error({
|
||||
message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}
|
||||
|
||||
const serverItem = {
|
||||
credential: data.credential,
|
||||
credential: response.credential,
|
||||
id: nanoid(),
|
||||
name: values.name,
|
||||
ndCredential: data.ndCredential,
|
||||
type: values.type as ServerType,
|
||||
url: values.url.replace(/\/$/, ''),
|
||||
userId: data.userId,
|
||||
username: data.username,
|
||||
username: response.username,
|
||||
};
|
||||
|
||||
addServer(serverItem);
|
||||
setCurrentServer(serverItem);
|
||||
closeAllModals();
|
||||
|
||||
toast.success({
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api/api-controller';
|
||||
import { AuthenticationRequest } from '/@/shared/types/domain/auth-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
|
||||
export function useSignIn() {
|
||||
const mutation = useMutation({
|
||||
mutationFn: (args: { request: AuthenticationRequest; serverType: ServerType }) => {
|
||||
const result = api.authenticate(args.serverType)(args.request);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import isElectron from 'is-electron';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||
import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
||||
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { ArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.createFavorite({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
@@ -72,10 +72,10 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
|
||||
id: variables.query.id[0],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<AlbumArtistDetailResponse>(queryKey);
|
||||
const previous = queryClient.getQueryData<ArtistDetailResponse>(queryKey);
|
||||
|
||||
if (previous) {
|
||||
queryClient.setQueryData<AlbumArtistDetailResponse>(queryKey, {
|
||||
queryClient.setQueryData<ArtistDetailResponse>(queryKey, {
|
||||
...previous,
|
||||
userFavorite: true,
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ import isElectron from 'is-electron';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||
import { useServerById, useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store';
|
||||
import { useFavoriteEvent } from '/@/renderer/store/event.store';
|
||||
import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { ArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { FavoriteResponse } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.deleteFavorite({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
@@ -71,10 +71,10 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
|
||||
const queryKey = queryKeys.albumArtists.detail(serverId, {
|
||||
id: variables.query.id[0],
|
||||
});
|
||||
const previous = queryClient.getQueryData<AlbumArtistDetailResponse>(queryKey);
|
||||
const previous = queryClient.getQueryData<ArtistDetailResponse>(queryKey);
|
||||
|
||||
if (previous) {
|
||||
queryClient.setQueryData<AlbumArtistDetailResponse>(queryKey, {
|
||||
queryClient.setQueryData<ArtistDetailResponse>(queryKey, {
|
||||
...previous,
|
||||
userFavorite: false,
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ import isElectron from 'is-electron';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store';
|
||||
import { useServerById, useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store';
|
||||
import { useRatingEvent } from '/@/renderer/store/event.store';
|
||||
import { Album, AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
|
||||
import { AlbumArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { ArtistDetailResponse } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { AnyLibraryItems, LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { RatingResponse, SetRatingRequest } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useSetRating = (args: MutationHookArgs) => {
|
||||
{ previous: undefined | { items: AnyLibraryItems } }
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.setRating({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
@@ -104,9 +104,9 @@ export const useSetRating = (args: MutationHookArgs) => {
|
||||
const queryKey = queryKeys.albumArtists.detail(serverId || '', {
|
||||
id: albumArtistId,
|
||||
});
|
||||
const previous = queryClient.getQueryData<AlbumArtistDetailResponse>(queryKey);
|
||||
const previous = queryClient.getQueryData<ArtistDetailResponse>(queryKey);
|
||||
if (previous) {
|
||||
queryClient.setQueryData<AlbumArtistDetailResponse>(queryKey, {
|
||||
queryClient.setQueryData<ArtistDetailResponse>(queryKey, {
|
||||
...previous,
|
||||
userRating: variables.query.rating,
|
||||
});
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { ServerMusicFolderListQuery } from '/@/shared/types/domain/server-domain-types';
|
||||
|
||||
export const useMusicFolders = (args: QueryHookArgs<ServerMusicFolderListQuery>) => {
|
||||
const { options, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
const query = useQuery({
|
||||
enabled: !!server,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { AnyLibraryItems } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { ShareItemRequest, ShareItemResponse } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
@@ -17,7 +17,7 @@ export const useShareItem = (args: MutationHookArgs) => {
|
||||
{ previous: undefined | { items: AnyLibraryItems } }
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
const server = getServerById(args.serverId);
|
||||
const server = useServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.shareItem({ ...args, apiClientProps: { server } });
|
||||
},
|
||||
|
||||
@@ -142,7 +142,7 @@ export const SidebarPlaylistList = () => {
|
||||
query: {
|
||||
sortBy: PlaylistListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -258,7 +258,7 @@ export const SidebarSharedPlaylistList = () => {
|
||||
query: {
|
||||
sortBy: PlaylistListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
||||
|
||||
const songQuery = useSimilarSongs({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 2,
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: { albumArtistIds: song.albumArtists.map((art) => art.id), count, songId: song.id },
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { SimilarSongsQuery } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export const useSimilarSongs = (args: QueryHookArgs<SimilarSongsQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const JellyfinSongFilters = ({
|
||||
musicFolderId: filter?.musicFolderId,
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export const NavidromeSongFilters = ({
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
|
||||
@@ -148,15 +148,11 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
||||
const itemData: Song[] = [];
|
||||
|
||||
for (const [, data] of queriesFromCache) {
|
||||
const { items, startIndex } = data || {};
|
||||
const { items, offset } = data || {};
|
||||
|
||||
if (items && items.length !== 1 && startIndex !== undefined) {
|
||||
if (items && items.length !== 1 && offset !== undefined) {
|
||||
let itemIndex = 0;
|
||||
for (
|
||||
let rowIndex = startIndex;
|
||||
rowIndex < startIndex + items.length;
|
||||
rowIndex += 1
|
||||
) {
|
||||
for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
|
||||
itemData[rowIndex] = items[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
@@ -177,7 +173,7 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
||||
limit: take,
|
||||
...filter,
|
||||
...customFilters,
|
||||
startIndex: skip,
|
||||
offset: skip,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.songs.list(server?.id || '', query, id);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const SubsonicSongFilters = ({
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId,
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export const useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
|
||||
@@ -50,13 +50,13 @@ const TrackListRoute = () => {
|
||||
|
||||
const genreList = useGenreList({
|
||||
options: {
|
||||
cacheTime: 1000 * 60 * 60,
|
||||
gcTime: 1000 * 60 * 60,
|
||||
enabled: !!genreId,
|
||||
},
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
@@ -72,7 +72,7 @@ const TrackListRoute = () => {
|
||||
|
||||
const itemCountCheck = useSongListCount({
|
||||
options: {
|
||||
cacheTime: 1000 * 60,
|
||||
gcTime: 1000 * 60,
|
||||
staleTime: 1000 * 60,
|
||||
},
|
||||
query: songListFilter,
|
||||
@@ -85,7 +85,7 @@ const TrackListRoute = () => {
|
||||
async (args: { initialSongId?: string; playType: Play }) => {
|
||||
if (!itemCount || itemCount === 0) return;
|
||||
const { initialSongId, playType } = args;
|
||||
const query: SongListQuery = { ...songListFilter, limit: itemCount, startIndex: 0 };
|
||||
const query: SongListQuery = { ...songListFilter, limit: itemCount, offset: 0 };
|
||||
|
||||
if (albumArtistId) {
|
||||
handlePlayQueueAdd?.({
|
||||
|
||||
@@ -3,14 +3,14 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
|
||||
import { TagQuery } from '/@/shared/types/domain/tag-domain-types';
|
||||
|
||||
export const useTagList = (args: QueryHookArgs<TagQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server && hasFeature(server, ServerFeature.TAGS),
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { useServerById } from '/@/renderer/store';
|
||||
import { UserListQuery } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
export const useUserList = (args: QueryHookArgs<UserListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
const server = useServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
|
||||
@@ -6,9 +6,9 @@ import { api } from '/@/renderer/api';
|
||||
import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||
import { AuthState } from '/@/shared/types/types';
|
||||
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useServerAuthenticated = () => {
|
||||
limit: 1,
|
||||
sortBy: SongListSort.NAME,
|
||||
sortOrder: ListSortOrder.ASC,
|
||||
startIndex: 0,
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -22,16 +22,12 @@ const queryConfig: DefaultOptions = {
|
||||
retry: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
queries: {
|
||||
cacheTime: 1000 * 60 * 3,
|
||||
onError: (err) => {
|
||||
console.error('react query error:', err);
|
||||
},
|
||||
gcTime: 1000 * 60 * 3,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: process.env.NODE_ENV === 'production',
|
||||
staleTime: 1000 * 5,
|
||||
useErrorBoundary: (error: any) => {
|
||||
return error?.response?.status >= 500;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -41,9 +37,8 @@ export const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
export type InfiniteQueryOptions = {
|
||||
cacheTime?: UseInfiniteQueryOptions['cacheTime'];
|
||||
enabled?: UseInfiniteQueryOptions['enabled'];
|
||||
keepPreviousData?: UseInfiniteQueryOptions['keepPreviousData'];
|
||||
gcTime?: UseInfiniteQueryOptions['gcTime'];
|
||||
meta?: UseInfiniteQueryOptions['meta'];
|
||||
onError?: (err: any) => void;
|
||||
onSettled?: any;
|
||||
@@ -55,7 +50,6 @@ export type InfiniteQueryOptions = {
|
||||
retry?: UseInfiniteQueryOptions['retry'];
|
||||
retryDelay?: UseInfiniteQueryOptions['retryDelay'];
|
||||
staleTime?: UseInfiniteQueryOptions['staleTime'];
|
||||
suspense?: UseInfiniteQueryOptions['suspense'];
|
||||
useErrorBoundary?: boolean;
|
||||
};
|
||||
|
||||
@@ -80,9 +74,8 @@ export type QueryHookArgs<T> = {
|
||||
};
|
||||
|
||||
export type QueryOptions = {
|
||||
cacheTime?: UseQueryOptions['cacheTime'];
|
||||
enabled?: UseQueryOptions['enabled'];
|
||||
keepPreviousData?: UseQueryOptions['keepPreviousData'];
|
||||
gcTime?: UseQueryOptions['gcTime'];
|
||||
meta?: UseQueryOptions['meta'];
|
||||
onError?: (err: any) => void;
|
||||
onSettled?: any;
|
||||
@@ -94,6 +87,5 @@ export type QueryOptions = {
|
||||
retry?: UseQueryOptions['retry'];
|
||||
retryDelay?: UseQueryOptions['retryDelay'];
|
||||
staleTime?: UseQueryOptions['staleTime'];
|
||||
suspense?: UseQueryOptions['suspense'];
|
||||
useErrorBoundary?: boolean;
|
||||
};
|
||||
|
||||
@@ -1,84 +1,83 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { createWithEqualityFn } from 'zustand/traditional';
|
||||
|
||||
import { useAlbumArtistListDataStore } from '/@/renderer/store/album-artist-list-data.store';
|
||||
import { useAlbumListDataStore } from '/@/renderer/store/album-list-data.store';
|
||||
import { useListStore } from '/@/renderer/store/list.store';
|
||||
import { createSelectors } from '/@/renderer/store/utils';
|
||||
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
||||
|
||||
export interface AuthSlice extends AuthState {
|
||||
actions: {
|
||||
addServer: (args: ServerListItem) => void;
|
||||
deleteServer: (id: string) => void;
|
||||
getServer: (id: string) => null | ServerListItem;
|
||||
setCurrentServer: (server: null | ServerListItem) => void;
|
||||
updateServer: (id: string, args: Partial<ServerListItem>) => void;
|
||||
};
|
||||
actions: Actions;
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
currentServer: null | ServerListItem;
|
||||
currentServerId: null | string;
|
||||
deviceId: string;
|
||||
serverList: Record<string, ServerListItem>;
|
||||
}
|
||||
|
||||
export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
||||
interface Actions {
|
||||
addServer: (args: ServerListItem) => void;
|
||||
deleteServer: (id: string) => void;
|
||||
setCurrentServer: (server: null | ServerListItem) => void;
|
||||
updateServer: (id: string, args: Partial<ServerListItem>) => void;
|
||||
}
|
||||
|
||||
const authStoreBase = create<AuthSlice>()(
|
||||
persist(
|
||||
devtools(
|
||||
immer((set, get) => ({
|
||||
actions: {
|
||||
addServer: (args) => {
|
||||
set((state) => {
|
||||
state.serverList[args.id] = args;
|
||||
});
|
||||
},
|
||||
deleteServer: (id) => {
|
||||
set((state) => {
|
||||
delete state.serverList[id];
|
||||
subscribeWithSelector(
|
||||
immer((set) => ({
|
||||
actions: {
|
||||
addServer: (args) => {
|
||||
set((state) => {
|
||||
state.serverList[args.id] = args;
|
||||
state.currentServerId = args.id;
|
||||
});
|
||||
},
|
||||
deleteServer: (id) => {
|
||||
set((state) => {
|
||||
delete state.serverList[id];
|
||||
|
||||
if (state.currentServer?.id === id) {
|
||||
state.currentServer = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
getServer: (id) => {
|
||||
const server = get().serverList[id];
|
||||
if (server) return server;
|
||||
return null;
|
||||
},
|
||||
setCurrentServer: (server) => {
|
||||
set((state) => {
|
||||
state.currentServer = server;
|
||||
if (state.currentServerId === id) {
|
||||
state.currentServerId = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
setCurrentServer: (server) => {
|
||||
set((state) => {
|
||||
state.currentServerId = server?.id || null;
|
||||
|
||||
if (server) {
|
||||
// Reset list filters
|
||||
useListStore.getState()._actions.resetFilter();
|
||||
if (server) {
|
||||
// Reset list filters
|
||||
useListStore.getState()._actions.resetFilter();
|
||||
|
||||
// Reset persisted grid list stores
|
||||
useAlbumListDataStore.getState().actions.setItemData([]);
|
||||
useAlbumArtistListDataStore.getState().actions.setItemData([]);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateServer: (id: string, args: Partial<ServerListItem>) => {
|
||||
set((state) => {
|
||||
const updatedServer = {
|
||||
...state.serverList[id],
|
||||
...args,
|
||||
};
|
||||
// Reset persisted grid list stores
|
||||
useAlbumListDataStore.getState().actions.setItemData([]);
|
||||
useAlbumArtistListDataStore.getState().actions.setItemData([]);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateServer: (id: string, args: Partial<ServerListItem>) => {
|
||||
set((state) => {
|
||||
const updatedServer = {
|
||||
...state.serverList[id],
|
||||
...args,
|
||||
};
|
||||
|
||||
state.serverList[id] = updatedServer as ServerListItem;
|
||||
state.currentServer = updatedServer as ServerListItem;
|
||||
});
|
||||
state.serverList[id] = updatedServer as ServerListItem;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
currentServer: null,
|
||||
deviceId: nanoid(),
|
||||
serverList: {},
|
||||
})),
|
||||
currentServerId: null,
|
||||
deviceId: nanoid(),
|
||||
serverList: {},
|
||||
})),
|
||||
),
|
||||
{ name: 'store_authentication' },
|
||||
),
|
||||
{
|
||||
@@ -89,18 +88,40 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
||||
),
|
||||
);
|
||||
|
||||
export const useCurrentServerId = () => useAuthStore((state) => state.currentServer)?.id || '';
|
||||
export const useAuthStore = createSelectors(authStoreBase);
|
||||
|
||||
export const useCurrentServer = () => useAuthStore((state) => state.currentServer);
|
||||
export const useCurrentServerId = () => {
|
||||
return useAuthStore.use.currentServerId();
|
||||
};
|
||||
|
||||
export const useServerList = () => useAuthStore((state) => state.serverList);
|
||||
export const useCurrentServer = () => {
|
||||
const currentServerId = useCurrentServerId();
|
||||
|
||||
export const useAuthStoreActions = () => useAuthStore((state) => state.actions);
|
||||
|
||||
export const getServerById = (id?: string) => {
|
||||
if (!id) {
|
||||
if (!currentServerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return useAuthStore.getState().actions.getServer(id);
|
||||
const servers = useAuthStore.use.serverList();
|
||||
const server = servers[currentServerId];
|
||||
|
||||
if (!server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
export const useServerList = () => useAuthStore.use.serverList();
|
||||
|
||||
export const useAuthStoreActions = () => useAuthStore.use.actions();
|
||||
|
||||
export const useServerById = (id: string) => {
|
||||
const servers = useAuthStore.use.serverList();
|
||||
const server = servers[id];
|
||||
|
||||
if (!server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mergeWith from 'lodash/mergeWith';
|
||||
import { StoreApi, UseBoundStore } from 'zustand';
|
||||
|
||||
/**
|
||||
* A custom deep merger that will replace all 'columns' items with the persistent
|
||||
@@ -17,3 +18,17 @@ export const mergeOverridingColumns = <T>(persistedState: unknown, currentState:
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
type WithSelectors<S> = S extends { getState: () => infer T }
|
||||
? S & { use: { [K in keyof T]: () => T[K] } }
|
||||
: never;
|
||||
|
||||
export function createSelectors<S extends UseBoundStore<StoreApi<object>>>(_store: S) {
|
||||
const store = _store as WithSelectors<typeof _store>;
|
||||
store.use = {};
|
||||
for (const k of Object.keys(store.getState())) {
|
||||
(store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import isElectron from 'is-electron';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { getServerById, useSettingsStore } from '/@/renderer/store';
|
||||
import { useServerById, useSettingsStore } from '/@/renderer/store';
|
||||
import { PlayerData, QueueSong, QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
|
||||
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
||||
@@ -11,7 +11,7 @@ const modifyUrl = (song: QueueSong): string => {
|
||||
if (transcode.enabled) {
|
||||
const streamUrl = api.controller.getTranscodingUrl({
|
||||
apiClientProps: {
|
||||
server: getServerById(song.serverId),
|
||||
server: useServerById(song.serverId),
|
||||
},
|
||||
query: {
|
||||
base: song.streamUrl,
|
||||
|
||||
@@ -384,7 +384,7 @@ const normalizeUser = (item: z.infer<typeof ndType._response.user>): User => {
|
||||
id: item.id,
|
||||
isAdmin: item.isAdmin,
|
||||
lastLoginAt: item.lastLoginAt,
|
||||
name: item.userName,
|
||||
username: item.userName,
|
||||
updatedAt: item.updatedAt,
|
||||
};
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,49 @@
|
||||
import { components } from './subsonic-schema.d';
|
||||
import { components, paths } from './subsonic-schema.d';
|
||||
|
||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||
import { Album } from '/@/shared/types/domain/album-domain-types';
|
||||
import { Album, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
|
||||
import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
|
||||
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song } from '/@/shared/types/domain/song-domain-types';
|
||||
import { User } from '/@/shared/types/domain/user-domain-types';
|
||||
import { formatDate } from '/@/shared/utils/format-date';
|
||||
|
||||
type AlbumSortType = paths['/rest/getAlbumList2']['get']['parameters']['query']['type'];
|
||||
|
||||
const defaultSortType: AlbumSortType = 'alphabeticalByName';
|
||||
|
||||
const albumSortByMap: Record<AlbumListSortOptions, AlbumSortType> = {
|
||||
[AlbumListSortOptions.ALBUM_ARTIST]: 'alphabeticalByArtist',
|
||||
[AlbumListSortOptions.ARTIST]: defaultSortType,
|
||||
[AlbumListSortOptions.COMMUNITY_RATING]: defaultSortType,
|
||||
[AlbumListSortOptions.CRITIC_RATING]: defaultSortType,
|
||||
[AlbumListSortOptions.DATE_ADDED]: 'newest',
|
||||
[AlbumListSortOptions.DATE_PLAYED]: 'recent',
|
||||
[AlbumListSortOptions.DURATION]: defaultSortType,
|
||||
[AlbumListSortOptions.IS_FAVORITE]: defaultSortType,
|
||||
[AlbumListSortOptions.NAME]: 'alphabeticalByName',
|
||||
[AlbumListSortOptions.PLAY_COUNT]: 'frequent',
|
||||
[AlbumListSortOptions.RANDOM]: defaultSortType,
|
||||
[AlbumListSortOptions.RATING]: 'highest',
|
||||
[AlbumListSortOptions.RELEASE_DATE]: defaultSortType,
|
||||
[AlbumListSortOptions.TRACK_COUNT]: defaultSortType,
|
||||
[AlbumListSortOptions.YEAR]: defaultSortType,
|
||||
};
|
||||
|
||||
export const normalize = {
|
||||
_sort: {
|
||||
album: (option: AlbumListSortOptions) => {
|
||||
return albumSortByMap[option] || defaultSortType;
|
||||
},
|
||||
},
|
||||
album: (
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
server: ServerListItem,
|
||||
): Album => {
|
||||
const imageUrl = item.coverArt ? getCoverArtUrl(item.coverArt, server) : null;
|
||||
@@ -24,7 +55,7 @@ export const normalize = {
|
||||
artistName: item.artist || null,
|
||||
artists: getArtistList(item.artists, item.artistId, item.artist),
|
||||
comment: null,
|
||||
createdDate: item.created,
|
||||
createdDate: item.created || null,
|
||||
discTitles: getDiscTitles(item),
|
||||
displayArtist: null,
|
||||
duration: getDuration(item.duration),
|
||||
@@ -33,12 +64,12 @@ export const normalize = {
|
||||
id: item.id.toString(),
|
||||
imagePlaceholderUrl: null,
|
||||
imageUrl,
|
||||
isCompilation: item.isCompilation || null,
|
||||
isCompilation: getIsCompilation(item),
|
||||
mbzId: item.musicBrainzId || null,
|
||||
mbzReleaseGroupId: null,
|
||||
missing: null,
|
||||
moods: getMoods(item),
|
||||
name: item.name,
|
||||
name: getName(item),
|
||||
originalReleaseDate: getOriginalReleaseDate(item),
|
||||
participants: {},
|
||||
recordLabels: getRecordLabels(item),
|
||||
@@ -46,8 +77,8 @@ export const normalize = {
|
||||
releaseTypes: getReleaseTypes(item),
|
||||
releaseYear: item.year || null,
|
||||
size: null,
|
||||
songCount: item.songCount,
|
||||
sortName: item.sortName || item.name,
|
||||
songCount: 'songCount' in item ? item.songCount : null,
|
||||
sortName: getSortName(item),
|
||||
tags: {},
|
||||
updatedDate: null,
|
||||
userFavorite: Boolean(item.starred),
|
||||
@@ -55,7 +86,7 @@ export const normalize = {
|
||||
userLastPlayedDate: item.played || null,
|
||||
userPlayCount: item.playCount ?? null,
|
||||
userRating: item.userRating || null,
|
||||
version: item.version || null,
|
||||
version: 'version' in item ? item.version || null : null,
|
||||
};
|
||||
},
|
||||
albumArtist: (
|
||||
@@ -167,6 +198,28 @@ export const normalize = {
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
},
|
||||
user: (item: components['schemas']['User'], server: ServerListItem): User => {
|
||||
return {
|
||||
_itemType: LibraryItem.USER,
|
||||
_serverId: server.id,
|
||||
_serverType: ServerType.SUBSONIC,
|
||||
id: item.username,
|
||||
permissions: {
|
||||
'jukebox.manage': item.jukeboxRole,
|
||||
'media.download': item.downloadRole,
|
||||
'media.folder': item.folder?.map((folder) => folder.toString()) || [],
|
||||
'media.share': item.shareRole,
|
||||
'media.stream': item.streamRole,
|
||||
'media.upload': item.uploadRole,
|
||||
'playlist.create': item.playlistRole,
|
||||
'playlist.delete': item.playlistRole,
|
||||
'playlist.edit': item.playlistRole,
|
||||
'server.admin': item.adminRole,
|
||||
'user.edit': item.settingsRole,
|
||||
},
|
||||
username: item.username,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function getArtistList(
|
||||
@@ -200,12 +253,19 @@ function getCoverArtUrl(id: string, server: ServerListItem) {
|
||||
}
|
||||
|
||||
function getDiscTitles(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return (item.discTitles || []).map((discTitle) => ({
|
||||
disc: discTitle.disc,
|
||||
title: discTitle.title,
|
||||
}));
|
||||
if ('discTitles' in item) {
|
||||
return (item.discTitles || []).map((discTitle) => ({
|
||||
disc: discTitle.disc,
|
||||
title: discTitle.title,
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getDuration(duration?: number) {
|
||||
@@ -214,12 +274,16 @@ function getDuration(duration?: number) {
|
||||
}
|
||||
|
||||
function getGainInfo(item: components['schemas']['Child']) {
|
||||
return item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain)
|
||||
? {
|
||||
album: item.replayGain.albumGain,
|
||||
track: item.replayGain.trackGain,
|
||||
}
|
||||
: null;
|
||||
if ('replayGain' in item) {
|
||||
return item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain)
|
||||
? {
|
||||
album: item.replayGain.albumGain,
|
||||
track: item.replayGain.trackGain,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGenres(
|
||||
@@ -228,15 +292,19 @@ function getGenres(
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
): RelatedGenre[] {
|
||||
if (item.genres) {
|
||||
return item.genres.map((genre) => ({
|
||||
if ('genres' in item) {
|
||||
return (item.genres || []).map((genre) => ({
|
||||
id: genre.name,
|
||||
imageUrl: null,
|
||||
name: genre.name,
|
||||
}));
|
||||
}
|
||||
|
||||
if (item.genre) {
|
||||
if ('genre' in item) {
|
||||
if (!item.genre) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: item.genre,
|
||||
@@ -249,26 +317,67 @@ function getGenres(
|
||||
return [];
|
||||
}
|
||||
|
||||
function getIsCompilation(
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
if ('isCompilation' in item) {
|
||||
return item.isCompilation || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getMoods(
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return (item.moods || []).map((mood) => ({
|
||||
id: mood,
|
||||
name: mood,
|
||||
}));
|
||||
if ('moods' in item) {
|
||||
return (item.moods || []).map((mood) => ({
|
||||
id: mood,
|
||||
name: mood,
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getName(
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
if ('name' in item) {
|
||||
return item.name || '';
|
||||
}
|
||||
|
||||
if ('title' in item) {
|
||||
return item.title || '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function getOriginalReleaseDate(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return item.originalReleaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.originalReleaseDate.year}-${item.originalReleaseDate.month}-${item.originalReleaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
if ('originalReleaseDate' in item) {
|
||||
return item.originalReleaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.originalReleaseDate.year}-${item.originalReleaseDate.month}-${item.originalReleaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParticipants(item: components['schemas']['Child']) {
|
||||
@@ -298,40 +407,86 @@ function getParticipants(item: components['schemas']['Child']) {
|
||||
}
|
||||
|
||||
function getPeakInfo(item: components['schemas']['Child']) {
|
||||
return item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
||||
? {
|
||||
album: item.replayGain.albumPeak,
|
||||
track: item.replayGain.trackPeak,
|
||||
}
|
||||
: null;
|
||||
if ('replayGain' in item) {
|
||||
return item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
|
||||
? {
|
||||
album: item.replayGain.albumPeak,
|
||||
track: item.replayGain.trackPeak,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRecordLabels(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return (item.recordLabels || []).map((recordLabel) => ({
|
||||
id: recordLabel.name,
|
||||
name: recordLabel.name,
|
||||
}));
|
||||
if ('recordLabels' in item) {
|
||||
return (item.recordLabels || []).map((recordLabel) => ({
|
||||
id: recordLabel.name,
|
||||
name: recordLabel.name,
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getReleaseDate(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return item.releaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
if ('releaseDate' in item) {
|
||||
return item.releaseDate
|
||||
? formatDate.toUTCDate(
|
||||
`${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getReleaseTypes(
|
||||
item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'],
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
return (item.releaseTypes || []).map((releaseType) => ({
|
||||
id: releaseType,
|
||||
name: releaseType,
|
||||
}));
|
||||
if ('releaseTypes' in item) {
|
||||
return (item.releaseTypes || []).map((releaseType) => ({
|
||||
id: releaseType,
|
||||
name: releaseType,
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getSortName(
|
||||
item:
|
||||
| components['schemas']['AlbumID3']
|
||||
| components['schemas']['AlbumID3WithSongs']
|
||||
| components['schemas']['Child'],
|
||||
) {
|
||||
if ('sortName' in item) {
|
||||
return item.sortName || '';
|
||||
}
|
||||
|
||||
if ('name' in item) {
|
||||
return item.name || '';
|
||||
}
|
||||
|
||||
if ('title' in item) {
|
||||
return item.title || '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function getStreamUrl(id: string, server: ServerListItem) {
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
import { AxiosHeaders } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import isElectron from 'is-electron';
|
||||
import { orderBy, shuffle } from 'lodash';
|
||||
import { stringify } from 'querystring';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Album, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
|
||||
import { Artist, ArtistListSortOptions } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { Genre, GenreListSortOptions } from '/@/shared/types/domain/genre-domain-types';
|
||||
import {
|
||||
Playlist,
|
||||
PlaylistListSortOptions,
|
||||
PlaylistSong,
|
||||
} from '/@/shared/types/domain/playlist-domain-types';
|
||||
import { ServerFeature, ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song, SongListSortOptions } from '/@/shared/types/domain/song-domain-types';
|
||||
import { User, UserListSortOptions } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
|
||||
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
|
||||
@@ -110,3 +124,522 @@ export const getClientType = (): string => {
|
||||
};
|
||||
|
||||
export const SEPARATOR_STRING = ' · ';
|
||||
|
||||
export async function estimateTotalRecordCount(args: {
|
||||
fetcher: (page: number, limit: number) => Promise<number>;
|
||||
limit: number;
|
||||
}) {
|
||||
const { fetcher, limit } = args;
|
||||
|
||||
// Recursive binary search across all pages to estimate total rows
|
||||
async function estimateTotalRowsRecursive(
|
||||
low: number,
|
||||
high: number,
|
||||
limit: number,
|
||||
): Promise<number> {
|
||||
if (low > high) {
|
||||
return 0; // This condition is just a safeguard and shouldn't be reached
|
||||
}
|
||||
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
const data = await fetcher(mid, limit);
|
||||
|
||||
if (data < limit) {
|
||||
// If the current page contains fewer than 500 items, it's close to the last page
|
||||
const itemCount = (mid - 1) * limit + data;
|
||||
return itemCount;
|
||||
} else {
|
||||
// If the current page is full, search in the higher half
|
||||
return estimateTotalRowsRecursive(mid + 1, high, limit);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to estimate total rows with limited page size
|
||||
async function estimateTotalRows(): Promise<number> {
|
||||
let low = 1;
|
||||
let high = 2;
|
||||
|
||||
// Step 1: Exponentially grow the number of pages to get an upper bound for the total pages
|
||||
|
||||
while (true) {
|
||||
const data = await fetcher(high, limit);
|
||||
|
||||
if (data < limit) {
|
||||
// If we encounter the last page, break out of the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Double the upper bound for the number of pages
|
||||
low = high;
|
||||
high *= 2;
|
||||
}
|
||||
|
||||
// Step 2: Perform binary search across all pages to find the exact number of rows
|
||||
return estimateTotalRowsRecursive(low, high, limit);
|
||||
}
|
||||
|
||||
return estimateTotalRows();
|
||||
}
|
||||
|
||||
export async function exactTotalRecordCount(args: {
|
||||
fetcher: (page: number, limit: number) => Promise<number>;
|
||||
limit: number;
|
||||
startPage: number;
|
||||
}) {
|
||||
const { fetcher, limit, startPage } = args;
|
||||
|
||||
// Add early return for page 1 with no results
|
||||
if (startPage === 1) {
|
||||
const firstPageCount = await fetcher(1, limit);
|
||||
if (firstPageCount === 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCountRecursive = async (
|
||||
page: number,
|
||||
limit: number,
|
||||
reverse: boolean,
|
||||
totalRecordCount: number,
|
||||
previousPageRecordCount?: number,
|
||||
): Promise<number> => {
|
||||
// Add guard against negative page numbers
|
||||
if (page < 1) {
|
||||
return totalRecordCount;
|
||||
}
|
||||
|
||||
const currentPageRecordCount = await fetcher(page, limit);
|
||||
|
||||
if (currentPageRecordCount !== limit && currentPageRecordCount !== 0) {
|
||||
totalRecordCount += currentPageRecordCount;
|
||||
return totalRecordCount;
|
||||
}
|
||||
|
||||
// Handle the case when the last page is equal to the limit and is ascending
|
||||
if (
|
||||
!reverse &&
|
||||
currentPageRecordCount !== limit &&
|
||||
currentPageRecordCount === 0 &&
|
||||
currentPageRecordCount === previousPageRecordCount
|
||||
) {
|
||||
return totalRecordCount;
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
totalRecordCount -= limit;
|
||||
return fetchCountRecursive(
|
||||
page - 1,
|
||||
limit,
|
||||
true,
|
||||
totalRecordCount,
|
||||
currentPageRecordCount,
|
||||
);
|
||||
} else {
|
||||
totalRecordCount += currentPageRecordCount;
|
||||
return fetchCountRecursive(
|
||||
page + 1,
|
||||
limit,
|
||||
false,
|
||||
totalRecordCount,
|
||||
currentPageRecordCount,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const estimatedStartRecordCount = startPage * limit;
|
||||
const startPageRecordCount = await fetcher(startPage, limit);
|
||||
const isLastPage = startPageRecordCount < limit && startPageRecordCount !== 0;
|
||||
|
||||
if (isLastPage) {
|
||||
if (estimatedStartRecordCount < limit) {
|
||||
return estimatedStartRecordCount + startPageRecordCount;
|
||||
}
|
||||
|
||||
return estimatedStartRecordCount - limit + startPageRecordCount;
|
||||
}
|
||||
|
||||
const shouldReverse = startPageRecordCount < limit;
|
||||
|
||||
const count = await fetchCountRecursive(
|
||||
startPage,
|
||||
limit,
|
||||
shouldReverse,
|
||||
estimatedStartRecordCount,
|
||||
);
|
||||
return count;
|
||||
}
|
||||
|
||||
export async function fetchAllRecords<T>(args: {
|
||||
fetcher: (page: number, limit: number) => Promise<T[]>;
|
||||
fetchLimit?: number;
|
||||
items?: T[];
|
||||
page?: number;
|
||||
}) {
|
||||
const limit = args.fetchLimit || 500;
|
||||
const page = args.page || 0;
|
||||
const items = args.items || [];
|
||||
|
||||
const result = await args.fetcher(page, limit);
|
||||
|
||||
// If we get an empty array, we've reached the end
|
||||
if (result.length === 0) {
|
||||
return items;
|
||||
}
|
||||
|
||||
// If we get less than the limit, we've reached the end
|
||||
if (result.length < limit) {
|
||||
return [...result, ...items];
|
||||
}
|
||||
|
||||
return fetchAllRecords({
|
||||
fetcher: args.fetcher,
|
||||
fetchLimit: args.fetchLimit,
|
||||
items: [...items, ...result],
|
||||
page: page + 1,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchTotalRecordCount(args: {
|
||||
fetcher: (page: number, limit: number) => Promise<number>;
|
||||
fetchLimit?: number;
|
||||
}) {
|
||||
const limit = args.fetchLimit || 500;
|
||||
|
||||
const estimatedCount = await estimateTotalRecordCount({
|
||||
fetcher: args.fetcher,
|
||||
limit,
|
||||
});
|
||||
const estimatedPages = Math.ceil(estimatedCount / limit);
|
||||
const totalRecordCount = await exactTotalRecordCount({
|
||||
fetcher: args.fetcher,
|
||||
limit,
|
||||
startPage: estimatedPages,
|
||||
});
|
||||
return totalRecordCount;
|
||||
}
|
||||
|
||||
function paginate<T>(array: T[], offset: number, limit: number) {
|
||||
let result: T[];
|
||||
|
||||
if (limit === -1) {
|
||||
result = array.slice(offset);
|
||||
} else {
|
||||
result = array.slice(offset, offset + limit);
|
||||
}
|
||||
|
||||
return {
|
||||
items: result,
|
||||
offset,
|
||||
totalRecordCount: array.length,
|
||||
};
|
||||
}
|
||||
|
||||
function search<T>(array: T[], searchTerm: string, keys: (keyof T)[]) {
|
||||
return array.filter((item) =>
|
||||
keys.some((key) => {
|
||||
const value = item[key];
|
||||
return String(value ?? '')
|
||||
.toLocaleLowerCase()
|
||||
.includes(searchTerm.toLocaleLowerCase());
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const counts = new Map<string, { count: number; expires: number }>();
|
||||
|
||||
setInterval(
|
||||
() => {
|
||||
counts.forEach((value, key) => {
|
||||
if (value.expires < dayjs().unix()) {
|
||||
counts.delete(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
1000 * 60 * 10,
|
||||
); // 10 minutes
|
||||
|
||||
async function getListCount(
|
||||
options: {
|
||||
expiration?: number; // Expiration in minutes
|
||||
fetchLimit?: number;
|
||||
query: Record<string, unknown>;
|
||||
serverId: string;
|
||||
type: LibraryItem | string;
|
||||
},
|
||||
fetcher?: (page: number, limit: number) => Promise<number>,
|
||||
) {
|
||||
const key = getListCountKey(options);
|
||||
const value = counts.get(key);
|
||||
|
||||
if (fetcher && (!value || value.expires < dayjs().unix())) {
|
||||
const totalRecordCount = await fetchTotalRecordCount({
|
||||
fetcher,
|
||||
fetchLimit: options.fetchLimit,
|
||||
});
|
||||
setListCount(key, totalRecordCount, options.expiration ?? 1440);
|
||||
return totalRecordCount;
|
||||
}
|
||||
|
||||
return value?.count;
|
||||
}
|
||||
|
||||
function getListCountKey(options: {
|
||||
query: Record<string, unknown>;
|
||||
serverId: string;
|
||||
type: LibraryItem | string;
|
||||
}) {
|
||||
const hash = stringify(options.query as Record<string, boolean | null | number | string>);
|
||||
return `${options.serverId}::${options.type}::${hash}`;
|
||||
}
|
||||
|
||||
function invalidateListCount(key?: string) {
|
||||
if (key) {
|
||||
return counts.delete(key);
|
||||
}
|
||||
|
||||
return counts.clear();
|
||||
}
|
||||
|
||||
function setListCount(key: string, count: number, expiration = 1440) {
|
||||
counts.set(key, { count, expires: dayjs().unix() + expiration * 1000 * 60 });
|
||||
}
|
||||
|
||||
const sortBy = {
|
||||
album: (array: Album[], key: AlbumListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case AlbumListSortOptions.ALBUM_ARTIST: {
|
||||
value = orderBy(value, ['artistId'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.ARTIST: {
|
||||
value = orderBy(value, ['artistId'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.COMMUNITY_RATING: {
|
||||
value = orderBy(value, ['userRating'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.CRITIC_RATING: {
|
||||
value = orderBy(value, ['userRating'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.DATE_ADDED: {
|
||||
value = orderBy(value, ['createdDate'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.DATE_PLAYED: {
|
||||
value = orderBy(value, ['userLastPlayedDate'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.DURATION: {
|
||||
value = orderBy(value, ['duration'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.IS_FAVORITE: {
|
||||
value = orderBy(value, ['userFavoriteDate', 'userFavorite'], [order, order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.PLAY_COUNT: {
|
||||
value = orderBy(value, ['userPlayCount'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.RANDOM: {
|
||||
value = shuffle(value);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.RELEASE_DATE: {
|
||||
value = orderBy(value, ['releaseDate'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.TRACK_COUNT: {
|
||||
value = orderBy(value, ['trackCount'], [order]);
|
||||
break;
|
||||
}
|
||||
case AlbumListSortOptions.YEAR: {
|
||||
value = orderBy(value, ['releaseYear'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
artist: (array: Artist[], key: ArtistListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case ArtistListSortOptions.ALBUM_COUNT: {
|
||||
value = orderBy(value, ['albumCount'], [order]);
|
||||
break;
|
||||
}
|
||||
case ArtistListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
case ArtistListSortOptions.TRACK_COUNT: {
|
||||
value = orderBy(value, ['trackCount'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
genre: (array: Genre[], key: GenreListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case GenreListSortOptions.ALBUM_COUNT: {
|
||||
value = orderBy(value, ['albumCount'], [order]);
|
||||
break;
|
||||
}
|
||||
case GenreListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
case GenreListSortOptions.TRACK_COUNT: {
|
||||
value = orderBy(value, ['trackCount'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
playlist: (array: Playlist[], key: PlaylistListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case PlaylistListSortOptions.DURATION: {
|
||||
value = orderBy(value, ['duration'], [order]);
|
||||
break;
|
||||
}
|
||||
case PlaylistListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
case PlaylistListSortOptions.OWNER: {
|
||||
value = orderBy(value, ['owner'], [order]);
|
||||
break;
|
||||
}
|
||||
case PlaylistListSortOptions.PUBLIC: {
|
||||
value = orderBy(value, ['isPublic'], [order]);
|
||||
break;
|
||||
}
|
||||
case PlaylistListSortOptions.TRACK_COUNT: {
|
||||
value = orderBy(value, ['trackCount'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
song: (array: PlaylistSong[] | Song[], key: SongListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case SongListSortOptions.ALBUM: {
|
||||
value = orderBy(value, ['album'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.ALBUM_ARTIST: {
|
||||
value = orderBy(value, ['artistId'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.ARTIST: {
|
||||
value = orderBy(value, ['artistId'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.BPM: {
|
||||
value = orderBy(value, ['bpm'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.CHANNELS: {
|
||||
value = orderBy(value, ['channels'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.COMMENT: {
|
||||
value = orderBy(value, ['comment'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.DURATION: {
|
||||
value = orderBy(value, ['duration'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.GENRE: {
|
||||
value = orderBy(value, ['genre'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.ID: {
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.IS_FAVORITE: {
|
||||
value = orderBy(value, ['userFavoriteDate', 'userFavorite'], [order, order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.PLAY_COUNT: {
|
||||
value = orderBy(value, ['userPlayCount'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.RANDOM: {
|
||||
value = shuffle(value);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.RATING: {
|
||||
value = orderBy(value, ['userRating'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.RECENTLY_ADDED: {
|
||||
value = orderBy(value, ['recentlyAdded'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.RECENTLY_PLAYED: {
|
||||
value = orderBy(value, ['userLastPlayedDate'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.RELEASE_DATE: {
|
||||
value = orderBy(value, ['releaseYear'], [order]);
|
||||
break;
|
||||
}
|
||||
case SongListSortOptions.YEAR: {
|
||||
value = orderBy(value, ['releaseYear'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
user: (array: User[], key: UserListSortOptions, order: ListSortOrder) => {
|
||||
let value = array;
|
||||
|
||||
switch (key) {
|
||||
case UserListSortOptions.NAME: {
|
||||
value = orderBy(value, ['name'], [order]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
export const helpers = {
|
||||
estimateTotalRecordCount,
|
||||
exactTotalRecordCount,
|
||||
fetchAllRecords,
|
||||
fetchTotalRecordCount,
|
||||
getListCount,
|
||||
getListCountKey,
|
||||
invalidateListCount,
|
||||
paginate,
|
||||
search,
|
||||
setListCount,
|
||||
sortBy,
|
||||
};
|
||||
|
||||
@@ -6,14 +6,15 @@ import {
|
||||
AlbumListResponse,
|
||||
} from '/@/shared/types/domain/album-domain-types';
|
||||
import {
|
||||
AlbumArtistDetailRequest,
|
||||
AlbumArtistDetailResponse,
|
||||
AlbumArtistListRequest,
|
||||
AlbumArtistListResponse,
|
||||
ArtistDetailRequest,
|
||||
ArtistDetailResponse,
|
||||
ArtistListRequest,
|
||||
ArtistListResponse,
|
||||
} from '/@/shared/types/domain/artist-domain-types';
|
||||
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
|
||||
import {
|
||||
AuthenticationRequest,
|
||||
AuthenticationResponse,
|
||||
} from '/@/shared/types/domain/auth-domain-types';
|
||||
import { GenreListRequest, GenreListResponse } from '/@/shared/types/domain/genre-domain-types';
|
||||
import {
|
||||
LyricsRequest,
|
||||
@@ -21,6 +22,12 @@ import {
|
||||
StructuredLyric,
|
||||
StructuredLyricsRequest,
|
||||
} from '/@/shared/types/domain/lyric-domain-types';
|
||||
import {
|
||||
FavoriteRequest,
|
||||
FavoriteResponse,
|
||||
RatingResponse,
|
||||
SetRatingRequest,
|
||||
} from '/@/shared/types/domain/metadata-domain-types';
|
||||
import { TranscodingRequest } from '/@/shared/types/domain/player-domain-types';
|
||||
import {
|
||||
AddToPlaylistArgs,
|
||||
@@ -64,18 +71,16 @@ import {
|
||||
import { TagRequest, TagsResponse } from '/@/shared/types/domain/tag-domain-types';
|
||||
import {
|
||||
DownloadRequest,
|
||||
FavoriteRequest,
|
||||
FavoriteResponse,
|
||||
RatingResponse,
|
||||
ScrobbleRequest,
|
||||
ScrobbleResponse,
|
||||
SetRatingRequest,
|
||||
ShareItemRequest,
|
||||
ShareItemResponse,
|
||||
UserListRequest,
|
||||
UserListResponse,
|
||||
} from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
export type ApiAuthentication = (args: AuthenticationRequest) => Promise<AuthenticationResponse>;
|
||||
|
||||
export type ApiClientProps = {
|
||||
baseUrl?: string;
|
||||
cache?: 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload';
|
||||
@@ -119,21 +124,23 @@ export type ApiController = {
|
||||
getListCount?: ApiControllerFn<AlbumListRequest, number>;
|
||||
};
|
||||
albumArtist: {
|
||||
getDetail?: ApiControllerFn<AlbumArtistDetailRequest, AlbumArtistDetailResponse>;
|
||||
getList?: ApiControllerFn<AlbumArtistListRequest, AlbumArtistListResponse>;
|
||||
getListCount?: ApiControllerFn<AlbumArtistListRequest, number>;
|
||||
};
|
||||
artist: {
|
||||
getDetail?: ApiControllerFn<ArtistDetailRequest, ArtistDetailResponse>;
|
||||
getList?: ApiControllerFn<ArtistListRequest, ArtistListResponse>;
|
||||
getListCount?: ApiControllerFn<ArtistListRequest, number>;
|
||||
};
|
||||
favorite: {
|
||||
create?: ApiControllerFn<FavoriteRequest, FavoriteResponse>;
|
||||
delete?: ApiControllerFn<FavoriteRequest, FavoriteResponse>;
|
||||
artist: {
|
||||
getDetail?: ApiControllerFn<ArtistDetailRequest, ArtistDetailResponse>;
|
||||
getList?: ApiControllerFn<ArtistListRequest, ArtistListResponse>;
|
||||
getListCount?: ApiControllerFn<ArtistListRequest, number>;
|
||||
};
|
||||
genre: {
|
||||
getList?: ApiControllerFn<GenreListRequest, GenreListResponse>;
|
||||
};
|
||||
metadata: {
|
||||
addFavorite?: ApiControllerFn<FavoriteRequest, FavoriteResponse>;
|
||||
removeFavorite?: ApiControllerFn<FavoriteRequest, FavoriteResponse>;
|
||||
setRating?: ApiControllerFn<SetRatingRequest, RatingResponse>;
|
||||
};
|
||||
musicFolder: {
|
||||
getList?: ApiControllerFn<ServerMusicFolderListRequest, ServerMusicFolderListResponse>;
|
||||
};
|
||||
@@ -150,14 +157,6 @@ export type ApiController = {
|
||||
update?: ApiControllerFn<UpdatePlaylistRequest, UpdatePlaylistResponse>;
|
||||
};
|
||||
server: {
|
||||
authenticate: (
|
||||
url: string,
|
||||
body: { legacy?: boolean; password: string; username: string },
|
||||
) => Promise<AuthenticationResponse>;
|
||||
getRoles?: ApiControllerFn<
|
||||
BaseEndpointArgs,
|
||||
Array<string | { label: string; value: string }>
|
||||
>;
|
||||
getServerInfo?: ApiControllerFn<ServerInfoRequest, ServerInfo>;
|
||||
getTags?: ApiControllerFn<TagRequest, TagsResponse>;
|
||||
getTranscodingUrl?: ApiControllerFn<TranscodingRequest, string>;
|
||||
@@ -177,7 +176,7 @@ export type ApiController = {
|
||||
};
|
||||
user: {
|
||||
getList?: ApiControllerFn<UserListRequest, UserListResponse>;
|
||||
setRating?: ApiControllerFn<SetRatingRequest, RatingResponse>;
|
||||
getListCount?: ApiControllerFn<UserListRequest, number>;
|
||||
shareItem?: ApiControllerFn<ShareItemRequest, ShareItemResponse>;
|
||||
};
|
||||
};
|
||||
@@ -189,7 +188,6 @@ export interface ApiControllerError {
|
||||
|
||||
export type ApiControllerFn<TRequest, TResponse> = (
|
||||
request: TRequest,
|
||||
server: ServerListItem,
|
||||
options?: ApiClientProps,
|
||||
) => Promise<[ApiControllerError, null] | [null, TResponse]>;
|
||||
|
||||
@@ -198,18 +196,19 @@ export type BaseEndpointArgs = {
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
};
|
||||
export interface BasePaginatedResponse<T> {
|
||||
error?: any | string;
|
||||
items: T;
|
||||
startIndex: number;
|
||||
totalRecordCount: null | number;
|
||||
}
|
||||
|
||||
export interface BaseQuery<T> {
|
||||
export interface BasePaginatedQuery<T> {
|
||||
limit: number;
|
||||
offset: number;
|
||||
sortBy: T;
|
||||
sortOrder: ListSortOrder;
|
||||
}
|
||||
|
||||
export interface BasePaginatedResponse<T> {
|
||||
items: T;
|
||||
offset: number;
|
||||
totalRecordCount: null | number;
|
||||
}
|
||||
|
||||
export type ExtractControllerResponse<T> = T extends ApiControllerFn<any, infer R> ? R : never;
|
||||
|
||||
export const API_CLIENT_NAME = 'Feishin';
|
||||
|
||||
@@ -6,7 +6,10 @@ import { JFAlbumListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import {
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
@@ -75,24 +78,18 @@ export enum AlbumListSort {
|
||||
YEAR = 'year',
|
||||
}
|
||||
|
||||
export interface AlbumListQuery extends BaseQuery<AlbumListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumList>>;
|
||||
navidrome?: Partial<z.infer<typeof ndType._parameters.albumList>>;
|
||||
};
|
||||
export interface AlbumListQuery extends BasePaginatedQuery<AlbumListSortOptions> {
|
||||
artistIds?: string[];
|
||||
compilation?: boolean;
|
||||
favorite?: boolean;
|
||||
genres?: string[];
|
||||
limit?: number;
|
||||
maxYear?: number;
|
||||
minYear?: number;
|
||||
musicFolderId?: string;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type AlbumListRequest = { query: AlbumListQuery };
|
||||
export type AlbumListRequest = { query: AlbumListQuery; totalRecordCount?: number };
|
||||
|
||||
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
AlbumListResponse,
|
||||
} from '/@/shared/types/domain/album-domain-types';
|
||||
import {
|
||||
AlbumArtistDetailRequest,
|
||||
AlbumArtistDetailResponse,
|
||||
ArtistDetailRequest,
|
||||
ArtistDetailResponse,
|
||||
AlbumArtistListRequest,
|
||||
AlbumArtistListResponse,
|
||||
ArtistListRequest,
|
||||
@@ -62,17 +62,19 @@ import {
|
||||
import { TagRequest, TagsResponse } from '/@/shared/types/domain/tag-domain-types';
|
||||
import {
|
||||
DownloadRequest,
|
||||
FavoriteRequest,
|
||||
FavoriteResponse,
|
||||
RatingResponse,
|
||||
ScrobbleRequest,
|
||||
ScrobbleResponse,
|
||||
SetRatingRequest,
|
||||
ShareItemRequest,
|
||||
ShareItemResponse,
|
||||
UserListRequest,
|
||||
UserListResponse,
|
||||
} from '/@/shared/types/domain/user-domain-types';
|
||||
import {
|
||||
FavoriteRequest,
|
||||
FavoriteResponse,
|
||||
RatingResponse,
|
||||
SetRatingRequest,
|
||||
} from './metadata-domain-types';
|
||||
|
||||
export const instanceOfCancellationError = (error: any) => {
|
||||
return 'revert' in error;
|
||||
@@ -88,7 +90,7 @@ export type ControllerEndpoint = {
|
||||
createPlaylist: (args: CreatePlaylistRequest) => Promise<CreatePlaylistResponse>;
|
||||
deleteFavorite: (args: FavoriteRequest) => Promise<FavoriteResponse>;
|
||||
deletePlaylist: (args: DeletePlaylistRequest) => Promise<DeletePlaylistResponse>;
|
||||
getAlbumArtistDetail: (args: AlbumArtistDetailRequest) => Promise<AlbumArtistDetailResponse>;
|
||||
getAlbumArtistDetail: (args: ArtistDetailRequest) => Promise<ArtistDetailResponse>;
|
||||
getAlbumArtistList: (args: AlbumArtistListRequest) => Promise<AlbumArtistListResponse>;
|
||||
getAlbumArtistListCount: (args: AlbumArtistListRequest) => Promise<number>;
|
||||
getAlbumDetail: (args: AlbumDetailRequest) => Promise<AlbumDetailResponse>;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { orderBy } from 'lodash';
|
||||
import { z } from 'zod';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { JFAlbumArtistListSort, JFArtistListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import { NDAlbumArtistListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import {
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
@@ -121,23 +121,17 @@ export enum ArtistListSort {
|
||||
|
||||
export type AlbumArtistDetailQuery = { id: string };
|
||||
|
||||
export type AlbumArtistDetailRequest = { query: AlbumArtistDetailQuery };
|
||||
export type ArtistDetailRequest = { query: AlbumArtistDetailQuery };
|
||||
|
||||
export type AlbumArtistDetailResponse = Artist | null;
|
||||
export type ArtistDetailResponse = Artist | null;
|
||||
|
||||
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumArtistList>>;
|
||||
navidrome?: Partial<z.infer<typeof ndType._parameters.albumArtistList>>;
|
||||
};
|
||||
limit?: number;
|
||||
export interface ArtistListQuery extends BasePaginatedQuery<ArtistListSortOptions> {
|
||||
musicFolderId?: string;
|
||||
role?: string;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type ArtistListRequest = { query: ArtistListQuery };
|
||||
export type ArtistListRequest = { query: ArtistListQuery; totalRecordCount?: number };
|
||||
|
||||
export type ArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
|
||||
type ArtistListSortMap = {
|
||||
@@ -201,21 +195,6 @@ export enum AlbumArtistListSort {
|
||||
SONG_COUNT = 'songCount',
|
||||
}
|
||||
|
||||
export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumArtistList>>;
|
||||
navidrome?: Partial<z.infer<typeof ndType._parameters.albumArtistList>>;
|
||||
};
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type AlbumArtistListRequest = { query: AlbumArtistListQuery };
|
||||
|
||||
export type AlbumArtistListResponse = BasePaginatedResponse<Artist[]> | null | undefined;
|
||||
|
||||
export type ArtistInfoQuery = {
|
||||
artistId: string;
|
||||
limit: number;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { UserPermissions } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
export type AuthenticationRequest = {
|
||||
body: { password: string; username: string };
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type AuthenticationResponse = {
|
||||
credential: string;
|
||||
ndCredential?: string;
|
||||
userId: null | string;
|
||||
permissions: UserPermissions;
|
||||
username: string;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { JFGenreListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { NDGenreListSort } from '/@/shared/api/navidrome.types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import {
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { UserListSort } from '/@/shared/types/domain/user-domain-types';
|
||||
|
||||
export enum GenreListSortOptions {
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
@@ -29,18 +29,12 @@ export type Genre = {
|
||||
songCount: null | number;
|
||||
};
|
||||
|
||||
export interface GenreListQuery extends BaseQuery<GenreListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: null;
|
||||
navidrome?: null;
|
||||
};
|
||||
limit?: number;
|
||||
export interface GenreListQuery extends BasePaginatedQuery<GenreListSortOptions> {
|
||||
musicFolderId?: string;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type GenreListRequest = { query: GenreListQuery };
|
||||
export type GenreListRequest = { query: GenreListQuery; totalRecordCount?: number };
|
||||
|
||||
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
|
||||
|
||||
@@ -49,24 +43,3 @@ export type RelatedGenre = {
|
||||
imageUrl: null | string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type GenreListSortMap = {
|
||||
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
|
||||
navidrome: Record<GenreListSort, NDGenreListSort | undefined>;
|
||||
subsonic: Record<UserListSort, undefined>;
|
||||
};
|
||||
|
||||
export const genreListSortMap: GenreListSortMap = {
|
||||
jellyfin: {
|
||||
name: JFGenreListSort.NAME,
|
||||
},
|
||||
navidrome: {
|
||||
name: NDGenreListSort.NAME,
|
||||
},
|
||||
subsonic: {
|
||||
name: undefined,
|
||||
},
|
||||
};
|
||||
export enum GenreListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
export type FavoriteQuery = {
|
||||
id: string[];
|
||||
type: LibraryItem;
|
||||
};
|
||||
|
||||
export type FavoriteRequest = { query: FavoriteQuery; serverId?: string };
|
||||
|
||||
export type FavoriteResponse = null;
|
||||
|
||||
export type RatingQuery = {
|
||||
id: string[];
|
||||
rating: number;
|
||||
};
|
||||
|
||||
export type RatingResponse = null;
|
||||
|
||||
export type SetRatingRequest = { query: RatingQuery; serverId?: string };
|
||||
@@ -1,19 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { JFPlaylistListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import { NDPlaylistListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import {
|
||||
BaseEndpointArgs,
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
BaseQuery,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song, SongListSort } from '/@/shared/types/domain/song-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import { Song, SongListSortOptions } from '/@/shared/types/domain/song-domain-types';
|
||||
|
||||
export enum PlaylistListSortOptions {
|
||||
DURATION = 'duration',
|
||||
@@ -33,15 +27,6 @@ export const PlaylistListSortOptionsLabels = {
|
||||
[PlaylistListSortOptions.UPDATED_AT]: i18n.t('filter.updatedAt'),
|
||||
};
|
||||
|
||||
export enum PlaylistListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
OWNER = 'owner',
|
||||
PUBLIC = 'public',
|
||||
SONG_COUNT = 'songCount',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
}
|
||||
|
||||
export type AddToPlaylistArgs = BaseEndpointArgs & {
|
||||
body: AddToPlaylistBody;
|
||||
query: AddToPlaylistQuery;
|
||||
@@ -88,6 +73,17 @@ export type DeletePlaylistRequest = {
|
||||
|
||||
export type DeletePlaylistResponse = null | undefined;
|
||||
|
||||
export type MoveItemQuery = {
|
||||
endingIndex: number;
|
||||
playlistId: string;
|
||||
startingIndex: number;
|
||||
trackId: string;
|
||||
};
|
||||
|
||||
export type MoveItemRequest = {
|
||||
query: MoveItemQuery;
|
||||
};
|
||||
|
||||
export type Playlist = {
|
||||
_itemType: LibraryItem.PLAYLIST;
|
||||
_serverId: string;
|
||||
@@ -109,17 +105,19 @@ export type Playlist = {
|
||||
updatedDate: null | string;
|
||||
};
|
||||
|
||||
export interface PlaylistListQuery extends BaseQuery<PlaylistListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.playlistList>>;
|
||||
navidrome?: Partial<z.infer<typeof ndType._parameters.playlistList>>;
|
||||
};
|
||||
limit?: number;
|
||||
export type PlaylistDetailQuery = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type PlaylistDetailRequest = { query: PlaylistDetailQuery };
|
||||
|
||||
export type PlaylistDetailResponse = Playlist;
|
||||
|
||||
export interface PlaylistListQuery extends BasePaginatedQuery<PlaylistListSortOptions> {
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type PlaylistListRequest = { query: PlaylistListQuery };
|
||||
export type PlaylistListRequest = { query: PlaylistListQuery; totalRecordCount?: number };
|
||||
|
||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]> | null | undefined;
|
||||
|
||||
@@ -127,6 +125,41 @@ export type PlaylistSong = Song & {
|
||||
playlistItemId: string;
|
||||
};
|
||||
|
||||
export type PlaylistSongListQuery = BasePaginatedQuery<SongListSortOptions> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type PlaylistSongListRequest = { query: PlaylistSongListQuery; totalRecordCount?: number };
|
||||
|
||||
// export const playlistListSortMap: PlaylistListSortMap = {
|
||||
// jellyfin: {
|
||||
// duration: JFPlaylistListSort.DURATION,
|
||||
// name: JFPlaylistListSort.NAME,
|
||||
// owner: undefined,
|
||||
// public: undefined,
|
||||
// songCount: JFPlaylistListSort.SONG_COUNT,
|
||||
// updatedAt: undefined,
|
||||
// },
|
||||
// navidrome: {
|
||||
// duration: NDPlaylistListSort.DURATION,
|
||||
// name: NDPlaylistListSort.NAME,
|
||||
// owner: NDPlaylistListSort.OWNER,
|
||||
// public: NDPlaylistListSort.PUBLIC,
|
||||
// songCount: NDPlaylistListSort.SONG_COUNT,
|
||||
// updatedAt: NDPlaylistListSort.UPDATED_AT,
|
||||
// },
|
||||
// subsonic: {
|
||||
// duration: undefined,
|
||||
// name: undefined,
|
||||
// owner: undefined,
|
||||
// public: undefined,
|
||||
// songCount: undefined,
|
||||
// updatedAt: undefined,
|
||||
// },
|
||||
// };
|
||||
|
||||
export type PlaylistSongListResponse = BasePaginatedResponse<PlaylistSong[]>;
|
||||
|
||||
export type RemoveFromPlaylistQuery = {
|
||||
id: string;
|
||||
songId: string[];
|
||||
@@ -165,66 +198,3 @@ export type UpdatePlaylistRequest = {
|
||||
};
|
||||
|
||||
export type UpdatePlaylistResponse = null | undefined;
|
||||
|
||||
type PlaylistListSortMap = {
|
||||
jellyfin: Record<PlaylistListSort, JFPlaylistListSort | undefined>;
|
||||
navidrome: Record<PlaylistListSort, NDPlaylistListSort | undefined>;
|
||||
subsonic: Record<PlaylistListSort, undefined>;
|
||||
};
|
||||
|
||||
export const playlistListSortMap: PlaylistListSortMap = {
|
||||
jellyfin: {
|
||||
duration: JFPlaylistListSort.DURATION,
|
||||
name: JFPlaylistListSort.NAME,
|
||||
owner: undefined,
|
||||
public: undefined,
|
||||
songCount: JFPlaylistListSort.SONG_COUNT,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
duration: NDPlaylistListSort.DURATION,
|
||||
name: NDPlaylistListSort.NAME,
|
||||
owner: NDPlaylistListSort.OWNER,
|
||||
public: NDPlaylistListSort.PUBLIC,
|
||||
songCount: NDPlaylistListSort.SONG_COUNT,
|
||||
updatedAt: NDPlaylistListSort.UPDATED_AT,
|
||||
},
|
||||
subsonic: {
|
||||
duration: undefined,
|
||||
name: undefined,
|
||||
owner: undefined,
|
||||
public: undefined,
|
||||
songCount: undefined,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
};
|
||||
export type MoveItemQuery = {
|
||||
endingIndex: number;
|
||||
playlistId: string;
|
||||
startingIndex: number;
|
||||
trackId: string;
|
||||
};
|
||||
|
||||
export type MoveItemRequest = {
|
||||
query: MoveItemQuery;
|
||||
};
|
||||
|
||||
export type PlaylistDetailQuery = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type PlaylistDetailRequest = { query: PlaylistDetailQuery };
|
||||
|
||||
export type PlaylistDetailResponse = Playlist;
|
||||
|
||||
export type PlaylistSongListQuery = {
|
||||
id: string;
|
||||
limit?: number;
|
||||
sortBy?: SongListSort;
|
||||
sortOrder?: ListSortOrder;
|
||||
startIndex: number;
|
||||
};
|
||||
|
||||
export type PlaylistSongListRequest = { query: PlaylistSongListQuery };
|
||||
|
||||
export type PlaylistSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
|
||||
|
||||
@@ -46,11 +46,9 @@ export type ServerListItem = {
|
||||
features?: ServerFeatures;
|
||||
id: string;
|
||||
name: string;
|
||||
ndCredential?: string;
|
||||
savePassword?: boolean;
|
||||
type: ServerType;
|
||||
url: string;
|
||||
userId: null | string;
|
||||
username: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
@@ -13,11 +13,12 @@ export enum LibraryItem {
|
||||
GENRE = 'genre',
|
||||
PLAYLIST = 'playlist',
|
||||
SONG = 'song',
|
||||
USER = 'user',
|
||||
}
|
||||
|
||||
export enum ListSortOrder {
|
||||
ASC = 'ASC',
|
||||
DESC = 'DESC',
|
||||
ASC = 'asc',
|
||||
DESC = 'desc',
|
||||
}
|
||||
|
||||
export type AnyLibraryItem = Album | Artist | Artist | Playlist | QueueSong | Song;
|
||||
|
||||
@@ -6,7 +6,10 @@ import { JFSongListSort } from '/@/shared/api/jellyfin.types';
|
||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import { NDSongListSort } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import {
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
|
||||
import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types';
|
||||
import { Played, QueueSong } from '/@/shared/types/domain/player-domain-types';
|
||||
@@ -134,27 +137,21 @@ export type Song = {
|
||||
userRating: null | number;
|
||||
};
|
||||
|
||||
export interface SongListQuery extends BaseQuery<SongListSort> {
|
||||
_custom?: {
|
||||
jellyfin?: Partial<z.infer<typeof jfType._parameters.songList>>;
|
||||
navidrome?: Partial<z.infer<typeof ndType._parameters.songList>>;
|
||||
};
|
||||
export interface SongListQuery extends BasePaginatedQuery<SongListSort> {
|
||||
albumArtistIds?: string[];
|
||||
albumIds?: string[];
|
||||
artistIds?: string[];
|
||||
favorite?: boolean;
|
||||
genreIds?: string[];
|
||||
imageSize?: number;
|
||||
limit?: number;
|
||||
maxYear?: number;
|
||||
minYear?: number;
|
||||
musicFolderId?: string;
|
||||
role?: string;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type SongListRequest = { query: SongListQuery };
|
||||
export type SongListRequest = { query: SongListQuery; totalRecordCount?: number };
|
||||
|
||||
export type SongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
|
||||
type SongListSortMap = {
|
||||
@@ -240,7 +237,7 @@ export type RandomSongListQuery = {
|
||||
played: Played;
|
||||
};
|
||||
|
||||
export type RandomSongListRequest = { query: RandomSongListQuery };
|
||||
export type RandomSongListRequest = { query: RandomSongListQuery; totalRecordCount?: number };
|
||||
|
||||
export type RandomSongListResponse = SongListResponse;
|
||||
|
||||
@@ -264,7 +261,7 @@ export type TopSongListQuery = {
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type TopSongListRequest = { query: TopSongListQuery };
|
||||
export type TopSongListRequest = { query: TopSongListQuery; totalRecordCount?: number };
|
||||
|
||||
export type TopSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
|
||||
|
||||
|
||||
@@ -1,71 +1,17 @@
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { NDUserListSort } from '/@/shared/api/navidrome.types';
|
||||
import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types';
|
||||
import { AnyLibraryItems, LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
import {
|
||||
BasePaginatedQuery,
|
||||
BasePaginatedResponse,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerType } from '/@/shared/types/domain/server-domain-types';
|
||||
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
|
||||
|
||||
export enum UserListSortOptions {
|
||||
CREATED_AT = 'createdAt',
|
||||
EMAIL = 'email',
|
||||
NAME = 'name',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
}
|
||||
|
||||
export const UserListSortOptionsLabels = {
|
||||
[UserListSortOptions.CREATED_AT]: i18n.t('filter.createdAt'),
|
||||
[UserListSortOptions.EMAIL]: i18n.t('filter.email'),
|
||||
[UserListSortOptions.NAME]: i18n.t('filter.name'),
|
||||
[UserListSortOptions.UPDATED_AT]: i18n.t('filter.updatedAt'),
|
||||
};
|
||||
|
||||
export type FavoriteQuery = {
|
||||
id: string[];
|
||||
type: LibraryItem;
|
||||
};
|
||||
|
||||
export type FavoriteRequest = { query: FavoriteQuery; serverId?: string };
|
||||
|
||||
export type FavoriteResponse = null;
|
||||
|
||||
export type RatingQuery = {
|
||||
item: AnyLibraryItems;
|
||||
rating: number;
|
||||
};
|
||||
|
||||
export type RatingResponse = null;
|
||||
|
||||
export type SetRatingRequest = { query: RatingQuery; serverId?: string };
|
||||
|
||||
export interface UserListQuery extends BaseQuery<UserListSort> {
|
||||
_custom?: {
|
||||
navidrome?: {
|
||||
owner_id?: string;
|
||||
};
|
||||
};
|
||||
limit?: number;
|
||||
searchTerm?: string;
|
||||
startIndex: number;
|
||||
}
|
||||
|
||||
export type UserListRequest = { query: UserListQuery };
|
||||
|
||||
export type UserListResponse = BasePaginatedResponse<User[]> | null | undefined;
|
||||
|
||||
type UserListSortMap = {
|
||||
jellyfin: Record<UserListSort, undefined>;
|
||||
navidrome: Record<UserListSort, NDUserListSort | undefined>;
|
||||
subsonic: Record<UserListSort, undefined>;
|
||||
};
|
||||
|
||||
export const userListSortMap: UserListSortMap = {
|
||||
jellyfin: {
|
||||
name: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
name: NDUserListSort.NAME,
|
||||
},
|
||||
subsonic: {
|
||||
name: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export enum UserListSort {
|
||||
@@ -93,20 +39,41 @@ export type ShareItemBody = {
|
||||
description: string;
|
||||
downloadable: boolean;
|
||||
expires: number;
|
||||
resourceIds: string;
|
||||
resourceIds: string[];
|
||||
resourceType: string;
|
||||
};
|
||||
|
||||
export type ShareItemRequest = { body: ShareItemBody; serverId?: string };
|
||||
|
||||
export type ShareItemResponse = null | { id: string };
|
||||
export type ShareItemResponse = null | { id: string; url: string };
|
||||
|
||||
export type User = {
|
||||
createdAt: null | string;
|
||||
email: null | string;
|
||||
_itemType: LibraryItem.USER;
|
||||
_serverId: string;
|
||||
_serverType: ServerType;
|
||||
id: string;
|
||||
isAdmin: boolean | null;
|
||||
lastLoginAt: null | string;
|
||||
name: string;
|
||||
updatedAt: null | string;
|
||||
permissions: UserPermissions;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export interface UserListQuery extends BasePaginatedQuery<UserListSortOptions> {
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export type UserListRequest = { query: UserListQuery; totalRecordCount?: number };
|
||||
|
||||
export type UserListResponse = BasePaginatedResponse<User[]>;
|
||||
|
||||
export type UserPermissions = {
|
||||
'jukebox.manage': boolean; // Allow managing the jukebox
|
||||
'media.download': boolean; // Allow downloading media
|
||||
'media.folder': string[]; // Viewable folders
|
||||
'media.share': boolean; // Allow sharing media
|
||||
'media.stream': boolean; // Allow streaming media
|
||||
'media.upload': boolean; // Allow uploading media
|
||||
'playlist.create': boolean; // Allow creating playlists
|
||||
'playlist.delete': boolean; // Allow deleting playlists
|
||||
'playlist.edit': boolean; // Allow editing playlists
|
||||
'server.admin': boolean; // Allow managing the server (user management / server settings)
|
||||
'user.edit': boolean; // Allow editing own user account
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const randomString = (length?: number) => {
|
||||
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let string = '';
|
||||
for (let i = 0; i < (length || 12); i += 1) {
|
||||
const randomPoz = Math.floor(Math.random() * charSet.length);
|
||||
string += charSet.substring(randomPoz, randomPoz + 1);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
Reference in New Issue
Block a user