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