mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b95f47a91 | |||
| 2267e9bc9d | |||
| 089311c673 | |||
| 773f349b66 | |||
| 3980c8ea97 | |||
| 257a5ceef0 | |||
| fb022891fe | |||
| 5d9906b8f2 | |||
| 6f7cb468b2 | |||
| 076693e969 | |||
| 781d8055b5 | |||
| 960bb5c660 | |||
| 42bb2bf66f | |||
| f03d88cd8c | |||
| 58f6535ba6 | |||
| 9a59ce3613 |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "feishin",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "feishin",
|
||||
"productName": "Feishin",
|
||||
"description": "Feishin music server",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"scripts": {
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
|
||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||
|
||||
Generated
+10
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "feishin",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -1318,9 +1318,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
@@ -1584,7 +1585,7 @@
|
||||
"jsbi": "^2.0.5",
|
||||
"long": "^4.0.0",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"xml2js": "^0.4.17"
|
||||
"xml2js": "0.5.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
@@ -2317,9 +2318,9 @@
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.12.5",
|
||||
"version": "0.12.7",
|
||||
"description": "",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
@@ -20,5 +20,11 @@
|
||||
"devDependencies": {
|
||||
"electron": "36.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"xml2js": "0.5.0"
|
||||
},
|
||||
"overrides": {
|
||||
"xml2js": "0.5.0"
|
||||
},
|
||||
"license": "GPL-3.0"
|
||||
}
|
||||
|
||||
@@ -747,8 +747,8 @@
|
||||
"folderWithCount_few": "{{count}} složky",
|
||||
"folderWithCount_other": "{{count}} složek",
|
||||
"albumArtist_one": "umělec alba",
|
||||
"albumArtist_few": "umělci alba",
|
||||
"albumArtist_other": "umělců alba",
|
||||
"albumArtist_few": "umělci alb",
|
||||
"albumArtist_other": "umělci alb",
|
||||
"track_one": "skladba",
|
||||
"track_few": "skladby",
|
||||
"track_other": "skladby",
|
||||
|
||||
@@ -494,6 +494,9 @@ const createWindow = async (first = true) => {
|
||||
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
|
||||
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
|
||||
app.commandLine.appendSwitch('gtk-version', '3');
|
||||
|
||||
// Must duplicate with the one in renderer process settings.store.ts
|
||||
enum BindingActions {
|
||||
GLOBAL_SEARCH = 'globalSearch',
|
||||
|
||||
@@ -695,58 +695,98 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
}
|
||||
|
||||
const yearsFilter = yearsGroup.length ? formatCommaDelimitedString(yearsGroup) : undefined;
|
||||
const albumIdsFilter = query.albumIds
|
||||
? formatCommaDelimitedString(query.albumIds)
|
||||
: undefined;
|
||||
const artistIdsFilter = query.artistIds
|
||||
? formatCommaDelimitedString(query.artistIds)
|
||||
: query.albumArtistIds
|
||||
? formatCommaDelimitedString(query.albumArtistIds)
|
||||
: undefined;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getSongList({
|
||||
params: {
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
query: {
|
||||
AlbumIds: albumIdsFilter,
|
||||
ArtistIds: artistIdsFilter,
|
||||
Fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||
GenreIds: query.genreIds?.join(','),
|
||||
IncludeItemTypes: 'Audio',
|
||||
IsFavorite: query.favorite,
|
||||
Limit: query.limit,
|
||||
ParentId: query.musicFolderId,
|
||||
Recursive: true,
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
});
|
||||
let items: z.infer<typeof jfType._response.song>[] = [];
|
||||
let totalRecordCount = 0;
|
||||
const batchSize = 50;
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get song list');
|
||||
}
|
||||
// Handle albumIds fetches in batches to prevent HTTP 414 errors
|
||||
if (query.albumIds && query.albumIds.length > batchSize) {
|
||||
const albumIdBatches = chunk(query.albumIds, batchSize);
|
||||
|
||||
let items: z.infer<typeof jfType._response.song>[];
|
||||
for (const batch of albumIdBatches) {
|
||||
const albumIdsFilter = formatCommaDelimitedString(batch);
|
||||
|
||||
// Jellyfin Bodge because of code from https://github.com/jellyfin/jellyfin/blob/c566ccb63bf61f9c36743ddb2108a57c65a2519b/Emby.Server.Implementations/Data/SqliteItemRepository.cs#L3622
|
||||
// If the Album ID filter is passed, Jellyfin will search for
|
||||
// 1. the matching album id
|
||||
// 2. An album with the name of the album.
|
||||
// It is this second condition causing issues,
|
||||
if (query.albumIds) {
|
||||
const albumIdSet = new Set(query.albumIds);
|
||||
items = res.body.Items.filter((item) => albumIdSet.has(item.AlbumId!));
|
||||
const res = await jfApiClient(apiClientProps).getSongList({
|
||||
params: {
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
query: {
|
||||
AlbumIds: albumIdsFilter,
|
||||
ArtistIds: artistIdsFilter,
|
||||
Fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||
GenreIds: query.genreIds?.join(','),
|
||||
IncludeItemTypes: 'Audio',
|
||||
IsFavorite: query.favorite,
|
||||
Limit: query.limit,
|
||||
ParentId: query.musicFolderId,
|
||||
Recursive: true,
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
});
|
||||
|
||||
if (items.length < res.body.Items.length) {
|
||||
res.body.TotalRecordCount -= res.body.Items.length - items.length;
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get song list');
|
||||
}
|
||||
|
||||
items = [...items, ...res.body.Items];
|
||||
totalRecordCount += res.body.Items.length;
|
||||
}
|
||||
} else {
|
||||
items = res.body.Items;
|
||||
const albumIdsFilter = query.albumIds
|
||||
? formatCommaDelimitedString(query.albumIds)
|
||||
: undefined;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getSongList({
|
||||
params: {
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
query: {
|
||||
AlbumIds: albumIdsFilter,
|
||||
ArtistIds: artistIdsFilter,
|
||||
Fields: 'Genres, DateCreated, MediaSources, ParentId',
|
||||
GenreIds: query.genreIds?.join(','),
|
||||
IncludeItemTypes: 'Audio',
|
||||
IsFavorite: query.favorite,
|
||||
Limit: query.limit,
|
||||
ParentId: query.musicFolderId,
|
||||
Recursive: true,
|
||||
SearchTerm: query.searchTerm,
|
||||
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
...query._custom?.jellyfin,
|
||||
Years: yearsFilter,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get song list');
|
||||
}
|
||||
|
||||
// Jellyfin Bodge because of code from https://github.com/jellyfin/jellyfin/blob/c566ccb63bf61f9c36743ddb2108a57c65a2519b/Emby.Server.Implementations/Data/SqliteItemRepository.cs#L3622
|
||||
// If the Album ID filter is passed, Jellyfin will search for
|
||||
// 1. the matching album id
|
||||
// 2. An album with the name of the album.
|
||||
// It is this second condition causing issues,
|
||||
if (query.albumIds) {
|
||||
const albumIdSet = new Set(query.albumIds);
|
||||
items = res.body.Items.filter((item) => albumIdSet.has(item.AlbumId!));
|
||||
totalRecordCount = items.length;
|
||||
} else {
|
||||
items = res.body.Items;
|
||||
totalRecordCount = res.body.TotalRecordCount;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -754,7 +794,7 @@ export const JellyfinController: ControllerEndpoint = {
|
||||
jfNormalize.song(item, apiClientProps.server, '', query.imageSize),
|
||||
),
|
||||
startIndex: query.startIndex,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
totalRecordCount,
|
||||
};
|
||||
},
|
||||
getSongListCount: async ({ apiClientProps, query }) =>
|
||||
|
||||
@@ -284,7 +284,7 @@ const normalizeAlbumArtist = (
|
||||
) || [];
|
||||
|
||||
return {
|
||||
albumCount: null,
|
||||
albumCount: item.AlbumCount ?? null,
|
||||
backgroundImageUrl: null,
|
||||
biography: item.Overview || null,
|
||||
duration: item.RunTimeTicks / 10000,
|
||||
@@ -308,7 +308,7 @@ const normalizeAlbumArtist = (
|
||||
serverId: server?.id || '',
|
||||
serverType: ServerType.JELLYFIN,
|
||||
similarArtists,
|
||||
songCount: null,
|
||||
songCount: item.SongCount ?? null,
|
||||
userFavorite: item.UserData?.IsFavorite || false,
|
||||
userRating: null,
|
||||
};
|
||||
|
||||
@@ -431,6 +431,7 @@ const providerIds = z.object({
|
||||
});
|
||||
|
||||
const albumArtist = z.object({
|
||||
AlbumCount: z.number().optional(),
|
||||
BackdropImageTags: z.array(z.string()),
|
||||
ChannelId: z.null(),
|
||||
DateCreated: z.string(),
|
||||
@@ -446,6 +447,7 @@ const albumArtist = z.object({
|
||||
ProviderIds: providerIds.optional(),
|
||||
RunTimeTicks: z.number(),
|
||||
ServerId: z.string(),
|
||||
SongCount: z.number().optional(),
|
||||
Type: z.string(),
|
||||
UserData: userData.optional(),
|
||||
});
|
||||
|
||||
@@ -278,11 +278,25 @@ const normalizeAlbumArtist = (
|
||||
});
|
||||
}
|
||||
|
||||
let albumCount: number;
|
||||
let songCount: number;
|
||||
|
||||
if (item.stats) {
|
||||
albumCount = Math.max(
|
||||
item.stats.albumartist?.albumCount ?? 0,
|
||||
item.stats.artist?.albumCount ?? 0,
|
||||
);
|
||||
songCount = Math.max(
|
||||
item.stats.albumartist?.songCount ?? 0,
|
||||
item.stats.artist?.songCount ?? 0,
|
||||
);
|
||||
} else {
|
||||
albumCount = item.albumCount;
|
||||
songCount = item.songCount;
|
||||
}
|
||||
|
||||
return {
|
||||
albumCount: Math.max(
|
||||
item.stats?.albumartist?.albumCount || item.albumCount,
|
||||
item.stats?.artist?.albumCount || 0,
|
||||
),
|
||||
albumCount,
|
||||
backgroundImageUrl: null,
|
||||
biography: item.biography || null,
|
||||
duration: null,
|
||||
@@ -307,7 +321,7 @@ const normalizeAlbumArtist = (
|
||||
imageUrl: artist?.artistImageUrl || null,
|
||||
name: artist.name,
|
||||
})) || null,
|
||||
songCount: item.stats?.albumartist?.songCount || item.songCount,
|
||||
songCount,
|
||||
userFavorite: item.starred,
|
||||
userRating: item.rating,
|
||||
};
|
||||
|
||||
@@ -190,7 +190,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
|
||||
return {
|
||||
...ssNormalize.albumArtist(artist, apiClientProps.server, 300),
|
||||
albums: artist.album.map((album) => ssNormalize.album(album, apiClientProps.server)),
|
||||
albums: artist.album?.map((album) => ssNormalize.album(album, apiClientProps.server)),
|
||||
similarArtists:
|
||||
artistInfo?.similarArtist?.map((artist) =>
|
||||
ssNormalize.albumArtist(artist, apiClientProps.server, 300),
|
||||
@@ -305,7 +305,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
return [];
|
||||
}
|
||||
|
||||
return artist.body.artist.album;
|
||||
return artist.body.artist.album ?? [];
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -935,7 +935,9 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
};
|
||||
}
|
||||
|
||||
if (query.albumIds || query.artistIds) {
|
||||
const artistIds = query.albumArtistIds || query.artistIds;
|
||||
|
||||
if (query.albumIds || artistIds) {
|
||||
if (query.albumIds) {
|
||||
for (const albumId of query.albumIds) {
|
||||
fromAlbumPromises.push(
|
||||
@@ -948,8 +950,8 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
}
|
||||
}
|
||||
|
||||
if (query.artistIds) {
|
||||
for (const artistId of query.artistIds) {
|
||||
if (artistIds) {
|
||||
for (const artistId of artistIds) {
|
||||
artistDetailPromises.push(
|
||||
ssApiClient(apiClientProps).getArtist({
|
||||
query: {
|
||||
@@ -966,7 +968,7 @@ export const SubsonicController: ControllerEndpoint = {
|
||||
return [];
|
||||
}
|
||||
|
||||
return artist.body.artist.album;
|
||||
return artist.body.artist.album ?? [];
|
||||
});
|
||||
|
||||
const albumIds = albums.map((album) => album.id);
|
||||
|
||||
@@ -156,7 +156,7 @@ const albumListParameters = z.object({
|
||||
const albumList = z.array(album.omit({ song: true }));
|
||||
|
||||
const albumArtist = z.object({
|
||||
album: z.array(album),
|
||||
album: z.array(album).optional(),
|
||||
albumCount: z.string(),
|
||||
artistImageUrl: z.string().optional(),
|
||||
coverArt: z.string().optional(),
|
||||
|
||||
@@ -207,11 +207,14 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
||||
</Badge>
|
||||
))}
|
||||
<Badge size="lg">{currentItem?.releaseYear}</Badge>
|
||||
<Badge size="lg">
|
||||
{t('entity.trackWithCount', {
|
||||
count: currentItem?.songCount || 0,
|
||||
})}
|
||||
</Badge>
|
||||
{currentItem?.songCount !== null &&
|
||||
currentItem?.songCount !== undefined && (
|
||||
<Badge size="lg">
|
||||
{t('entity.trackWithCount', {
|
||||
count: currentItem?.songCount || 0,
|
||||
})}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Group position="apart">
|
||||
<Button
|
||||
|
||||
@@ -388,8 +388,8 @@ export const useVirtualTable = <TFilter extends BaseQuery<any>>({
|
||||
break;
|
||||
case LibraryItem.ARTIST:
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: e.data.id,
|
||||
generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, {
|
||||
artistId: e.data.id,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -129,7 +129,7 @@ const AlbumListRoute = () => {
|
||||
|
||||
const artist = searchParams.get('artistName');
|
||||
const title = artist
|
||||
? t('page.albumList.artistAlbums', { artist })
|
||||
? t('page.albumList.artistAlbums', { artist, postProcess: 'sentenceCase' })
|
||||
: genreId
|
||||
? t('page.albumList.genreAlbums', { genre: titleCase(genreTitle) })
|
||||
: undefined;
|
||||
|
||||
@@ -297,7 +297,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [routeId],
|
||||
type: albumArtistId ? LibraryItem.ALBUM : LibraryItem.ALBUM_ARTIST,
|
||||
type: albumArtistId ? LibraryItem.ALBUM_ARTIST : LibraryItem.ARTIST,
|
||||
},
|
||||
playType: playType || playButtonBehavior,
|
||||
});
|
||||
@@ -340,9 +340,15 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||
}
|
||||
};
|
||||
|
||||
const albumCount = detailQuery?.data?.albumCount;
|
||||
const artistContextItems =
|
||||
(albumCount ?? 1) > 0
|
||||
? ARTIST_CONTEXT_MENU_ITEMS
|
||||
: ARTIST_CONTEXT_MENU_ITEMS.filter((item) => !item.id.toLowerCase().includes('play'));
|
||||
|
||||
const handleGeneralContextMenu = useHandleGeneralContextMenu(
|
||||
LibraryItem.ALBUM_ARTIST,
|
||||
ARTIST_CONTEXT_MENU_ITEMS,
|
||||
artistContextItems,
|
||||
);
|
||||
|
||||
const topSongs = topSongsQuery?.data?.items?.slice(0, 10);
|
||||
@@ -369,7 +375,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||
<LibraryBackgroundOverlay $backgroundColor={background} />
|
||||
<DetailContainer>
|
||||
<Group spacing="md">
|
||||
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||
<PlayButton
|
||||
disabled={albumCount === 0}
|
||||
onClick={() => handlePlay(playButtonBehavior)}
|
||||
/>
|
||||
<Group spacing="xs">
|
||||
<Button
|
||||
compact
|
||||
|
||||
@@ -28,25 +28,29 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||
serverId: server?.id,
|
||||
});
|
||||
|
||||
const albumCount = detailQuery?.data?.albumCount;
|
||||
const songCount = detailQuery?.data?.songCount;
|
||||
const duration = detailQuery?.data?.duration;
|
||||
const durationEnabled = duration !== null && duration !== undefined;
|
||||
|
||||
const metadataItems = [
|
||||
{
|
||||
enabled: detailQuery?.data?.albumCount,
|
||||
enabled: albumCount !== null && albumCount !== undefined,
|
||||
id: 'albumCount',
|
||||
secondary: false,
|
||||
value: t('entity.albumWithCount', { count: detailQuery?.data?.albumCount || 0 }),
|
||||
value: t('entity.albumWithCount', { count: albumCount || 0 }),
|
||||
},
|
||||
{
|
||||
enabled: detailQuery?.data?.songCount,
|
||||
enabled: songCount !== null && songCount !== undefined,
|
||||
id: 'songCount',
|
||||
secondary: false,
|
||||
value: t('entity.trackWithCount', { count: detailQuery?.data?.songCount || 0 }),
|
||||
value: t('entity.trackWithCount', { count: songCount || 0 }),
|
||||
},
|
||||
{
|
||||
enabled: detailQuery.data?.duration,
|
||||
enabled: durationEnabled,
|
||||
id: 'duration',
|
||||
secondary: true,
|
||||
value:
|
||||
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
value: durationEnabled && formatDurationString(duration),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useCallback, useState, Fragment, useRef } from 'react';
|
||||
import { ActionIcon, Group, Kbd, ScrollArea } from '@mantine/core';
|
||||
import { useDisclosure, useDebouncedValue } from '@mantine/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiSearchLine, RiCloseFill } from 'react-icons/ri';
|
||||
import { generatePath, useNavigate } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
@@ -37,6 +38,7 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
const activePage = pages[pages.length - 1];
|
||||
const isHome = activePage === CommandPalettePages.HOME;
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const popPage = useCallback(() => {
|
||||
setPages((pages) => {
|
||||
@@ -187,13 +189,17 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
disabled={artist?.albumCount === 0}
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={artist.id}
|
||||
imageUrl={artist.imageUrl}
|
||||
itemType={LibraryItem.ALBUM_ARTIST}
|
||||
subtitle={
|
||||
(artist?.albumCount || 0) > 0
|
||||
? `${artist.albumCount} albums`
|
||||
artist?.albumCount !== undefined &&
|
||||
artist?.albumCount !== null
|
||||
? t('entity.albumWithCount', {
|
||||
count: artist.albumCount,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
title={artist.name}
|
||||
|
||||
@@ -53,6 +53,7 @@ const StyledImage = styled.img`
|
||||
const ActionsContainer = styled(Flex)``;
|
||||
|
||||
interface LibraryCommandItemProps {
|
||||
disabled?: boolean;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
@@ -62,6 +63,7 @@ interface LibraryCommandItemProps {
|
||||
}
|
||||
|
||||
export const LibraryCommandItem = ({
|
||||
disabled,
|
||||
id,
|
||||
imageUrl,
|
||||
subtitle,
|
||||
@@ -154,6 +156,7 @@ export const LibraryCommandItem = ({
|
||||
>
|
||||
<Button
|
||||
compact
|
||||
disabled={disabled}
|
||||
size="md"
|
||||
tooltip={{
|
||||
label: t('player.play', { postProcess: 'sentenceCase' }),
|
||||
@@ -166,6 +169,7 @@ export const LibraryCommandItem = ({
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
disabled={disabled}
|
||||
size="md"
|
||||
tooltip={{
|
||||
label: t('player.addLast', { postProcess: 'sentenceCase' }),
|
||||
@@ -179,6 +183,7 @@ export const LibraryCommandItem = ({
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
disabled={disabled}
|
||||
size="md"
|
||||
tooltip={{
|
||||
label: t('player.addNext', { postProcess: 'sentenceCase' }),
|
||||
|
||||
@@ -15,7 +15,7 @@ const MotionButton = styled(UnstyledButton)`
|
||||
fill: var(--btn-filled-fg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover:not([disabled]) {
|
||||
background: var(--btn-filled-bg);
|
||||
transform: scale(1.1);
|
||||
|
||||
@@ -28,6 +28,10 @@ const MotionButton = styled(UnstyledButton)`
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
`;
|
||||
|
||||
@@ -734,6 +734,22 @@ export const useSettingsStore = create<SettingsSlice>()(
|
||||
),
|
||||
{
|
||||
merge: mergeOverridingColumns,
|
||||
migrate(persistedState, version) {
|
||||
if (version === 8) {
|
||||
const state = persistedState as SettingsSlice;
|
||||
state.general.sidebarItems = state.general.sidebarItems.filter(
|
||||
(item) => item.id !== 'Folders',
|
||||
);
|
||||
state.general.sidebarItems.push({
|
||||
disabled: false,
|
||||
id: 'Artists-all',
|
||||
label: i18n.t('page.sidebar.artists'),
|
||||
route: AppRoute.LIBRARY_ARTISTS,
|
||||
});
|
||||
}
|
||||
|
||||
return persistedState;
|
||||
},
|
||||
name: 'store_settings',
|
||||
version: 9,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user