mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
add artist release type configuration, fix page configurations
This commit is contained in:
@@ -698,6 +698,8 @@
|
|||||||
"artistBackgroundBlur_description": "adjusts the amount of blur applied to the artist background image",
|
"artistBackgroundBlur_description": "adjusts the amount of blur applied to the artist background image",
|
||||||
"artistConfiguration": "album artist page configuration",
|
"artistConfiguration": "album artist page configuration",
|
||||||
"artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page",
|
"artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page",
|
||||||
|
"artistReleaseTypeConfiguration": "artist release type configuration",
|
||||||
|
"artistReleaseTypeConfiguration_description": "configure what release types are shown, and in what order, on the album artist page",
|
||||||
"audioDevice_description": "select the audio device to use for playback (web player only)",
|
"audioDevice_description": "select the audio device to use for playback (web player only)",
|
||||||
"audioDevice": "audio device",
|
"audioDevice": "audio device",
|
||||||
"audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio",
|
"audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
|||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import {
|
||||||
ArtistItem,
|
ArtistItem,
|
||||||
|
ArtistReleaseTypeItem,
|
||||||
useAppStore,
|
useAppStore,
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
useCurrentServerId,
|
useCurrentServerId,
|
||||||
@@ -998,8 +999,30 @@ const groupAlbumsByReleaseType = (
|
|||||||
return grouped;
|
return grouped;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const releaseTypeToEnumMap: Record<string, ArtistReleaseTypeItem> = {
|
||||||
|
album: ArtistReleaseTypeItem.RELEASE_TYPE_ALBUM,
|
||||||
|
'appears-on': ArtistReleaseTypeItem.APPEARS_ON,
|
||||||
|
audiobook: ArtistReleaseTypeItem.RELEASE_TYPE_AUDIOBOOK,
|
||||||
|
'audio drama': ArtistReleaseTypeItem.RELEASE_TYPE_AUDIO_DRAMA,
|
||||||
|
broadcast: ArtistReleaseTypeItem.RELEASE_TYPE_BROADCAST,
|
||||||
|
compilation: ArtistReleaseTypeItem.RELEASE_TYPE_COMPILATION,
|
||||||
|
demo: ArtistReleaseTypeItem.RELEASE_TYPE_DEMO,
|
||||||
|
'dj-mix': ArtistReleaseTypeItem.RELEASE_TYPE_DJ_MIX,
|
||||||
|
ep: ArtistReleaseTypeItem.RELEASE_TYPE_EP,
|
||||||
|
'field recording': ArtistReleaseTypeItem.RELEASE_TYPE_FIELD_RECORDING,
|
||||||
|
interview: ArtistReleaseTypeItem.RELEASE_TYPE_INTERVIEW,
|
||||||
|
live: ArtistReleaseTypeItem.RELEASE_TYPE_LIVE,
|
||||||
|
'mixtape/street': ArtistReleaseTypeItem.RELEASE_TYPE_MIXTAPE_STREET,
|
||||||
|
other: ArtistReleaseTypeItem.RELEASE_TYPE_OTHER,
|
||||||
|
remix: ArtistReleaseTypeItem.RELEASE_TYPE_REMIX,
|
||||||
|
single: ArtistReleaseTypeItem.RELEASE_TYPE_SINGLE,
|
||||||
|
soundtrack: ArtistReleaseTypeItem.RELEASE_TYPE_SOUNDTRACK,
|
||||||
|
spokenword: ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD,
|
||||||
|
};
|
||||||
|
|
||||||
const ArtistAlbums = () => {
|
const ArtistAlbums = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { artistReleaseTypeItems } = useGeneralSettings();
|
||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
|
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
|
||||||
@@ -1042,21 +1065,33 @@ const ArtistAlbums = () => {
|
|||||||
}, [filteredAndSortedAlbums, routeId, groupingType]);
|
}, [filteredAndSortedAlbums, routeId, groupingType]);
|
||||||
|
|
||||||
const releaseTypeEntries = useMemo(() => {
|
const releaseTypeEntries = useMemo(() => {
|
||||||
const priorityOrder = [
|
const enabledReleaseTypeEnums = new Set(
|
||||||
'album',
|
artistReleaseTypeItems.filter((item) => !item.disabled).map((item) => item.id),
|
||||||
'ep',
|
);
|
||||||
'single',
|
|
||||||
'broadcast',
|
const priorityMap = new Map<string, number>();
|
||||||
'other',
|
artistReleaseTypeItems
|
||||||
'compilation',
|
.filter((item) => !item.disabled)
|
||||||
'appears-on',
|
.forEach((item, index) => {
|
||||||
];
|
const releaseTypeKey = Object.keys(releaseTypeToEnumMap).find(
|
||||||
|
(key) => releaseTypeToEnumMap[key] === item.id,
|
||||||
|
);
|
||||||
|
if (releaseTypeKey) {
|
||||||
|
priorityMap.set(releaseTypeKey, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const getPriority = (releaseType: string) => {
|
const getPriority = (releaseType: string) => {
|
||||||
const index = priorityOrder.indexOf(releaseType);
|
return priorityMap.get(releaseType) ?? 999;
|
||||||
return index === -1 ? 999 : index;
|
};
|
||||||
|
|
||||||
|
const isReleaseTypeEnabled = (releaseType: string): boolean => {
|
||||||
|
const enumValue = releaseTypeToEnumMap[releaseType];
|
||||||
|
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return Object.entries(albumsByReleaseType)
|
return Object.entries(albumsByReleaseType)
|
||||||
|
.filter(([releaseType]) => isReleaseTypeEnabled(releaseType))
|
||||||
.map(([releaseType, albums]) => {
|
.map(([releaseType, albums]) => {
|
||||||
let displayName: React.ReactNode | string;
|
let displayName: React.ReactNode | string;
|
||||||
switch (releaseType) {
|
switch (releaseType) {
|
||||||
@@ -1156,7 +1191,7 @@ const ArtistAlbums = () => {
|
|||||||
return { albums, displayName, releaseType };
|
return { albums, displayName, releaseType };
|
||||||
})
|
})
|
||||||
.sort((a, b) => getPriority(a.releaseType) - getPriority(b.releaseType));
|
.sort((a, b) => getPriority(a.releaseType) - getPriority(b.releaseType));
|
||||||
}, [albumsByReleaseType, t]);
|
}, [albumsByReleaseType, artistReleaseTypeItems, t]);
|
||||||
|
|
||||||
const cq = useContainerQuery({
|
const cq = useContainerQuery({
|
||||||
'2xl': 1280,
|
'2xl': 1280,
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import i18n, { languages } from '/@/i18n/i18n';
|
import i18n, { languages } from '/@/i18n/i18n';
|
||||||
import { ImageResolutionSettings } from '/@/renderer/features/settings/components/general/art-resolution-settings';
|
import { ImageResolutionSettings } from '/@/renderer/features/settings/components/general/art-resolution-settings';
|
||||||
import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings';
|
import {
|
||||||
|
ArtistReleaseTypeSettings,
|
||||||
|
ArtistSettings,
|
||||||
|
} from '/@/renderer/features/settings/components/general/artist-settings';
|
||||||
import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings';
|
import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings';
|
||||||
import {
|
import {
|
||||||
SettingOption,
|
SettingOption,
|
||||||
@@ -606,6 +609,7 @@ export const ApplicationSettings = () => {
|
|||||||
<ImageResolutionSettings />
|
<ImageResolutionSettings />
|
||||||
<HomeSettings />
|
<HomeSettings />
|
||||||
<ArtistSettings />
|
<ArtistSettings />
|
||||||
|
<ArtistReleaseTypeSettings />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
options={options}
|
options={options}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
||||||
import {
|
import {
|
||||||
ArtistItem,
|
ArtistItem,
|
||||||
|
ArtistReleaseTypeItem,
|
||||||
SortableItem,
|
SortableItem,
|
||||||
useGeneralSettings,
|
useGeneralSettings,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
@@ -12,7 +11,6 @@ const ARTIST_ITEMS: Array<[ArtistItem, string]> = [
|
|||||||
[ArtistItem.BIOGRAPHY, 'table.column.biography'],
|
[ArtistItem.BIOGRAPHY, 'table.column.biography'],
|
||||||
[ArtistItem.TOP_SONGS, 'page.albumArtistDetail.topSongs'],
|
[ArtistItem.TOP_SONGS, 'page.albumArtistDetail.topSongs'],
|
||||||
[ArtistItem.RECENT_ALBUMS, 'page.albumArtistDetail.recentReleases'],
|
[ArtistItem.RECENT_ALBUMS, 'page.albumArtistDetail.recentReleases'],
|
||||||
[ArtistItem.COMPILATIONS, 'page.albumArtistDetail.appearsOn'],
|
|
||||||
[ArtistItem.SIMILAR_ARTISTS, 'page.albumArtistDetail.relatedArtists'],
|
[ArtistItem.SIMILAR_ARTISTS, 'page.albumArtistDetail.relatedArtists'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -20,36 +18,49 @@ export const ArtistSettings = () => {
|
|||||||
const { artistItems } = useGeneralSettings();
|
const { artistItems } = useGeneralSettings();
|
||||||
const { setArtistItems } = useSettingsStoreActions();
|
const { setArtistItems } = useSettingsStoreActions();
|
||||||
|
|
||||||
const mergedArtistItems = useMemo(() => {
|
|
||||||
const settingsMap = new Map(
|
|
||||||
artistItems.map((item) => [item.id, item as SortableItem<ArtistItem>]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const merged = artistItems.map((item) => ({
|
|
||||||
...item,
|
|
||||||
id: item.id as ArtistItem,
|
|
||||||
}));
|
|
||||||
|
|
||||||
ARTIST_ITEMS.forEach(([itemId]) => {
|
|
||||||
const artistItemId = itemId as ArtistItem;
|
|
||||||
if (!settingsMap.has(artistItemId)) {
|
|
||||||
merged.push({
|
|
||||||
disabled: true,
|
|
||||||
id: artistItemId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return merged;
|
|
||||||
}, [artistItems]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableItems
|
<DraggableItems
|
||||||
description="setting.artistConfiguration"
|
description="setting.artistConfiguration"
|
||||||
itemLabels={ARTIST_ITEMS}
|
itemLabels={ARTIST_ITEMS}
|
||||||
|
items={artistItems as SortableItem<ArtistItem>[]}
|
||||||
setItems={setArtistItems}
|
setItems={setArtistItems}
|
||||||
settings={mergedArtistItems}
|
|
||||||
title="setting.artistConfiguration"
|
title="setting.artistConfiguration"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ARTIST_RELEASE_TYPE_ITEMS: Array<[ArtistReleaseTypeItem, string]> = [
|
||||||
|
[ArtistReleaseTypeItem.APPEARS_ON, 'page.albumArtistDetail.appearsOn'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_ALBUM, 'releaseType.primary.album'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_EP, 'releaseType.primary.ep'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_SINGLE, 'releaseType.primary.single'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_BROADCAST, 'releaseType.primary.broadcast'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_COMPILATION, 'releaseType.secondary.compilation'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_AUDIO_DRAMA, 'releaseType.secondary.audioDrama'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_AUDIOBOOK, 'releaseType.secondary.audiobook'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_INTERVIEW, 'releaseType.secondary.interview'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_LIVE, 'releaseType.secondary.live'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_MIXTAPE_STREET, 'releaseType.secondary.mixtape'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_OTHER, 'releaseType.primary.other'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_REMIX, 'releaseType.secondary.remix'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_DJ_MIX, 'releaseType.secondary.djMix'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_DEMO, 'releaseType.secondary.demo'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_FIELD_RECORDING, 'releaseType.secondary.fieldRecording'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_SOUNDTRACK, 'releaseType.secondary.soundtrack'],
|
||||||
|
[ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD, 'releaseType.secondary.spokenWord'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ArtistReleaseTypeSettings = () => {
|
||||||
|
const { artistReleaseTypeItems } = useGeneralSettings();
|
||||||
|
const { setArtistReleaseTypeItems } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DraggableItems
|
||||||
|
description="setting.artistReleaseTypeConfiguration"
|
||||||
|
itemLabels={ARTIST_RELEASE_TYPE_ITEMS}
|
||||||
|
items={artistReleaseTypeItems as SortableItem<ArtistReleaseTypeItem>[]}
|
||||||
|
setItems={setArtistReleaseTypeItems}
|
||||||
|
title="setting.artistReleaseTypeConfiguration"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,16 +12,41 @@ import { Button } from '/@/shared/components/button/button';
|
|||||||
export type DraggableItemsProps<K, T> = {
|
export type DraggableItemsProps<K, T> = {
|
||||||
description: string;
|
description: string;
|
||||||
itemLabels: Array<[K, string]>;
|
itemLabels: Array<[K, string]>;
|
||||||
|
items: T[];
|
||||||
setItems: (items: T[]) => void;
|
setItems: (items: T[]) => void;
|
||||||
settings: T[];
|
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mergeItems = <K extends string, T extends SortableItem<K>>(
|
||||||
|
items: T[],
|
||||||
|
itemLabels: Array<[string, string]>,
|
||||||
|
): T[] => {
|
||||||
|
const allItemIds = itemLabels.map(([key]) => key);
|
||||||
|
|
||||||
|
const missingItemIds = allItemIds.filter((id) => !items.some((item) => item.id === id));
|
||||||
|
|
||||||
|
const merged = [
|
||||||
|
...items,
|
||||||
|
...(missingItemIds.map((id) => ({
|
||||||
|
disabled: true,
|
||||||
|
id,
|
||||||
|
})) as T[]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Remove any duplicates
|
||||||
|
const uniqueMerged = merged.filter(
|
||||||
|
(item, index, self) => index === self.findIndex((t) => t.id === item.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove any that don't match the itemLabels
|
||||||
|
return uniqueMerged.filter((item) => itemLabels.some(([key]) => key === item.id));
|
||||||
|
};
|
||||||
|
|
||||||
export const DraggableItems = <K extends string, T extends SortableItem<K>>({
|
export const DraggableItems = <K extends string, T extends SortableItem<K>>({
|
||||||
description,
|
description,
|
||||||
itemLabels,
|
itemLabels,
|
||||||
|
items,
|
||||||
setItems,
|
setItems,
|
||||||
settings,
|
|
||||||
title,
|
title,
|
||||||
}: DraggableItemsProps<K, T>) => {
|
}: DraggableItemsProps<K, T>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -31,12 +56,12 @@ export const DraggableItems = <K extends string, T extends SortableItem<K>>({
|
|||||||
const translatedItemMap = useMemo(
|
const translatedItemMap = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
itemLabels.map((label) => [label[0], t(label[1], { postProcess: 'sentenceCase' })]),
|
itemLabels.map(([key, value]) => [key, t(value, { postProcess: 'sentenceCase' })]),
|
||||||
) as Record<K, string>,
|
) as Record<K, string>,
|
||||||
[itemLabels, t],
|
[itemLabels, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [localItems, setLocalItems] = useState(settings);
|
const [localItems, setLocalItems] = useState(mergeItems(items, itemLabels));
|
||||||
|
|
||||||
const handleChangeDisabled = useCallback((id: string, e: boolean) => {
|
const handleChangeDisabled = useCallback((id: string, e: boolean) => {
|
||||||
setLocalItems((items) =>
|
setLocalItems((items) =>
|
||||||
@@ -68,10 +93,10 @@ export const DraggableItems = <K extends string, T extends SortableItem<K>>({
|
|||||||
}, [description, keyword, title]);
|
}, [description, keyword, title]);
|
||||||
|
|
||||||
if (!shouldShow) {
|
if (!shouldShow) {
|
||||||
return <></>;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSaveButtonDisabled = isEqual(settings, localItems);
|
const isSaveButtonDisabled = isEqual(items, localItems);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setItems(localItems);
|
setItems(localItems);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
||||||
import {
|
import {
|
||||||
HomeItem,
|
HomeItem,
|
||||||
@@ -21,35 +19,12 @@ export const HomeSettings = () => {
|
|||||||
const { homeItems } = useGeneralSettings();
|
const { homeItems } = useGeneralSettings();
|
||||||
const { setHomeItems } = useSettingsStoreActions();
|
const { setHomeItems } = useSettingsStoreActions();
|
||||||
|
|
||||||
const mergedHomeItems = useMemo(() => {
|
|
||||||
const settingsMap = new Map(
|
|
||||||
homeItems.map((item) => [item.id, item as SortableItem<HomeItem>]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const merged = homeItems.map((item) => ({
|
|
||||||
...item,
|
|
||||||
id: item.id as HomeItem,
|
|
||||||
}));
|
|
||||||
|
|
||||||
HOME_ITEMS.forEach(([itemId]) => {
|
|
||||||
const homeItemId = itemId as HomeItem;
|
|
||||||
if (!settingsMap.has(homeItemId)) {
|
|
||||||
merged.push({
|
|
||||||
disabled: true,
|
|
||||||
id: homeItemId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return merged;
|
|
||||||
}, [homeItems]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableItems
|
<DraggableItems
|
||||||
description="setting.homeConfiguration"
|
description="setting.homeConfiguration"
|
||||||
itemLabels={HOME_ITEMS}
|
itemLabels={HOME_ITEMS}
|
||||||
|
items={homeItems as SortableItem<HomeItem>[]}
|
||||||
setItems={setHomeItems}
|
setItems={setHomeItems}
|
||||||
settings={mergedHomeItems}
|
|
||||||
title="setting.homeConfiguration"
|
title="setting.homeConfiguration"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,24 +3,26 @@ import { useMemo } from 'react';
|
|||||||
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
|
||||||
import {
|
import {
|
||||||
sidebarItems as defaultSidebarItems,
|
sidebarItems as defaultSidebarItems,
|
||||||
|
SidebarItem,
|
||||||
|
SidebarItemType,
|
||||||
useGeneralSettings,
|
useGeneralSettings,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
|
|
||||||
const SIDEBAR_ITEMS: Array<[string, string]> = [
|
const SIDEBAR_ITEMS: Array<[string, string]> = [
|
||||||
['Albums', 'page.sidebar.albums'],
|
[SidebarItem.ALBUMS, 'page.sidebar.albums'],
|
||||||
['Artists', 'page.sidebar.albumArtists'],
|
[SidebarItem.ARTISTS, 'page.sidebar.albumArtists'],
|
||||||
['Artists-all', 'page.sidebar.artists'],
|
[SidebarItem.ARTISTS_ALL, 'page.sidebar.artists'],
|
||||||
['Favorites', 'page.sidebar.favorites'],
|
[SidebarItem.FAVORITES, 'page.sidebar.favorites'],
|
||||||
['Folders', 'page.sidebar.folders'],
|
[SidebarItem.FOLDERS, 'page.sidebar.folders'],
|
||||||
['Genres', 'page.sidebar.genres'],
|
[SidebarItem.GENRES, 'page.sidebar.genres'],
|
||||||
['Home', 'page.sidebar.home'],
|
[SidebarItem.HOME, 'page.sidebar.home'],
|
||||||
['Now Playing', 'page.sidebar.nowPlaying'],
|
[SidebarItem.NOW_PLAYING, 'page.sidebar.nowPlaying'],
|
||||||
['Playlists', 'page.sidebar.playlists'],
|
[SidebarItem.PLAYLISTS, 'page.sidebar.playlists'],
|
||||||
['Radio', 'page.sidebar.radio'],
|
[SidebarItem.RADIO, 'page.sidebar.radio'],
|
||||||
['Search', 'page.sidebar.search'],
|
[SidebarItem.SEARCH, 'page.sidebar.search'],
|
||||||
['Settings', 'page.sidebar.settings'],
|
[SidebarItem.SETTINGS, 'page.sidebar.settings'],
|
||||||
['Tracks', 'page.sidebar.tracks'],
|
[SidebarItem.TRACKS, 'page.sidebar.tracks'],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SidebarReorder = () => {
|
export const SidebarReorder = () => {
|
||||||
@@ -48,15 +50,20 @@ export const SidebarReorder = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return merged;
|
// Remove any duplicates
|
||||||
|
const uniqueMerged = merged.filter(
|
||||||
|
(item, index, self) => index === self.findIndex((t) => t.id === item.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
return uniqueMerged;
|
||||||
}, [sidebarItems]);
|
}, [sidebarItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableItems
|
<DraggableItems
|
||||||
description="setting.sidebarCollapsedNavigation"
|
description="setting.sidebarCollapsedNavigation"
|
||||||
itemLabels={SIDEBAR_ITEMS}
|
itemLabels={SIDEBAR_ITEMS}
|
||||||
|
items={mergedSidebarItems as unknown as SidebarItemType[]}
|
||||||
setItems={setSidebarItems}
|
setItems={setSidebarItems}
|
||||||
settings={mergedSidebarItems}
|
|
||||||
title="setting.sidebarConfiguration"
|
title="setting.sidebarConfiguration"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,6 +58,27 @@ const ArtistItemSchema = z.enum([
|
|||||||
'topSongs',
|
'topSongs',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const ArtistReleaseTypeItemSchema = z.enum([
|
||||||
|
'releaseTypeAlbum',
|
||||||
|
'releaseTypeEp',
|
||||||
|
'releaseTypeSingle',
|
||||||
|
'releaseTypeBroadcast',
|
||||||
|
'releaseTypeOther',
|
||||||
|
'releaseTypeCompilation',
|
||||||
|
'appearsOn',
|
||||||
|
'releaseTypeAudioDrama',
|
||||||
|
'releaseTypeAudiobook',
|
||||||
|
'releaseTypeDemo',
|
||||||
|
'releaseTypeDjMix',
|
||||||
|
'releaseTypeFieldRecording',
|
||||||
|
'releaseTypeInterview',
|
||||||
|
'releaseTypeLive',
|
||||||
|
'releaseTypeMixtapeStreet',
|
||||||
|
'releaseTypeRemix',
|
||||||
|
'releaseTypeSoundtrack',
|
||||||
|
'releaseTypeSpokenWord',
|
||||||
|
]);
|
||||||
|
|
||||||
const BindingActionsSchema = z.enum([
|
const BindingActionsSchema = z.enum([
|
||||||
'browserBack',
|
'browserBack',
|
||||||
'browserForward',
|
'browserForward',
|
||||||
@@ -350,6 +371,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
artistBackgroundBlur: z.number(),
|
artistBackgroundBlur: z.number(),
|
||||||
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
||||||
artistRadioCount: z.number(),
|
artistRadioCount: z.number(),
|
||||||
|
artistReleaseTypeItems: z.array(SortableItemSchema(ArtistReleaseTypeItemSchema)),
|
||||||
buttonSize: z.number(),
|
buttonSize: z.number(),
|
||||||
combinedLyricsAndVisualizer: z.boolean(),
|
combinedLyricsAndVisualizer: z.boolean(),
|
||||||
disabledContextMenu: z.record(z.string(), z.boolean()),
|
disabledContextMenu: z.record(z.string(), z.boolean()),
|
||||||
@@ -587,6 +609,27 @@ export enum ArtistItem {
|
|||||||
TOP_SONGS = 'topSongs',
|
TOP_SONGS = 'topSongs',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ArtistReleaseTypeItem {
|
||||||
|
APPEARS_ON = 'appearsOn',
|
||||||
|
RELEASE_TYPE_ALBUM = 'releaseTypeAlbum',
|
||||||
|
RELEASE_TYPE_AUDIO_DRAMA = 'releaseTypeAudioDrama',
|
||||||
|
RELEASE_TYPE_AUDIOBOOK = 'releaseTypeAudiobook',
|
||||||
|
RELEASE_TYPE_BROADCAST = 'releaseTypeBroadcast',
|
||||||
|
RELEASE_TYPE_COMPILATION = 'releaseTypeCompilation',
|
||||||
|
RELEASE_TYPE_DEMO = 'releaseTypeDemo',
|
||||||
|
RELEASE_TYPE_DJ_MIX = 'releaseTypeDjMix',
|
||||||
|
RELEASE_TYPE_EP = 'releaseTypeEp',
|
||||||
|
RELEASE_TYPE_FIELD_RECORDING = 'releaseTypeFieldRecording',
|
||||||
|
RELEASE_TYPE_INTERVIEW = 'releaseTypeInterview',
|
||||||
|
RELEASE_TYPE_LIVE = 'releaseTypeLive',
|
||||||
|
RELEASE_TYPE_MIXTAPE_STREET = 'releaseTypeMixtapeStreet',
|
||||||
|
RELEASE_TYPE_OTHER = 'releaseTypeOther',
|
||||||
|
RELEASE_TYPE_REMIX = 'releaseTypeRemix',
|
||||||
|
RELEASE_TYPE_SINGLE = 'releaseTypeSingle',
|
||||||
|
RELEASE_TYPE_SOUNDTRACK = 'releaseTypeSoundtrack',
|
||||||
|
RELEASE_TYPE_SPOKENWORD = 'releaseTypeSpokenWord',
|
||||||
|
}
|
||||||
|
|
||||||
export enum BarAlign {
|
export enum BarAlign {
|
||||||
BOTTOM = 'bottom',
|
BOTTOM = 'bottom',
|
||||||
CENTER = 'center',
|
CENTER = 'center',
|
||||||
@@ -662,6 +705,22 @@ export enum PlayerbarSliderType {
|
|||||||
WAVEFORM = 'waveform',
|
WAVEFORM = 'waveform',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SidebarItem {
|
||||||
|
ALBUMS = 'Albums',
|
||||||
|
ARTISTS = 'Artists',
|
||||||
|
ARTISTS_ALL = 'Artists-all',
|
||||||
|
FAVORITES = 'Favorites',
|
||||||
|
FOLDERS = 'Folders',
|
||||||
|
GENRES = 'Genres',
|
||||||
|
HOME = 'Home',
|
||||||
|
NOW_PLAYING = 'Now Playing',
|
||||||
|
PLAYLISTS = 'Playlists',
|
||||||
|
RADIO = 'Radio',
|
||||||
|
SEARCH = 'Search',
|
||||||
|
SETTINGS = 'Settings',
|
||||||
|
TRACKS = 'Tracks',
|
||||||
|
}
|
||||||
|
|
||||||
export type DataGridProps = {
|
export type DataGridProps = {
|
||||||
itemGap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
itemGap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
itemsPerRow: number;
|
itemsPerRow: number;
|
||||||
@@ -690,6 +749,7 @@ export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
|
|||||||
reset: () => void;
|
reset: () => void;
|
||||||
resetSampleRate: () => void;
|
resetSampleRate: () => void;
|
||||||
setArtistItems: (item: SortableItem<ArtistItem>[]) => void;
|
setArtistItems: (item: SortableItem<ArtistItem>[]) => void;
|
||||||
|
setArtistReleaseTypeItems: (item: SortableItem<ArtistReleaseTypeItem>[]) => void;
|
||||||
setGenreBehavior: (target: GenreTarget) => void;
|
setGenreBehavior: (target: GenreTarget) => void;
|
||||||
setHomeItems: (item: SortableItem<HomeItem>[]) => void;
|
setHomeItems: (item: SortableItem<HomeItem>[]) => void;
|
||||||
setList: (type: ItemListKey, data: DeepPartial<ItemListSettings>) => void;
|
setList: (type: ItemListKey, data: DeepPartial<ItemListSettings>) => void;
|
||||||
@@ -707,7 +767,7 @@ export type SidebarItemType = z.infer<typeof SidebarItemTypeSchema>;
|
|||||||
|
|
||||||
export type SideQueueType = z.infer<typeof SideQueueTypeSchema>;
|
export type SideQueueType = z.infer<typeof SideQueueTypeSchema>;
|
||||||
|
|
||||||
export type SortableItem<T> = {
|
export type SortableItem<T extends string> = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
id: T;
|
id: T;
|
||||||
};
|
};
|
||||||
@@ -802,6 +862,11 @@ const artistItems = Object.values(ArtistItem).map((item) => ({
|
|||||||
id: item,
|
id: item,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const artistReleaseTypeItems = Object.values(ArtistReleaseTypeItem).map((item) => ({
|
||||||
|
disabled: false,
|
||||||
|
id: item,
|
||||||
|
}));
|
||||||
|
|
||||||
// Determines the default/initial windowBarStyle value based on the current platform.
|
// Determines the default/initial windowBarStyle value based on the current platform.
|
||||||
const getPlatformDefaultWindowBarStyle = (): Platform => {
|
const getPlatformDefaultWindowBarStyle = (): Platform => {
|
||||||
if (utils?.isWindows()) {
|
if (utils?.isWindows()) {
|
||||||
@@ -854,6 +919,7 @@ const initialState: SettingsState = {
|
|||||||
artistBackgroundBlur: 3,
|
artistBackgroundBlur: 3,
|
||||||
artistItems,
|
artistItems,
|
||||||
artistRadioCount: 20,
|
artistRadioCount: 20,
|
||||||
|
artistReleaseTypeItems,
|
||||||
buttonSize: 15,
|
buttonSize: 15,
|
||||||
combinedLyricsAndVisualizer: false,
|
combinedLyricsAndVisualizer: false,
|
||||||
disabledContextMenu: {},
|
disabledContextMenu: {},
|
||||||
@@ -1513,12 +1579,18 @@ const getInitialState = (): SettingsState => {
|
|||||||
id: item,
|
id: item,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const freshArtistReleaseTypeItems = Object.values(ArtistReleaseTypeItem).map((item) => ({
|
||||||
|
disabled: false,
|
||||||
|
id: item,
|
||||||
|
}));
|
||||||
|
|
||||||
// Deep clone using JSON to ensure all nested objects/arrays are fresh copies
|
// Deep clone using JSON to ensure all nested objects/arrays are fresh copies
|
||||||
const clonedState = JSON.parse(JSON.stringify(initialState)) as SettingsState;
|
const clonedState = JSON.parse(JSON.stringify(initialState)) as SettingsState;
|
||||||
|
|
||||||
// Replace arrays that need fresh references
|
// Replace arrays that need fresh references
|
||||||
clonedState.general.homeItems = freshHomeItems;
|
clonedState.general.homeItems = freshHomeItems;
|
||||||
clonedState.general.artistItems = freshArtistItems;
|
clonedState.general.artistItems = freshArtistItems;
|
||||||
|
clonedState.general.artistReleaseTypeItems = freshArtistReleaseTypeItems;
|
||||||
clonedState.general.sidebarItems = JSON.parse(
|
clonedState.general.sidebarItems = JSON.parse(
|
||||||
JSON.stringify(sidebarItems),
|
JSON.stringify(sidebarItems),
|
||||||
) as SidebarItemType[];
|
) as SidebarItemType[];
|
||||||
@@ -1573,6 +1645,11 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
|||||||
state.general.artistItems = items;
|
state.general.artistItems = items;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setArtistReleaseTypeItems: (items: SortableItem<ArtistReleaseTypeItem>[]) => {
|
||||||
|
set((state) => {
|
||||||
|
state.general.artistReleaseTypeItems = items;
|
||||||
|
});
|
||||||
|
},
|
||||||
setGenreBehavior: (target: GenreTarget) => {
|
setGenreBehavior: (target: GenreTarget) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.general.genreTarget = target;
|
state.general.genreTarget = target;
|
||||||
|
|||||||
Reference in New Issue
Block a user