support vertical play queue layout

This commit is contained in:
jeffvli
2026-03-17 19:00:55 -07:00
parent 8ccd97b574
commit db88a6bc22
14 changed files with 268 additions and 32 deletions
+1
View File
@@ -44,6 +44,7 @@ These variables override app settings **on first run** when no persisted setting
| `general.sidebarPlaylistList` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_LIST` | `true` / `false` — Show playlist list in sidebar. | | `general.sidebarPlaylistList` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_LIST` | `true` / `false` — Show playlist list in sidebar. |
| `general.sidebarPlaylistSorting` | `false` | `FS_GENERAL_SIDEBAR_PLAYLIST_SORTING` | `true` / `false` — Enable playlist sorting in sidebar. | | `general.sidebarPlaylistSorting` | `false` | `FS_GENERAL_SIDEBAR_PLAYLIST_SORTING` | `true` / `false` — Enable playlist sorting in sidebar. |
| `general.sideQueueType` | `sideQueue` | `FS_GENERAL_SIDE_QUEUE_TYPE` | `sideDrawerQueue` / `sideQueue` — Side play queue style. | | `general.sideQueueType` | `sideQueue` | `FS_GENERAL_SIDE_QUEUE_TYPE` | `sideDrawerQueue` / `sideQueue` — Side play queue style. |
| `general.sideQueueLayout` | `horizontal` | `FS_GENERAL_SIDE_QUEUE_LAYOUT` | `horizontal` / `vertical` — Attached side queue layout orientation. |
| `general.useThemeAccentColor` | `false` | `FS_GENERAL_USE_THEME_ACCENT_COLOR` | `true` / `false` — Use themes accent color instead of custom. | | `general.useThemeAccentColor` | `false` | `FS_GENERAL_USE_THEME_ACCENT_COLOR` | `true` / `false` — Use themes accent color instead of custom. |
| `general.useThemePrimaryShade` | `true` | `FS_GENERAL_USE_THEME_PRIMARY_SHADE` | `true` / `false` — Use themes primary shade. | | `general.useThemePrimaryShade` | `true` | `FS_GENERAL_USE_THEME_PRIMARY_SHADE` | `true` / `false` — Use themes primary shade. |
| `general.zoomFactor` | `100` | `FS_GENERAL_ZOOM_FACTOR` | UI zoom percentage (number). | | `general.zoomFactor` | `100` | `FS_GENERAL_ZOOM_FACTOR` | UI zoom percentage (number). |
+1
View File
@@ -39,6 +39,7 @@ window.FS_GENERAL_SIDEBAR_COLLAPSE_SHARED = "${FS_GENERAL_SIDEBAR_COLLAPSE_SHARE
window.FS_GENERAL_SIDEBAR_PLAYLIST_LIST = "${FS_GENERAL_SIDEBAR_PLAYLIST_LIST}"; window.FS_GENERAL_SIDEBAR_PLAYLIST_LIST = "${FS_GENERAL_SIDEBAR_PLAYLIST_LIST}";
window.FS_GENERAL_SIDEBAR_PLAYLIST_SORTING = "${FS_GENERAL_SIDEBAR_PLAYLIST_SORTING}"; window.FS_GENERAL_SIDEBAR_PLAYLIST_SORTING = "${FS_GENERAL_SIDEBAR_PLAYLIST_SORTING}";
window.FS_GENERAL_SIDE_QUEUE_TYPE = "${FS_GENERAL_SIDE_QUEUE_TYPE}"; window.FS_GENERAL_SIDE_QUEUE_TYPE = "${FS_GENERAL_SIDE_QUEUE_TYPE}";
window.FS_GENERAL_SIDE_QUEUE_LAYOUT = "${FS_GENERAL_SIDE_QUEUE_LAYOUT}";
window.FS_GENERAL_THEME = "${FS_GENERAL_THEME}"; window.FS_GENERAL_THEME = "${FS_GENERAL_THEME}";
window.FS_GENERAL_THEME_DARK = "${FS_GENERAL_THEME_DARK}"; window.FS_GENERAL_THEME_DARK = "${FS_GENERAL_THEME_DARK}";
window.FS_GENERAL_THEME_LIGHT = "${FS_GENERAL_THEME_LIGHT}"; window.FS_GENERAL_THEME_LIGHT = "${FS_GENERAL_THEME_LIGHT}";
+4
View File
@@ -1036,6 +1036,10 @@
"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",
"sidePlayQueueLayout": "side play queue layout",
"sidePlayQueueLayout_description": "sets the layout of the attached side play queue",
"sidePlayQueueLayout_optionHorizontal": "horizontal",
"sidePlayQueueLayout_optionVertical": "vertical",
"mediaSession_description": "enables Media Session integration, displaying media controls and metadata in the system volume overlay and lock screen", "mediaSession_description": "enables Media Session integration, displaying media controls and metadata in the system volume overlay and lock screen",
"mediaSession": "enable media session", "mediaSession": "enable media session",
"sidePlayQueueStyle": "side play queue style", "sidePlayQueueStyle": "side play queue style",
@@ -20,6 +20,7 @@ import {
} from '/@/renderer/features/settings/components/settings-section'; } from '/@/renderer/features/settings/components/settings-section';
import { import {
HomeFeatureStyle, HomeFeatureStyle,
SideQueueLayout,
SideQueueType, SideQueueType,
useFontSettings, useFontSettings,
useGeneralSettings, useGeneralSettings,
@@ -74,6 +75,23 @@ const SIDE_QUEUE_OPTIONS = [
}, },
]; ];
const SIDE_QUEUE_LAYOUT_OPTIONS = [
{
label: t('setting.sidePlayQueueLayout', {
context: 'optionHorizontal',
postProcess: 'sentenceCase',
}),
value: 'horizontal',
},
{
label: t('setting.sidePlayQueueLayout', {
context: 'optionVertical',
postProcess: 'sentenceCase',
}),
value: 'vertical',
},
];
const FONT_TYPES: Font[] = [ const FONT_TYPES: Font[] = [
{ {
label: i18n.t('setting.fontType', { label: i18n.t('setting.fontType', {
@@ -539,6 +557,29 @@ export const ApplicationSettings = memo(() => {
isHidden: false, isHidden: false,
title: t('setting.sidePlayQueueStyle', { postProcess: 'sentenceCase' }), title: t('setting.sidePlayQueueStyle', { postProcess: 'sentenceCase' }),
}, },
{
control: (
<SegmentedControl
aria-label={t('setting.sidePlayQueueLayout', { postProcess: 'sentenceCase' })}
data={SIDE_QUEUE_LAYOUT_OPTIONS}
defaultValue={settings.sideQueueLayout}
onChange={(e) =>
setSettings({
general: {
...settings,
sideQueueLayout: e as SideQueueLayout,
},
})
}
/>
),
description: t('setting.sidePlayQueueLayout', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: settings.sideQueueType !== 'sideQueue',
title: t('setting.sidePlayQueueLayout', { postProcess: 'sentenceCase' }),
},
{ {
control: ( control: (
<Switch <Switch
@@ -21,6 +21,10 @@
.handle-top { .handle-top {
top: 0; top: 0;
left: 0;
width: 100%;
height: 4px;
cursor: ns-resize;
} }
.handle-right { .handle-right {
@@ -29,6 +33,10 @@
.handle-bottom { .handle-bottom {
bottom: 0; bottom: 0;
left: 0;
width: 100%;
height: 4px;
cursor: ns-resize;
} }
.handle-left { .handle-left {
@@ -10,8 +10,16 @@ import { isServerLock } from '/@/renderer/features/action-required/utils/window-
import { ServerList } from '/@/renderer/features/servers/components/server-list'; import { ServerList } from '/@/renderer/features/servers/components/server-list';
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal'; import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
import { openReleaseNotesModal } from '/@/renderer/release-notes-modal'; import { openReleaseNotesModal } from '/@/renderer/release-notes-modal';
import { useAppStore, useAppStoreActions, useCommandPalette } from '/@/renderer/store'; import {
useAppStore,
useAppStoreActions,
useCommandPalette,
useGeneralSettings,
useSettingsStoreActions,
} from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { DropdownMenu, MenuItemProps } from '/@/shared/components/dropdown-menu/dropdown-menu'; import { DropdownMenu, MenuItemProps } from '/@/shared/components/dropdown-menu/dropdown-menu';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
@@ -74,6 +82,8 @@ export const AppMenu = () => {
const collapsed = useAppStore((state) => state.sidebar.collapsed); const collapsed = useAppStore((state) => state.sidebar.collapsed);
const privateMode = useAppStore((state) => state.privateMode); const privateMode = useAppStore((state) => state.privateMode);
const { setPrivateMode, setSideBar } = useAppStoreActions(); const { setPrivateMode, setSideBar } = useAppStoreActions();
const { setSettings } = useSettingsStoreActions();
const settings = useGeneralSettings();
const { open: openCommandPalette } = useCommandPalette(); const { open: openCommandPalette } = useCommandPalette();
const handleBrowserDevTools = () => { const handleBrowserDevTools = () => {
@@ -115,6 +125,15 @@ export const AppMenu = () => {
browser?.quit(); browser?.quit();
}; };
const handleSetSideQueueLayout = (sideQueueLayout: 'horizontal' | 'vertical') => {
setSettings({
general: {
...settings,
sideQueueLayout,
},
});
};
const menuConfig: MenuItem[] = [ const menuConfig: MenuItem[] = [
{ {
icon: 'search', icon: 'search',
@@ -265,6 +284,61 @@ export const AppMenu = () => {
}, },
type: 'conditional-item', type: 'conditional-item',
}, },
{
id: 'divider-5',
type: 'divider',
},
{
condition: settings.sideQueueType === 'sideQueue',
id: 'layout-toggle-group',
items: [
{
component: (
<Group gap="xs" grow w="100%">
<ActionIcon
icon="layoutPanelRight"
iconProps={{
size: 'xl',
}}
onClick={() => handleSetSideQueueLayout('horizontal')}
tooltip={{
label: t('setting.sidePlayQueueLayout', {
context: 'optionHorizontal',
postProcess: 'sentenceCase',
}),
openDelay: 0,
position: 'bottom',
}}
variant={
settings.sideQueueLayout === 'horizontal' ? 'light' : 'default'
}
/>
<ActionIcon
icon="layoutPanelBottom"
iconProps={{
size: 'xl',
}}
onClick={() => handleSetSideQueueLayout('vertical')}
tooltip={{
label: t('setting.sidePlayQueueLayout', {
context: 'optionVertical',
postProcess: 'sentenceCase',
}),
openDelay: 0,
position: 'bottom',
}}
variant={
settings.sideQueueLayout === 'vertical' ? 'light' : 'default'
}
/>
</Group>
),
id: 'layout-toggle',
type: 'custom',
},
],
type: 'conditional-group',
},
]; ];
const renderMenuItem = (item: MenuItem): ReactNode => { const renderMenuItem = (item: MenuItem): ReactNode => {
@@ -28,6 +28,23 @@
grid-template-columns: 80px 1fr var(--right-sidebar-width); grid-template-columns: 80px 1fr var(--right-sidebar-width);
} }
.main-content-container.vertical-layout {
grid-template-areas:
'sidebar .'
'sidebar right-sidebar';
grid-template-rows: minmax(0, 1fr) var(--right-sidebar-height);
grid-template-columns: var(--sidebar-width) 1fr;
}
.main-content-container.sidebar-collapsed.vertical-layout {
grid-template-columns: 80px 1fr;
}
.main-content-container.vertical-layout #sidebar-queue {
border-top: 1px solid alpha(var(--theme-colors-border), 0.5);
border-left: 0;
}
.main-content-body { .main-content-body {
display: flex; display: flex;
flex: 1; flex: 1;
@@ -16,6 +16,7 @@ import {
useAppStore, useAppStore,
useAppStoreActions, useAppStoreActions,
useGlobalExpanded, useGlobalExpanded,
useSideQueueLayout,
useSideQueueType, useSideQueueType,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/renderer/utils'; import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/renderer/utils';
@@ -24,56 +25,77 @@ import { Spinner } from '/@/shared/components/spinner/spinner';
const MINIMUM_SIDEBAR_WIDTH = 260; const MINIMUM_SIDEBAR_WIDTH = 260;
export const MainContent = ({ shell }: { shell?: boolean }) => { export const MainContent = ({ shell }: { shell?: boolean }) => {
const { collapsed, leftWidth, rightExpanded, rightWidth } = useAppStore( const { collapsed, leftWidth, rightExpanded, rightHeight, rightWidth } = useAppStore(
(state) => ({ (state) => ({
collapsed: state.sidebar.collapsed, collapsed: state.sidebar.collapsed,
leftWidth: state.sidebar.leftWidth, leftWidth: state.sidebar.leftWidth,
rightExpanded: state.sidebar.rightExpanded, rightExpanded: state.sidebar.rightExpanded,
rightHeight: state.sidebar.rightHeight,
rightWidth: state.sidebar.rightWidth, rightWidth: state.sidebar.rightWidth,
}), }),
shallow, shallow,
); );
const { setSideBar } = useAppStoreActions(); const { setSideBar } = useAppStoreActions();
const sideQueueType = useSideQueueType(); const sideQueueType = useSideQueueType();
const sideQueueLayout = useSideQueueLayout();
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const [isResizingRight, setIsResizingRight] = useState(false); const [isResizingRight, setIsResizingRight] = useState(false);
const rightSidebarRef = useRef<HTMLDivElement | null>(null); const rightSidebarRef = useRef<HTMLDivElement | null>(null);
const mainContentRef = useRef<HTMLDivElement | null>(null); const mainContentRef = useRef<HTMLDivElement | null>(null);
const initialRightWidthRef = useRef<string>(rightWidth); const initialRightWidthRef = useRef<string>(rightWidth);
const initialRightHeightRef = useRef<string>(rightHeight);
const initialMouseXRef = useRef<number>(0); const initialMouseXRef = useRef<number>(0);
const initialMouseYRef = useRef<number>(0);
const wasCollapsedDuringDragRef = useRef<boolean>(false); const wasCollapsedDuringDragRef = useRef<boolean>(false);
useEffect(() => { useEffect(() => {
if (mainContentRef.current && !isResizing && !isResizingRight) { if (mainContentRef.current && !isResizing && !isResizingRight) {
mainContentRef.current.style.setProperty('--sidebar-width', leftWidth); mainContentRef.current.style.setProperty('--sidebar-width', leftWidth);
mainContentRef.current.style.setProperty('--right-sidebar-width', rightWidth); mainContentRef.current.style.setProperty('--right-sidebar-width', rightWidth);
mainContentRef.current.style.setProperty('--right-sidebar-height', rightHeight);
initialRightWidthRef.current = rightWidth; initialRightWidthRef.current = rightWidth;
initialRightHeightRef.current = rightHeight;
} }
}, [leftWidth, rightWidth, isResizing, isResizingRight]); }, [leftWidth, rightWidth, rightHeight, isResizing, isResizingRight]);
const startResizing = useCallback( const startResizing = useCallback(
(position: 'left' | 'right', mouseEvent?: MouseEvent) => { (position: 'left' | 'right' | 'top', mouseEvent?: MouseEvent) => {
if (position === 'left') { if (position === 'left') {
setIsResizing(true); setIsResizing(true);
wasCollapsedDuringDragRef.current = false; wasCollapsedDuringDragRef.current = false;
} else { } else {
setIsResizingRight(true); setIsResizingRight(true);
if (mainContentRef.current && rightSidebarRef.current && mouseEvent) { if (mainContentRef.current && rightSidebarRef.current && mouseEvent) {
const currentWidth = if (position === 'top') {
mainContentRef.current.style.getPropertyValue('--right-sidebar-width'); const currentHeight =
if (currentWidth) { mainContentRef.current.style.getPropertyValue('--right-sidebar-height');
initialRightWidthRef.current = currentWidth; if (currentHeight) {
initialRightHeightRef.current = currentHeight;
} else {
initialRightHeightRef.current = rightHeight;
}
initialMouseYRef.current = mouseEvent.clientY;
} else {
const currentWidth =
mainContentRef.current.style.getPropertyValue('--right-sidebar-width');
if (currentWidth) {
initialRightWidthRef.current = currentWidth;
} else {
initialRightWidthRef.current = rightWidth;
}
initialMouseXRef.current = mouseEvent.clientX;
}
} else {
if (position === 'top') {
initialRightHeightRef.current = rightHeight;
} else { } else {
initialRightWidthRef.current = rightWidth; initialRightWidthRef.current = rightWidth;
} }
initialMouseXRef.current = mouseEvent.clientX;
} else {
initialRightWidthRef.current = rightWidth;
} }
} }
}, },
[rightWidth], [rightHeight, rightWidth],
); );
const stopResizing = useCallback(() => { const stopResizing = useCallback(() => {
@@ -87,14 +109,22 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
setIsResizing(false); setIsResizing(false);
wasCollapsedDuringDragRef.current = false; wasCollapsedDuringDragRef.current = false;
} else if (isResizingRight && mainContentRef.current) { } else if (isResizingRight && mainContentRef.current) {
const finalWidth = if (sideQueueLayout === 'vertical') {
mainContentRef.current.style.getPropertyValue('--right-sidebar-width'); const finalHeight =
if (finalWidth) { mainContentRef.current.style.getPropertyValue('--right-sidebar-height');
setSideBar({ rightWidth: finalWidth }); if (finalHeight) {
setSideBar({ rightHeight: finalHeight });
}
} else {
const finalWidth =
mainContentRef.current.style.getPropertyValue('--right-sidebar-width');
if (finalWidth) {
setSideBar({ rightWidth: finalWidth });
}
} }
setIsResizingRight(false); setIsResizingRight(false);
} }
}, [isResizing, isResizingRight, setSideBar]); }, [isResizing, isResizingRight, setSideBar, sideQueueLayout]);
const resize = useCallback( const resize = useCallback(
(mouseMoveEvent: any) => { (mouseMoveEvent: any) => {
@@ -118,15 +148,30 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
mainContentRef.current.style.setProperty('--sidebar-width', constrainedWidth); mainContentRef.current.style.setProperty('--sidebar-width', constrainedWidth);
} }
} else if (isResizingRight) { } else if (isResizingRight) {
const initialWidth = Number(initialRightWidthRef.current.split('px')[0]); if (sideQueueLayout === 'vertical') {
const initialMouseX = initialMouseXRef.current; const initialHeight = Number(initialRightHeightRef.current.split('px')[0]);
const deltaX = mouseMoveEvent.clientX - initialMouseX; const initialMouseY = initialMouseYRef.current;
const newWidth = initialWidth - deltaX; const deltaY = mouseMoveEvent.clientY - initialMouseY;
const width = `${constrainRightSidebarWidth(newWidth)}px`; const containerHeight = mainContentRef.current.clientHeight;
mainContentRef.current.style.setProperty('--right-sidebar-width', width); const minHeight = 220;
const maxHeight = Math.max(minHeight, containerHeight - 200);
const newHeight = initialHeight - deltaY;
const clampedHeight = Math.min(Math.max(newHeight, minHeight), maxHeight);
mainContentRef.current.style.setProperty(
'--right-sidebar-height',
`${clampedHeight}px`,
);
} else {
const initialWidth = Number(initialRightWidthRef.current.split('px')[0]);
const initialMouseX = initialMouseXRef.current;
const deltaX = mouseMoveEvent.clientX - initialMouseX;
const newWidth = initialWidth - deltaX;
const width = `${constrainRightSidebarWidth(newWidth)}px`;
mainContentRef.current.style.setProperty('--right-sidebar-width', width);
}
} }
}, },
[isResizing, isResizingRight, setSideBar], [isResizing, isResizingRight, setSideBar, sideQueueLayout],
); );
useEffect(() => { useEffect(() => {
@@ -145,6 +190,10 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
[styles.shell]: shell, [styles.shell]: shell,
[styles.sidebarCollapsed]: collapsed, [styles.sidebarCollapsed]: collapsed,
[styles.sidebarExpanded]: !collapsed, [styles.sidebarExpanded]: !collapsed,
[styles.verticalLayout]:
rightExpanded &&
sideQueueType === 'sideQueue' &&
sideQueueLayout === 'vertical',
})} })}
id="main-content" id="main-content"
ref={mainContentRef} ref={mainContentRef}
@@ -14,6 +14,11 @@
} }
} }
.right-sidebar-container.vertical-layout {
border-top: 1px solid alpha(var(--theme-colors-border), 0.5);
border-left: 0;
}
.queue-drawer { .queue-drawer {
border-radius: var(--theme-radius-lg); border-radius: var(--theme-radius-lg);
} }
@@ -1,10 +1,11 @@
import clsx from 'clsx';
import { forwardRef, Ref } from 'react'; import { forwardRef, Ref } from 'react';
import styles from './right-sidebar.module.css'; import styles from './right-sidebar.module.css';
import { SidebarPlayQueue } from '/@/renderer/features/now-playing/components/sidebar-play-queue'; import { SidebarPlayQueue } from '/@/renderer/features/now-playing/components/sidebar-play-queue';
import { ResizeHandle } from '/@/renderer/features/shared/components/resize-handle'; import { ResizeHandle } from '/@/renderer/features/shared/components/resize-handle';
import { useAppStore, useSideQueueType } from '/@/renderer/store'; import { useAppStore, useSideQueueLayout, useSideQueueType } from '/@/renderer/store';
// const queueDrawerVariants: Variants = { // const queueDrawerVariants: Variants = {
// closed: (windowBarStyle) => ({ // closed: (windowBarStyle) => ({
@@ -46,7 +47,7 @@ import { useAppStore, useSideQueueType } from '/@/renderer/store';
interface RightSidebarProps { interface RightSidebarProps {
isResizing: boolean; isResizing: boolean;
startResizing: (direction: 'left' | 'right', mouseEvent?: MouseEvent) => void; startResizing: (direction: 'left' | 'right' | 'top', mouseEvent?: MouseEvent) => void;
} }
export const RightSidebar = forwardRef( export const RightSidebar = forwardRef(
@@ -56,12 +57,16 @@ export const RightSidebar = forwardRef(
) => { ) => {
const rightExpanded = useAppStore((state) => state.sidebar.rightExpanded); const rightExpanded = useAppStore((state) => state.sidebar.rightExpanded);
const sideQueueType = useSideQueueType(); const sideQueueType = useSideQueueType();
const sideQueueLayout = useSideQueueLayout();
const isVerticalLayout = sideQueueLayout === 'vertical';
return ( return (
<> <>
{rightExpanded && sideQueueType === 'sideQueue' && ( {rightExpanded && sideQueueType === 'sideQueue' && (
<aside <aside
className={styles.rightSidebarContainer} className={clsx(styles.rightSidebarContainer, {
[styles.verticalLayout]: isVerticalLayout,
})}
id="sidebar-queue" id="sidebar-queue"
key="queue-sidebar" key="queue-sidebar"
> >
@@ -69,9 +74,9 @@ export const RightSidebar = forwardRef(
isResizing={isResizingRight} isResizing={isResizingRight}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
startResizing('right', e.nativeEvent); startResizing(isVerticalLayout ? 'top' : 'right', e.nativeEvent);
}} }}
placement="left" placement={isVerticalLayout ? 'top' : 'left'}
ref={ref} ref={ref}
/> />
<SidebarPlayQueue /> <SidebarPlayQueue />
+9 -2
View File
@@ -75,6 +75,7 @@ type SidebarProps = {
image: boolean; image: boolean;
leftWidth: string; leftWidth: string;
rightExpanded: boolean; rightExpanded: boolean;
rightHeight: string;
rightWidth: string; rightWidth: string;
}; };
@@ -222,6 +223,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
image: false, image: false,
leftWidth: '400px', leftWidth: '400px',
rightExpanded: false, rightExpanded: false,
rightHeight: '320px',
rightWidth: '600px', rightWidth: '600px',
}, },
titlebar: { titlebar: {
@@ -240,7 +242,12 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
return {} as AppState; return {} as AppState;
} }
return persistedState; const state = persistedState as AppState;
if (version <= 4 && !state.sidebar.rightHeight) {
state.sidebar.rightHeight = '320px';
}
return state;
}, },
name: 'store_app', name: 'store_app',
partialize: (state) => { partialize: (state) => {
@@ -248,7 +255,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
const { globalExpanded: _, ...rest } = state; const { globalExpanded: _, ...rest } = state;
return rest; return rest;
}, },
version: 4, version: 5,
}, },
), ),
); );
@@ -40,6 +40,7 @@ const LYRICS_ALIGNMENTS = new Set(['center', 'left', 'right']);
const FONT_TYPES = new Set(['builtIn', 'custom', 'system']); const FONT_TYPES = new Set(['builtIn', 'custom', 'system']);
const HOME_FEATURE_STYLES = new Set(['multiple', 'single']); const HOME_FEATURE_STYLES = new Set(['multiple', 'single']);
const SIDE_QUEUE_TYPES = new Set(['sideDrawerQueue', 'sideQueue']); const SIDE_QUEUE_TYPES = new Set(['sideDrawerQueue', 'sideQueue']);
const SIDE_QUEUE_LAYOUTS = new Set(['horizontal', 'vertical']);
export type EnvSettingsOverrides = DeepPartial< export type EnvSettingsOverrides = DeepPartial<
Pick<SettingsState, 'autoDJ' | 'css' | 'discord' | 'font' | 'general' | 'lyrics' | 'playback'> Pick<SettingsState, 'autoDJ' | 'css' | 'discord' | 'font' | 'general' | 'lyrics' | 'playback'>
@@ -200,6 +201,12 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
path: ['general', 'sideQueueType'], path: ['general', 'sideQueueType'],
type: 'enum', type: 'enum',
}, },
{
enumSet: SIDE_QUEUE_LAYOUTS,
key: 'FS_GENERAL_SIDE_QUEUE_LAYOUT',
path: ['general', 'sideQueueLayout'],
type: 'enum',
},
{ key: 'FS_GENERAL_RESUME', path: ['general', 'resume'], type: 'bool' }, { key: 'FS_GENERAL_RESUME', path: ['general', 'resume'], type: 'bool' },
{ {
key: 'FS_GENERAL_USE_THEME_ACCENT_COLOR', key: 'FS_GENERAL_USE_THEME_ACCENT_COLOR',
+14 -1
View File
@@ -171,6 +171,7 @@ const GenreTargetSchema = z.enum(['album', 'track']);
const PlaylistTargetSchema = z.enum(['album', 'track']); const PlaylistTargetSchema = z.enum(['album', 'track']);
const SideQueueTypeSchema = z.enum(['sideDrawerQueue', 'sideQueue']); const SideQueueTypeSchema = z.enum(['sideDrawerQueue', 'sideQueue']);
const SideQueueLayoutSchema = z.enum(['horizontal', 'vertical']);
const SidebarPanelTypeSchema = z.enum(['queue', 'lyrics', 'visualizer']); const SidebarPanelTypeSchema = z.enum(['queue', 'lyrics', 'visualizer']);
@@ -498,6 +499,7 @@ export const GeneralSettingsSchema = z.object({
sidebarPlaylistList: z.boolean(), sidebarPlaylistList: z.boolean(),
sidebarPlaylistListFilterRegex: z.string(), sidebarPlaylistListFilterRegex: z.string(),
sidebarPlaylistSorting: z.boolean(), sidebarPlaylistSorting: z.boolean(),
sideQueueLayout: SideQueueLayoutSchema,
sideQueueType: SideQueueTypeSchema, sideQueueType: SideQueueTypeSchema,
skipButtons: SkipButtonsSchema, skipButtons: SkipButtonsSchema,
spotify: z.boolean(), spotify: z.boolean(),
@@ -893,6 +895,7 @@ export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
export interface SettingsState extends z.infer<typeof SettingsStateSchema> {} export interface SettingsState extends z.infer<typeof SettingsStateSchema> {}
export type SidebarItemType = z.infer<typeof SidebarItemTypeSchema>; export type SidebarItemType = z.infer<typeof SidebarItemTypeSchema>;
export type SideQueueLayout = z.infer<typeof SideQueueLayoutSchema>;
export type SideQueueType = z.infer<typeof SideQueueTypeSchema>; export type SideQueueType = z.infer<typeof SideQueueTypeSchema>;
export type SortableItem<T extends string> = { export type SortableItem<T extends string> = {
@@ -1158,6 +1161,7 @@ const initialState: SettingsState = {
sidebarPlaylistList: true, sidebarPlaylistList: true,
sidebarPlaylistListFilterRegex: '', sidebarPlaylistListFilterRegex: '',
sidebarPlaylistSorting: false, sidebarPlaylistSorting: false,
sideQueueLayout: 'horizontal',
sideQueueType: 'sideQueue', sideQueueType: 'sideQueue',
skipButtons: { skipButtons: {
enabled: false, enabled: false,
@@ -2381,10 +2385,16 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
}); });
} }
if (version <= 27) {
if (!state.general.sideQueueLayout) {
state.general.sideQueueLayout = initialState.general.sideQueueLayout;
}
}
return persistedState; return persistedState;
}, },
name: 'store_settings', name: 'store_settings',
version: 26, version: 27,
}, },
), ),
); );
@@ -2492,6 +2502,9 @@ export const useThemeSettings = () =>
export const useSideQueueType = () => export const useSideQueueType = () =>
useSettingsStore((state) => state.general.sideQueueType, shallow); useSettingsStore((state) => state.general.sideQueueType, shallow);
export const useSideQueueLayout = () =>
useSettingsStore((state) => state.general.sideQueueLayout, shallow);
export const useVolumeWheelStep = () => export const useVolumeWheelStep = () =>
useSettingsStore((state) => state.general.volumeWheelStep, shallow); useSettingsStore((state) => state.general.volumeWheelStep, shallow);
+4
View File
@@ -79,6 +79,8 @@ import {
LuMusic, LuMusic,
LuMusic2, LuMusic2,
LuPackage2, LuPackage2,
LuPanelBottom,
LuPanelRight,
LuPanelRightClose, LuPanelRightClose,
LuPanelRightOpen, LuPanelRightOpen,
LuPause, LuPause,
@@ -198,6 +200,8 @@ export const AppIcon = {
layoutDetail: LuLayoutList, layoutDetail: LuLayoutList,
layoutGrid: LuLayoutGrid, layoutGrid: LuLayoutGrid,
layoutList: LuList, layoutList: LuList,
layoutPanelBottom: LuPanelBottom,
layoutPanelRight: LuPanelRight,
layoutTable: LuTable, layoutTable: LuTable,
library: LuLibrary, library: LuLibrary,
list: LuList, list: LuList,