persist command palette collapsed sections to app store

This commit is contained in:
jeffvli
2026-03-17 22:34:07 -07:00
parent 3c562c1398
commit b7cbdb4d6c
3 changed files with 65 additions and 7 deletions
@@ -8,19 +8,30 @@ import { Paper } from '/@/shared/components/paper/paper';
interface CollapsibleCommandGroupProps { interface CollapsibleCommandGroupProps {
children: ReactNode; children: ReactNode;
defaultExpanded?: boolean; defaultExpanded?: boolean;
expanded?: boolean;
heading: string; heading: string;
onToggle?: () => void;
} }
export function CollapsibleCommandGroup({ export function CollapsibleCommandGroup({
children, children,
defaultExpanded = true, defaultExpanded = true,
expanded: controlledExpanded,
heading, heading,
onToggle,
}: CollapsibleCommandGroupProps) { }: CollapsibleCommandGroupProps) {
const [expanded, setExpanded] = useState(defaultExpanded); const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
const isControlled = controlledExpanded !== undefined && onToggle !== undefined;
const expanded = isControlled ? controlledExpanded : internalExpanded;
const toggle = useCallback(() => { const toggle = useCallback(() => {
setExpanded((prev) => !prev); if (isControlled) {
}, []); onToggle?.();
} else {
setInternalExpanded((prev) => !prev);
}
}, [isControlled, onToggle]);
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => { (e: React.KeyboardEvent) => {
@@ -12,7 +12,7 @@ import { HomeCommands } from '/@/renderer/features/search/components/home-comman
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item'; import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
import { ServerCommands } from '/@/renderer/features/search/components/server-commands'; import { ServerCommands } from '/@/renderer/features/search/components/server-commands';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { useAppStore, useCurrentServer } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Breadcrumb } from '/@/shared/components/breadcrumb/breadcrumb'; import { Breadcrumb } from '/@/shared/components/breadcrumb/breadcrumb';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
@@ -31,9 +31,21 @@ interface CommandPaletteProps {
modalProps: (typeof useDisclosure)['arguments']; modalProps: (typeof useDisclosure)['arguments'];
} }
const SEARCH_SECTION_IDS = {
albums: 'albums',
artists: 'artists',
tracks: 'tracks',
} as const;
export const CommandPalette = ({ modalProps }: CommandPaletteProps) => { export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const server = useCurrentServer(); const server = useCurrentServer();
const searchSectionsExpanded = useAppStore(
(state) => state.commandPaletteSearchSectionsExpanded,
);
const setSearchSectionExpanded = useAppStore(
(state) => state.actions.setCommandPaletteSearchSectionExpanded,
);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [debouncedQuery] = useDebouncedValue(query, 400); const [debouncedQuery] = useDebouncedValue(query, 400);
@@ -148,7 +160,16 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
{t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })} {t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })}
</Command.Empty> </Command.Empty>
{showAlbumGroup && ( {showAlbumGroup && (
<CollapsibleCommandGroup heading="Albums"> <CollapsibleCommandGroup
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.albums] ?? true}
heading="Albums"
onToggle={() =>
setSearchSectionExpanded(
SEARCH_SECTION_IDS.albums,
!(searchSectionsExpanded[SEARCH_SECTION_IDS.albums] ?? true),
)
}
>
{data?.albums?.map((album) => ( {data?.albums?.map((album) => (
<CommandItemSelectable <CommandItemSelectable
key={`search-album-${album.id}`} key={`search-album-${album.id}`}
@@ -182,7 +203,16 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
</CollapsibleCommandGroup> </CollapsibleCommandGroup>
)} )}
{showArtistGroup && ( {showArtistGroup && (
<CollapsibleCommandGroup heading="Artists"> <CollapsibleCommandGroup
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.artists] ?? true}
heading="Artists"
onToggle={() =>
setSearchSectionExpanded(
SEARCH_SECTION_IDS.artists,
!(searchSectionsExpanded[SEARCH_SECTION_IDS.artists] ?? true),
)
}
>
{data?.albumArtists.map((artist) => ( {data?.albumArtists.map((artist) => (
<CommandItemSelectable <CommandItemSelectable
key={`artist-${artist.id}`} key={`artist-${artist.id}`}
@@ -221,7 +251,16 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
</CollapsibleCommandGroup> </CollapsibleCommandGroup>
)} )}
{showTrackGroup && ( {showTrackGroup && (
<CollapsibleCommandGroup heading="Tracks"> <CollapsibleCommandGroup
expanded={searchSectionsExpanded[SEARCH_SECTION_IDS.tracks] ?? true}
heading="Tracks"
onToggle={() =>
setSearchSectionExpanded(
SEARCH_SECTION_IDS.tracks,
!(searchSectionsExpanded[SEARCH_SECTION_IDS.tracks] ?? true),
)
}
>
{data?.songs.map((song) => ( {data?.songs.map((song) => (
<CommandItemSelectable <CommandItemSelectable
key={`artist-${song.id}`} key={`artist-${song.id}`}
+8
View File
@@ -19,6 +19,7 @@ export interface AppSlice extends AppState {
setAppStore: (data: Partial<AppSlice>) => void; setAppStore: (data: Partial<AppSlice>) => void;
setArtistIdsMode: (mode: 'and' | 'or') => void; setArtistIdsMode: (mode: 'and' | 'or') => void;
setArtistSelectMode: (mode: 'multi' | 'single') => void; setArtistSelectMode: (mode: 'multi' | 'single') => void;
setCommandPaletteSearchSectionExpanded: (sectionId: string, expanded: boolean) => void;
setGenreIdsMode: (mode: 'and' | 'or') => void; setGenreIdsMode: (mode: 'and' | 'or') => void;
setGenreSelectMode: (mode: 'multi' | 'single') => void; setGenreSelectMode: (mode: 'multi' | 'single') => void;
setGlobalExpanded: (value: GlobalExpandedState | null) => void; setGlobalExpanded: (value: GlobalExpandedState | null) => void;
@@ -45,6 +46,7 @@ export interface AppState {
artistIdsMode: 'and' | 'or'; artistIdsMode: 'and' | 'or';
artistSelectMode: 'multi' | 'single'; artistSelectMode: 'multi' | 'single';
commandPalette: CommandPaletteProps; commandPalette: CommandPaletteProps;
commandPaletteSearchSectionsExpanded: Record<string, boolean>;
genreIdsMode: 'and' | 'or'; genreIdsMode: 'and' | 'or';
genreSelectMode: 'multi' | 'single'; genreSelectMode: 'multi' | 'single';
globalExpanded: GlobalExpandedState | null; globalExpanded: GlobalExpandedState | null;
@@ -134,6 +136,11 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
state.artistSelectMode = mode; state.artistSelectMode = mode;
}); });
}, },
setCommandPaletteSearchSectionExpanded: (sectionId, expanded) => {
set((state) => {
state.commandPaletteSearchSectionsExpanded[sectionId] = expanded;
});
},
setGenreIdsMode: (mode) => { setGenreIdsMode: (mode) => {
set((state) => { set((state) => {
state.genreIdsMode = mode; state.genreIdsMode = mode;
@@ -206,6 +213,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
}); });
}, },
}, },
commandPaletteSearchSectionsExpanded: {},
genreIdsMode: 'and', genreIdsMode: 'and',
genreSelectMode: 'multi', genreSelectMode: 'multi',
globalExpanded: null, globalExpanded: null,