mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
refactor album expansion to global scope
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
.container {
|
.list-expanded-container {
|
||||||
height: 500px;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,23 @@
|
|||||||
import { motion, Variants } from 'motion/react';
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import styles from './expanded-list-container.module.css';
|
import styles from './expanded-list-container.module.css';
|
||||||
|
|
||||||
const expandedAnimationVariants: Variants = {
|
const EXPANDED_HEIGHT = 300;
|
||||||
hidden: {
|
|
||||||
height: 0,
|
|
||||||
minHeight: 0,
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
minHeight: '300px',
|
|
||||||
transition: {
|
|
||||||
duration: 0.3,
|
|
||||||
ease: 'easeInOut',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ExpandedListContainer = ({ children }: { children: ReactNode }) => {
|
export interface ExpandedListContainerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpandedListContainer = ({ children }: ExpandedListContainerProps) => {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div
|
||||||
animate="show"
|
|
||||||
className={styles.listExpandedContainer}
|
className={styles.listExpandedContainer}
|
||||||
exit="hidden"
|
style={{
|
||||||
initial="hidden"
|
height: EXPANDED_HEIGHT,
|
||||||
variants={expandedAnimationVariants}
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</motion.div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,27 +2,18 @@ import { Suspense } from 'react';
|
|||||||
|
|
||||||
import styles from './expanded-list-item.module.css';
|
import styles from './expanded-list-item.module.css';
|
||||||
|
|
||||||
import {
|
import { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
ItemListStateActions,
|
|
||||||
ItemListStateItem,
|
|
||||||
useItemListStateSubscription,
|
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
|
||||||
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface ExpandedListItemProps {
|
interface ExpandedListItemProps {
|
||||||
internalState: ItemListStateActions;
|
item?: ItemListStateItem;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemProps) => {
|
export const ExpandedListItem = ({ item, itemType }: ExpandedListItemProps) => {
|
||||||
const expandedItems = useItemListStateSubscription(internalState, () =>
|
if (!item) {
|
||||||
internalState ? internalState.getExpandedItemsCached() : [],
|
|
||||||
);
|
|
||||||
const currentItem = expandedItems[0];
|
|
||||||
|
|
||||||
if (!currentItem) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +21,7 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.inner}>
|
<div className={styles.inner}>
|
||||||
<Suspense fallback={<Spinner container />}>
|
<Suspense fallback={<Spinner container />}>
|
||||||
<SelectedItem
|
<SelectedItem item={item} itemType={itemType} />
|
||||||
internalState={internalState}
|
|
||||||
item={currentItem as ItemListStateItem}
|
|
||||||
itemType={itemType}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,15 +29,14 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface SelectedItemProps {
|
interface SelectedItemProps {
|
||||||
internalState: ItemListStateActions;
|
|
||||||
item: ItemListStateItem;
|
item: ItemListStateItem;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectedItem = ({ internalState, item, itemType }: SelectedItemProps) => {
|
const SelectedItem = ({ item, itemType }: SelectedItemProps) => {
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case LibraryItem.ALBUM:
|
case LibraryItem.ALBUM:
|
||||||
return <ExpandedAlbumListItem internalState={internalState} item={item} />;
|
return <ExpandedAlbumListItem item={item} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ContextMenuController } from '/@/renderer/features/context-menu/context
|
|||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
|
import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite';
|
||||||
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
|
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
|
||||||
|
import { useAppStore } from '/@/renderer/store';
|
||||||
import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
|
import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
|
||||||
import { Play, TableColumn } from '/@/shared/types/types';
|
import { Play, TableColumn } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -277,19 +278,27 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onExpand: ({ internalState, item }: DefaultItemControlProps) => {
|
onExpand: ({ item, itemType }: DefaultItemControlProps) => {
|
||||||
if (!item || !internalState) {
|
if (!item) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract rowId from the item
|
|
||||||
const rowId = internalState.extractRowId(item);
|
|
||||||
if (!rowId) return;
|
|
||||||
|
|
||||||
// Use the item directly (rowId is separate, used only as key in state)
|
|
||||||
const itemListItem = item as ItemListStateItemWithRequiredProperties;
|
const itemListItem = item as ItemListStateItemWithRequiredProperties;
|
||||||
|
const setGlobalExpanded = useAppStore.getState().actions.setGlobalExpanded;
|
||||||
|
const globalExpanded = useAppStore.getState().globalExpanded;
|
||||||
|
|
||||||
return internalState?.toggleExpanded(itemListItem);
|
if (globalExpanded?.item?.id === item.id) {
|
||||||
|
setGlobalExpanded(null);
|
||||||
|
} else {
|
||||||
|
const itemForStore: ItemListStateItemWithRequiredProperties & {
|
||||||
|
imageId: null | string;
|
||||||
|
} = {
|
||||||
|
...itemListItem,
|
||||||
|
imageId: (itemListItem as { imageId?: null | string }).imageId ?? null,
|
||||||
|
};
|
||||||
|
setGlobalExpanded({
|
||||||
|
item: itemForStore,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onFavorite: ({
|
onFavorite: ({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
import React, {
|
import React, {
|
||||||
CSSProperties,
|
CSSProperties,
|
||||||
@@ -31,15 +31,12 @@ import {
|
|||||||
ItemCard,
|
ItemCard,
|
||||||
ItemCardProps,
|
ItemCardProps,
|
||||||
} from '/@/renderer/components/item-card/item-card';
|
} from '/@/renderer/components/item-card/item-card';
|
||||||
import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded-list-container';
|
|
||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
|
||||||
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
useItemListStateSubscription,
|
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
|
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
|
||||||
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
@@ -829,10 +826,6 @@ const BaseItemGridList = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
<AnimatePresence presenceAffectsLayout>
|
|
||||||
<ExpandedContainer internalState={internalState} itemType={itemType} />
|
|
||||||
{/* {enableSelectionDialog && <SelectionDialog internalState={internalState} />} */}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -903,25 +896,3 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
|||||||
export const ItemGridList = memo(BaseItemGridList);
|
export const ItemGridList = memo(BaseItemGridList);
|
||||||
|
|
||||||
ItemGridList.displayName = 'ItemGridList';
|
ItemGridList.displayName = 'ItemGridList';
|
||||||
|
|
||||||
const ExpandedContainer = ({
|
|
||||||
internalState,
|
|
||||||
itemType,
|
|
||||||
}: {
|
|
||||||
internalState: ItemListStateActions;
|
|
||||||
itemType: LibraryItem;
|
|
||||||
}) => {
|
|
||||||
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
|
|
||||||
state ? state.expanded.size > 0 : false,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{hasExpanded && (
|
|
||||||
<ExpandedListContainer>
|
|
||||||
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
|
||||||
</ExpandedListContainer>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Component adapted from https://github.com/bvaughn/react-window/issues/826
|
// Component adapted from https://github.com/bvaughn/react-window/issues/826
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import React, {
|
import React, {
|
||||||
type JSXElementConstructor,
|
type JSXElementConstructor,
|
||||||
memo,
|
memo,
|
||||||
@@ -18,15 +18,12 @@ import { type CellComponentProps, Grid } from 'react-window-v2';
|
|||||||
|
|
||||||
import styles from './item-table-list.module.css';
|
import styles from './item-table-list.module.css';
|
||||||
|
|
||||||
import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded-list-container';
|
|
||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
|
||||||
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
useItemListStateSubscription,
|
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
||||||
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
|
import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys';
|
||||||
@@ -1651,8 +1648,6 @@ const BaseItemTableList = ({
|
|||||||
totalColumnCount={totalColumnCount}
|
totalColumnCount={totalColumnCount}
|
||||||
totalRowCount={totalRowCount}
|
totalRowCount={totalRowCount}
|
||||||
/>
|
/>
|
||||||
<ExpandedContainer internalState={internalState} itemType={itemType} />
|
|
||||||
{/* {enableSelectionDialog && <SelectionDialog internalState={internalState} />} */}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</ItemTableListConfigProvider>
|
</ItemTableListConfigProvider>
|
||||||
</ItemTableListStoreProvider>
|
</ItemTableListStoreProvider>
|
||||||
@@ -1661,26 +1656,4 @@ const BaseItemTableList = ({
|
|||||||
|
|
||||||
export const ItemTableList = memo(BaseItemTableList);
|
export const ItemTableList = memo(BaseItemTableList);
|
||||||
|
|
||||||
const ExpandedContainer = ({
|
|
||||||
internalState,
|
|
||||||
itemType,
|
|
||||||
}: {
|
|
||||||
internalState: ItemListStateActions;
|
|
||||||
itemType: LibraryItem;
|
|
||||||
}) => {
|
|
||||||
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
|
|
||||||
state ? state.expanded.size > 0 : false,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{hasExpanded && (
|
|
||||||
<ExpandedListContainer>
|
|
||||||
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
|
||||||
</ExpandedListContainer>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ItemTableList.displayName = 'ItemTableList';
|
ItemTableList.displayName = 'ItemTableList';
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) {
|
|||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
const cards = useMemo(() => {
|
const cards = useMemo(() => {
|
||||||
// Filter out excluded IDs if provided
|
|
||||||
const filteredItems = excludeIds
|
const filteredItems = excludeIds
|
||||||
? data.filter((album) => !excludeIds.includes(album.id))
|
? data.filter((album) => !excludeIds.includes(album.id))
|
||||||
: data;
|
: data;
|
||||||
@@ -31,6 +30,7 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) {
|
|||||||
controls={controls}
|
controls={controls}
|
||||||
data={album}
|
data={album}
|
||||||
enableDrag
|
enableDrag
|
||||||
|
enableExpansion
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
type="poster"
|
type="poster"
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[]
|
|||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
const cards = useMemo(() => {
|
const cards = useMemo(() => {
|
||||||
// Flatten all pages and filter excluded IDs
|
|
||||||
const allItems = albums?.pages.flatMap((page: AlbumListResponse) => page.items) || [];
|
const allItems = albums?.pages.flatMap((page: AlbumListResponse) => page.items) || [];
|
||||||
const filteredItems = excludeIds
|
const filteredItems = excludeIds
|
||||||
? allItems.filter((album) => !excludeIds.includes(album.id))
|
? allItems.filter((album) => !excludeIds.includes(album.id))
|
||||||
@@ -70,6 +69,7 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[]
|
|||||||
controls={controls}
|
controls={controls}
|
||||||
data={album}
|
data={album}
|
||||||
enableDrag
|
enableDrag
|
||||||
|
enableExpansion
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
type="poster"
|
type="poster"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
|||||||
import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
|
import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
|
import { useSetGlobalExpanded } from '/@/renderer/store';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
||||||
@@ -30,10 +31,24 @@ import { Spinner } from '/@/shared/components/spinner/spinner';
|
|||||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
import { LibraryItem, RelatedArtist, Song } from '/@/shared/types/domain-types';
|
||||||
import { DragOperation, DragTarget, DragTargetMap } from '/@/shared/types/drag-and-drop';
|
import { DragOperation, DragTarget, DragTargetMap } from '/@/shared/types/drag-and-drop';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
export interface ExpandedAlbumData {
|
||||||
|
_serverId: string;
|
||||||
|
albumArtists: RelatedArtist[];
|
||||||
|
id: string;
|
||||||
|
imageId: null | string;
|
||||||
|
name: string;
|
||||||
|
songs?: null | Song[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExpandedAlbumListItemProps {
|
||||||
|
album?: ExpandedAlbumData;
|
||||||
|
item?: ItemListStateItem;
|
||||||
|
}
|
||||||
|
|
||||||
interface AlbumTracksTableProps {
|
interface AlbumTracksTableProps {
|
||||||
isDark?: boolean;
|
isDark?: boolean;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
@@ -46,11 +61,6 @@ interface AlbumTracksTableProps {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExpandedAlbumListItemProps {
|
|
||||||
internalState?: ItemListStateActions;
|
|
||||||
item: ItemListStateItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TrackRowProps {
|
interface TrackRowProps {
|
||||||
controls: ReturnType<typeof useDefaultItemListControls>;
|
controls: ReturnType<typeof useDefaultItemListControls>;
|
||||||
internalState: ItemListStateActions;
|
internalState: ItemListStateActions;
|
||||||
@@ -60,6 +70,23 @@ interface TrackRowProps {
|
|||||||
songs: Song[];
|
songs: Song[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CloseExpandedButton = () => {
|
||||||
|
const setGlobalExpanded = useSetGlobalExpanded();
|
||||||
|
return (
|
||||||
|
<ActionIcon
|
||||||
|
className={clsx(styles.closeButton)}
|
||||||
|
icon="x"
|
||||||
|
iconProps={{
|
||||||
|
size: 'xl',
|
||||||
|
}}
|
||||||
|
onClick={() => setGlobalExpanded(null)}
|
||||||
|
radius="50%"
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const TrackRow = ({ controls, internalState, player, serverId, song, songs }: TrackRowProps) => {
|
const TrackRow = ({ controls, internalState, player, serverId, song, songs }: TrackRowProps) => {
|
||||||
const rowId = internalState.extractRowId(song);
|
const rowId = internalState.extractRowId(song);
|
||||||
const isSelected = useItemSelectionState(internalState, rowId);
|
const isSelected = useItemSelectionState(internalState, rowId);
|
||||||
@@ -188,136 +215,165 @@ const AlbumTracksTable = ({ isDark, serverId, songs }: AlbumTracksTableProps) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpandedAlbumListItem = ({ internalState, item }: ExpandedAlbumListItemProps) => {
|
interface ExpandedAlbumListItemContentProps {
|
||||||
const { data, isLoading } = useSuspenseQuery(
|
albumData: ExpandedAlbumData;
|
||||||
albumQueries.detail({
|
}
|
||||||
query: { id: item.id },
|
|
||||||
serverId: item._serverId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const ExpandedAlbumListItemContent = ({ albumData }: ExpandedAlbumListItemContentProps) => {
|
||||||
const player = usePlayer();
|
const player = usePlayer();
|
||||||
|
|
||||||
const imageUrl = useItemImageUrl({
|
const imageUrl = useItemImageUrl({
|
||||||
id: item.imageId || undefined,
|
id: albumData.imageId || undefined,
|
||||||
itemType: LibraryItem.ALBUM,
|
itemType: LibraryItem.ALBUM,
|
||||||
type: 'itemCard',
|
type: 'itemCard',
|
||||||
});
|
});
|
||||||
|
|
||||||
const color = useFastAverageColor({
|
const color = useFastAverageColor({
|
||||||
algorithm: 'sqrt',
|
algorithm: 'sqrt',
|
||||||
id: item.id,
|
id: albumData.id,
|
||||||
src: imageUrl,
|
src: imageUrl,
|
||||||
srcLoaded: true,
|
srcLoaded: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlePlay = useCallback(
|
const handlePlay = useCallback(
|
||||||
(playType: Play) => {
|
(playType: Play) => {
|
||||||
if (!data) {
|
if (albumData.songs?.length) {
|
||||||
return;
|
player.addToQueueByData(albumData.songs, playType);
|
||||||
}
|
|
||||||
|
|
||||||
if (data.songs) {
|
|
||||||
player.addToQueueByData(data.songs, playType);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data, player],
|
[albumData.songs, player],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (color.isLoading) {
|
if (color.isLoading) {
|
||||||
return null;
|
return <Spinner container />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const songs = albumData.songs ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{
|
animate={{ opacity: 1 }}
|
||||||
opacity: 1,
|
|
||||||
}}
|
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
style={{ backgroundColor: color.background }}
|
style={{ backgroundColor: color.background }}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
<div className={styles.expanded}>
|
||||||
<div className={styles.loading}>
|
<div className={styles.content}>
|
||||||
<Spinner />
|
<div className={styles.header}>
|
||||||
</div>
|
<div className={styles.headerTitle}>
|
||||||
)}
|
<TextTitle
|
||||||
<Suspense>
|
className={clsx(styles.itemTitle, { [styles.dark]: color.isDark })}
|
||||||
<div className={styles.expanded}>
|
fw={700}
|
||||||
<div className={styles.content}>
|
order={4}
|
||||||
<div className={styles.header}>
|
|
||||||
<div className={styles.headerTitle}>
|
|
||||||
<TextTitle
|
|
||||||
className={clsx(styles.itemTitle, {
|
|
||||||
[styles.dark]: color.isDark,
|
|
||||||
})}
|
|
||||||
fw={700}
|
|
||||||
order={4}
|
|
||||||
>
|
|
||||||
{data?.name}
|
|
||||||
</TextTitle>
|
|
||||||
{internalState && (
|
|
||||||
<ActionIcon
|
|
||||||
className={clsx(styles.closeButton)}
|
|
||||||
icon="x"
|
|
||||||
iconProps={{
|
|
||||||
size: 'xl',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
const rowId = internalState.extractRowId(item);
|
|
||||||
if (rowId) {
|
|
||||||
internalState.clearExpanded();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
radius="50%"
|
|
||||||
size="sm"
|
|
||||||
variant="default"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Group
|
|
||||||
className={clsx(styles.itemSubtitle, {
|
|
||||||
[styles.dark]: color.isDark,
|
|
||||||
})}
|
|
||||||
gap="xs"
|
|
||||||
>
|
>
|
||||||
{data?.albumArtists.map((artist, index) => (
|
{albumData.name}
|
||||||
<Fragment key={artist.id}>
|
</TextTitle>
|
||||||
<Text
|
<CloseExpandedButton />
|
||||||
className={clsx(styles.itemSubtitle, {
|
|
||||||
[styles.dark]: color.isDark,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{artist.name}
|
|
||||||
</Text>
|
|
||||||
{index < data?.albumArtists.length - 1 && <Separator />}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
</div>
|
</div>
|
||||||
<AlbumTracksTable
|
<Group
|
||||||
isDark={color.isDark}
|
className={clsx(styles.itemSubtitle, { [styles.dark]: color.isDark })}
|
||||||
serverId={item._serverId}
|
gap="xs"
|
||||||
songs={data?.songs}
|
>
|
||||||
/>
|
{albumData.albumArtists?.map((artist, index) => (
|
||||||
</div>
|
<Fragment key={artist.id}>
|
||||||
<div className={styles.imageContainer}>
|
<Text
|
||||||
<div
|
className={clsx(styles.itemSubtitle, {
|
||||||
className={styles.backgroundImage}
|
[styles.dark]: color.isDark,
|
||||||
style={{
|
})}
|
||||||
['--bg-color' as string]: color?.background,
|
>
|
||||||
backgroundImage: `url(${imageUrl})`,
|
{artist.name}
|
||||||
}}
|
</Text>
|
||||||
/>
|
{index < (albumData.albumArtists?.length ?? 0) - 1 && (
|
||||||
{data?.songs && data.songs.length > 0 && (
|
<Separator />
|
||||||
<div className={styles.playButtonGroup}>
|
)}
|
||||||
<PlayButtonGroup onPlay={handlePlay} />
|
</Fragment>
|
||||||
</div>
|
))}
|
||||||
)}
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
<AlbumTracksTable
|
||||||
|
isDark={color.isDark}
|
||||||
|
serverId={albumData._serverId}
|
||||||
|
songs={songs ?? undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
<div className={styles.imageContainer}>
|
||||||
|
<div
|
||||||
|
className={styles.backgroundImage}
|
||||||
|
style={{
|
||||||
|
['--bg-color' as string]: color?.background,
|
||||||
|
backgroundImage: `url(${imageUrl})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{songs && songs.length > 0 && (
|
||||||
|
<div className={styles.playButtonGroup}>
|
||||||
|
<PlayButtonGroup onPlay={handlePlay} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ExpandedAlbumListItemWithFetch = ({ item }: { item: ItemListStateItem }) => {
|
||||||
|
const { data } = useSuspenseQuery(
|
||||||
|
albumQueries.detail({
|
||||||
|
query: { id: item.id },
|
||||||
|
serverId: item._serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const albumData: ExpandedAlbumData = {
|
||||||
|
_serverId: item._serverId,
|
||||||
|
albumArtists: data?.albumArtists ?? [],
|
||||||
|
id: item.id,
|
||||||
|
imageId: item.imageId ?? data?.imageId ?? null,
|
||||||
|
name: data?.name ?? '',
|
||||||
|
songs: data?.songs ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ExpandedAlbumListItemContent albumData={albumData} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
function itemToExpandedAlbumData(
|
||||||
|
item: ItemListStateItem & {
|
||||||
|
_playlistSongs?: Song[];
|
||||||
|
albumArtists?: RelatedArtist[];
|
||||||
|
name?: string;
|
||||||
|
},
|
||||||
|
): ExpandedAlbumData | null {
|
||||||
|
const songs =
|
||||||
|
(item as { songs?: Song[] }).songs ?? (item as { _playlistSongs?: Song[] })._playlistSongs;
|
||||||
|
if (songs == null) return null;
|
||||||
|
return {
|
||||||
|
_serverId: item._serverId,
|
||||||
|
albumArtists: item.albumArtists ?? [],
|
||||||
|
id: item.id,
|
||||||
|
imageId: (item as { imageId?: null | string }).imageId ?? null,
|
||||||
|
name: (item as { name?: string }).name ?? '',
|
||||||
|
songs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpandedAlbumListItem = (props: ExpandedAlbumListItemProps) => {
|
||||||
|
if (props.album != null) {
|
||||||
|
return <ExpandedAlbumListItemContent albumData={props.album} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.item != null) {
|
||||||
|
const albumData = itemToExpandedAlbumData(props.item);
|
||||||
|
|
||||||
|
if (albumData != null) {
|
||||||
|
return <ExpandedAlbumListItemContent albumData={albumData} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Spinner container />}>
|
||||||
|
<ExpandedAlbumListItemWithFetch item={props.item} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1055,6 +1055,7 @@ interface AlbumSectionProps {
|
|||||||
albums: Album[];
|
albums: Album[];
|
||||||
controls: ItemControls;
|
controls: ItemControls;
|
||||||
cq: ReturnType<typeof useContainerQuery>;
|
cq: ReturnType<typeof useContainerQuery>;
|
||||||
|
enableExpansion?: boolean;
|
||||||
releaseType: string;
|
releaseType: string;
|
||||||
rows: DataRow[] | undefined;
|
rows: DataRow[] | undefined;
|
||||||
title: React.ReactNode | string;
|
title: React.ReactNode | string;
|
||||||
@@ -1074,7 +1075,15 @@ const getItemsPerRow = (cq: ReturnType<typeof useContainerQuery>) => {
|
|||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AlbumSection = ({ albums, controls, cq, releaseType, rows, title }: AlbumSectionProps) => {
|
const AlbumSection = ({
|
||||||
|
albums,
|
||||||
|
controls,
|
||||||
|
cq,
|
||||||
|
enableExpansion,
|
||||||
|
releaseType,
|
||||||
|
rows,
|
||||||
|
title,
|
||||||
|
}: AlbumSectionProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const itemsPerRow = getItemsPerRow(cq);
|
const itemsPerRow = getItemsPerRow(cq);
|
||||||
@@ -1199,6 +1208,7 @@ const AlbumSection = ({ albums, controls, cq, releaseType, rows, title }: AlbumS
|
|||||||
controls={controls}
|
controls={controls}
|
||||||
data={album}
|
data={album}
|
||||||
enableDrag
|
enableDrag
|
||||||
|
enableExpansion={enableExpansion ?? true}
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
type="poster"
|
type="poster"
|
||||||
@@ -1376,7 +1386,6 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
|
|||||||
const routeId = (artistId || albumArtistId) as string;
|
const routeId = (artistId || albumArtistId) as string;
|
||||||
|
|
||||||
const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM);
|
const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM);
|
||||||
const controls = useDefaultItemListControls();
|
|
||||||
|
|
||||||
const filteredAndSortedAlbums = useMemo(() => {
|
const filteredAndSortedAlbums = useMemo(() => {
|
||||||
const albums = albumsQuery.data?.items || [];
|
const albums = albumsQuery.data?.items || [];
|
||||||
@@ -1384,6 +1393,8 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
|
|||||||
return sortAlbumList(searched, sortBy, sortOrder);
|
return sortAlbumList(searched, sortBy, sortOrder);
|
||||||
}, [albumsQuery.data?.items, debouncedSearchTerm, sortBy, sortOrder]);
|
}, [albumsQuery.data?.items, debouncedSearchTerm, sortBy, sortOrder]);
|
||||||
|
|
||||||
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
const albumsByReleaseType = useMemo(() => {
|
const albumsByReleaseType = useMemo(() => {
|
||||||
return groupAlbumsByReleaseType(filteredAndSortedAlbums, routeId, groupingType);
|
return groupAlbumsByReleaseType(filteredAndSortedAlbums, routeId, groupingType);
|
||||||
}, [filteredAndSortedAlbums, routeId, groupingType]);
|
}, [filteredAndSortedAlbums, routeId, groupingType]);
|
||||||
@@ -1652,6 +1663,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
|
|||||||
albums={albums}
|
albums={albums}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
cq={cq}
|
cq={cq}
|
||||||
|
enableExpansion
|
||||||
key={releaseType}
|
key={releaseType}
|
||||||
releaseType={releaseType}
|
releaseType={releaseType}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export function AlbumArtistGridCarousel(props: AlbumArtistGridCarouselProps) {
|
|||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
const cards = useMemo(() => {
|
const cards = useMemo(() => {
|
||||||
// Filter out excluded IDs if provided
|
|
||||||
const filteredItems = excludeIds
|
const filteredItems = excludeIds
|
||||||
? data.filter((albumArtist) => !excludeIds.includes(albumArtist.id))
|
? data.filter((albumArtist) => !excludeIds.includes(albumArtist.id))
|
||||||
: data;
|
: data;
|
||||||
|
|||||||
@@ -27,3 +27,17 @@
|
|||||||
.main-content-container.sidebar-collapsed.right-expanded {
|
.main-content-container.sidebar-collapsed.right-expanded {
|
||||||
grid-template-columns: 80px 1fr var(--right-sidebar-width);
|
grid-template-columns: 80px 1fr var(--right-sidebar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-content-body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content-body-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,18 @@ import { shallow } from 'zustand/shallow';
|
|||||||
|
|
||||||
import styles from './main-content.module.css';
|
import styles from './main-content.module.css';
|
||||||
|
|
||||||
|
import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded-list-container';
|
||||||
|
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
||||||
import { FullScreenOverlay } from '/@/renderer/layouts/default-layout/full-screen-overlay';
|
import { FullScreenOverlay } from '/@/renderer/layouts/default-layout/full-screen-overlay';
|
||||||
import { FullScreenVisualizerOverlay } from '/@/renderer/layouts/default-layout/full-screen-visualizer-overlay';
|
import { FullScreenVisualizerOverlay } from '/@/renderer/layouts/default-layout/full-screen-visualizer-overlay';
|
||||||
import { LeftSidebar } from '/@/renderer/layouts/default-layout/left-sidebar';
|
import { LeftSidebar } from '/@/renderer/layouts/default-layout/left-sidebar';
|
||||||
import { RightSidebar } from '/@/renderer/layouts/default-layout/right-sidebar';
|
import { RightSidebar } from '/@/renderer/layouts/default-layout/right-sidebar';
|
||||||
import { useAppStore, useAppStoreActions, useSideQueueType } from '/@/renderer/store';
|
import {
|
||||||
|
useAppStore,
|
||||||
|
useAppStoreActions,
|
||||||
|
useGlobalExpanded,
|
||||||
|
useSideQueueType,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/renderer/utils';
|
import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/renderer/utils';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
|
|
||||||
@@ -159,10 +166,30 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function MainContentBody() {
|
function GlobalExpandedPanel() {
|
||||||
|
const globalExpanded = useGlobalExpanded();
|
||||||
|
|
||||||
|
if (!globalExpanded) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Spinner container />}>
|
<ExpandedListContainer>
|
||||||
<Outlet />
|
<ExpandedListItem
|
||||||
</Suspense>
|
item={globalExpanded.item}
|
||||||
|
itemType={globalExpanded.itemType}
|
||||||
|
/>
|
||||||
|
</ExpandedListContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MainContentBody() {
|
||||||
|
return (
|
||||||
|
<div className={styles.mainContentBody}>
|
||||||
|
<div className={styles.mainContentBodyScroll}>
|
||||||
|
<Suspense fallback={<Spinner container />}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<GlobalExpandedPanel />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import type { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
|
import type { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from 'zustand/middleware/immer';
|
||||||
@@ -17,6 +20,7 @@ export interface AppSlice extends AppState {
|
|||||||
setArtistSelectMode: (mode: 'multi' | 'single') => void;
|
setArtistSelectMode: (mode: 'multi' | 'single') => 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;
|
||||||
setPageSidebar: (key: string, value: boolean) => void;
|
setPageSidebar: (key: string, value: boolean) => void;
|
||||||
setPrivateMode: (enabled: boolean) => void;
|
setPrivateMode: (enabled: boolean) => void;
|
||||||
setShowTimeRemaining: (enabled: boolean) => void;
|
setShowTimeRemaining: (enabled: boolean) => void;
|
||||||
@@ -38,6 +42,7 @@ export interface AppState {
|
|||||||
commandPalette: CommandPaletteProps;
|
commandPalette: CommandPaletteProps;
|
||||||
genreIdsMode: 'and' | 'or';
|
genreIdsMode: 'and' | 'or';
|
||||||
genreSelectMode: 'multi' | 'single';
|
genreSelectMode: 'multi' | 'single';
|
||||||
|
globalExpanded: GlobalExpandedState | null;
|
||||||
isReorderingQueue: boolean;
|
isReorderingQueue: boolean;
|
||||||
pageSidebar: Record<string, boolean>;
|
pageSidebar: Record<string, boolean>;
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
@@ -47,6 +52,11 @@ export interface AppState {
|
|||||||
titlebar: TitlebarProps;
|
titlebar: TitlebarProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GlobalExpandedState {
|
||||||
|
item: ItemListStateItem;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
type CommandPaletteProps = {
|
type CommandPaletteProps = {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
open: () => void;
|
open: () => void;
|
||||||
@@ -120,6 +130,11 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
|
|||||||
state.genreSelectMode = mode;
|
state.genreSelectMode = mode;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setGlobalExpanded: (value) => {
|
||||||
|
set((state) => {
|
||||||
|
state.globalExpanded = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
setPageSidebar: (key, value) => {
|
setPageSidebar: (key, value) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.pageSidebar[key] = value;
|
state.pageSidebar[key] = value;
|
||||||
@@ -175,6 +190,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
|
|||||||
},
|
},
|
||||||
genreIdsMode: 'and',
|
genreIdsMode: 'and',
|
||||||
genreSelectMode: 'multi',
|
genreSelectMode: 'multi',
|
||||||
|
globalExpanded: null,
|
||||||
isReorderingQueue: false,
|
isReorderingQueue: false,
|
||||||
pageSidebar: {
|
pageSidebar: {
|
||||||
album: true,
|
album: true,
|
||||||
@@ -210,7 +226,12 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
|
|||||||
return persistedState;
|
return persistedState;
|
||||||
},
|
},
|
||||||
name: 'store_app',
|
name: 'store_app',
|
||||||
version: 3,
|
partialize: (state) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- ignore non-persisted state
|
||||||
|
const { globalExpanded: _, ...rest } = state;
|
||||||
|
return rest;
|
||||||
|
},
|
||||||
|
version: 4,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -237,3 +258,16 @@ export const usePageSidebar = (key: string): [boolean, (value: boolean) => void]
|
|||||||
|
|
||||||
return [isOpen, setIsOpen];
|
return [isOpen, setIsOpen];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGlobalExpanded = () => useAppStore((state) => state.globalExpanded);
|
||||||
|
|
||||||
|
export const useSetGlobalExpanded = () => useAppStore((state) => state.actions.setGlobalExpanded);
|
||||||
|
|
||||||
|
export const useGlobalExpandedState = () => {
|
||||||
|
const globalExpanded = useGlobalExpanded();
|
||||||
|
const setGlobalExpanded = useSetGlobalExpanded();
|
||||||
|
|
||||||
|
const clearGlobalExpanded = () => setGlobalExpanded(null);
|
||||||
|
|
||||||
|
return { clearGlobalExpanded, globalExpanded, setGlobalExpanded };
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user