mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add additional configuration to player sidebar
- allow reordering of panels - allow separation between lyrics and visualizer panels - allow resize of panels
This commit is contained in:
@@ -122,6 +122,7 @@
|
||||
"react-image": "^4.1.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-resizable-panels": "^4.0.15",
|
||||
"react-router": "^7.9.6",
|
||||
"react-virtualized-auto-sizer": "^1.0.26",
|
||||
"react-window": "1.8.11",
|
||||
|
||||
Generated
+14
@@ -191,6 +191,9 @@ importers:
|
||||
react-player:
|
||||
specifier: ^2.16.0
|
||||
version: 2.16.0(react@19.1.0)
|
||||
react-resizable-panels:
|
||||
specifier: ^4.0.15
|
||||
version: 4.0.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react-router:
|
||||
specifier: ^7.9.6
|
||||
version: 7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -4616,6 +4619,12 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-resizable-panels@4.0.15:
|
||||
resolution: {integrity: sha512-+ygM/EI2h4Qc/cl2fasQ2qwOgNfpQwXLNTU5PqhhPerliX+wnbf7ejcqran7lz3BqABzjddf0pJ3j3G/+A0v9Q==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
|
||||
react-router-dom@7.9.4:
|
||||
resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -10609,6 +10618,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.5
|
||||
|
||||
react-resizable-panels@4.0.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
@@ -898,6 +898,8 @@
|
||||
"showLyricsInSidebar": "show lyrics in player sidebar",
|
||||
"showVisualizerInSidebar_description": "a panel will be added to the player sidebar that displays the visualizer",
|
||||
"showVisualizerInSidebar": "show visualizer in player sidebar",
|
||||
"combinedLyricsAndVisualizer_description": "combine lyrics and visualizer into the same panel",
|
||||
"combinedLyricsAndVisualizer": "combine lyrics and visualizer in player sidebar",
|
||||
"preservePitch_description": "preserves pitch when modifying playback speed",
|
||||
"preservePitch": "preserve pitch",
|
||||
"audioFadeOnStatusChange": "audio fade on status change",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
|
||||
@@ -18,6 +19,20 @@
|
||||
&:focus-within {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
:global(> *),
|
||||
:global(> * > *),
|
||||
:global(div) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:global(button),
|
||||
:global(input),
|
||||
:global([role='button']),
|
||||
:global([role='combobox']),
|
||||
:global([role='textbox']) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.lyrics-container {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.play-queue-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -9,10 +11,9 @@
|
||||
.lyrics-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 0 0 300px;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: var(--theme-spacing-md);
|
||||
overflow: hidden;
|
||||
background: var(--theme-colors-background);
|
||||
@@ -41,12 +42,38 @@
|
||||
.visualizer-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 0 0 300px;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: var(--theme-spacing-md);
|
||||
overflow: hidden;
|
||||
background: var(--theme-colors-background);
|
||||
background-color: var(--theme-colors-background-alternate);
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
cursor: row-resize;
|
||||
background-color: var(--theme-colors-border);
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
height 0.2s ease;
|
||||
}
|
||||
|
||||
.panel-reorder-controls {
|
||||
position: absolute;
|
||||
top: var(--theme-spacing-md);
|
||||
left: var(--theme-spacing-md);
|
||||
z-index: 100;
|
||||
pointer-events: auto;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.lyrics-section:hover .panel-reorder-controls,
|
||||
.visualizer-section:hover .panel-reorder-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { lazy, Suspense, useMemo, useRef, useState } from 'react';
|
||||
import { lazy, Suspense, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Group, Panel, Separator } from 'react-resizable-panels';
|
||||
|
||||
import styles from './sidebar-play-queue.module.css';
|
||||
|
||||
@@ -13,12 +15,16 @@ import {
|
||||
usePlaybackSettings,
|
||||
usePlayerSong,
|
||||
useSettingsStore,
|
||||
useSettingsStoreActions,
|
||||
} from '/@/renderer/store';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { ItemListKey, PlayerType } from '/@/shared/types/types';
|
||||
|
||||
type SidebarPanelType = 'lyrics' | 'queue' | 'visualizer';
|
||||
|
||||
const AudioMotionAnalyzerVisualizer = lazy(() =>
|
||||
import('../../visualizer/components/audiomotionanalyzer/visualizer').then((module) => ({
|
||||
default: module.Visualizer,
|
||||
@@ -34,6 +40,91 @@ const ButterchurnVisualizer = lazy(() =>
|
||||
export const SidebarPlayQueue = () => {
|
||||
const tableRef = useRef<ItemListHandle | null>(null);
|
||||
const [search, setSearch] = useState<string | undefined>(undefined);
|
||||
const {
|
||||
combinedLyricsAndVisualizer,
|
||||
showLyricsInSidebar,
|
||||
showVisualizerInSidebar,
|
||||
sidebarPanelOrder,
|
||||
} = useGeneralSettings();
|
||||
const { type, webAudio } = usePlaybackSettings();
|
||||
const showVisualizer = showVisualizerInSidebar && type === PlayerType.WEB && webAudio;
|
||||
const showPanel = showLyricsInSidebar || showVisualizer;
|
||||
|
||||
// Filter and order panels based on what's enabled
|
||||
const orderedPanels = useMemo(() => {
|
||||
if (combinedLyricsAndVisualizer) {
|
||||
// When combined, use the order from settings but filter to only show queue and lyrics (combined)
|
||||
const visiblePanels = sidebarPanelOrder.filter((panel) => {
|
||||
if (panel === 'queue') return true;
|
||||
if (panel === 'lyrics') return showLyricsInSidebar || showVisualizer;
|
||||
return false;
|
||||
});
|
||||
return visiblePanels;
|
||||
}
|
||||
|
||||
const visiblePanels = sidebarPanelOrder.filter((panel) => {
|
||||
if (panel === 'queue') return true;
|
||||
if (panel === 'lyrics') return showLyricsInSidebar;
|
||||
if (panel === 'visualizer') return showVisualizer;
|
||||
return false;
|
||||
});
|
||||
|
||||
return visiblePanels;
|
||||
}, [combinedLyricsAndVisualizer, showLyricsInSidebar, showVisualizer, sidebarPanelOrder]);
|
||||
|
||||
const renderPanel = (panelType: SidebarPanelType, index: number, totalPanels: number) => {
|
||||
if (panelType === 'queue') {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && <Separator className={styles.resizeHandle} />}
|
||||
<Panel defaultSize={50} key="queue" minSize={20}>
|
||||
<div className={styles.playQueueSection}>
|
||||
<PlayQueue
|
||||
listKey={ItemListKey.SIDE_QUEUE}
|
||||
ref={tableRef}
|
||||
searchTerm={search}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (combinedLyricsAndVisualizer && (panelType === 'lyrics' || panelType === 'visualizer')) {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && <Separator className={styles.resizeHandle} />}
|
||||
<Panel defaultSize={50} key="combined" minSize={20}>
|
||||
<CombinedLyricsAndVisualizerPanel />
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (panelType === 'lyrics') {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && <Separator className={styles.resizeHandle} />}
|
||||
<Panel defaultSize={totalPanels > 2 ? 25 : 50} key="lyrics" minSize={15}>
|
||||
<LyricsPanel />
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (panelType === 'visualizer') {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && <Separator className={styles.resizeHandle} />}
|
||||
<Panel defaultSize={totalPanels > 2 ? 25 : 50} key="visualizer" minSize={15}>
|
||||
<VisualizerPanel />
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={0} h="100%" id="sidebar-play-queue-container" pos="relative" w="100%">
|
||||
@@ -42,31 +133,174 @@ export const SidebarPlayQueue = () => {
|
||||
searchTerm={search}
|
||||
type={ItemListKey.SIDE_QUEUE}
|
||||
/>
|
||||
<Flex direction="column" style={{ flex: 1, minHeight: 0 }}>
|
||||
<div className={styles.playQueueSection}>
|
||||
<PlayQueue
|
||||
listKey={ItemListKey.SIDE_QUEUE}
|
||||
ref={tableRef}
|
||||
searchTerm={search}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
<BottomPanel />
|
||||
{showPanel ? (
|
||||
<Group orientation="vertical" style={{ flex: 1, minHeight: 0 }}>
|
||||
{orderedPanels.map((panel, index) =>
|
||||
renderPanel(panel, index, orderedPanels.length),
|
||||
)}
|
||||
</Group>
|
||||
) : (
|
||||
<Flex direction="column" style={{ flex: 1, minHeight: 0 }}>
|
||||
<div className={styles.playQueueSection}>
|
||||
<PlayQueue
|
||||
listKey={ItemListKey.SIDE_QUEUE}
|
||||
ref={tableRef}
|
||||
searchTerm={search}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const BottomPanel = () => {
|
||||
const { showLyricsInSidebar, showVisualizerInSidebar } = useGeneralSettings();
|
||||
const { type, webAudio } = usePlaybackSettings();
|
||||
const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer' }) => {
|
||||
const { t } = useTranslation();
|
||||
const generalSettings = useGeneralSettings();
|
||||
const { combinedLyricsAndVisualizer, sidebarPanelOrder } = generalSettings;
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
|
||||
const currentIndex = sidebarPanelOrder.indexOf(panelType);
|
||||
const canMoveUp = currentIndex > 0;
|
||||
const canMoveDown = currentIndex < sidebarPanelOrder.length - 1;
|
||||
|
||||
const handleMoveUp = useCallback(() => {
|
||||
if (!canMoveUp) return;
|
||||
|
||||
const newOrder = [...sidebarPanelOrder];
|
||||
const targetIndex = currentIndex - 1;
|
||||
|
||||
[newOrder[currentIndex], newOrder[targetIndex]] = [
|
||||
newOrder[targetIndex],
|
||||
newOrder[currentIndex],
|
||||
];
|
||||
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
sidebarPanelOrder: newOrder,
|
||||
},
|
||||
});
|
||||
}, [canMoveUp, currentIndex, generalSettings, sidebarPanelOrder, setSettings]);
|
||||
|
||||
const handleMoveDown = useCallback(() => {
|
||||
if (!canMoveDown) return;
|
||||
|
||||
const newOrder = [...sidebarPanelOrder];
|
||||
[newOrder[currentIndex], newOrder[currentIndex + 1]] = [
|
||||
newOrder[currentIndex + 1],
|
||||
newOrder[currentIndex],
|
||||
];
|
||||
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
sidebarPanelOrder: newOrder,
|
||||
},
|
||||
});
|
||||
}, [canMoveDown, currentIndex, generalSettings, sidebarPanelOrder, setSettings]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (combinedLyricsAndVisualizer && panelType === 'lyrics') {
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
showLyricsInSidebar: false,
|
||||
showVisualizerInSidebar: false,
|
||||
},
|
||||
});
|
||||
} else if (panelType === 'lyrics') {
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
showLyricsInSidebar: false,
|
||||
},
|
||||
});
|
||||
} else if (panelType === 'visualizer') {
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
showVisualizerInSidebar: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [combinedLyricsAndVisualizer, generalSettings, panelType, setSettings]);
|
||||
|
||||
return (
|
||||
<div className={styles.panelReorderControls}>
|
||||
<ActionIconGroup>
|
||||
<ActionIcon
|
||||
disabled={!canMoveUp}
|
||||
icon="arrowUp"
|
||||
iconProps={{ size: 'sm' }}
|
||||
onClick={handleMoveUp}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('action.moveUp', { postProcess: 'sentenceCase' }),
|
||||
}}
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={!canMoveDown}
|
||||
icon="arrowDown"
|
||||
iconProps={{ size: 'sm' }}
|
||||
onClick={handleMoveDown}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('action.moveDown', { postProcess: 'sentenceCase' }),
|
||||
}}
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
iconProps={{ size: 'sm' }}
|
||||
onClick={handleClose}
|
||||
size="xs"
|
||||
tooltip={{
|
||||
label: t('common.close', { postProcess: 'sentenceCase' }),
|
||||
}}
|
||||
variant="subtle"
|
||||
/>
|
||||
</ActionIconGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LyricsPanel = () => {
|
||||
return (
|
||||
<div className={styles.lyricsSection}>
|
||||
<PanelReorderControls panelType="lyrics" />
|
||||
<Lyrics fadeOutNoLyricsMessage={false} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VisualizerPanel = () => {
|
||||
const visualizerType = useSettingsStore((store) => store.visualizer.type);
|
||||
|
||||
return (
|
||||
<div className={styles.visualizerSection}>
|
||||
<PanelReorderControls panelType="visualizer" />
|
||||
<Suspense fallback={<></>}>
|
||||
{visualizerType === 'butterchurn' ? (
|
||||
<ButterchurnVisualizer />
|
||||
) : (
|
||||
<AudioMotionAnalyzerVisualizer />
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CombinedLyricsAndVisualizerPanel = () => {
|
||||
const currentSong = usePlayerSong();
|
||||
const visualizerType = useSettingsStore((store) => store.visualizer.type);
|
||||
|
||||
const { data: lyricsData } = useQuery(
|
||||
lyricsQueries.songLyrics(
|
||||
{
|
||||
options: {
|
||||
enabled: showLyricsInSidebar && !!currentSong?.id,
|
||||
enabled: !!currentSong?.id,
|
||||
},
|
||||
query: { songId: currentSong?.id || '' },
|
||||
serverId: currentSong?._serverId || '',
|
||||
@@ -93,49 +327,24 @@ const BottomPanel = () => {
|
||||
return false;
|
||||
}, [lyricsData]);
|
||||
|
||||
const showVisualizer = showVisualizerInSidebar && type === PlayerType.WEB && webAudio;
|
||||
const showPanel = showLyricsInSidebar || showVisualizer;
|
||||
|
||||
if (!showPanel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider />
|
||||
{showLyricsInSidebar ? (
|
||||
<div className={styles.lyricsSection}>
|
||||
<Lyrics fadeOutNoLyricsMessage={showVisualizer} />
|
||||
{showVisualizer && (
|
||||
<div
|
||||
className={styles.visualizerOverlay}
|
||||
style={{
|
||||
opacity: hasLyrics ? 0.2 : 1,
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<></>}>
|
||||
{visualizerType === 'butterchurn' ? (
|
||||
<ButterchurnVisualizer />
|
||||
) : (
|
||||
<AudioMotionAnalyzerVisualizer />
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className={styles.lyricsSection}>
|
||||
<PanelReorderControls panelType="lyrics" />
|
||||
<Lyrics fadeOutNoLyricsMessage={true} />
|
||||
<div
|
||||
className={styles.visualizerOverlay}
|
||||
style={{
|
||||
opacity: hasLyrics ? 0.2 : 1,
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<></>}>
|
||||
{visualizerType === 'butterchurn' ? (
|
||||
<ButterchurnVisualizer />
|
||||
) : (
|
||||
<AudioMotionAnalyzerVisualizer />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
showVisualizer && (
|
||||
<div className={styles.visualizerSection}>
|
||||
<Suspense fallback={<></>}>
|
||||
{visualizerType === 'butterchurn' ? (
|
||||
<ButterchurnVisualizer />
|
||||
) : (
|
||||
<AudioMotionAnalyzerVisualizer />
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -356,6 +356,23 @@ export const PlayerConfig = () => {
|
||||
id: 'showVisualizerInSidebar',
|
||||
label: t('setting.showVisualizerInSidebar', { postProcess: 'titleCase' }),
|
||||
},
|
||||
{
|
||||
component: (
|
||||
<Switch
|
||||
defaultChecked={generalSettings.combinedLyricsAndVisualizer}
|
||||
onChange={(e) => {
|
||||
setSettings({
|
||||
general: {
|
||||
...generalSettings,
|
||||
combinedLyricsAndVisualizer: e.currentTarget.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
id: 'combinedLyricsAndVisualizer',
|
||||
label: t('setting.combinedLyricsAndVisualizer', { postProcess: 'titleCase' }),
|
||||
},
|
||||
];
|
||||
|
||||
return allOptions;
|
||||
|
||||
@@ -101,6 +101,27 @@ export const SidebarSettings = () => {
|
||||
}),
|
||||
title: t('setting.showVisualizerInSidebar', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Combine lyrics and visualizer"
|
||||
defaultChecked={settings.combinedLyricsAndVisualizer}
|
||||
onChange={(e) => {
|
||||
setSettings({
|
||||
general: {
|
||||
...settings,
|
||||
combinedLyricsAndVisualizer: e.currentTarget.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description: t('setting.combinedLyricsAndVisualizer', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
title: t('setting.combinedLyricsAndVisualizer', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -103,6 +103,8 @@ const GenreTargetSchema = z.enum(['album', 'track']);
|
||||
|
||||
const SideQueueTypeSchema = z.enum(['sideDrawerQueue', 'sideQueue']);
|
||||
|
||||
const SidebarPanelTypeSchema = z.enum(['queue', 'lyrics', 'visualizer']);
|
||||
|
||||
const SidebarItemTypeSchema = z.object({
|
||||
disabled: z.boolean(),
|
||||
id: z.string(),
|
||||
@@ -346,6 +348,7 @@ export const GeneralSettingsSchema = z.object({
|
||||
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
||||
artistRadioCount: z.number(),
|
||||
buttonSize: z.number(),
|
||||
combinedLyricsAndVisualizer: z.boolean(),
|
||||
disabledContextMenu: z.record(z.string(), z.boolean()),
|
||||
externalLinks: z.boolean(),
|
||||
followCurrentSong: z.boolean(),
|
||||
@@ -375,6 +378,7 @@ export const GeneralSettingsSchema = z.object({
|
||||
sidebarCollapsedNavigation: z.boolean(),
|
||||
sidebarCollapseShared: z.boolean(),
|
||||
sidebarItems: z.array(SidebarItemTypeSchema),
|
||||
sidebarPanelOrder: z.array(SidebarPanelTypeSchema),
|
||||
sidebarPlaylistList: z.boolean(),
|
||||
sideQueueType: SideQueueTypeSchema,
|
||||
skipButtons: SkipButtonsSchema,
|
||||
@@ -843,6 +847,7 @@ const initialState: SettingsState = {
|
||||
artistItems,
|
||||
artistRadioCount: 20,
|
||||
buttonSize: 15,
|
||||
combinedLyricsAndVisualizer: false,
|
||||
disabledContextMenu: {},
|
||||
externalLinks: true,
|
||||
followCurrentSong: true,
|
||||
@@ -878,6 +883,7 @@ const initialState: SettingsState = {
|
||||
sidebarCollapsedNavigation: true,
|
||||
sidebarCollapseShared: false,
|
||||
sidebarItems,
|
||||
sidebarPanelOrder: ['queue', 'lyrics', 'visualizer'],
|
||||
sidebarPlaylistList: true,
|
||||
sideQueueType: 'sideQueue',
|
||||
skipButtons: {
|
||||
|
||||
Reference in New Issue
Block a user