diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index d331e55c0..dec82402e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -979,6 +979,9 @@ "sidebarPlaylistList": "sidebar playlist list", "sidebarPlaylistSorting_description": "allows manual playlist sorting in the sidebar using drag and drop instead of the default server order", "sidebarPlaylistSorting": "sidebar playlist sorting", + "sidebarPlaylistListFilterRegex_description": "hide playlists in the sidebar that match this regular expression", + "sidebarPlaylistListFilterRegex_placeholder": "e.g. ^Daily Mix.*", + "sidebarPlaylistListFilterRegex": "playlist filter regex", "sidePlayQueueStyle_description": "sets the style of the side play queue", "sidePlayQueueStyle_optionAttached": "attached", "sidePlayQueueStyle_optionDetached": "detached", diff --git a/src/renderer/features/settings/components/general/sidebar-settings.tsx b/src/renderer/features/settings/components/general/sidebar-settings.tsx index 1f799912d..579f4cca6 100644 --- a/src/renderer/features/settings/components/general/sidebar-settings.tsx +++ b/src/renderer/features/settings/components/general/sidebar-settings.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, memo } from 'react'; +import { ChangeEvent, memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder'; @@ -8,6 +8,8 @@ import { } from '/@/renderer/features/settings/components/settings-section'; import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store'; import { Switch } from '/@/shared/components/switch/switch'; +import { TextInput } from '/@/shared/components/text-input/text-input'; +import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback'; export const SidebarSettings = memo(() => { const { t } = useTranslation(); @@ -38,6 +40,22 @@ export const SidebarSettings = memo(() => { }); }; + const [localFilterRegex, setLocalFilterRegex] = useState( + settings.sidebarPlaylistListFilterRegex, + ); + + useEffect(() => { + setLocalFilterRegex(settings.sidebarPlaylistListFilterRegex); + }, [settings.sidebarPlaylistListFilterRegex]); + + const debouncedSetFilterRegex = useDebouncedCallback((value: string) => { + setSettings({ + general: { + sidebarPlaylistListFilterRegex: value, + }, + }); + }, 500); + const options: SettingOption[] = [ { control: ( @@ -52,6 +70,26 @@ export const SidebarSettings = memo(() => { }), title: t('setting.sidebarPlaylistList', { postProcess: 'sentenceCase' }), }, + { + control: ( + { + const value = e.currentTarget.value; + setLocalFilterRegex(value); + debouncedSetFilterRegex(value); + }} + placeholder={t('setting.sidebarPlaylistListFilterRegex_placeholder', { + postProcess: 'sentenceCase', + })} + value={localFilterRegex} + /> + ), + description: t('setting.sidebarPlaylistListFilterRegex', { + context: 'description', + postProcess: 'sentenceCase', + }), + title: t('setting.sidebarPlaylistListFilterRegex', { postProcess: 'sentenceCase' }), + }, { control: ( { const { t } = useTranslation(); const server = useCurrentServer(); const sidebarPlaylistSorting = useSidebarPlaylistSorting(); + const filterRegex = useSidebarPlaylistListFilterRegex(); const playlistsQuery = useQuery( playlistsQueries.list({ @@ -400,10 +407,23 @@ export const SidebarPlaylistList = () => { return { ...base, items: playlistsQuery.data?.items }; } + let regex: null | RegExp = null; + if (filterRegex) { + try { + regex = new RegExp(filterRegex, 'i'); + } catch { + // Invalid regex, ignore filtering + } + } + const ownedPlaylistItems: Array = []; for (const playlist of playlistsQuery.data?.items ?? []) { if (!playlist.owner || playlist.owner === server.username) { + // Filter out playlists that match the regex + if (regex && regex.test(playlist.name)) { + continue; + } ownedPlaylistItems.push(playlist); } } @@ -429,6 +449,7 @@ export const SidebarPlaylistList = () => { server.username, sidebarPlaylistSorting, playlistOrder, + filterRegex, ]); const handleReorder = ( @@ -533,6 +554,7 @@ export const SidebarSharedPlaylistList = () => { const { t } = useTranslation(); const server = useCurrentServer(); const sidebarPlaylistSorting = useSidebarPlaylistSorting(); + const filterRegex = useSidebarPlaylistListFilterRegex(); const playlistsQuery = useQuery( playlistsQueries.list({ @@ -580,10 +602,23 @@ export const SidebarSharedPlaylistList = () => { return { ...base, items: playlistsQuery.data?.items }; } + let regex: null | RegExp = null; + if (filterRegex) { + try { + regex = new RegExp(filterRegex, 'i'); + } catch { + // Invalid regex, ignore filtering + } + } + const sharedPlaylistItems: Array = []; for (const playlist of playlistsQuery.data?.items ?? []) { if (playlist.owner && playlist.owner !== server.username) { + // Filter out playlists that match the regex + if (regex && regex.test(playlist.name)) { + continue; + } sharedPlaylistItems.push(playlist); } } @@ -609,6 +644,7 @@ export const SidebarSharedPlaylistList = () => { server.username, sidebarPlaylistSorting, playlistOrder, + filterRegex, ]); const handleReorder = ( diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 8a989f0d6..157a86e66 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -454,6 +454,7 @@ export const GeneralSettingsSchema = z.object({ sidebarItems: z.array(SidebarItemTypeSchema), sidebarPanelOrder: z.array(SidebarPanelTypeSchema), sidebarPlaylistList: z.boolean(), + sidebarPlaylistListFilterRegex: z.string(), sidebarPlaylistSorting: z.boolean(), sideQueueType: SideQueueTypeSchema, skipButtons: SkipButtonsSchema, @@ -1030,6 +1031,7 @@ const initialState: SettingsState = { sidebarItems, sidebarPanelOrder: ['queue', 'lyrics', 'visualizer'], sidebarPlaylistList: true, + sidebarPlaylistListFilterRegex: '', sidebarPlaylistSorting: false, sideQueueType: 'sideQueue', skipButtons: { @@ -2193,6 +2195,9 @@ export const useSidebarPlaylistList = () => export const useSidebarPlaylistSorting = () => useSettingsStore((state) => state.general.sidebarPlaylistSorting, shallow); +export const useSidebarPlaylistListFilterRegex = () => + useSettingsStore((state) => state.general.sidebarPlaylistListFilterRegex, shallow); + export const useSidebarItems = () => useSettingsStore((state) => state.general.sidebarItems, shallow);