Lint all files

This commit is contained in:
jeffvli 2023-07-01 19:10:05 -07:00
parent 22af76b4d6
commit 30e52ebb54
334 changed files with 76519 additions and 75932 deletions

6
.github/stale.yml vendored
View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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"
},
{

View File

@ -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':

View File

@ -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();

View File

@ -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',
},

View File

@ -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);
};

View File

@ -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, '')),
};

View File

@ -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,

View File

@ -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

View File

@ -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,
},
});

View File

@ -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,

View File

@ -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,
};

View File

@ -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,

View File

@ -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
}
/>
</>
);

View File

@ -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:

View File

@ -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
],
};
}, {}),
)}

View File

@ -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}

View File

@ -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({

View File

@ -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;

View File

@ -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;

View File

@ -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[];

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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;
}

View File

@ -7,11 +7,12 @@ export const ServerCredentialRequired = () => {
return (
<>
<Text>
The selected server &apos;{currentServer?.name}&apos; requires an additional login to
access.
The selected server &apos;{currentServer?.name}&apos; requires an additional login
to access.
</Text>
<Text>
Add your credentials in the &apos;manage servers&apos; menu or switch to a different server.
Add your credentials in the &apos;manage servers&apos; menu or switch to a different
server.
</Text>
</>
);

View File

@ -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',
},
],
},
},
]}

View File

@ -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}

View File

@ -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}
/>

View File

@ -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>

View File

@ -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 }));

View File

@ -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,

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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}
/>

View File

@ -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>

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 },

View File

@ -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>
)}

View File

@ -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();

View File

@ -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' },
],
},
},
]}

View File

@ -188,7 +188,9 @@ export const Lyrics = () => {
{isSynchronizedLyrics ? (
<SynchronizedLyrics {...lyricsMetadata} />
) : (
<UnsynchronizedLyrics {...(lyricsMetadata as UnsynchronizedLyricMetadata)} />
<UnsynchronizedLyrics
{...(lyricsMetadata as UnsynchronizedLyricMetadata)}
/>
)}
</ScrollContainer>
)}

View File

@ -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);

View File

@ -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

View File

@ -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 });

View File

@ -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',

View File

@ -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>

View File

@ -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}

View File

@ -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);
}

View File

@ -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={{

View File

@ -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 {

View File

@ -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;

View File

@ -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: {

View File

@ -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',
});

View File

@ -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 && (

View File

@ -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

View File

@ -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),
},
];

View File

@ -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({

View File

@ -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}
/>

View File

@ -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>

View File

@ -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({

View File

@ -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}
/>

View File

@ -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>

View File

@ -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,
);

View File

@ -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),

View File

@ -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,

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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?.({

View File

@ -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'}

View File

@ -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',
},

View File

@ -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

View File

@ -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)',

View File

@ -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:

View File

@ -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*',
},

View File

@ -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);

View File

@ -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 {

View File

@ -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],
});

View File

@ -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],
});

View File

@ -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;

View File

@ -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}

View File

@ -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}

View File

@ -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({

View File

@ -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}

View File

@ -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>

View File

@ -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);

View File

@ -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

View File

@ -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}

View File

@ -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();

View File

@ -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%;

View File

@ -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;

View File

@ -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,
};
});
},
},

View File

@ -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,
};
}

View File

@ -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