allow user to unpin list sidebar

This commit is contained in:
jeffvli
2025-12-13 02:03:04 -08:00
parent ab1176d4f6
commit a14e5f08ee
13 changed files with 193 additions and 23 deletions
+2
View File
@@ -5,11 +5,13 @@ import { ItemListKey } from '/@/shared/types/types';
interface ListContextProps { interface ListContextProps {
customFilters?: Record<string, unknown>; customFilters?: Record<string, unknown>;
id?: string; id?: string;
isSidebarOpen?: boolean;
isSmartPlaylist?: boolean; isSmartPlaylist?: boolean;
itemCount?: number; itemCount?: number;
listData?: unknown[]; listData?: unknown[];
mode?: 'edit' | 'view'; mode?: 'edit' | 'view';
pageKey: ItemListKey | string; pageKey: ItemListKey | string;
setIsSidebarOpen?: (isSidebarOpen: boolean) => void;
setItemCount?: (itemCount: number) => void; setItemCount?: (itemCount: number) => void;
setListData?: (items: unknown[]) => void; setListData?: (items: unknown[]) => void;
setMode?: (mode: 'edit' | 'view') => void; setMode?: (mode: 'edit' | 'view') => void;
@@ -2,11 +2,12 @@ import { lazy, Suspense, useMemo } from 'react';
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; import { ListFilters, ListFiltersTitle } from '/@/renderer/features/shared/components/list-filters';
import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container'; import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container';
import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store'; import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Spinner } from '/@/shared/components/spinner/spinner'; import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { AlbumListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { AlbumListQuery, LibraryItem } from '/@/shared/types/domain-types';
import { ItemListKey, ListDisplayType, ListPaginationType } from '/@/shared/types/types'; import { ItemListKey, ListDisplayType, ListPaginationType } from '/@/shared/types/types';
@@ -37,9 +38,12 @@ const AlbumListPaginatedTable = lazy(() =>
const AlbumListFilters = () => { const AlbumListFilters = () => {
return ( return (
<ListWithSidebarContainer.SidebarPortal> <ListWithSidebarContainer.SidebarPortal>
<ScrollArea> <Stack h="100%">
<ListFilters itemType={LibraryItem.ALBUM} /> <ListFiltersTitle />
</ScrollArea> <ScrollArea>
<ListFilters itemType={LibraryItem.ALBUM} />
</ScrollArea>
</Stack>
</ListWithSidebarContainer.SidebarPortal> </ListWithSidebarContainer.SidebarPortal>
); );
}; };
@@ -6,10 +6,14 @@ import { useListContext } from '/@/renderer/context/list-context';
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import { ListDisplayTypeToggleButton } from '/@/renderer/features/shared/components/list-display-type-toggle-button'; import { ListDisplayTypeToggleButton } from '/@/renderer/features/shared/components/list-display-type-toggle-button';
import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import {
isFilterValueSet,
ListFiltersModal,
} from '/@/renderer/features/shared/components/list-filters';
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters'; import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
import { GenreTarget, useGenreTarget, useSettingsStoreActions } from '/@/renderer/store'; import { GenreTarget, useGenreTarget, useSettingsStoreActions } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
@@ -36,14 +40,29 @@ export const AlbumListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarge
}, [target, t]); }, [target, t]);
const handleToggleGenreTarget = useCallback(() => { const handleToggleGenreTarget = useCallback(() => {
// Clear all filter query states
albumFilters.clear(); albumFilters.clear();
songFilters.clear(); songFilters.clear();
// Toggle the genre target
setGenreBehavior(target === GenreTarget.ALBUM ? GenreTarget.TRACK : GenreTarget.ALBUM); setGenreBehavior(target === GenreTarget.ALBUM ? GenreTarget.TRACK : GenreTarget.ALBUM);
}, [target, setGenreBehavior, albumFilters, songFilters]); }, [target, setGenreBehavior, albumFilters, songFilters]);
const hasActiveFilters = useMemo(() => {
const query = albumFilters.query;
return Boolean(
isFilterValueSet(query[FILTER_KEYS.ALBUM._CUSTOM]) ||
isFilterValueSet(query[FILTER_KEYS.ALBUM.ARTIST_IDS]) ||
query[FILTER_KEYS.ALBUM.COMPILATION] !== undefined ||
query[FILTER_KEYS.ALBUM.FAVORITE] !== undefined ||
isFilterValueSet(query[FILTER_KEYS.ALBUM.GENRE_ID]) ||
query[FILTER_KEYS.ALBUM.HAS_RATING] !== undefined ||
isFilterValueSet(query[FILTER_KEYS.ALBUM.MAX_YEAR]) ||
isFilterValueSet(query[FILTER_KEYS.ALBUM.MIN_YEAR]) ||
query[FILTER_KEYS.ALBUM.RECENTLY_PLAYED] !== undefined ||
isFilterValueSet(query[FILTER_KEYS.SHARED.SEARCH_TERM]),
);
}, [albumFilters.query]);
return ( return (
<Flex justify="space-between"> <Flex justify="space-between">
<Group gap="sm" w="100%"> <Group gap="sm" w="100%">
@@ -69,7 +88,7 @@ export const AlbumListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarge
defaultSortOrder={SortOrder.ASC} defaultSortOrder={SortOrder.ASC}
listKey={pageKey as ItemListKey} listKey={pageKey as ItemListKey}
/> />
<ListFiltersModal itemType={LibraryItem.ALBUM} /> <ListFiltersModal isActive={hasActiveFilters} itemType={LibraryItem.ALBUM} />
<ListRefreshButton listKey={pageKey as ItemListKey} /> <ListRefreshButton listKey={pageKey as ItemListKey} />
</Group> </Group>
<Group gap="sm" wrap="nowrap"> <Group gap="sm" wrap="nowrap">
@@ -7,6 +7,7 @@ import { AlbumListHeader } from '/@/renderer/features/albums/components/album-li
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container'; import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container';
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
import { usePageSidebar } from '/@/renderer/store/app.store';
import { AlbumListQuery } from '/@/shared/types/domain-types'; import { AlbumListQuery } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types'; import { ItemListKey } from '/@/shared/types/types';
@@ -27,6 +28,7 @@ const AlbumListRoute = () => {
const pageKey = getPageKey({ albumArtistId, genreId }); const pageKey = getPageKey({ albumArtistId, genreId });
const [itemCount, setItemCount] = useState<number | undefined>(undefined); const [itemCount, setItemCount] = useState<number | undefined>(undefined);
const [isSidebarOpen, setIsSidebarOpen] = usePageSidebar(pageKey);
const customFilters: Partial<AlbumListQuery> = useMemo(() => { const customFilters: Partial<AlbumListQuery> = useMemo(() => {
if (albumArtistId) { if (albumArtistId) {
@@ -48,11 +50,21 @@ const AlbumListRoute = () => {
return { return {
customFilters, customFilters,
id: albumArtistId ?? genreId, id: albumArtistId ?? genreId,
isSidebarOpen,
itemCount, itemCount,
pageKey, pageKey,
setIsSidebarOpen,
setItemCount, setItemCount,
}; };
}, [albumArtistId, customFilters, genreId, itemCount, pageKey]); }, [
albumArtistId,
customFilters,
genreId,
isSidebarOpen,
itemCount,
pageKey,
setIsSidebarOpen,
]);
return ( return (
<AnimatedPage> <AnimatedPage>
@@ -20,13 +20,13 @@ const FolderListRoute = () => {
pageKey, pageKey,
setItemCount, setItemCount,
}; };
}, [itemCount, pageKey, setItemCount]); }, [itemCount, pageKey]);
return ( return (
<AnimatedPage> <AnimatedPage>
<ListContext.Provider value={providerValue}> <ListContext.Provider value={providerValue}>
<FolderListHeader /> <FolderListHeader />
<ListWithSidebarContainer> <ListWithSidebarContainer useBreakpoint>
<FolderListContent /> <FolderListContent />
</ListWithSidebarContainer> </ListWithSidebarContainer>
</ListContext.Provider> </ListContext.Provider>
@@ -1,6 +1,7 @@
import { Suspense } from 'react'; import { Suspense } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListContext } from '/@/renderer/context/list-context';
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters'; import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters'; import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/subsonic-album-filters'; import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/subsonic-album-filters';
@@ -10,8 +11,11 @@ import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jelly
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters'; import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filters'; import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filters';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group';
import { Modal } from '/@/shared/components/modal/modal'; import { Modal } from '/@/shared/components/modal/modal';
import { Spinner } from '/@/shared/components/spinner/spinner'; import { Spinner } from '/@/shared/components/spinner/spinner';
import { Text } from '/@/shared/components/text/text';
import { useDisclosure } from '/@/shared/hooks/use-disclosure'; import { useDisclosure } from '/@/shared/hooks/use-disclosure';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
@@ -20,9 +24,18 @@ interface ListFiltersProps {
itemType: LibraryItem; itemType: LibraryItem;
} }
export const isFilterValueSet = (value: unknown): boolean => {
if (value === undefined || value === null) return false;
if (typeof value === 'string' && value.trim() === '') return false;
if (Array.isArray(value) && value.length === 0) return false;
if (typeof value === 'object' && Object.keys(value).length === 0) return false;
return true;
};
export const ListFiltersModal = ({ isActive, itemType }: ListFiltersProps) => { export const ListFiltersModal = ({ isActive, itemType }: ListFiltersProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const server = useCurrentServer(); const server = useCurrentServer();
const { isSidebarOpen, setIsSidebarOpen } = useListContext();
const serverType = server.type; const serverType = server.type;
@@ -30,13 +43,39 @@ export const ListFiltersModal = ({ isActive, itemType }: ListFiltersProps) => {
const [isOpen, handlers] = useDisclosure(false); const [isOpen, handlers] = useDisclosure(false);
const handlePin = () => {
setIsSidebarOpen?.(!isSidebarOpen);
};
const canPin = Boolean(setIsSidebarOpen);
return ( return (
<> <>
<FilterButton isActive={isActive} onClick={handlers.toggle} /> <FilterButton isActive={isActive} onClick={handlers.toggle} />
<Modal <Modal
handlers={handlers} handlers={handlers}
opened={isOpen} opened={isOpen}
title={t('common.filters', { postProcess: 'sentenceCase' })} size="lg"
styles={{
content: {
height: '100%',
maxHeight: '640px',
maxWidth: 'var(--theme-content-max-width)',
width: '100%',
},
}}
title={
<Group>
{canPin && (
<ActionIcon
icon={isSidebarOpen ? 'unpin' : 'pin'}
onClick={handlePin}
variant="subtle"
/>
)}
{t('common.filters', { postProcess: 'sentenceCase' })}
</Group>
}
> >
<FilterComponent /> <FilterComponent />
</Modal> </Modal>
@@ -58,6 +97,26 @@ export const ListFilters = ({ itemType }: ListFiltersProps) => {
); );
}; };
export const ListFiltersTitle = () => {
const { t } = useTranslation();
const { setIsSidebarOpen } = useListContext();
const handleUnpin = () => {
setIsSidebarOpen?.(false);
};
const canUnpin = Boolean(setIsSidebarOpen);
return (
<Group justify="space-between" p="md" pb={0}>
<Text fw={500} size="xl">
{t('common.filters', { postProcess: 'sentenceCase' })}
</Text>
{canUnpin && <ActionIcon icon="unpin" onClick={handleUnpin} variant="subtle" />}
</Group>
);
};
const FILTERS = { const FILTERS = {
[ServerType.JELLYFIN]: { [ServerType.JELLYFIN]: {
[LibraryItem.ALBUM]: JellyfinAlbumFilters, [LibraryItem.ALBUM]: JellyfinAlbumFilters,
@@ -21,10 +21,16 @@
border-right: 1px solid var(--theme-colors-border); border-right: 1px solid var(--theme-colors-border);
} }
@container (min-width: $mantine-breakpoint-lg) { @container (min-width: $mantine-breakpoint-xs) {
.sidebar-container { .container[data-sidebar-open='true'] .sidebar-container {
display: block; display: block;
} }
@container (min-width: $mantine-breakpoint-lg) {
.container[data-use-breakpoint='true'] .sidebar-container {
display: block;
}
}
} }
.content-container { .content-container {
@@ -3,6 +3,7 @@ import { createContext, ReactNode, useContext, useMemo, useRef } from 'react';
import styles from './list-with-sidebar-container.module.css'; import styles from './list-with-sidebar-container.module.css';
import { useListContext } from '/@/renderer/context/list-context';
import { animationProps } from '/@/shared/components/animations/animation-props'; import { animationProps } from '/@/shared/components/animations/animation-props';
import { Portal } from '/@/shared/components/portal/portal'; import { Portal } from '/@/shared/components/portal/portal';
@@ -17,6 +18,7 @@ const ListWithSidebarContainerContext = createContext<ListWithSidebarContainerCo
interface ListWithSidebarContainerProps { interface ListWithSidebarContainerProps {
children: ReactNode; children: ReactNode;
sidebarBreakpoint?: number; sidebarBreakpoint?: number;
useBreakpoint?: boolean;
} }
interface SidebarPortalProps { interface SidebarPortalProps {
@@ -63,9 +65,10 @@ function SidebarPortal({ children }: SidebarPortalProps) {
export const ListWithSidebarContainer = ({ export const ListWithSidebarContainer = ({
children, children,
sidebarBreakpoint = 1200, useBreakpoint = false,
}: ListWithSidebarContainerProps) => { }: ListWithSidebarContainerProps) => {
const sidebarRef = useRef<HTMLDivElement>(null); const sidebarRef = useRef<HTMLDivElement>(null);
const { isSidebarOpen = false } = useListContext();
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
@@ -76,7 +79,11 @@ export const ListWithSidebarContainer = ({
return ( return (
<ListWithSidebarContainerContext.Provider value={contextValue}> <ListWithSidebarContainerContext.Provider value={contextValue}>
<div className={styles.container} data-sidebar-breakpoint={sidebarBreakpoint}> <div
className={styles.container}
data-sidebar-open={useBreakpoint ? undefined : isSidebarOpen}
data-use-breakpoint={useBreakpoint}
>
<div className={styles.sidebarContainer} ref={sidebarRef} /> <div className={styles.sidebarContainer} ref={sidebarRef} />
<div className={styles.contentContainer}>{children}</div> <div className={styles.contentContainer}>{children}</div>
</div> </div>
@@ -1,12 +1,13 @@
import { lazy, Suspense, useMemo } from 'react'; import { lazy, Suspense, useMemo } from 'react';
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { ListFilters } from '/@/renderer/features/shared/components/list-filters'; import { ListFilters, ListFiltersTitle } from '/@/renderer/features/shared/components/list-filters';
import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container'; import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container';
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters'; import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store'; import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Spinner } from '/@/shared/components/spinner/spinner'; import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { LibraryItem, SongListQuery } from '/@/shared/types/domain-types'; import { LibraryItem, SongListQuery } from '/@/shared/types/domain-types';
import { ItemListKey, ListDisplayType, ListPaginationType } from '/@/shared/types/types'; import { ItemListKey, ListDisplayType, ListPaginationType } from '/@/shared/types/types';
@@ -43,9 +44,12 @@ export const SongListContent = () => {
const SongListFilters = () => { const SongListFilters = () => {
return ( return (
<ListWithSidebarContainer.SidebarPortal> <ListWithSidebarContainer.SidebarPortal>
<ScrollArea> <Stack h="100%">
<ListFilters itemType={LibraryItem.SONG} /> <ListFiltersTitle />
</ScrollArea> <ScrollArea>
<ListFilters itemType={LibraryItem.SONG} />
</ScrollArea>
</Stack>
</ListWithSidebarContainer.SidebarPortal> </ListWithSidebarContainer.SidebarPortal>
); );
}; };
@@ -6,10 +6,14 @@ import { useListContext } from '/@/renderer/context/list-context';
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import { ListDisplayTypeToggleButton } from '/@/renderer/features/shared/components/list-display-type-toggle-button'; import { ListDisplayTypeToggleButton } from '/@/renderer/features/shared/components/list-display-type-toggle-button';
import { ListFiltersModal } from '/@/renderer/features/shared/components/list-filters'; import {
isFilterValueSet,
ListFiltersModal,
} from '/@/renderer/features/shared/components/list-filters';
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button'; import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown'; import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button'; import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters'; import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
import { GenreTarget, useGenreTarget, useSettingsStoreActions } from '/@/renderer/store'; import { GenreTarget, useGenreTarget, useSettingsStoreActions } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
@@ -44,6 +48,20 @@ export const SongListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarget
: t('entity.track_other', { postProcess: 'titleCase' }); : t('entity.track_other', { postProcess: 'titleCase' });
}, [target, t]); }, [target, t]);
const hasActiveFilters = useMemo(() => {
const query = songFilters.query;
return Boolean(
isFilterValueSet(query[FILTER_KEYS.SONG._CUSTOM]) ||
isFilterValueSet(query[FILTER_KEYS.SONG.ALBUM_IDS]) ||
isFilterValueSet(query[FILTER_KEYS.SONG.ARTIST_IDS]) ||
query[FILTER_KEYS.SONG.FAVORITE] !== undefined ||
isFilterValueSet(query[FILTER_KEYS.SONG.GENRE_ID]) ||
isFilterValueSet(query[FILTER_KEYS.SONG.MAX_YEAR]) ||
isFilterValueSet(query[FILTER_KEYS.SONG.MIN_YEAR]) ||
isFilterValueSet(query[FILTER_KEYS.SHARED.SEARCH_TERM]),
);
}, [songFilters.query]);
return ( return (
<Flex justify="space-between"> <Flex justify="space-between">
<Group gap="sm" w="100%"> <Group gap="sm" w="100%">
@@ -69,7 +87,7 @@ export const SongListHeaderFilters = ({ toggleGenreTarget }: { toggleGenreTarget
defaultSortOrder={SortOrder.ASC} defaultSortOrder={SortOrder.ASC}
listKey={pageKey as ItemListKey} listKey={pageKey as ItemListKey}
/> />
<ListFiltersModal itemType={LibraryItem.SONG} /> <ListFiltersModal isActive={hasActiveFilters} itemType={LibraryItem.SONG} />
<ListRefreshButton listKey={pageKey as ItemListKey} /> <ListRefreshButton listKey={pageKey as ItemListKey} />
</Group> </Group>
<Group gap="sm" wrap="nowrap"> <Group gap="sm" wrap="nowrap">
@@ -7,6 +7,7 @@ import { ListWithSidebarContainer } from '/@/renderer/features/shared/components
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content'; import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header'; import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
import { usePageSidebar } from '/@/renderer/store/app.store';
import { SongListQuery } from '/@/shared/types/domain-types'; import { SongListQuery } from '/@/shared/types/domain-types';
import { ItemListKey } from '/@/shared/types/types'; import { ItemListKey } from '/@/shared/types/types';
@@ -27,6 +28,7 @@ const SongListRoute = () => {
const pageKey = getPageKey({ albumArtistId, genreId }); const pageKey = getPageKey({ albumArtistId, genreId });
const [itemCount, setItemCount] = useState<number | undefined>(undefined); const [itemCount, setItemCount] = useState<number | undefined>(undefined);
const [isSidebarOpen, setIsSidebarOpen] = usePageSidebar(pageKey);
const customFilters: Partial<SongListQuery> = useMemo(() => { const customFilters: Partial<SongListQuery> = useMemo(() => {
if (albumArtistId) { if (albumArtistId) {
@@ -48,11 +50,21 @@ const SongListRoute = () => {
return { return {
customFilters, customFilters,
id: albumArtistId ?? genreId, id: albumArtistId ?? genreId,
isSidebarOpen,
itemCount, itemCount,
pageKey, pageKey,
setIsSidebarOpen,
setItemCount, setItemCount,
}; };
}, [albumArtistId, customFilters, genreId, itemCount, pageKey]); }, [
albumArtistId,
customFilters,
genreId,
isSidebarOpen,
itemCount,
pageKey,
setIsSidebarOpen,
]);
return ( return (
<AnimatedPage> <AnimatedPage>
+23
View File
@@ -8,6 +8,7 @@ import { Platform } from '/@/shared/types/types';
export interface AppSlice extends AppState { export interface AppSlice extends AppState {
actions: { actions: {
setAppStore: (data: Partial<AppSlice>) => void; setAppStore: (data: Partial<AppSlice>) => void;
setPageSidebar: (key: string, value: boolean) => void;
setPrivateMode: (enabled: boolean) => void; setPrivateMode: (enabled: boolean) => void;
setShowTimeRemaining: (enabled: boolean) => void; setShowTimeRemaining: (enabled: boolean) => void;
setSideBar: (options: Partial<SidebarProps>) => void; setSideBar: (options: Partial<SidebarProps>) => void;
@@ -18,6 +19,7 @@ export interface AppSlice extends AppState {
export interface AppState { export interface AppState {
commandPalette: CommandPaletteProps; commandPalette: CommandPaletteProps;
isReorderingQueue: boolean; isReorderingQueue: boolean;
pageSidebar: Record<string, boolean>;
platform: Platform; platform: Platform;
privateMode: boolean; privateMode: boolean;
showTimeRemaining: boolean; showTimeRemaining: boolean;
@@ -54,6 +56,15 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
setAppStore: (data) => { setAppStore: (data) => {
set({ ...get(), ...data }); set({ ...get(), ...data });
}, },
setPageSidebar: (key, value) => {
set((state) => {
if (value) {
state.pageSidebar[key] = value;
} else {
delete state.pageSidebar[key];
}
});
},
setPrivateMode: (privateMode) => { setPrivateMode: (privateMode) => {
set((state) => { set((state) => {
state.privateMode = privateMode; state.privateMode = privateMode;
@@ -94,6 +105,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
}, },
}, },
isReorderingQueue: false, isReorderingQueue: false,
pageSidebar: {},
platform: Platform.WINDOWS, platform: Platform.WINDOWS,
privateMode: false, privateMode: false,
showTimeRemaining: false, showTimeRemaining: false,
@@ -140,3 +152,14 @@ export const useSetTitlebar = () => useAppStore((state) => state.actions.setTitl
export const useTitlebarStore = () => useAppStore((state) => state.titlebar); export const useTitlebarStore = () => useAppStore((state) => state.titlebar);
export const useCommandPalette = () => useAppStore((state) => state.commandPalette); export const useCommandPalette = () => useAppStore((state) => state.commandPalette);
export const usePageSidebar = (key: string): [boolean, (value: boolean) => void] => {
const isOpen = useAppStore((state) => state.pageSidebar[key] ?? false);
const setPageSidebar = useAppStore((state) => state.actions.setPageSidebar);
const setIsOpen = (value: boolean) => {
setPageSidebar(key, value);
};
return [isOpen, setIsOpen];
};
+4
View File
@@ -77,6 +77,8 @@ import {
LuPanelRightOpen, LuPanelRightOpen,
LuPause, LuPause,
LuPencilLine, LuPencilLine,
LuPin,
LuPinOff,
LuPlay, LuPlay,
LuPlus, LuPlus,
LuRadio, LuRadio,
@@ -200,6 +202,7 @@ export const AppIcon = {
minus: LuMinus, minus: LuMinus,
panelRightClose: LuPanelRightClose, panelRightClose: LuPanelRightClose,
panelRightOpen: LuPanelRightOpen, panelRightOpen: LuPanelRightOpen,
pin: LuPin,
playlist: LuListMusic, playlist: LuListMusic,
playlistAdd: LuListPlus, playlistAdd: LuListPlus,
playlistDelete: LuListMinus, playlistDelete: LuListMinus,
@@ -228,6 +231,7 @@ export const AppIcon = {
themeLight: LuSun, themeLight: LuSun,
track: LuMusic2, track: LuMusic2,
unfavorite: LuHeartCrack, unfavorite: LuHeartCrack,
unpin: LuPinOff,
upload: LuUpload, upload: LuUpload,
user: LuUser, user: LuUser,
userManage: LuUserRoundCog, userManage: LuUserRoundCog,