mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
feat: add regex filter setting for sidebar playlists (#1589)
* added regex filter for sidebar playlists --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
@@ -979,6 +979,9 @@
|
|||||||
"sidebarPlaylistList": "sidebar playlist list",
|
"sidebarPlaylistList": "sidebar playlist list",
|
||||||
"sidebarPlaylistSorting_description": "allows manual playlist sorting in the sidebar using drag and drop instead of the default server order",
|
"sidebarPlaylistSorting_description": "allows manual playlist sorting in the sidebar using drag and drop instead of the default server order",
|
||||||
"sidebarPlaylistSorting": "sidebar playlist sorting",
|
"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_description": "sets the style of the side play queue",
|
||||||
"sidePlayQueueStyle_optionAttached": "attached",
|
"sidePlayQueueStyle_optionAttached": "attached",
|
||||||
"sidePlayQueueStyle_optionDetached": "detached",
|
"sidePlayQueueStyle_optionDetached": "detached",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeEvent, memo } from 'react';
|
import { ChangeEvent, memo, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder';
|
import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder';
|
||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
} from '/@/renderer/features/settings/components/settings-section';
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
|
import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
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(() => {
|
export const SidebarSettings = memo(() => {
|
||||||
const { t } = useTranslation();
|
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[] = [
|
const options: SettingOption[] = [
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
@@ -52,6 +70,26 @@ export const SidebarSettings = memo(() => {
|
|||||||
}),
|
}),
|
||||||
title: t('setting.sidebarPlaylistList', { postProcess: 'sentenceCase' }),
|
title: t('setting.sidebarPlaylistList', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<TextInput
|
||||||
|
onChange={(e) => {
|
||||||
|
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: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ import {
|
|||||||
usePermissions,
|
usePermissions,
|
||||||
useSidebarPlaylistSorting,
|
useSidebarPlaylistSorting,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
|
import {
|
||||||
|
useCurrentServer,
|
||||||
|
useCurrentServerId,
|
||||||
|
usePermissions,
|
||||||
|
useSidebarPlaylistListFilterRegex,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
import { Accordion } from '/@/shared/components/accordion/accordion';
|
import { Accordion } from '/@/shared/components/accordion/accordion';
|
||||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||||
@@ -357,6 +363,7 @@ export const SidebarPlaylistList = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const sidebarPlaylistSorting = useSidebarPlaylistSorting();
|
const sidebarPlaylistSorting = useSidebarPlaylistSorting();
|
||||||
|
const filterRegex = useSidebarPlaylistListFilterRegex();
|
||||||
|
|
||||||
const playlistsQuery = useQuery(
|
const playlistsQuery = useQuery(
|
||||||
playlistsQueries.list({
|
playlistsQueries.list({
|
||||||
@@ -400,10 +407,23 @@ export const SidebarPlaylistList = () => {
|
|||||||
return { ...base, items: playlistsQuery.data?.items };
|
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<Playlist> = [];
|
const ownedPlaylistItems: Array<Playlist> = [];
|
||||||
|
|
||||||
for (const playlist of playlistsQuery.data?.items ?? []) {
|
for (const playlist of playlistsQuery.data?.items ?? []) {
|
||||||
if (!playlist.owner || playlist.owner === server.username) {
|
if (!playlist.owner || playlist.owner === server.username) {
|
||||||
|
// Filter out playlists that match the regex
|
||||||
|
if (regex && regex.test(playlist.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ownedPlaylistItems.push(playlist);
|
ownedPlaylistItems.push(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,6 +449,7 @@ export const SidebarPlaylistList = () => {
|
|||||||
server.username,
|
server.username,
|
||||||
sidebarPlaylistSorting,
|
sidebarPlaylistSorting,
|
||||||
playlistOrder,
|
playlistOrder,
|
||||||
|
filterRegex,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleReorder = (
|
const handleReorder = (
|
||||||
@@ -533,6 +554,7 @@ export const SidebarSharedPlaylistList = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const sidebarPlaylistSorting = useSidebarPlaylistSorting();
|
const sidebarPlaylistSorting = useSidebarPlaylistSorting();
|
||||||
|
const filterRegex = useSidebarPlaylistListFilterRegex();
|
||||||
|
|
||||||
const playlistsQuery = useQuery(
|
const playlistsQuery = useQuery(
|
||||||
playlistsQueries.list({
|
playlistsQueries.list({
|
||||||
@@ -580,10 +602,23 @@ export const SidebarSharedPlaylistList = () => {
|
|||||||
return { ...base, items: playlistsQuery.data?.items };
|
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<Playlist> = [];
|
const sharedPlaylistItems: Array<Playlist> = [];
|
||||||
|
|
||||||
for (const playlist of playlistsQuery.data?.items ?? []) {
|
for (const playlist of playlistsQuery.data?.items ?? []) {
|
||||||
if (playlist.owner && playlist.owner !== server.username) {
|
if (playlist.owner && playlist.owner !== server.username) {
|
||||||
|
// Filter out playlists that match the regex
|
||||||
|
if (regex && regex.test(playlist.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
sharedPlaylistItems.push(playlist);
|
sharedPlaylistItems.push(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,6 +644,7 @@ export const SidebarSharedPlaylistList = () => {
|
|||||||
server.username,
|
server.username,
|
||||||
sidebarPlaylistSorting,
|
sidebarPlaylistSorting,
|
||||||
playlistOrder,
|
playlistOrder,
|
||||||
|
filterRegex,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleReorder = (
|
const handleReorder = (
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
sidebarItems: z.array(SidebarItemTypeSchema),
|
sidebarItems: z.array(SidebarItemTypeSchema),
|
||||||
sidebarPanelOrder: z.array(SidebarPanelTypeSchema),
|
sidebarPanelOrder: z.array(SidebarPanelTypeSchema),
|
||||||
sidebarPlaylistList: z.boolean(),
|
sidebarPlaylistList: z.boolean(),
|
||||||
|
sidebarPlaylistListFilterRegex: z.string(),
|
||||||
sidebarPlaylistSorting: z.boolean(),
|
sidebarPlaylistSorting: z.boolean(),
|
||||||
sideQueueType: SideQueueTypeSchema,
|
sideQueueType: SideQueueTypeSchema,
|
||||||
skipButtons: SkipButtonsSchema,
|
skipButtons: SkipButtonsSchema,
|
||||||
@@ -1030,6 +1031,7 @@ const initialState: SettingsState = {
|
|||||||
sidebarItems,
|
sidebarItems,
|
||||||
sidebarPanelOrder: ['queue', 'lyrics', 'visualizer'],
|
sidebarPanelOrder: ['queue', 'lyrics', 'visualizer'],
|
||||||
sidebarPlaylistList: true,
|
sidebarPlaylistList: true,
|
||||||
|
sidebarPlaylistListFilterRegex: '',
|
||||||
sidebarPlaylistSorting: false,
|
sidebarPlaylistSorting: false,
|
||||||
sideQueueType: 'sideQueue',
|
sideQueueType: 'sideQueue',
|
||||||
skipButtons: {
|
skipButtons: {
|
||||||
@@ -2193,6 +2195,9 @@ export const useSidebarPlaylistList = () =>
|
|||||||
export const useSidebarPlaylistSorting = () =>
|
export const useSidebarPlaylistSorting = () =>
|
||||||
useSettingsStore((state) => state.general.sidebarPlaylistSorting, shallow);
|
useSettingsStore((state) => state.general.sidebarPlaylistSorting, shallow);
|
||||||
|
|
||||||
|
export const useSidebarPlaylistListFilterRegex = () =>
|
||||||
|
useSettingsStore((state) => state.general.sidebarPlaylistListFilterRegex, shallow);
|
||||||
|
|
||||||
export const useSidebarItems = () =>
|
export const useSidebarItems = () =>
|
||||||
useSettingsStore((state) => state.general.sidebarItems, shallow);
|
useSettingsStore((state) => state.general.sidebarItems, shallow);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user