mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
persist command palette collapsed sections to app store
This commit is contained in:
@@ -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}`}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user