mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 06:27:09 +01:00
Lint all files
This commit is contained in:
parent
22af76b4d6
commit
30e52ebb54
6
.github/stale.yml
vendored
6
.github/stale.yml
vendored
@ -10,8 +10,8 @@ exemptLabels:
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
|
||||
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run package
|
||||
npm run lint
|
||||
npm run package
|
||||
npm exec tsc
|
||||
npm test
|
||||
|
@ -21,10 +21,7 @@
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"selector-class-pattern": null,
|
||||
"selector-type-case": ["lower", { "ignoreTypes": ["/^\\$\\w+/"] }],
|
||||
"selector-type-no-unknown": [
|
||||
true,
|
||||
{ "ignoreTypes": ["/-styled-mixin/", "/^\\$\\w+/"] }
|
||||
],
|
||||
"selector-type-no-unknown": [true, { "ignoreTypes": ["/-styled-mixin/", "/^\\$\\w+/"] }],
|
||||
"value-keyword-case": ["lower", { "ignoreKeywords": ["dummyValue"] }],
|
||||
"declaration-colon-newline-after": null
|
||||
}
|
||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -7,9 +7,7 @@
|
||||
"request": "launch",
|
||||
"protocol": "inspector",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run start:main --inspect=5858 --remote-debugging-port=9223"
|
||||
],
|
||||
"runtimeArgs": ["run start:main --inspect=5858 --remote-debugging-port=9223"],
|
||||
"preLaunchTask": "Start Webpack Dev"
|
||||
},
|
||||
{
|
||||
|
@ -155,7 +155,9 @@ ipcMain.on(
|
||||
? song.artists?.map((artist: RelatedArtist) => artist.name)
|
||||
: null,
|
||||
'xesam:discNumber': song.discNumber ? song.discNumber : null,
|
||||
'xesam:genre': song.genres?.length ? song.genres.map((genre: any) => genre.name) : null,
|
||||
'xesam:genre': song.genres?.length
|
||||
? song.genres.map((genre: any) => genre.name)
|
||||
: null,
|
||||
'xesam:title': song.name || null,
|
||||
'xesam:trackNumber': song.trackNumber ? song.trackNumber : null,
|
||||
'xesam:useCount':
|
||||
|
@ -85,7 +85,7 @@ const singleInstance = app.requestSingleInstanceLock();
|
||||
if (!singleInstance) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', (_event, _argv, _workingDirectory) => {
|
||||
app.on('second-instance', () => {
|
||||
mainWindow?.show();
|
||||
});
|
||||
}
|
||||
@ -579,7 +579,8 @@ const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
|
||||
[BindingActions.STOP]: () => getMainWindow()?.webContents.send('renderer-player-stop'),
|
||||
[BindingActions.TOGGLE_REPEAT]: () =>
|
||||
getMainWindow()?.webContents.send('renderer-player-toggle-repeat'),
|
||||
[BindingActions.VOLUME_UP]: () => getMainWindow()?.webContents.send('renderer-player-volume-up'),
|
||||
[BindingActions.VOLUME_UP]: () =>
|
||||
getMainWindow()?.webContents.send('renderer-player-volume-up'),
|
||||
[BindingActions.VOLUME_DOWN]: () =>
|
||||
getMainWindow()?.webContents.send('renderer-player-volume-down'),
|
||||
[BindingActions.GLOBAL_SEARCH]: () => {},
|
||||
@ -600,10 +601,13 @@ ipcMain.on(
|
||||
for (const shortcut of Object.keys(data)) {
|
||||
const isGlobalHotkey = data[shortcut as BindingActions].isGlobal;
|
||||
const isValidHotkey =
|
||||
data[shortcut as BindingActions].hotkey && data[shortcut as BindingActions].hotkey !== '';
|
||||
data[shortcut as BindingActions].hotkey &&
|
||||
data[shortcut as BindingActions].hotkey !== '';
|
||||
|
||||
if (isGlobalHotkey && isValidHotkey) {
|
||||
const accelerator = hotkeyToElectronAccelerator(data[shortcut as BindingActions].hotkey);
|
||||
const accelerator = hotkeyToElectronAccelerator(
|
||||
data[shortcut as BindingActions].hotkey,
|
||||
);
|
||||
|
||||
globalShortcut.register(accelerator, () => {
|
||||
HOTKEY_ACTIONS[shortcut as BindingActions]();
|
||||
@ -636,8 +640,7 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
app.whenReady()
|
||||
.then(() => {
|
||||
createWindow();
|
||||
createTray();
|
||||
|
@ -18,7 +18,9 @@ export default class MenuBuilder {
|
||||
}
|
||||
|
||||
const template =
|
||||
process.platform === 'darwin' ? this.buildDarwinTemplate() : this.buildDefaultTemplate();
|
||||
process.platform === 'darwin'
|
||||
? this.buildDarwinTemplate()
|
||||
: this.buildDefaultTemplate();
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
@ -151,7 +153,9 @@ export default class MenuBuilder {
|
||||
},
|
||||
{
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/tree/main/docs#readme');
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme',
|
||||
);
|
||||
},
|
||||
label: 'Documentation',
|
||||
},
|
||||
@ -211,7 +215,9 @@ export default class MenuBuilder {
|
||||
{
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen(),
|
||||
);
|
||||
},
|
||||
label: 'Toggle &Full Screen',
|
||||
},
|
||||
@ -227,7 +233,9 @@ export default class MenuBuilder {
|
||||
{
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen(),
|
||||
);
|
||||
},
|
||||
label: 'Toggle &Full Screen',
|
||||
},
|
||||
@ -244,7 +252,9 @@ export default class MenuBuilder {
|
||||
},
|
||||
{
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/tree/main/docs#readme');
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme',
|
||||
);
|
||||
},
|
||||
label: 'Documentation',
|
||||
},
|
||||
|
@ -420,7 +420,10 @@ const deleteFavorite = async (args: FavoriteArgs) => {
|
||||
|
||||
const updateRating = async (args: SetRatingArgs) => {
|
||||
return (
|
||||
apiController('setRating', args.apiClientProps.server?.type) as ControllerEndpoint['setRating']
|
||||
apiController(
|
||||
'setRating',
|
||||
args.apiClientProps.server?.type,
|
||||
) as ControllerEndpoint['setRating']
|
||||
)?.(args);
|
||||
};
|
||||
|
||||
@ -435,7 +438,10 @@ const getTopSongList = async (args: TopSongListArgs) => {
|
||||
|
||||
const scrobble = async (args: ScrobbleArgs) => {
|
||||
return (
|
||||
apiController('scrobble', args.apiClientProps.server?.type) as ControllerEndpoint['scrobble']
|
||||
apiController(
|
||||
'scrobble',
|
||||
args.apiClientProps.server?.type,
|
||||
) as ControllerEndpoint['scrobble']
|
||||
)?.(args);
|
||||
};
|
||||
|
||||
@ -456,7 +462,10 @@ const getRandomSongList = async (args: RandomSongListArgs) => {
|
||||
|
||||
const getLyrics = async (args: LyricsArgs) => {
|
||||
return (
|
||||
apiController('getLyrics', args.apiClientProps.server?.type) as ControllerEndpoint['getLyrics']
|
||||
apiController(
|
||||
'getLyrics',
|
||||
args.apiClientProps.server?.type,
|
||||
) as ControllerEndpoint['getLyrics']
|
||||
)?.(args);
|
||||
};
|
||||
|
||||
|
@ -284,7 +284,9 @@ const getAlbumList = async (args: AlbumListArgs): Promise<AlbumListResponse> =>
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
query: {
|
||||
AlbumArtistIds: query.artistIds ? formatCommaDelimitedString(query.artistIds) : undefined,
|
||||
AlbumArtistIds: query.artistIds
|
||||
? formatCommaDelimitedString(query.artistIds)
|
||||
: undefined,
|
||||
IncludeItemTypes: 'MusicAlbum',
|
||||
Limit: query.limit,
|
||||
ParentId: query.musicFolderId,
|
||||
@ -363,7 +365,9 @@ const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
||||
|
||||
const yearsFilter = yearsGroup.length ? formatCommaDelimitedString(yearsGroup) : undefined;
|
||||
const albumIdsFilter = query.albumIds ? formatCommaDelimitedString(query.albumIds) : undefined;
|
||||
const artistIdsFilter = query.artistIds ? formatCommaDelimitedString(query.artistIds) : undefined;
|
||||
const artistIdsFilter = query.artistIds
|
||||
? formatCommaDelimitedString(query.artistIds)
|
||||
: undefined;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getSongList({
|
||||
params: {
|
||||
@ -798,7 +802,9 @@ const search = async (args: SearchArgs): Promise<SearchResponse> => {
|
||||
}
|
||||
|
||||
return {
|
||||
albumArtists: albumArtists.map((item) => jfNormalize.albumArtist(item, apiClientProps.server)),
|
||||
albumArtists: albumArtists.map((item) =>
|
||||
jfNormalize.albumArtist(item, apiClientProps.server),
|
||||
),
|
||||
albums: albums.map((item) => jfNormalize.album(item, apiClientProps.server)),
|
||||
songs: songs.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||
};
|
||||
|
@ -194,7 +194,11 @@ const normalizeAlbum = (
|
||||
imageUrl: null,
|
||||
name: entry.Name,
|
||||
})) || [],
|
||||
artists: item.ArtistItems?.map((entry) => ({ id: entry.Id, imageUrl: null, name: entry.Name })),
|
||||
artists: item.ArtistItems?.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: null,
|
||||
name: entry.Name,
|
||||
})),
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.DateCreated,
|
||||
duration: item.RunTimeTicks / 10000,
|
||||
|
@ -181,7 +181,8 @@ const parsePath = (fullPath: string) => {
|
||||
const newParams: Record<string, any> = {};
|
||||
Object.keys(parsedParams).forEach((key) => {
|
||||
const isIndexedArrayObject =
|
||||
typeof parsedParams[key] === 'object' && Object.keys(parsedParams[key] || {}).includes('0');
|
||||
typeof parsedParams[key] === 'object' &&
|
||||
Object.keys(parsedParams[key] || {}).includes('0');
|
||||
|
||||
if (!isIndexedArrayObject) {
|
||||
newParams[key] = parsedParams[key];
|
||||
@ -280,7 +281,9 @@ axiosClient.interceptors.response.use(
|
||||
});
|
||||
|
||||
const serverId = currentServer.id;
|
||||
useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
||||
useAuthStore
|
||||
.getState()
|
||||
.actions.updateServer(serverId, { ndCredential: undefined });
|
||||
useAuthStore.getState().actions.setCurrentServer(null);
|
||||
|
||||
// special error to prevent sending a second message, and stop other messages that could be enqueued
|
||||
|
@ -394,7 +394,9 @@ const getPlaylistSongList = async (
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : 'ASC',
|
||||
_sort: query.sortBy ? songListSortMap.navidrome[query.sortBy] : ndType._enum.songList.ID,
|
||||
_sort: query.sortBy
|
||||
? songListSortMap.navidrome[query.sortBy]
|
||||
: ndType._enum.songList.ID,
|
||||
_start: query.startIndex,
|
||||
},
|
||||
});
|
||||
|
@ -161,7 +161,9 @@ export const ssApiClient = (args: {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await axiosClient.request<z.infer<typeof ssType._response.baseResponse>>({
|
||||
const result = await axiosClient.request<
|
||||
z.infer<typeof ssType._response.baseResponse>
|
||||
>({
|
||||
data: body,
|
||||
headers,
|
||||
method: method as Method,
|
||||
|
@ -266,8 +266,9 @@ const getTopSongList = async (args: TopSongListArgs): Promise<SongListResponse>
|
||||
|
||||
return {
|
||||
items:
|
||||
res.body.topSongs?.song?.map((song) => ssNormalize.song(song, apiClientProps.server, '')) ||
|
||||
[],
|
||||
res.body.topSongs?.song?.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server, ''),
|
||||
) || [],
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
};
|
||||
|
@ -145,7 +145,9 @@ const normalizeAlbum = (
|
||||
}) || null;
|
||||
|
||||
return {
|
||||
albumArtists: item.artistId ? [{ id: item.artistId, imageUrl: null, name: item.artist }] : [],
|
||||
albumArtists: item.artistId
|
||||
? [{ id: item.artistId, imageUrl: null, name: item.artist }]
|
||||
: [],
|
||||
artists: item.artistId ? [{ id: item.artistId, imageUrl: null, name: item.artist }] : [],
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.created,
|
||||
|
@ -171,7 +171,9 @@ export const AudioPlayer = forwardRef(
|
||||
volume={volume}
|
||||
width={0}
|
||||
onEnded={handleOnEnded}
|
||||
onProgress={playbackStyle === PlaybackStyle.GAPLESS ? handleGapless1 : handleCrossfade1}
|
||||
onProgress={
|
||||
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless1 : handleCrossfade1
|
||||
}
|
||||
/>
|
||||
<ReactPlayer
|
||||
ref={player2Ref}
|
||||
@ -183,7 +185,9 @@ export const AudioPlayer = forwardRef(
|
||||
volume={volume}
|
||||
width={0}
|
||||
onEnded={handleOnEnded}
|
||||
onProgress={playbackStyle === PlaybackStyle.GAPLESS ? handleGapless2 : handleCrossfade2}
|
||||
onProgress={
|
||||
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless2 : handleCrossfade2
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -101,9 +101,11 @@ export const crossfadeHandler = (args: {
|
||||
|
||||
percentageOfFadeLeft = timeLeft / fadeDuration;
|
||||
currentPlayerVolumeCalculation =
|
||||
Math.cos((Math.PI / 4) * ((2 * percentageOfFadeLeft - 1) ** (2 * n + 1) - 1)) * volume;
|
||||
Math.cos((Math.PI / 4) * ((2 * percentageOfFadeLeft - 1) ** (2 * n + 1) - 1)) *
|
||||
volume;
|
||||
nextPlayerVolumeCalculation =
|
||||
Math.cos((Math.PI / 4) * ((2 * percentageOfFadeLeft - 1) ** (2 * n + 1) + 1)) * volume;
|
||||
Math.cos((Math.PI / 4) * ((2 * percentageOfFadeLeft - 1) ** (2 * n + 1) + 1)) *
|
||||
volume;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -60,7 +60,10 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
|
||||
row.route!.slugs?.reduce((acc, slug) => {
|
||||
return {
|
||||
...acc,
|
||||
[slug.slugProperty]: data[row.property][itemIndex][slug.idProperty],
|
||||
[slug.slugProperty]:
|
||||
data[row.property][itemIndex][
|
||||
slug.idProperty
|
||||
],
|
||||
};
|
||||
}, {}),
|
||||
)}
|
||||
|
@ -138,7 +138,9 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: currentItem?.id || '' })}>
|
||||
<Wrapper
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: currentItem?.id || '' })}
|
||||
>
|
||||
<AnimatePresence
|
||||
custom={direction}
|
||||
initial={false}
|
||||
|
@ -118,7 +118,12 @@ export const SwiperGridCarousel = ({
|
||||
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||
|
||||
const handleFavorite = useCallback(
|
||||
(options: { id: string[]; isFavorite: boolean; itemType: LibraryItem; serverId: string }) => {
|
||||
(options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
serverId: string;
|
||||
}) => {
|
||||
const { id, itemType, isFavorite, serverId } = options;
|
||||
if (isFavorite) {
|
||||
deleteFavoriteMutation.mutate({
|
||||
|
@ -14,7 +14,11 @@ interface BaseGridCardProps {
|
||||
columnIndex: number;
|
||||
controls: {
|
||||
cardRows: CardRow<Album | AlbumArtist | Artist>[];
|
||||
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
|
||||
handleFavorite: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
}) => void;
|
||||
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
|
@ -14,7 +14,11 @@ interface BaseGridCardProps {
|
||||
columnIndex: number;
|
||||
controls: {
|
||||
cardRows: CardRow<Album | AlbumArtist | Artist>[];
|
||||
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
|
||||
handleFavorite: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
}) => void;
|
||||
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
|
||||
itemType: LibraryItem;
|
||||
playButtonBehavior: Play;
|
||||
|
@ -64,7 +64,11 @@ export const VirtualGridWrapper = ({
|
||||
cardRows: CardRow<Album | AlbumArtist | Artist>[];
|
||||
columnCount: number;
|
||||
display: ListDisplayType;
|
||||
handleFavorite?: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
|
||||
handleFavorite?: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
}) => void;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
height?: number;
|
||||
itemData: any[];
|
||||
|
@ -26,7 +26,11 @@ interface VirtualGridProps
|
||||
cardRows: CardRow<any>[];
|
||||
display?: ListDisplayType;
|
||||
fetchFn: (options: { columnCount: number; skip: number; take: number }) => Promise<any>;
|
||||
handleFavorite?: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
|
||||
handleFavorite?: (options: {
|
||||
id: string[];
|
||||
isFavorite: boolean;
|
||||
itemType: LibraryItem;
|
||||
}) => void;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
height?: number;
|
||||
itemGap: number;
|
||||
|
@ -60,7 +60,8 @@ export const useUpdateRating = () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
// We only need to set if we're already on the album detail page
|
||||
const isAlbumDetailPage =
|
||||
variables.query.item.length === 1 && variables.query.item[0].itemType === LibraryItem.ALBUM;
|
||||
variables.query.item.length === 1 &&
|
||||
variables.query.item[0].itemType === LibraryItem.ALBUM;
|
||||
|
||||
if (isAlbumDetailPage) {
|
||||
const { serverType, id: albumId, serverId } = variables.query.item[0] as Album;
|
||||
@ -94,7 +95,11 @@ export const useUpdateRating = () => {
|
||||
variables.query.item[0].itemType === LibraryItem.ALBUM_ARTIST;
|
||||
|
||||
if (isAlbumArtistDetailPage) {
|
||||
const { serverType, id: albumArtistId, serverId } = variables.query.item[0] as AlbumArtist;
|
||||
const {
|
||||
serverType,
|
||||
id: albumArtistId,
|
||||
serverId,
|
||||
} = variables.query.item[0] as AlbumArtist;
|
||||
|
||||
const queryKey = queryKeys.albumArtists.detail(serverId || '', {
|
||||
id: albumArtistId,
|
||||
|
@ -77,10 +77,12 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.ALBUM_COUNT,
|
||||
field: 'albumCount',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Albums',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.albumCount : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.albumCount : undefined,
|
||||
width: 80,
|
||||
},
|
||||
artist: {
|
||||
@ -102,7 +104,8 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.BIT_RATE,
|
||||
field: 'bitRate',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
suppressSizeToFit: true,
|
||||
valueFormatter: (params: ValueFormatterParams) => `${params.value} kbps`,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.bitRate : undefined),
|
||||
@ -111,7 +114,8 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
bpm: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.BPM,
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'BPM',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.bpm : undefined),
|
||||
@ -121,8 +125,10 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.CHANNELS,
|
||||
field: 'channels',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.channels : undefined),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.channels : undefined,
|
||||
width: 100,
|
||||
},
|
||||
comment: {
|
||||
@ -136,22 +142,26 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.DATE_ADDED,
|
||||
field: 'createdAt',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Date Added',
|
||||
suppressSizeToFit: true,
|
||||
valueFormatter: (params: ValueFormatterParams) =>
|
||||
params.value ? dayjs(params.value).format('MMM D, YYYY') : '',
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.createdAt : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.createdAt : undefined,
|
||||
width: 130,
|
||||
},
|
||||
discNumber: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.DISC_NUMBER,
|
||||
field: 'discNumber',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Disc',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.discNumber : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.discNumber : undefined,
|
||||
width: 60,
|
||||
},
|
||||
duration: {
|
||||
@ -162,7 +172,8 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
GenericTableHeader(params, { position: 'center', preset: 'duration' }),
|
||||
suppressSizeToFit: true,
|
||||
valueFormatter: (params: ValueFormatterParams) => formatDuration(params.value * 1000),
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.duration : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.duration : undefined,
|
||||
width: 70,
|
||||
},
|
||||
genre: {
|
||||
@ -175,7 +186,8 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
lastPlayedAt: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.LAST_PLAYED,
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Last Played',
|
||||
valueFormatter: (params: ValueFormatterParams) =>
|
||||
params.value ? dayjs(params.value).fromNow() : '',
|
||||
@ -194,32 +206,38 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.PLAY_COUNT,
|
||||
field: 'playCount',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Plays',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.playCount : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.playCount : undefined,
|
||||
width: 90,
|
||||
},
|
||||
releaseDate: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.RELEASE_DATE,
|
||||
field: 'releaseDate',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Release Date',
|
||||
suppressSizeToFit: true,
|
||||
valueFormatter: (params: ValueFormatterParams) =>
|
||||
params.value ? dayjs(params.value).format('MMM D, YYYY') : '',
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseDate : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.releaseDate : undefined,
|
||||
width: 130,
|
||||
},
|
||||
releaseYear: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.YEAR,
|
||||
field: 'releaseYear',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Year',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseYear : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.releaseYear : undefined,
|
||||
width: 80,
|
||||
},
|
||||
rowIndex: {
|
||||
@ -237,10 +255,12 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.SONG_COUNT,
|
||||
field: 'songCount',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Songs',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.songCount : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.songCount : undefined,
|
||||
width: 80,
|
||||
},
|
||||
title: {
|
||||
@ -276,10 +296,12 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.TRACK_NUMBER,
|
||||
field: 'trackNumber',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Track',
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.trackNumber : undefined),
|
||||
valueGetter: (params: ValueGetterParams) =>
|
||||
params.data ? params.data.trackNumber : undefined,
|
||||
width: 80,
|
||||
},
|
||||
userFavorite: {
|
||||
@ -296,7 +318,8 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||
width: 50,
|
||||
},
|
||||
userRating: {
|
||||
cellClass: (params) => (params.value?.userRating ? 'visible ag-cell-rating' : 'ag-cell-rating'),
|
||||
cellClass: (params) =>
|
||||
params.value?.userRating ? 'visible ag-cell-rating' : 'ag-cell-rating',
|
||||
cellRenderer: RatingCell,
|
||||
colId: TableColumn.USER_RATING,
|
||||
field: 'userRating',
|
||||
@ -433,7 +456,9 @@ export const VirtualTable = forwardRef(
|
||||
<TableWrapper
|
||||
ref={deselectRef}
|
||||
className={
|
||||
transparentHeader ? 'ag-header-transparent ag-theme-alpine-dark' : 'ag-theme-alpine-dark'
|
||||
transparentHeader
|
||||
? 'ag-header-transparent ag-theme-alpine-dark'
|
||||
: 'ag-theme-alpine-dark'
|
||||
}
|
||||
>
|
||||
<AgGridReact
|
||||
|
@ -47,7 +47,11 @@ export const TablePagination = ({
|
||||
|
||||
const handleGoSubmit = goToForm.onSubmit((values) => {
|
||||
handlers.close();
|
||||
if (!values.pageNumber || values.pageNumber < 1 || values.pageNumber > pagination.totalPages) {
|
||||
if (
|
||||
!values.pageNumber ||
|
||||
values.pageNumber < 1 ||
|
||||
values.pageNumber > pagination.totalPages
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,12 @@ export const ServerCredentialRequired = () => {
|
||||
return (
|
||||
<>
|
||||
<Text>
|
||||
The selected server '{currentServer?.name}' requires an additional login to
|
||||
access.
|
||||
The selected server '{currentServer?.name}' requires an additional login
|
||||
to access.
|
||||
</Text>
|
||||
<Text>
|
||||
Add your credentials in the 'manage servers' menu or switch to a different server.
|
||||
Add your credentials in the 'manage servers' menu or switch to a different
|
||||
server.
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
@ -129,8 +129,13 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||
const rowData: (QueueSong | { id: string; name: string })[] = [];
|
||||
|
||||
for (const discNumber of uniqueDiscNumbers.values()) {
|
||||
const songsByDiscNumber = detailQuery.data?.songs.filter((s) => s.discNumber === discNumber);
|
||||
rowData.push({ id: `disc-${discNumber}`, name: `Disc ${discNumber}`.toLocaleUpperCase() });
|
||||
const songsByDiscNumber = detailQuery.data?.songs.filter(
|
||||
(s) => s.discNumber === discNumber,
|
||||
);
|
||||
rowData.push({
|
||||
id: `disc-${discNumber}`,
|
||||
name: `Disc ${discNumber}`.toLocaleUpperCase(),
|
||||
});
|
||||
rowData.push(...songsByDiscNumber);
|
||||
}
|
||||
|
||||
@ -275,7 +280,9 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||
<Group spacing="xs">
|
||||
<Button
|
||||
compact
|
||||
loading={createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading}
|
||||
loading={
|
||||
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
|
||||
}
|
||||
variant="subtle"
|
||||
onClick={handleFavorite}
|
||||
>
|
||||
@ -315,7 +322,9 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||
component={Link}
|
||||
radius={0}
|
||||
size="md"
|
||||
to={generatePath(`${AppRoute.LIBRARY_ALBUMS}?genre=${genre.id}`, { albumId })}
|
||||
to={generatePath(`${AppRoute.LIBRARY_ALBUMS}?genre=${genre.id}`, {
|
||||
albumId,
|
||||
})}
|
||||
variant="outline"
|
||||
>
|
||||
{genre.name}
|
||||
@ -369,7 +378,9 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
slugs: [
|
||||
{ idProperty: 'id', slugProperty: 'albumId' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -377,7 +388,12 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
|
||||
property: 'albumArtists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
slugs: [
|
||||
{
|
||||
idProperty: 'id',
|
||||
slugProperty: 'albumArtistId',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
@ -36,7 +36,8 @@ export const AlbumDetailHeader = forwardRef(
|
||||
{
|
||||
id: 'duration',
|
||||
secondary: true,
|
||||
value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
value:
|
||||
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
},
|
||||
];
|
||||
|
||||
@ -89,7 +90,10 @@ export const AlbumDetailHeader = forwardRef(
|
||||
<>
|
||||
<Text $noSelect>•</Text>
|
||||
<Rating
|
||||
readOnly={detailQuery?.isFetching || updateRatingMutation.isLoading}
|
||||
readOnly={
|
||||
detailQuery?.isFetching ||
|
||||
updateRatingMutation.isLoading
|
||||
}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
onChange={handleUpdateRating}
|
||||
onClick={handleClearRating}
|
||||
|
@ -48,7 +48,11 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Critic Rating', value: AlbumListSort.CRITIC_RATING },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: AlbumListSort.NAME },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Random', value: AlbumListSort.RANDOM },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Added',
|
||||
value: AlbumListSort.RECENTLY_ADDED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Release Date', value: AlbumListSort.RELEASE_DATE },
|
||||
],
|
||||
navidrome: [
|
||||
@ -59,8 +63,16 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: AlbumListSort.NAME },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Random', value: AlbumListSort.RANDOM },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Rating', value: AlbumListSort.RATING },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: AlbumListSort.RECENTLY_ADDED },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Played', value: AlbumListSort.RECENTLY_PLAYED },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Added',
|
||||
value: AlbumListSort.RECENTLY_ADDED,
|
||||
},
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Played',
|
||||
value: AlbumListSort.RECENTLY_PLAYED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Song Count', value: AlbumListSort.SONG_COUNT },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Favorited', value: AlbumListSort.FAVORITED },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Year', value: AlbumListSort.YEAR },
|
||||
@ -97,7 +109,8 @@ export const AlbumListHeaderFilters = ({
|
||||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filter.sortBy)?.name) ||
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filter.sortBy)
|
||||
?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const sortOrderLabel = ORDER.find((o) => o.value === filter.sortOrder)?.name || 'Unknown';
|
||||
@ -181,7 +194,10 @@ export const AlbumListHeaderFilters = ({
|
||||
{ cacheTime: 1000 * 60 * 1 },
|
||||
);
|
||||
|
||||
return params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||
return params.successCallback(
|
||||
albumsRes?.items || [],
|
||||
albumsRes?.totalRecordCount || 0,
|
||||
);
|
||||
},
|
||||
rowCount: undefined,
|
||||
};
|
||||
@ -523,7 +539,11 @@ export const AlbumListHeaderFilters = ({
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
sx={{ svg: { fill: isFilterApplied ? 'var(--primary-color) !important' : undefined } }}
|
||||
sx={{
|
||||
svg: {
|
||||
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
|
||||
},
|
||||
}}
|
||||
tooltip={{ label: 'Filters' }}
|
||||
variant="subtle"
|
||||
onClick={handleOpenFiltersModal}
|
||||
@ -580,7 +600,8 @@ export const AlbumListHeaderFilters = ({
|
||||
<DropdownMenu.Item closeMenuOnClick={false}>
|
||||
<Slider
|
||||
defaultValue={
|
||||
display === ListDisplayType.CARD || display === ListDisplayType.POSTER
|
||||
display === ListDisplayType.CARD ||
|
||||
display === ListDisplayType.POSTER
|
||||
? grid?.itemsPerRow || 0
|
||||
: table.rowHeight
|
||||
}
|
||||
@ -590,7 +611,8 @@ export const AlbumListHeaderFilters = ({
|
||||
onChange={debouncedHandleItemSize}
|
||||
/>
|
||||
</DropdownMenu.Item>
|
||||
{(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && (
|
||||
{(display === ListDisplayType.TABLE ||
|
||||
display === ListDisplayType.TABLE_PAGINATED) && (
|
||||
<>
|
||||
<DropdownMenu.Label>Table Columns</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
@ -602,7 +624,9 @@ export const AlbumListHeaderFilters = ({
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={ALBUM_TABLE_COLUMNS}
|
||||
defaultValue={table?.columns.map((column) => column.column)}
|
||||
defaultValue={table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
/>
|
||||
|
@ -128,7 +128,10 @@ export const AlbumListHeader = ({
|
||||
{ cacheTime: 1000 * 60 * 1 },
|
||||
);
|
||||
|
||||
params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||
params.successCallback(
|
||||
albumsRes?.items || [],
|
||||
albumsRes?.totalRecordCount || 0,
|
||||
);
|
||||
},
|
||||
rowCount: undefined,
|
||||
};
|
||||
@ -217,9 +220,13 @@ export const AlbumListHeader = ({
|
||||
w="100%"
|
||||
>
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||
<LibraryHeaderBar.PlayButton
|
||||
onClick={() => handlePlay(playButtonBehavior)}
|
||||
/>
|
||||
<LibraryHeaderBar.Title>{title || 'Albums'}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge isLoading={itemCount === null || itemCount === undefined}>
|
||||
<LibraryHeaderBar.Badge
|
||||
isLoading={itemCount === null || itemCount === undefined}
|
||||
>
|
||||
{itemCount}
|
||||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
|
@ -79,7 +79,10 @@ export const AlbumListTableView = ({ tableRef, itemCount }: any) => {
|
||||
{ cacheTime: 1000 * 60 * 1 },
|
||||
);
|
||||
|
||||
return params.successCallback(albumsRes?.items || [], albumsRes?.totalRecordCount || 0);
|
||||
return params.successCallback(
|
||||
albumsRes?.items || [],
|
||||
albumsRes?.totalRecordCount || 0,
|
||||
);
|
||||
},
|
||||
rowCount: undefined,
|
||||
};
|
||||
@ -95,7 +98,8 @@ export const AlbumListTableView = ({ tableRef, itemCount }: any) => {
|
||||
|
||||
try {
|
||||
// Scroll to top of page on pagination change
|
||||
const currentPageStartIndex = table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
const currentPageStartIndex =
|
||||
table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
@ -128,7 +132,9 @@ export const AlbumListTableView = ({ tableRef, itemCount }: any) => {
|
||||
const columnsInSettings = table.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
@ -150,7 +156,10 @@ export const AlbumListTableView = ({ tableRef, itemCount }: any) => {
|
||||
setTable({ data: { scrollOffset }, key: pageKey });
|
||||
};
|
||||
|
||||
const handleContextMenu = useHandleTableContextMenu(LibraryItem.ALBUM, ALBUM_CONTEXT_MENU_ITEMS);
|
||||
const handleContextMenu = useHandleTableContextMenu(
|
||||
LibraryItem.ALBUM,
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
);
|
||||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent) => {
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||
|
@ -45,7 +45,9 @@ const AlbumDetailRoute = () => {
|
||||
children: (
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton onClick={handlePlay} />
|
||||
<LibraryHeaderBar.Title>{detailQuery?.data?.name}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Title>
|
||||
{detailQuery?.data?.name}
|
||||
</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
),
|
||||
target: headerRef,
|
||||
|
@ -59,11 +59,17 @@ export const AlbumArtistDetailContent = () => {
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||
const detailQuery = useAlbumArtistDetail({
|
||||
query: { id: albumArtistId },
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
const artistDiscographyLink = `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, {
|
||||
const artistDiscographyLink = `${generatePath(
|
||||
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY,
|
||||
{
|
||||
albumArtistId,
|
||||
})}?${createSearchParams({
|
||||
},
|
||||
)}?${createSearchParams({
|
||||
artistId: albumArtistId,
|
||||
artistName: detailQuery?.data?.name || '',
|
||||
})}`;
|
||||
@ -79,7 +85,9 @@ export const AlbumArtistDetailContent = () => {
|
||||
query: {
|
||||
_custom: {
|
||||
jellyfin: {
|
||||
...(server?.type === ServerType.JELLYFIN ? { ArtistIds: albumArtistId } : undefined),
|
||||
...(server?.type === ServerType.JELLYFIN
|
||||
? { ArtistIds: albumArtistId }
|
||||
: undefined),
|
||||
},
|
||||
navidrome: {
|
||||
...(server?.type === ServerType.NAVIDROME
|
||||
@ -303,7 +311,8 @@ export const AlbumArtistDetailContent = () => {
|
||||
const showGenres = detailQuery?.data?.genres ? detailQuery?.data?.genres.length !== 0 : false;
|
||||
|
||||
const isLoading =
|
||||
detailQuery?.isLoading || (server?.type === ServerType.NAVIDROME && topSongsQuery?.isLoading);
|
||||
detailQuery?.isLoading ||
|
||||
(server?.type === ServerType.NAVIDROME && topSongsQuery?.isLoading);
|
||||
|
||||
if (isLoading) return <ContentContainer ref={cq.ref} />;
|
||||
|
||||
@ -315,7 +324,9 @@ export const AlbumArtistDetailContent = () => {
|
||||
<Group spacing="xs">
|
||||
<Button
|
||||
compact
|
||||
loading={createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading}
|
||||
loading={
|
||||
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
|
||||
}
|
||||
variant="subtle"
|
||||
onClick={handleFavorite}
|
||||
>
|
||||
@ -369,9 +380,12 @@ export const AlbumArtistDetailContent = () => {
|
||||
component={Link}
|
||||
radius="md"
|
||||
size="md"
|
||||
to={generatePath(`${AppRoute.LIBRARY_ALBUM_ARTISTS}?genre=${genre.id}`, {
|
||||
to={generatePath(
|
||||
`${AppRoute.LIBRARY_ALBUM_ARTISTS}?genre=${genre.id}`,
|
||||
{
|
||||
albumArtistId,
|
||||
})}
|
||||
},
|
||||
)}
|
||||
variant="outline"
|
||||
>
|
||||
{genre.name}
|
||||
|
@ -38,7 +38,8 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||
{
|
||||
id: 'duration',
|
||||
secondary: true,
|
||||
value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
value:
|
||||
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
},
|
||||
];
|
||||
|
||||
@ -96,7 +97,10 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||
<>
|
||||
<Text $noSelect>•</Text>
|
||||
<Rating
|
||||
readOnly={detailQuery?.isFetching || updateRatingMutation.isLoading}
|
||||
readOnly={
|
||||
detailQuery?.isFetching ||
|
||||
updateRatingMutation.isLoading
|
||||
}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
onChange={handleUpdateRating}
|
||||
onClick={handleClearRating}
|
||||
|
@ -117,7 +117,8 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||
|
||||
try {
|
||||
// Scroll to top of page on pagination change
|
||||
const currentPageStartIndex = table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
const currentPageStartIndex =
|
||||
table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
@ -150,7 +151,9 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||
const columnsInSettings = table.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
@ -281,7 +284,9 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
|
||||
minimumBatchSize={40}
|
||||
route={{
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
slugs: [
|
||||
{ idProperty: 'id', slugProperty: 'albumArtistId' },
|
||||
],
|
||||
}}
|
||||
width={width}
|
||||
onScroll={handleGridScroll}
|
||||
|
@ -44,9 +44,17 @@ const FILTERS = {
|
||||
// { defaultOrder: SortOrder.DESC, name: 'Release Date', value: AlbumArtistListSort.RELEASE_DATE },
|
||||
],
|
||||
navidrome: [
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Album Count', value: AlbumArtistListSort.ALBUM_COUNT },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Album Count',
|
||||
value: AlbumArtistListSort.ALBUM_COUNT,
|
||||
},
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Favorited', value: AlbumArtistListSort.FAVORITED },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Most Played', value: AlbumArtistListSort.PLAY_COUNT },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Most Played',
|
||||
value: AlbumArtistListSort.PLAY_COUNT,
|
||||
},
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: AlbumArtistListSort.NAME },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Rating', value: AlbumArtistListSort.RATING },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Song Count', value: AlbumArtistListSort.SONG_COUNT },
|
||||
@ -80,7 +88,8 @@ export const AlbumArtistListHeaderFilters = ({
|
||||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filter.sortBy)?.name) ||
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filter.sortBy)
|
||||
?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const sortOrderLabel = ORDER.find((o) => o.value === filter.sortOrder)?.name || 'Unknown';
|
||||
@ -432,7 +441,8 @@ export const AlbumArtistListHeaderFilters = ({
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Label>Item size</DropdownMenu.Label>
|
||||
<DropdownMenu.Item closeMenuOnClick={false}>
|
||||
{display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? (
|
||||
{display === ListDisplayType.CARD ||
|
||||
display === ListDisplayType.POSTER ? (
|
||||
<Slider
|
||||
defaultValue={grid?.itemsPerRow}
|
||||
label={null}
|
||||
@ -450,7 +460,8 @@ export const AlbumArtistListHeaderFilters = ({
|
||||
/>
|
||||
)}
|
||||
</DropdownMenu.Item>
|
||||
{(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && (
|
||||
{(display === ListDisplayType.TABLE ||
|
||||
display === ListDisplayType.TABLE_PAGINATED) && (
|
||||
<>
|
||||
<DropdownMenu.Label>Table Columns</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
@ -462,7 +473,9 @@ export const AlbumArtistListHeaderFilters = ({
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={ALBUMARTIST_TABLE_COLUMNS}
|
||||
defaultValue={table?.columns.map((column) => column.column)}
|
||||
defaultValue={table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
/>
|
||||
|
@ -155,7 +155,9 @@ export const AlbumArtistListHeader = ({
|
||||
>
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>Album Artists</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge isLoading={itemCount === null || itemCount === undefined}>
|
||||
<LibraryHeaderBar.Badge
|
||||
isLoading={itemCount === null || itemCount === undefined}
|
||||
>
|
||||
{itemCount}
|
||||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
|
@ -13,7 +13,10 @@ export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>
|
||||
enabled: !!server?.id && !!query.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistDetail({ apiClientProps: { server, signal }, query });
|
||||
return api.controller.getAlbumArtistDetail({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
||||
...options,
|
||||
|
@ -13,7 +13,10 @@ export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>)
|
||||
enabled: !!server?.id && !!query.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistDetail({ apiClientProps: { server, signal }, query });
|
||||
return api.controller.getAlbumArtistDetail({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
||||
...options,
|
||||
|
@ -19,7 +19,10 @@ const AlbumArtistDetailRoute = () => {
|
||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||
const detailQuery = useAlbumArtistDetail({
|
||||
query: { id: albumArtistId },
|
||||
serverId: server?.id,
|
||||
});
|
||||
const background = useFastAverageColor(detailQuery.data?.imageUrl, !detailQuery.isLoading);
|
||||
|
||||
const handlePlay = () => {
|
||||
@ -43,7 +46,9 @@ const AlbumArtistDetailRoute = () => {
|
||||
children: (
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton onClick={handlePlay} />
|
||||
<LibraryHeaderBar.Title>{detailQuery?.data?.name}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Title>
|
||||
{detailQuery?.data?.name}
|
||||
</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
),
|
||||
target: headerRef,
|
||||
|
@ -13,7 +13,10 @@ const AlbumArtistDetailTopSongsListRoute = () => {
|
||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||
const server = useCurrentServer();
|
||||
|
||||
const detailQuery = useAlbumArtistDetail({ query: { id: albumArtistId }, serverId: server?.id });
|
||||
const detailQuery = useAlbumArtistDetail({
|
||||
query: { id: albumArtistId },
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
const topSongsQuery = useTopSongsList({
|
||||
options: { enabled: !!detailQuery?.data?.name },
|
||||
|
@ -112,7 +112,9 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
let validMenuItems = menuItems;
|
||||
|
||||
if (serverType === ServerType.JELLYFIN) {
|
||||
validMenuItems = menuItems.filter((item) => !JELLYFIN_IGNORED_MENU_ITEMS.includes(item.id));
|
||||
validMenuItems = menuItems.filter(
|
||||
(item) => !JELLYFIN_IGNORED_MENU_ITEMS.includes(item.id),
|
||||
);
|
||||
}
|
||||
|
||||
// If the context menu dimension can't be automatically calculated, calculate it manually
|
||||
@ -361,7 +363,9 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
}, {} as Record<string, AnyLibraryItems>);
|
||||
|
||||
for (const serverId of Object.keys(itemsByServerId)) {
|
||||
const idsToUnfavorite = itemsByServerId[serverId].map((item: AnyLibraryItem) => item.id);
|
||||
const idsToUnfavorite = itemsByServerId[serverId].map(
|
||||
(item: AnyLibraryItem) => item.id,
|
||||
);
|
||||
deleteFavoriteMutation.mutate({
|
||||
query: {
|
||||
id: idsToUnfavorite,
|
||||
@ -779,16 +783,27 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
<HoverCard.Target>
|
||||
<ContextMenuButton
|
||||
disabled={item.disabled}
|
||||
leftIcon={contextMenuItems[item.id].leftIcon}
|
||||
rightIcon={contextMenuItems[item.id].rightIcon}
|
||||
onClick={contextMenuItems[item.id].onClick}
|
||||
leftIcon={
|
||||
contextMenuItems[item.id]
|
||||
.leftIcon
|
||||
}
|
||||
rightIcon={
|
||||
contextMenuItems[item.id]
|
||||
.rightIcon
|
||||
}
|
||||
onClick={
|
||||
contextMenuItems[item.id]
|
||||
.onClick
|
||||
}
|
||||
>
|
||||
{contextMenuItems[item.id].label}
|
||||
</ContextMenuButton>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Stack spacing={0}>
|
||||
{contextMenuItems[item.id].children?.map((child) => (
|
||||
{contextMenuItems[
|
||||
item.id
|
||||
].children?.map((child) => (
|
||||
<ContextMenuButton
|
||||
key={`sub-${child.id}`}
|
||||
disabled={child.disabled}
|
||||
@ -805,8 +820,12 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
) : (
|
||||
<ContextMenuButton
|
||||
disabled={item.disabled}
|
||||
leftIcon={contextMenuItems[item.id].leftIcon}
|
||||
rightIcon={contextMenuItems[item.id].rightIcon}
|
||||
leftIcon={
|
||||
contextMenuItems[item.id].leftIcon
|
||||
}
|
||||
rightIcon={
|
||||
contextMenuItems[item.id].rightIcon
|
||||
}
|
||||
onClick={contextMenuItems[item.id].onClick}
|
||||
>
|
||||
{contextMenuItems[item.id].label}
|
||||
@ -828,7 +847,9 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||
color="rgb(62, 62, 62)"
|
||||
size="sm"
|
||||
/>
|
||||
<ContextMenuButton disabled>{ctx.data?.length} selected</ContextMenuButton>
|
||||
<ContextMenuButton disabled>
|
||||
{ctx.data?.length} selected
|
||||
</ContextMenuButton>
|
||||
</Stack>
|
||||
</ContextMenu>
|
||||
)}
|
||||
|
@ -20,7 +20,9 @@ export const useHandleTableContextMenu = (
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldReplaceSelected = !selectedNodes.map((node) => node.data.id).includes(e.data.id);
|
||||
const shouldReplaceSelected = !selectedNodes
|
||||
.map((node) => node.data.id)
|
||||
.includes(e.data.id);
|
||||
|
||||
if (shouldReplaceSelected) {
|
||||
e.api.deselectAll();
|
||||
|
@ -151,7 +151,10 @@ const HomeRoute = () => {
|
||||
<FeatureCarousel data={featureItemsWithImage} />
|
||||
{carousels
|
||||
.filter((carousel) => {
|
||||
if (server?.type === ServerType.JELLYFIN && carousel.uniqueId === 'recentlyPlayed') {
|
||||
if (
|
||||
server?.type === ServerType.JELLYFIN &&
|
||||
carousel.uniqueId === 'recentlyPlayed'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -173,7 +176,9 @@ const HomeRoute = () => {
|
||||
property: 'albumArtists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
slugs: [
|
||||
{ idProperty: 'id', slugProperty: 'albumArtistId' },
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
@ -188,7 +188,9 @@ export const Lyrics = () => {
|
||||
{isSynchronizedLyrics ? (
|
||||
<SynchronizedLyrics {...lyricsMetadata} />
|
||||
) : (
|
||||
<UnsynchronizedLyrics {...(lyricsMetadata as UnsynchronizedLyricMetadata)} />
|
||||
<UnsynchronizedLyrics
|
||||
{...(lyricsMetadata as UnsynchronizedLyricMetadata)}
|
||||
/>
|
||||
)}
|
||||
</ScrollContainer>
|
||||
)}
|
||||
|
@ -152,7 +152,9 @@ export const useSongLyricsByRemoteId = (
|
||||
enabled: !!query.remoteSongId && !!query.remoteSource,
|
||||
onError: () => {},
|
||||
queryFn: async () => {
|
||||
const remoteLyricsResult: string | null = await lyricsIpc?.getRemoteLyricsByRemoteId(query);
|
||||
const remoteLyricsResult: string | null = await lyricsIpc?.getRemoteLyricsByRemoteId(
|
||||
query,
|
||||
);
|
||||
|
||||
if (remoteLyricsResult) {
|
||||
return formatLyrics(remoteLyricsResult);
|
||||
|
@ -99,7 +99,9 @@ export const SynchronizedLyrics = ({
|
||||
return 0;
|
||||
}
|
||||
|
||||
const player = (playersRef.current.player1 ?? playersRef.current.player2).getInternalPlayer();
|
||||
const player = (
|
||||
playersRef.current.player1 ?? playersRef.current.player2
|
||||
).getInternalPlayer();
|
||||
|
||||
// If it is null, this probably means we added a new song while the lyrics tab is open
|
||||
// and the queue was previously empty
|
||||
@ -108,7 +110,8 @@ export const SynchronizedLyrics = ({
|
||||
return player.currentTime;
|
||||
}, [playerType, playersRef]);
|
||||
|
||||
const setCurrentLyric = useCallback((timeInMs: number, epoch?: number, targetIndex?: number) => {
|
||||
const setCurrentLyric = useCallback(
|
||||
(timeInMs: number, epoch?: number, targetIndex?: number) => {
|
||||
const start = performance.now();
|
||||
let nextEpoch: number;
|
||||
|
||||
@ -139,7 +142,9 @@ export const SynchronizedLyrics = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = document.getElementById('sychronized-lyrics-scroll-container') as HTMLElement;
|
||||
const doc = document.getElementById(
|
||||
'sychronized-lyrics-scroll-container',
|
||||
) as HTMLElement;
|
||||
const currentLyric = document.querySelector(`#lyric-${index}`) as HTMLElement;
|
||||
const offsetTop = currentLyric?.offsetTop - doc?.clientHeight / 2 ?? 0;
|
||||
|
||||
@ -163,7 +168,9 @@ export const SynchronizedLyrics = ({
|
||||
setCurrentLyric(nextTime, nextEpoch, index + 1);
|
||||
}, nextTime - timeInMs - elapsed);
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Copy the follow settings into a ref that can be accessed in the timeout
|
||||
|
@ -134,7 +134,9 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
@ -181,12 +183,16 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentNode = currentSong?.uniqueId ? api.getRowNode(currentSong.uniqueId) : undefined;
|
||||
const currentNode = currentSong?.uniqueId
|
||||
? api.getRowNode(currentSong.uniqueId)
|
||||
: undefined;
|
||||
const previousNode = previousSong?.uniqueId
|
||||
? api.getRowNode(previousSong?.uniqueId)
|
||||
: undefined;
|
||||
|
||||
const rowNodes = [currentNode, previousNode].filter((e) => e !== undefined) as RowNode<any>[];
|
||||
const rowNodes = [currentNode, previousNode].filter(
|
||||
(e) => e !== undefined,
|
||||
) as RowNode<any>[];
|
||||
|
||||
if (rowNodes) {
|
||||
api.redrawRows({ rowNodes });
|
||||
|
@ -211,7 +211,11 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
||||
)}
|
||||
<PlayerButton
|
||||
icon={
|
||||
status === PlayerStatus.PAUSED ? <RiPlayFill size={20} /> : <IoIosPause size={20} />
|
||||
status === PlayerStatus.PAUSED ? (
|
||||
<RiPlayFill size={20} />
|
||||
) : (
|
||||
<IoIosPause size={20} />
|
||||
)
|
||||
}
|
||||
tooltip={{
|
||||
label: status === PlayerStatus.PAUSED ? 'Play' : 'Pause',
|
||||
|
@ -266,7 +266,9 @@ export const FullScreenPlayerImage = () => {
|
||||
{currentSong?.container} {currentSong?.bitRate}
|
||||
</Badge>
|
||||
)}
|
||||
{currentSong?.releaseYear && <Badge size="lg">{currentSong?.releaseYear}</Badge>}
|
||||
{currentSong?.releaseYear && (
|
||||
<Badge size="lg">{currentSong?.releaseYear}</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</MetadataContainer>
|
||||
</PlayerContainer>
|
||||
|
@ -121,7 +121,9 @@ export const LeftControls = () => {
|
||||
|
||||
useHotkeys([
|
||||
[
|
||||
bindings.toggleFullscreenPlayer.allowGlobal ? '' : bindings.toggleFullscreenPlayer.hotkey,
|
||||
bindings.toggleFullscreenPlayer.allowGlobal
|
||||
? ''
|
||||
: bindings.toggleFullscreenPlayer.hotkey,
|
||||
handleToggleFullScreenPlayer,
|
||||
],
|
||||
]);
|
||||
@ -174,7 +176,12 @@ export const LeftControls = () => {
|
||||
opacity={0.8}
|
||||
radius={50}
|
||||
size="md"
|
||||
sx={{ cursor: 'default', position: 'absolute', right: 2, top: 2 }}
|
||||
sx={{
|
||||
cursor: 'default',
|
||||
position: 'absolute',
|
||||
right: 2,
|
||||
top: 2,
|
||||
}}
|
||||
tooltip={{ label: 'Expand', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={handleToggleSidebarImage}
|
||||
|
@ -97,7 +97,8 @@ const StyledPlayerButton = styled(UnstyledButton)<StyledPlayerButtonProps>`
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
fill: ${({ $isActive }) => ($isActive ? 'var(--primary-color)' : 'var(--playerbar-btn-fg)')};
|
||||
fill: ${({ $isActive }) =>
|
||||
$isActive ? 'var(--primary-color)' : 'var(--playerbar-btn-fg)'};
|
||||
stroke: var(--playerbar-btn-fg);
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,9 @@ export const RightControls = () => {
|
||||
}
|
||||
sx={{
|
||||
svg: {
|
||||
fill: !currentSong?.userFavorite ? undefined : 'var(--primary-color) !important',
|
||||
fill: !currentSong?.userFavorite
|
||||
? undefined
|
||||
: 'var(--primary-color) !important',
|
||||
},
|
||||
}}
|
||||
tooltip={{
|
||||
|
@ -227,7 +227,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
pause();
|
||||
} else {
|
||||
const playerData = autoNext();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
mpvPlayer.autoNext(playerData);
|
||||
play();
|
||||
}
|
||||
@ -239,7 +242,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
pause();
|
||||
} else {
|
||||
const playerData = autoNext();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
}
|
||||
},
|
||||
@ -258,7 +264,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
resetPlayers();
|
||||
} else {
|
||||
const playerData = autoNext();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
}
|
||||
},
|
||||
@ -316,7 +325,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
pause();
|
||||
} else {
|
||||
const playerData = next();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
mpvPlayer.setQueue(playerData);
|
||||
mpvPlayer.next();
|
||||
}
|
||||
@ -324,12 +336,18 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
web: () => {
|
||||
if (isLastTrack) {
|
||||
const playerData = setCurrentIndex(0);
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
pause();
|
||||
} else {
|
||||
const playerData = next();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
}
|
||||
},
|
||||
@ -345,7 +363,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
web: () => {
|
||||
if (!isLastTrack) {
|
||||
const playerData = next();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -399,12 +420,18 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
local: () => {
|
||||
if (!isFirstTrack) {
|
||||
const playerData = previous();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
mpvPlayer.setQueue(playerData);
|
||||
mpvPlayer.previous();
|
||||
} else {
|
||||
const playerData = setCurrentIndex(queue.length - 1);
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
mpvPlayer.setQueue(playerData);
|
||||
mpvPlayer.previous();
|
||||
}
|
||||
@ -412,11 +439,17 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
web: () => {
|
||||
if (isFirstTrack) {
|
||||
const playerData = setCurrentIndex(queue.length - 1);
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
} else {
|
||||
const playerData = previous();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
}
|
||||
},
|
||||
@ -439,7 +472,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
pause();
|
||||
} else {
|
||||
const playerData = previous();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
resetPlayers();
|
||||
}
|
||||
},
|
||||
@ -449,7 +485,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||
local: () => {
|
||||
if (!isFirstTrack) {
|
||||
const playerData = previous();
|
||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
||||
mprisUpdateSong({
|
||||
song: playerData.current.song,
|
||||
status: PlayerStatus.PLAYING,
|
||||
});
|
||||
mpvPlayer.setQueue(playerData);
|
||||
mpvPlayer.previous();
|
||||
} else {
|
||||
|
@ -77,7 +77,8 @@ export const useHandlePlayQueueAdd = () => {
|
||||
toast.info({
|
||||
autoClose: false,
|
||||
id: fetchId,
|
||||
message: 'This is taking a while... close the notification to cancel the request',
|
||||
message:
|
||||
'This is taking a while... close the notification to cancel the request',
|
||||
onClose: () => {
|
||||
queryClient.cancelQueries({
|
||||
exact: false,
|
||||
@ -91,11 +92,21 @@ export const useHandlePlayQueueAdd = () => {
|
||||
|
||||
try {
|
||||
if (itemType === LibraryItem.PLAYLIST) {
|
||||
songList = await getPlaylistSongsById({ id: id?.[0], query, queryClient, server });
|
||||
songList = await getPlaylistSongsById({
|
||||
id: id?.[0],
|
||||
query,
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
} else if (itemType === LibraryItem.ALBUM) {
|
||||
songList = await getAlbumSongsById({ id, query, queryClient, server });
|
||||
} else if (itemType === LibraryItem.ALBUM_ARTIST) {
|
||||
songList = await getAlbumArtistSongsById({ id, query, queryClient, server });
|
||||
songList = await getAlbumArtistSongsById({
|
||||
id,
|
||||
query,
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
} else if (itemType === LibraryItem.SONG) {
|
||||
if (id?.length === 1) {
|
||||
songList = await getSongById({ id: id?.[0], queryClient, server });
|
||||
@ -122,13 +133,17 @@ export const useHandlePlayQueueAdd = () => {
|
||||
});
|
||||
}
|
||||
|
||||
songs = songList?.items?.map((song: Song) => ({ ...song, uniqueId: nanoid() })) || null;
|
||||
songs =
|
||||
songList?.items?.map((song: Song) => ({ ...song, uniqueId: nanoid() })) || null;
|
||||
} else if (byData) {
|
||||
songs = byData.map((song) => ({ ...song, uniqueId: nanoid() })) || null;
|
||||
}
|
||||
|
||||
if (!songs || songs?.length === 0)
|
||||
return toast.warn({ message: 'The query returned no results', title: 'No tracks added' });
|
||||
return toast.warn({
|
||||
message: 'The query returned no results',
|
||||
title: 'No tracks added',
|
||||
});
|
||||
|
||||
if (initialIndex) {
|
||||
initialSongIndex = initialIndex;
|
||||
|
@ -40,10 +40,13 @@ const checkScrobbleConditions = (args: {
|
||||
songDuration: number;
|
||||
}) => {
|
||||
const { scrobbleAtDuration, scrobbleAtPercentage, songCompletedDuration, songDuration } = args;
|
||||
const percentageOfSongCompleted = songDuration ? (songCompletedDuration / songDuration) * 100 : 0;
|
||||
const percentageOfSongCompleted = songDuration
|
||||
? (songCompletedDuration / songDuration) * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
percentageOfSongCompleted >= scrobbleAtPercentage || songCompletedDuration >= scrobbleAtDuration
|
||||
percentageOfSongCompleted >= scrobbleAtPercentage ||
|
||||
songCompletedDuration >= scrobbleAtDuration
|
||||
);
|
||||
};
|
||||
|
||||
@ -82,7 +85,10 @@ export const useScrobble = () => {
|
||||
const progressIntervalId = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const songChangeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const handleScrobbleFromSongChange = useCallback(
|
||||
(current: (QueueSong | number | undefined)[], previous: (QueueSong | number | undefined)[]) => {
|
||||
(
|
||||
current: (QueueSong | number | undefined)[],
|
||||
previous: (QueueSong | number | undefined)[],
|
||||
) => {
|
||||
if (!isScrobbleEnabled) return;
|
||||
|
||||
if (progressIntervalId.current) {
|
||||
@ -107,7 +113,9 @@ export const useScrobble = () => {
|
||||
previousSong?.serverType === ServerType.JELLYFIN
|
||||
) {
|
||||
const position =
|
||||
previousSong?.serverType === ServerType.JELLYFIN ? previousSongTime * 1e7 : undefined;
|
||||
previousSong?.serverType === ServerType.JELLYFIN
|
||||
? previousSongTime * 1e7
|
||||
: undefined;
|
||||
|
||||
sendScrobble.mutate({
|
||||
query: {
|
||||
|
@ -157,7 +157,8 @@ export const AddToPlaylistContextModal = ({
|
||||
onError: (err) => {
|
||||
toast.error({
|
||||
message: `[${
|
||||
playlistSelect.find((playlist) => playlist.value === playlistId)?.label
|
||||
playlistSelect.find((playlist) => playlist.value === playlistId)
|
||||
?.label
|
||||
}] ${err.message}`,
|
||||
title: 'Failed to add songs to playlist',
|
||||
});
|
||||
|
@ -97,7 +97,9 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
||||
{isPublicDisplayed && (
|
||||
<Switch
|
||||
label="Is public?"
|
||||
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
|
||||
{...form.getInputProps('_custom.navidrome.public', {
|
||||
type: 'checkbox',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{server?.type === ServerType.NAVIDROME && (
|
||||
|
@ -78,7 +78,9 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() =>
|
||||
getColumnDefs(page.table.columns).filter((c) => c.colId !== 'album' && c.colId !== 'artist'),
|
||||
getColumnDefs(page.table.columns).filter(
|
||||
(c) => c.colId !== 'album' && c.colId !== 'artist',
|
||||
),
|
||||
[page.table.columns],
|
||||
);
|
||||
|
||||
@ -182,14 +184,16 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||
</Button>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
{PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
|
||||
{PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map(
|
||||
(type) => (
|
||||
<DropdownMenu.Item
|
||||
key={`playtype-${type.play}`}
|
||||
onClick={() => handlePlay(type.play)}
|
||||
>
|
||||
{type.label}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
onClick={() => {
|
||||
@ -199,7 +203,9 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
||||
>
|
||||
Edit playlist
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onClick={openDeletePlaylist}>Delete playlist</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onClick={openDeletePlaylist}>
|
||||
Delete playlist
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
|
@ -33,7 +33,8 @@ export const PlaylistDetailHeader = forwardRef(
|
||||
{
|
||||
id: 'duration',
|
||||
secondary: true,
|
||||
value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
value:
|
||||
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -175,7 +175,9 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||
const columnsInSettings = page.table.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
|
@ -57,7 +57,11 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: SongListSort.NAME },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Random', value: SongListSort.RANDOM },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Recently Added', value: SongListSort.RECENTLY_ADDED },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Recently Played', value: SongListSort.RECENTLY_PLAYED },
|
||||
{
|
||||
defaultOrder: SortOrder.ASC,
|
||||
name: 'Recently Played',
|
||||
value: SongListSort.RECENTLY_PLAYED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Release Date', value: SongListSort.RELEASE_DATE },
|
||||
],
|
||||
navidrome: [
|
||||
@ -74,8 +78,16 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: SongListSort.NAME },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Play Count', value: SongListSort.PLAY_COUNT },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Rating', value: SongListSort.RATING },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: SongListSort.RECENTLY_ADDED },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Played', value: SongListSort.RECENTLY_PLAYED },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Added',
|
||||
value: SongListSort.RECENTLY_ADDED,
|
||||
},
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Played',
|
||||
value: SongListSort.RECENTLY_PLAYED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Year', value: SongListSort.YEAR },
|
||||
],
|
||||
};
|
||||
@ -117,7 +129,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)?.name) ||
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)
|
||||
?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const sortOrderLabel = ORDER.find((o) => o.value === filters.sortOrder)?.name || 'Unknown';
|
||||
@ -455,7 +468,9 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={SONG_TABLE_COLUMNS}
|
||||
defaultValue={page.table?.columns.map((column) => column.column)}
|
||||
defaultValue={page.table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
/>
|
||||
|
@ -52,7 +52,11 @@ export const PlaylistDetailSongListHeader = ({
|
||||
py="0.3rem"
|
||||
radius="sm"
|
||||
>
|
||||
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
|
||||
{itemCount === null || itemCount === undefined ? (
|
||||
<SpinnerIcon />
|
||||
) : (
|
||||
itemCount
|
||||
)}
|
||||
</Paper>
|
||||
{isSmartPlaylist && <Badge size="lg">Smart playlist</Badge>}
|
||||
</LibraryHeaderBar>
|
||||
|
@ -90,7 +90,10 @@ export const PlaylistListContent = ({ tableRef, itemCount }: PlaylistListContent
|
||||
{ cacheTime: 1000 * 60 * 1 },
|
||||
);
|
||||
|
||||
params.successCallback(playlistsRes?.items || [], playlistsRes?.totalRecordCount || 0);
|
||||
params.successCallback(
|
||||
playlistsRes?.items || [],
|
||||
playlistsRes?.totalRecordCount || 0,
|
||||
);
|
||||
},
|
||||
rowCount: undefined,
|
||||
};
|
||||
@ -138,7 +141,9 @@ export const PlaylistListContent = ({ tableRef, itemCount }: PlaylistListContent
|
||||
const columnsInSettings = page.table.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
|
@ -58,9 +58,9 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
(FILTERS[server.type as keyof typeof FILTERS] as { name: string; value: string }[]).find(
|
||||
(f) => f.value === page.filter.sortBy,
|
||||
)?.name) ||
|
||||
(
|
||||
FILTERS[server.type as keyof typeof FILTERS] as { name: string; value: string }[]
|
||||
).find((f) => f.value === page.filter.sortBy)?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const sortOrderLabel = ORDER.find((s) => s.value === page.filter.sortOrder)?.name;
|
||||
@ -97,7 +97,10 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||
{ cacheTime: 1000 * 60 * 1 },
|
||||
);
|
||||
|
||||
params.successCallback(playlistsRes?.items || [], playlistsRes?.totalRecordCount || 0);
|
||||
params.successCallback(
|
||||
playlistsRes?.items || [],
|
||||
playlistsRes?.totalRecordCount || 0,
|
||||
);
|
||||
},
|
||||
rowCount: undefined,
|
||||
};
|
||||
@ -128,7 +131,8 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||
);
|
||||
|
||||
const handleToggleSortOrder = useCallback(() => {
|
||||
const newSortOrder = page.filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||
const newSortOrder =
|
||||
page.filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||
const updatedFilters = setFilter({ sortOrder: newSortOrder });
|
||||
handleFilterChange(updatedFilters);
|
||||
}, [page.filter.sortOrder, handleFilterChange, setFilter]);
|
||||
@ -140,7 +144,9 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||
setPage({ list: { ...page, display: e.currentTarget.value as ListDisplayType } });
|
||||
|
||||
if (display === ListDisplayType.TABLE) {
|
||||
tableRef.current?.api.paginationSetPageSize(tableRef.current.props.infiniteInitialRowCount);
|
||||
tableRef.current?.api.paginationSetPageSize(
|
||||
tableRef.current.props.infiniteInitialRowCount,
|
||||
);
|
||||
setPagination({ data: { currentPage: 0 } });
|
||||
} else if (display === ListDisplayType.TABLE_PAGINATED) {
|
||||
setPagination({ data: { currentPage: 0 } });
|
||||
@ -307,7 +313,9 @@ export const PlaylistListHeaderFilters = ({ tableRef }: PlaylistListHeaderFilter
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={PLAYLIST_TABLE_COLUMNS}
|
||||
defaultValue={page.table?.columns.map((column) => column.column)}
|
||||
defaultValue={page.table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
/>
|
||||
|
@ -49,7 +49,11 @@ export const PlaylistListHeader = ({ itemCount, tableRef }: PlaylistListHeaderPr
|
||||
py="0.3rem"
|
||||
radius="sm"
|
||||
>
|
||||
{itemCount === null || itemCount === undefined ? <SpinnerIcon /> : itemCount}
|
||||
{itemCount === null || itemCount === undefined ? (
|
||||
<SpinnerIcon />
|
||||
) : (
|
||||
itemCount
|
||||
)}
|
||||
</Paper>
|
||||
</LibraryHeaderBar>
|
||||
<Button onClick={handleCreatePlaylistModal}>Create playlist</Button>
|
||||
|
@ -247,7 +247,9 @@ export const PlaylistQueryBuilder = forwardRef(
|
||||
const updatedFilters = setWith(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).filter((rule: QueryBuilderRule) => rule.uniqueId !== uniqueId),
|
||||
get(filtersCopy, path).filter(
|
||||
(rule: QueryBuilderRule) => rule.uniqueId !== uniqueId,
|
||||
),
|
||||
clone,
|
||||
);
|
||||
|
||||
|
@ -13,7 +13,10 @@ export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>)
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistSongList({ apiClientProps: { server, signal }, query });
|
||||
return api.controller.getPlaylistSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.songList(server?.id || '', query.id, query),
|
||||
...options,
|
||||
@ -37,7 +40,11 @@ export const usePlaylistSongListInfinite = (args: QueryHookArgs<PlaylistSongList
|
||||
queryFn: ({ pageParam = 0, signal }) => {
|
||||
return api.controller.getPlaylistSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query: { ...query, limit: query.limit || 50, startIndex: pageParam * (query.limit || 50) },
|
||||
query: {
|
||||
...query,
|
||||
limit: query.limit || 50,
|
||||
startIndex: pageParam * (query.limit || 50),
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.detailSongList(server?.id || '', query.id, query),
|
||||
|
@ -50,7 +50,9 @@ const PlaylistDetailRoute = () => {
|
||||
children: (
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton onClick={handlePlay} />
|
||||
<LibraryHeaderBar.Title>{detailQuery?.data?.name}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Title>
|
||||
{detailQuery?.data?.name}
|
||||
</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
),
|
||||
target: headerRef,
|
||||
|
@ -62,9 +62,14 @@ const PlaylistDetailSongListRoute = () => {
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
toast.success({ message: 'Playlist has been saved' });
|
||||
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }), {
|
||||
navigate(
|
||||
generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, {
|
||||
playlistId: data?.id || '',
|
||||
}),
|
||||
{
|
||||
replace: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
deletePlaylistMutation.mutate({
|
||||
query: { id: playlistId },
|
||||
serverId: detailQuery?.data?.serverId,
|
||||
@ -102,7 +107,11 @@ const PlaylistDetailSongListRoute = () => {
|
||||
serverId={detailQuery?.data?.serverId}
|
||||
onCancel={closeAllModals}
|
||||
onSuccess={(data) =>
|
||||
navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: data?.id || '' }))
|
||||
navigate(
|
||||
generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, {
|
||||
playlistId: data?.id || '',
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
@ -133,7 +142,9 @@ const PlaylistDetailSongListRoute = () => {
|
||||
};
|
||||
|
||||
const isSmartPlaylist =
|
||||
!detailQuery?.isLoading && detailQuery?.data?.rules && server?.type === ServerType.NAVIDROME;
|
||||
!detailQuery?.isLoading &&
|
||||
detailQuery?.data?.rules &&
|
||||
server?.type === ServerType.NAVIDROME;
|
||||
|
||||
const [showQueryBuilder, setShowQueryBuilder] = useState(false);
|
||||
const [isQueryBuilderExpanded, setIsQueryBuilderExpanded] = useState(false);
|
||||
|
@ -147,7 +147,11 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
key={`search-album-${album.id}`}
|
||||
value={`search-${album.id}`}
|
||||
onSelect={() => {
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: album.id }));
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||
albumId: album.id,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
@ -157,7 +161,9 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
id={album.id}
|
||||
imageUrl={album.imageUrl}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
subtitle={album.albumArtists.map((artist) => artist.name).join(', ')}
|
||||
subtitle={album.albumArtists
|
||||
.map((artist) => artist.name)
|
||||
.join(', ')}
|
||||
title={album.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
@ -186,7 +192,9 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
imageUrl={artist.imageUrl}
|
||||
itemType={LibraryItem.ALBUM_ARTIST}
|
||||
subtitle={
|
||||
(artist?.albumCount || 0) > 0 ? `${artist.albumCount} albums` : undefined
|
||||
(artist?.albumCount || 0) > 0
|
||||
? `${artist.albumCount} albums`
|
||||
: undefined
|
||||
}
|
||||
title={artist.name}
|
||||
/>
|
||||
@ -215,7 +223,9 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
id={song.id}
|
||||
imageUrl={song.imageUrl}
|
||||
itemType={LibraryItem.SONG}
|
||||
subtitle={song.artists.map((artist) => artist.name).join(', ')}
|
||||
subtitle={song.artists
|
||||
.map((artist) => artist.name)
|
||||
.join(', ')}
|
||||
title={song.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
@ -252,7 +262,9 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
p="0.5rem"
|
||||
>
|
||||
<Group position="apart">
|
||||
<Command.Loading>{isHome && isLoading && query !== '' && <Spinner />}</Command.Loading>
|
||||
<Command.Loading>
|
||||
{isHome && isLoading && query !== '' && <Spinner />}
|
||||
</Command.Loading>
|
||||
<Group spacing="sm">
|
||||
<Kbd size="md">ESC</Kbd>
|
||||
<Kbd size="md">↑</Kbd>
|
||||
|
@ -69,7 +69,9 @@ export const HomeCommands = ({
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.GO_TO])}>
|
||||
Go to page...
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.MANAGE_SERVERS])}>
|
||||
<Command.Item
|
||||
onSelect={() => setPages([...pages, CommandPalettePages.MANAGE_SERVERS])}
|
||||
>
|
||||
Server commands...
|
||||
</Command.Item>
|
||||
</Command.Group>
|
||||
|
@ -103,7 +103,11 @@ export const SearchContent = ({ tableRef, getDatasource }: SearchContentProps) =
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId: e.data.id }));
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: e.data.id,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
handlePlayQueueAdd?.({
|
||||
|
@ -77,7 +77,9 @@ export const SearchHeader = ({ tableRef, getDatasource, navigationId }: SearchHe
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.ALBUM }),
|
||||
pathname: generatePath(AppRoute.SEARCH, {
|
||||
itemType: LibraryItem.ALBUM,
|
||||
}),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'subtle'}
|
||||
@ -92,7 +94,9 @@ export const SearchHeader = ({ tableRef, getDatasource, navigationId }: SearchHe
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.ALBUM_ARTIST }),
|
||||
pathname: generatePath(AppRoute.SEARCH, {
|
||||
itemType: LibraryItem.ALBUM_ARTIST,
|
||||
}),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'subtle'}
|
||||
|
@ -77,7 +77,9 @@ export const ControlSettings = () => {
|
||||
...settings,
|
||||
skipButtons: {
|
||||
...settings.skipButtons,
|
||||
skipForwardSeconds: e.currentTarget.value ? Number(e.currentTarget.value) : 0,
|
||||
skipForwardSeconds: e.currentTarget.value
|
||||
? Number(e.currentTarget.value)
|
||||
: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -147,7 +149,8 @@ export const ControlSettings = () => {
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description: 'Display a hover icon on the right side of the application view the play queue',
|
||||
description:
|
||||
'Display a hover icon on the right side of the application view the play queue',
|
||||
isHidden: false,
|
||||
title: 'Show floating queue hover area',
|
||||
},
|
||||
|
@ -199,13 +199,22 @@ export const HotkeyManagerSettings = () => {
|
||||
/>
|
||||
{isElectron() && (
|
||||
<Checkbox
|
||||
checked={bindings[binding as keyof typeof BINDINGS_MAP].isGlobal}
|
||||
disabled={bindings[binding as keyof typeof BINDINGS_MAP].hotkey === ''}
|
||||
checked={
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].isGlobal
|
||||
}
|
||||
disabled={
|
||||
bindings[binding as keyof typeof BINDINGS_MAP].hotkey === ''
|
||||
}
|
||||
size="xl"
|
||||
style={{
|
||||
opacity: bindings[binding as keyof typeof BINDINGS_MAP].allowGlobal ? 1 : 0,
|
||||
opacity: bindings[binding as keyof typeof BINDINGS_MAP]
|
||||
.allowGlobal
|
||||
? 1
|
||||
: 0,
|
||||
}}
|
||||
onChange={(e) => handleSetGlobalHotkey(binding as BindingActions, e)}
|
||||
onChange={(e) =>
|
||||
handleSetGlobalHotkey(binding as BindingActions, e)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
|
@ -27,7 +27,9 @@ export const AudioSettings = () => {
|
||||
useEffect(() => {
|
||||
const getAudioDevices = () => {
|
||||
getAudioDevice()
|
||||
.then((dev) => setAudioDevices(dev.map((d) => ({ label: d.label, value: d.deviceId }))))
|
||||
.then((dev) =>
|
||||
setAudioDevices(dev.map((d) => ({ label: d.label, value: d.deviceId }))),
|
||||
)
|
||||
.catch(() => toast.error({ message: 'Error fetching audio devices' }));
|
||||
};
|
||||
|
||||
@ -87,7 +89,9 @@ export const AudioSettings = () => {
|
||||
]}
|
||||
defaultValue={settings.style}
|
||||
disabled={settings.type !== PlaybackType.WEB || status === PlayerStatus.PLAYING}
|
||||
onChange={(e) => setSettings({ playback: { ...settings, style: e as PlaybackStyle } })}
|
||||
onChange={(e) =>
|
||||
setSettings({ playback: { ...settings, style: e as PlaybackStyle } })
|
||||
}
|
||||
/>
|
||||
),
|
||||
description: 'Adjust the playback style (web player only)',
|
||||
@ -107,7 +111,9 @@ export const AudioSettings = () => {
|
||||
max={15}
|
||||
min={0}
|
||||
w={100}
|
||||
onChangeEnd={(e) => setSettings({ playback: { ...settings, crossfadeDuration: e } })}
|
||||
onChangeEnd={(e) =>
|
||||
setSettings({ playback: { ...settings, crossfadeDuration: e } })
|
||||
}
|
||||
/>
|
||||
),
|
||||
description: 'Adjust the crossfade duration (web player only)',
|
||||
|
@ -43,7 +43,8 @@ export const getMpvSetting = (
|
||||
export const getMpvProperties = (settings: SettingsState['playback']['mpvProperties']) => {
|
||||
const properties: Record<string, any> = {
|
||||
'audio-exclusive': settings.audioExclusiveMode || 'no',
|
||||
'audio-samplerate': settings.audioSampleRateHz === 0 ? undefined : settings.audioSampleRateHz,
|
||||
'audio-samplerate':
|
||||
settings.audioSampleRateHz === 0 ? undefined : settings.audioSampleRateHz,
|
||||
'gapless-audio': settings.gaplessAudio || 'weak',
|
||||
replaygain: settings.replayGainMode || 'no',
|
||||
'replaygain-clip': settings.replayGainClip || 'no',
|
||||
@ -127,7 +128,9 @@ export const MpvSettings = () => {
|
||||
autosize
|
||||
defaultValue={settings.mpvExtraParameters.join('\n')}
|
||||
minRows={4}
|
||||
placeholder={'(Add one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'}
|
||||
placeholder={
|
||||
'(Add one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'
|
||||
}
|
||||
width={225}
|
||||
onBlur={(e) => {
|
||||
handleSetExtraParameters(e.currentTarget.value.split('\n'));
|
||||
@ -200,7 +203,10 @@ export const MpvSettings = () => {
|
||||
<Switch
|
||||
defaultChecked={settings.mpvProperties.audioExclusiveMode === 'yes'}
|
||||
onChange={(e) =>
|
||||
handleSetMpvProperty('audioExclusiveMode', e.currentTarget.checked ? 'yes' : 'no')
|
||||
handleSetMpvProperty(
|
||||
'audioExclusiveMode',
|
||||
e.currentTarget.checked ? 'yes' : 'no',
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
@ -248,7 +254,9 @@ export const MpvSettings = () => {
|
||||
control: (
|
||||
<Switch
|
||||
defaultChecked={settings.mpvProperties.replayGainClip}
|
||||
onChange={(e) => handleSetMpvProperty('replayGainClip', e.currentTarget.checked)}
|
||||
onChange={(e) =>
|
||||
handleSetMpvProperty('replayGainClip', e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
),
|
||||
description:
|
||||
|
@ -52,7 +52,8 @@ export const ScrobbleSettings = () => {
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description: 'The percentage of the song that must be played before submitting a scrobble',
|
||||
description:
|
||||
'The percentage of the song that must be played before submitting a scrobble',
|
||||
isHidden: !isElectron(),
|
||||
title: 'Minimum scrobble percentage*',
|
||||
},
|
||||
|
@ -31,7 +31,9 @@ export const WindowSettings = () => {
|
||||
if (!e) return;
|
||||
|
||||
// Platform.LINUX is used as the native frame option regardless of the actual platform
|
||||
const hasFrame = localSettings?.get('window_has_frame') as boolean | undefined;
|
||||
const hasFrame = localSettings?.get('window_has_frame') as
|
||||
| boolean
|
||||
| undefined;
|
||||
const isSwitchingToFrame = !hasFrame && e === Platform.LINUX;
|
||||
const isSwitchingToNoFrame = hasFrame && e !== Platform.LINUX;
|
||||
|
||||
@ -41,14 +43,20 @@ export const WindowSettings = () => {
|
||||
toast.info({
|
||||
autoClose: false,
|
||||
id: 'restart-toast',
|
||||
message: 'Restart to apply changes... close the notification to restart Feishin',
|
||||
message:
|
||||
'Restart to apply changes... close the notification to restart Feishin',
|
||||
onClose: () => {
|
||||
window.electron.ipc.send('app-restart');
|
||||
},
|
||||
title: 'Restart required',
|
||||
});
|
||||
} else {
|
||||
toast.update({ autoClose: 0, id: 'restart-toast', message: '', onClose: () => {} }); // clean old toasts
|
||||
toast.update({
|
||||
autoClose: 0,
|
||||
id: 'restart-toast',
|
||||
message: '',
|
||||
onClose: () => {},
|
||||
}); // clean old toasts
|
||||
}
|
||||
|
||||
localSettings?.set('window_window_bar_style', e as Platform);
|
||||
|
@ -66,7 +66,8 @@ const BackgroundImageOverlay = styled.div`
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)), var(--background-noise);
|
||||
background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)),
|
||||
var(--background-noise);
|
||||
`;
|
||||
|
||||
interface LibraryHeaderProps {
|
||||
|
@ -59,7 +59,10 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
|
||||
}
|
||||
|
||||
// We only need to set if we're already on the album detail page
|
||||
if (variables.query.type === LibraryItem.ALBUM_ARTIST && variables.query.id.length === 1) {
|
||||
if (
|
||||
variables.query.type === LibraryItem.ALBUM_ARTIST &&
|
||||
variables.query.id.length === 1
|
||||
) {
|
||||
const queryKey = queryKeys.albumArtists.detail(serverId, {
|
||||
id: variables.query.id[0],
|
||||
});
|
||||
|
@ -59,7 +59,10 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
|
||||
}
|
||||
|
||||
// We only need to set if we're already on the album artist detail page
|
||||
if (variables.query.type === LibraryItem.ALBUM_ARTIST && variables.query.id.length === 1) {
|
||||
if (
|
||||
variables.query.type === LibraryItem.ALBUM_ARTIST &&
|
||||
variables.query.id.length === 1
|
||||
) {
|
||||
const queryKey = queryKeys.albumArtists.detail(serverId, {
|
||||
id: variables.query.id[0],
|
||||
});
|
||||
|
@ -61,7 +61,8 @@ export const useSetRating = (args: MutationHookArgs) => {
|
||||
onSuccess: (_data, variables) => {
|
||||
// We only need to set if we're already on the album detail page
|
||||
const isAlbumDetailPage =
|
||||
variables.query.item.length === 1 && variables.query.item[0].itemType === LibraryItem.ALBUM;
|
||||
variables.query.item.length === 1 &&
|
||||
variables.query.item[0].itemType === LibraryItem.ALBUM;
|
||||
|
||||
if (isAlbumDetailPage) {
|
||||
const { id: albumId, serverId } = variables.query.item[0] as Album;
|
||||
|
@ -146,7 +146,11 @@ export const SidebarPlaylistList = ({ data }: SidebarPlaylistListProps) => {
|
||||
<AutoSizer onResize={(e) => setRect(e as { height: number; width: number })}>
|
||||
{() => (
|
||||
<FixedSizeList
|
||||
className={isScrollbarHidden ? 'hide-scrollbar overlay-scrollbar' : 'overlay-scrollbar'}
|
||||
className={
|
||||
isScrollbarHidden
|
||||
? 'hide-scrollbar overlay-scrollbar'
|
||||
: 'overlay-scrollbar'
|
||||
}
|
||||
height={debounced.height}
|
||||
itemCount={data?.items?.length || 0}
|
||||
itemData={memoizedItemData}
|
||||
|
@ -298,7 +298,9 @@ export const Sidebar = () => {
|
||||
src={upsizedImageUrl}
|
||||
/>
|
||||
) : (
|
||||
<Center sx={{ background: 'var(--placeholder-bg)', height: '100%' }}>
|
||||
<Center
|
||||
sx={{ background: 'var(--placeholder-bg)', height: '100%' }}
|
||||
>
|
||||
<RiDiscLine
|
||||
color="var(--placeholder-fg)"
|
||||
size={50}
|
||||
|
@ -94,7 +94,8 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) =
|
||||
|
||||
try {
|
||||
// Scroll to top of page on pagination change
|
||||
const currentPageStartIndex = table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
const currentPageStartIndex =
|
||||
table.pagination.currentPage * table.pagination.itemsPerPage;
|
||||
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
@ -132,7 +133,9 @@ export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) =
|
||||
const columnsInSettings = table.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
|
@ -45,7 +45,11 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: SongListSort.NAME },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Random', value: SongListSort.RANDOM },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Recently Added', value: SongListSort.RECENTLY_ADDED },
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Recently Played', value: SongListSort.RECENTLY_PLAYED },
|
||||
{
|
||||
defaultOrder: SortOrder.ASC,
|
||||
name: 'Recently Played',
|
||||
value: SongListSort.RECENTLY_PLAYED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Release Date', value: SongListSort.RELEASE_DATE },
|
||||
],
|
||||
navidrome: [
|
||||
@ -61,8 +65,16 @@ const FILTERS = {
|
||||
{ defaultOrder: SortOrder.ASC, name: 'Name', value: SongListSort.NAME },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Play Count', value: SongListSort.PLAY_COUNT },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Rating', value: SongListSort.RATING },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Added', value: SongListSort.RECENTLY_ADDED },
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Recently Played', value: SongListSort.RECENTLY_PLAYED },
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Added',
|
||||
value: SongListSort.RECENTLY_ADDED,
|
||||
},
|
||||
{
|
||||
defaultOrder: SortOrder.DESC,
|
||||
name: 'Recently Played',
|
||||
value: SongListSort.RECENTLY_PLAYED,
|
||||
},
|
||||
{ defaultOrder: SortOrder.DESC, name: 'Year', value: SongListSort.YEAR },
|
||||
],
|
||||
};
|
||||
@ -89,9 +101,9 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
|
||||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
(FILTERS[server.type as keyof typeof FILTERS] as { name: string; value: string }[]).find(
|
||||
(f) => f.value === filter.sortBy,
|
||||
)?.name) ||
|
||||
(
|
||||
FILTERS[server.type as keyof typeof FILTERS] as { name: string; value: string }[]
|
||||
).find((f) => f.value === filter.sortBy)?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const sortOrderLabel = ORDER.find((s) => s.value === filter.sortOrder)?.name;
|
||||
@ -204,7 +216,9 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
|
||||
});
|
||||
|
||||
if (display === ListDisplayType.TABLE) {
|
||||
tableRef.current?.api.paginationSetPageSize(tableRef.current.props.infiniteInitialRowCount);
|
||||
tableRef.current?.api.paginationSetPageSize(
|
||||
tableRef.current.props.infiniteInitialRowCount,
|
||||
);
|
||||
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
|
||||
} else if (display === ListDisplayType.TABLE_PAGINATED) {
|
||||
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
|
||||
@ -418,7 +432,11 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
sx={{ svg: { fill: isFilterApplied ? 'var(--primary-color) !important' : undefined } }}
|
||||
sx={{
|
||||
svg: {
|
||||
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
|
||||
},
|
||||
}}
|
||||
tooltip={{ label: 'Filters' }}
|
||||
variant="subtle"
|
||||
onClick={handleOpenFiltersModal}
|
||||
|
@ -103,7 +103,9 @@ export const SongListHeader = ({ title, itemCount, tableRef }: SongListHeaderPro
|
||||
onClick={() => handlePlay?.({ playType: playButtonBehavior })}
|
||||
/>
|
||||
<LibraryHeaderBar.Title>{title || 'Tracks'}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge isLoading={itemCount === null || itemCount === undefined}>
|
||||
<LibraryHeaderBar.Badge
|
||||
isLoading={itemCount === null || itemCount === undefined}
|
||||
>
|
||||
{itemCount}
|
||||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
|
@ -139,7 +139,8 @@ export const AppMenu = () => {
|
||||
<DropdownMenu.Label>Select a server</DropdownMenu.Label>
|
||||
{Object.keys(serverList).map((serverId) => {
|
||||
const server = serverList[serverId];
|
||||
const isNavidromeExpired = server.type === ServerType.NAVIDROME && !server.ndCredential;
|
||||
const isNavidromeExpired =
|
||||
server.type === ServerType.NAVIDROME && !server.ndCredential;
|
||||
const isJellyfinExpired = false;
|
||||
const isSessionExpired = isNavidromeExpired || isJellyfinExpired;
|
||||
|
||||
@ -147,7 +148,13 @@ export const AppMenu = () => {
|
||||
<DropdownMenu.Item
|
||||
key={`server-${server.id}`}
|
||||
$isActive={server.id === currentServer?.id}
|
||||
icon={isSessionExpired ? <RiLockLine color="var(--danger-color)" /> : <RiServerLine />}
|
||||
icon={
|
||||
isSessionExpired ? (
|
||||
<RiLockLine color="var(--danger-color)" />
|
||||
) : (
|
||||
<RiServerLine />
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
if (!isSessionExpired) return handleSetCurrentServer(server);
|
||||
return handleCredentialsModal(server);
|
||||
|
@ -12,8 +12,7 @@ export const useFastAverageColor = (
|
||||
const fac = new FastAverageColor();
|
||||
|
||||
if (src && srcLoaded) {
|
||||
fac
|
||||
.getColorAsync(src, {
|
||||
fac.getColorAsync(src, {
|
||||
algorithm: aglorithm || 'dominant',
|
||||
ignoredColor: [
|
||||
[255, 255, 255, 255, 55], // White
|
||||
|
@ -74,7 +74,9 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
|
||||
} else if (isResizingRight) {
|
||||
const start = Number(rightWidth.split('px')[0]);
|
||||
const { left } = rightSidebarRef!.current!.getBoundingClientRect();
|
||||
const width = `${constrainRightSidebarWidth(start + left - mouseMoveEvent.clientX)}px`;
|
||||
const width = `${constrainRightSidebarWidth(
|
||||
start + left - mouseMoveEvent.clientX,
|
||||
)}px`;
|
||||
setSideBar({ rightWidth: width });
|
||||
}
|
||||
},
|
||||
@ -103,7 +105,9 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
|
||||
>
|
||||
{!shell && (
|
||||
<>
|
||||
<Suspense fallback={<></>}>{showQueueDrawerButton && <SideDrawerQueue />}</Suspense>
|
||||
<Suspense fallback={<></>}>
|
||||
{showQueueDrawerButton && <SideDrawerQueue />}
|
||||
</Suspense>
|
||||
<FullScreenOverlay />
|
||||
<LeftSidebar
|
||||
isResizing={isResizing}
|
||||
|
@ -84,7 +84,10 @@ interface RightSidebarProps {
|
||||
}
|
||||
|
||||
export const RightSidebar = forwardRef(
|
||||
({ isResizing: isResizingRight, startResizing }: RightSidebarProps, ref: Ref<HTMLDivElement>) => {
|
||||
(
|
||||
{ isResizing: isResizingRight, startResizing }: RightSidebarProps,
|
||||
ref: Ref<HTMLDivElement>,
|
||||
) => {
|
||||
const { windowBarStyle } = useWindowSettings();
|
||||
const { rightWidth, rightExpanded } = useSidebarStore();
|
||||
const { sideQueueType } = useGeneralSettings();
|
||||
|
@ -140,7 +140,8 @@ export const MacOsButton = styled.div<{
|
||||
restoreButton?: boolean;
|
||||
}>`
|
||||
grid-row: 1 / span 1;
|
||||
grid-column: ${(props) => (props.minButton ? 2 : props.maxButton || props.restoreButton ? 3 : 1)};
|
||||
grid-column: ${(props) =>
|
||||
props.minButton ? 2 : props.maxButton || props.restoreButton ? 3 : 1};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
8
src/renderer/preload.d.ts
vendored
8
src/renderer/preload.d.ts
vendored
@ -38,13 +38,17 @@ declare global {
|
||||
PLAYER_STOP(): void;
|
||||
PLAYER_VOLUME(value: number): void;
|
||||
RENDERER_PLAYER_AUTO_NEXT(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_CURRENT_TIME(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_CURRENT_TIME(
|
||||
cb: (event: IpcRendererEvent, data: any) => void,
|
||||
): void;
|
||||
RENDERER_PLAYER_NEXT(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_PAUSE(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_PLAY(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_PLAY_PAUSE(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_PREVIOUS(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_RESTORE_QUEUE(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_RESTORE_QUEUE(
|
||||
cb: (event: IpcRendererEvent, data: any) => void,
|
||||
): void;
|
||||
RENDERER_PLAYER_SAVE_QUEUE(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
RENDERER_PLAYER_STOP(cb: (event: IpcRendererEvent, data: any) => void): void;
|
||||
SETTINGS_GET(data: { property: string }): any;
|
||||
|
@ -58,7 +58,10 @@ export const useAlbumArtistStore = create<AlbumArtistSlice>()(
|
||||
},
|
||||
setTablePagination: (data) => {
|
||||
set((state) => {
|
||||
state.list.table.pagination = { ...state.list.table.pagination, ...data };
|
||||
state.list.table.pagination = {
|
||||
...state.list.table.pagination,
|
||||
...data,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -93,7 +93,10 @@ export const useListStore = create<ListSlice>()(
|
||||
...state.detail[args.key]?.filter?._custom,
|
||||
jellyfin: {
|
||||
...state.detail[args.key]?.filter?._custom?.jellyfin,
|
||||
includeItemTypes: args?.itemType === LibraryItem.ALBUM ? 'MusicAlbum' : 'Audio',
|
||||
includeItemTypes:
|
||||
args?.itemType === LibraryItem.ALBUM
|
||||
? 'MusicAlbum'
|
||||
: 'Audio',
|
||||
},
|
||||
navidrome: {
|
||||
...state.detail[args.key]?.filter?._custom?.navidrome,
|
||||
@ -160,7 +163,11 @@ export const useListStore = create<ListSlice>()(
|
||||
}
|
||||
});
|
||||
|
||||
return get()._actions.getFilter({ id, itemType: args.itemType, key: args.key });
|
||||
return get()._actions.getFilter({
|
||||
id,
|
||||
itemType: args.itemType,
|
||||
key: args.key,
|
||||
});
|
||||
},
|
||||
setGrid: (args) => {
|
||||
const [page, id] = args.key.split('_');
|
||||
@ -172,7 +179,8 @@ export const useListStore = create<ListSlice>()(
|
||||
filter: {} as FilterType,
|
||||
grid: {
|
||||
itemsPerRow:
|
||||
state.item[page as keyof ListState['item']].grid?.itemsPerRow || 5,
|
||||
state.item[page as keyof ListState['item']].grid
|
||||
?.itemsPerRow || 5,
|
||||
scrollOffset: 0,
|
||||
},
|
||||
table: {
|
||||
@ -266,13 +274,16 @@ export const useListStore = create<ListSlice>()(
|
||||
};
|
||||
}
|
||||
|
||||
state.detail[args.key as keyof ListState['item']].table.pagination = {
|
||||
...state.detail[args.key as keyof ListState['item']].table.pagination,
|
||||
state.detail[args.key as keyof ListState['item']].table.pagination =
|
||||
{
|
||||
...state.detail[args.key as keyof ListState['item']].table
|
||||
.pagination,
|
||||
...args.data,
|
||||
};
|
||||
} else {
|
||||
state.item[args.key as keyof ListState['item']].table.pagination = {
|
||||
...state.item[args.key as keyof ListState['item']].table.pagination,
|
||||
...state.item[args.key as keyof ListState['item']].table
|
||||
.pagination,
|
||||
...args.data,
|
||||
};
|
||||
}
|
||||
|
@ -57,7 +57,11 @@ export interface QueueData {
|
||||
|
||||
export interface PlayerSlice extends PlayerState {
|
||||
actions: {
|
||||
addToQueue: (args: { initialIndex: number; playType: Play; songs: QueueSong[] }) => PlayerData;
|
||||
addToQueue: (args: {
|
||||
initialIndex: number;
|
||||
playType: Play;
|
||||
songs: QueueSong[];
|
||||
}) => PlayerData;
|
||||
autoNext: () => PlayerData;
|
||||
checkIsFirstTrack: () => boolean;
|
||||
checkIsLastTrack: (type?: 'next' | 'prev') => boolean;
|
||||
@ -115,9 +119,9 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
// Splice the initial song from the queue
|
||||
queueCopy.splice(index, 1);
|
||||
|
||||
const shuffledSongIndicesWithoutInitial = shuffle(queueCopy).map(
|
||||
(song) => song.uniqueId,
|
||||
);
|
||||
const shuffledSongIndicesWithoutInitial = shuffle(
|
||||
queueCopy,
|
||||
).map((song) => song.uniqueId);
|
||||
|
||||
// Add the initial song to the start of the shuffled queue
|
||||
const shuffledSongIndices = [
|
||||
@ -159,7 +163,10 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
: [];
|
||||
|
||||
set((state) => {
|
||||
state.queue.default = [...get().queue.default, ...songsToAddToQueue];
|
||||
state.queue.default = [
|
||||
...get().queue.default,
|
||||
...songsToAddToQueue,
|
||||
];
|
||||
state.queue.shuffled = shuffledQueueWithNewSongs;
|
||||
});
|
||||
} else if (playType === Play.NEXT) {
|
||||
@ -215,10 +222,13 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
state.queue.previousNode = get().current.song;
|
||||
});
|
||||
} else if (get().shuffle === PlayerShuffle.TRACK) {
|
||||
const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1;
|
||||
const nextShuffleIndex = isLastTrack
|
||||
? 0
|
||||
: get().current.shuffledIndex + 1;
|
||||
|
||||
const nextSong = get().queue.default.find(
|
||||
(song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex],
|
||||
(song) =>
|
||||
song.uniqueId === get().queue.shuffled[nextShuffleIndex],
|
||||
);
|
||||
|
||||
const nextSongIndex = get().queue.default.findIndex(
|
||||
@ -325,7 +335,9 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
|
||||
const next = nextSongIndex
|
||||
? (queue.find(
|
||||
(song) => song.uniqueId === shuffledQueue[nextSongIndex as number],
|
||||
(song) =>
|
||||
song.uniqueId ===
|
||||
shuffledQueue[nextSongIndex as number],
|
||||
) as QueueSong)
|
||||
: undefined;
|
||||
|
||||
@ -411,7 +423,10 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
queue: {
|
||||
current: queue[currentIndex],
|
||||
length: get().queue.default.length || 0,
|
||||
next: nextSongIndex !== undefined ? queue[nextSongIndex] : undefined,
|
||||
next:
|
||||
nextSongIndex !== undefined
|
||||
? queue[nextSongIndex]
|
||||
: undefined,
|
||||
previous: queue[currentIndex - 1],
|
||||
},
|
||||
};
|
||||
@ -453,8 +468,12 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
moveToBottomOfQueue: (uniqueIds) => {
|
||||
const queue = get().queue.default;
|
||||
|
||||
const songsToMove = queue.filter((song) => uniqueIds.includes(song.uniqueId));
|
||||
const songsToStay = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
|
||||
const songsToMove = queue.filter((song) =>
|
||||
uniqueIds.includes(song.uniqueId),
|
||||
);
|
||||
const songsToStay = queue.filter(
|
||||
(song) => !uniqueIds.includes(song.uniqueId),
|
||||
);
|
||||
|
||||
const reorderedQueue = [...songsToStay, ...songsToMove];
|
||||
|
||||
@ -473,8 +492,12 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
moveToTopOfQueue: (uniqueIds) => {
|
||||
const queue = get().queue.default;
|
||||
|
||||
const songsToMove = queue.filter((song) => uniqueIds.includes(song.uniqueId));
|
||||
const songsToStay = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
|
||||
const songsToMove = queue.filter((song) =>
|
||||
uniqueIds.includes(song.uniqueId),
|
||||
);
|
||||
const songsToStay = queue.filter(
|
||||
(song) => !uniqueIds.includes(song.uniqueId),
|
||||
);
|
||||
|
||||
const reorderedQueue = [...songsToMove, ...songsToStay];
|
||||
|
||||
@ -495,10 +518,13 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
const { repeat } = get();
|
||||
|
||||
if (get().shuffle === PlayerShuffle.TRACK) {
|
||||
const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1;
|
||||
const nextShuffleIndex = isLastTrack
|
||||
? 0
|
||||
: get().current.shuffledIndex + 1;
|
||||
|
||||
const nextSong = get().queue.default.find(
|
||||
(song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex],
|
||||
(song) =>
|
||||
song.uniqueId === get().queue.shuffled[nextShuffleIndex],
|
||||
);
|
||||
|
||||
const nextSongIndex = get().queue.default.findIndex(
|
||||
@ -523,7 +549,9 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
if (repeat === PlayerRepeat.ALL) {
|
||||
nextIndex = isLastTrack ? 0 : get().current.index + 1;
|
||||
} else {
|
||||
nextIndex = isLastTrack ? get().current.index : get().current.index + 1;
|
||||
nextIndex = isLastTrack
|
||||
? get().current.index
|
||||
: get().current.index + 1;
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
@ -558,10 +586,13 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
const { repeat } = get();
|
||||
|
||||
if (get().shuffle === PlayerShuffle.TRACK) {
|
||||
const prevShuffleIndex = isFirstTrack ? 0 : get().current.shuffledIndex - 1;
|
||||
const prevShuffleIndex = isFirstTrack
|
||||
? 0
|
||||
: get().current.shuffledIndex - 1;
|
||||
|
||||
const prevSong = get().queue.default.find(
|
||||
(song) => song.uniqueId === get().queue.shuffled[prevShuffleIndex],
|
||||
(song) =>
|
||||
song.uniqueId === get().queue.shuffled[prevShuffleIndex],
|
||||
);
|
||||
|
||||
const prevIndex = get().queue.default.findIndex(
|
||||
@ -601,12 +632,15 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
const queue = get().queue.default;
|
||||
const currentSong = get().current.song;
|
||||
|
||||
const newQueue = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
|
||||
const newQueue = queue.filter(
|
||||
(song) => !uniqueIds.includes(song.uniqueId),
|
||||
);
|
||||
const newShuffledQueue = get().queue.shuffled.filter(
|
||||
(uniqueId) => !uniqueIds.includes(uniqueId),
|
||||
);
|
||||
|
||||
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId);
|
||||
const isCurrentSongRemoved =
|
||||
currentSong && uniqueIds.includes(currentSong?.uniqueId);
|
||||
|
||||
set((state) => {
|
||||
state.queue.default = newQueue;
|
||||
@ -639,12 +673,16 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
const reorderedQueue = afterUniqueId
|
||||
? [
|
||||
...queueWithoutSelectedRows.slice(0, moveBeforeIndex),
|
||||
...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)),
|
||||
...queue.filter((song) =>
|
||||
rowUniqueIds.includes(song.uniqueId),
|
||||
),
|
||||
...queueWithoutSelectedRows.slice(moveBeforeIndex),
|
||||
]
|
||||
: [
|
||||
...queueWithoutSelectedRows,
|
||||
...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)),
|
||||
...queue.filter((song) =>
|
||||
rowUniqueIds.includes(song.uniqueId),
|
||||
),
|
||||
];
|
||||
|
||||
const currentSongIndex = reorderedQueue.findIndex(
|
||||
@ -712,7 +750,9 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
(song) => song.uniqueId === uniqueId,
|
||||
);
|
||||
|
||||
const shuffledIndex = get().queue.shuffled.findIndex((id) => id === uniqueId);
|
||||
const shuffledIndex = get().queue.shuffled.findIndex(
|
||||
(id) => id === uniqueId,
|
||||
);
|
||||
|
||||
set((state) => {
|
||||
state.current.time = 0;
|
||||
@ -816,7 +856,9 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
(song) => song.uniqueId !== currentSongId,
|
||||
);
|
||||
|
||||
const shuffledSongIds = shuffle(queueWithoutCurrentSong).map((song) => song.uniqueId);
|
||||
const shuffledSongIds = shuffle(queueWithoutCurrentSong).map(
|
||||
(song) => song.uniqueId,
|
||||
);
|
||||
|
||||
set((state) => {
|
||||
state.shuffle = type;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user