From 70fdd4bdc3e050dac41736304d403bcf87ff01f2 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 13 Feb 2026 14:59:15 -0800 Subject: [PATCH] refactor album expansion to global scope --- .../expanded-list-container.module.css | 4 +- .../item-list/expanded-list-container.tsx | 33 +-- .../item-list/expanded-list-item.tsx | 28 +- .../item-list/helpers/item-list-controls.ts | 29 +- .../item-grid-list/item-grid-list.tsx | 31 +-- .../item-table-list/item-table-list.tsx | 29 +- .../albums/components/album-grid-carousel.tsx | 2 +- .../components/album-infinite-carousel.tsx | 2 +- .../components/expanded-album-list-item.tsx | 260 +++++++++++------- .../album-artist-detail-content.tsx | 16 +- .../components/album-artist-grid-carousel.tsx | 1 - .../default-layout/main-content.module.css | 14 + .../layouts/default-layout/main-content.tsx | 37 ++- src/renderer/store/app.store.ts | 36 ++- 14 files changed, 297 insertions(+), 225 deletions(-) diff --git a/src/renderer/components/item-list/expanded-list-container.module.css b/src/renderer/components/item-list/expanded-list-container.module.css index 9be2a48e1..1ed6e301f 100644 --- a/src/renderer/components/item-list/expanded-list-container.module.css +++ b/src/renderer/components/item-list/expanded-list-container.module.css @@ -1,3 +1,3 @@ -.container { - height: 500px; +.list-expanded-container { + overflow: auto; } diff --git a/src/renderer/components/item-list/expanded-list-container.tsx b/src/renderer/components/item-list/expanded-list-container.tsx index 9217637a9..9811c8a86 100644 --- a/src/renderer/components/item-list/expanded-list-container.tsx +++ b/src/renderer/components/item-list/expanded-list-container.tsx @@ -1,32 +1,23 @@ -import { motion, Variants } from 'motion/react'; import { ReactNode } from 'react'; import styles from './expanded-list-container.module.css'; -const expandedAnimationVariants: Variants = { - hidden: { - height: 0, - minHeight: 0, - }, - show: { - minHeight: '300px', - transition: { - duration: 0.3, - ease: 'easeInOut', - }, - }, -}; +const EXPANDED_HEIGHT = 300; -export const ExpandedListContainer = ({ children }: { children: ReactNode }) => { +export interface ExpandedListContainerProps { + children: ReactNode; +} + +export const ExpandedListContainer = ({ children }: ExpandedListContainerProps) => { return ( - {children} - + ); }; diff --git a/src/renderer/components/item-list/expanded-list-item.tsx b/src/renderer/components/item-list/expanded-list-item.tsx index bb62bef09..1a0ffc17b 100644 --- a/src/renderer/components/item-list/expanded-list-item.tsx +++ b/src/renderer/components/item-list/expanded-list-item.tsx @@ -2,27 +2,18 @@ import { Suspense } from 'react'; import styles from './expanded-list-item.module.css'; -import { - ItemListStateActions, - ItemListStateItem, - useItemListStateSubscription, -} from '/@/renderer/components/item-list/helpers/item-list-state'; +import { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state'; import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item'; import { Spinner } from '/@/shared/components/spinner/spinner'; import { LibraryItem } from '/@/shared/types/domain-types'; interface ExpandedListItemProps { - internalState: ItemListStateActions; + item?: ItemListStateItem; itemType: LibraryItem; } -export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemProps) => { - const expandedItems = useItemListStateSubscription(internalState, () => - internalState ? internalState.getExpandedItemsCached() : [], - ); - const currentItem = expandedItems[0]; - - if (!currentItem) { +export const ExpandedListItem = ({ item, itemType }: ExpandedListItemProps) => { + if (!item) { return null; } @@ -30,11 +21,7 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
}> - +
@@ -42,15 +29,14 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr }; interface SelectedItemProps { - internalState: ItemListStateActions; item: ItemListStateItem; itemType: LibraryItem; } -const SelectedItem = ({ internalState, item, itemType }: SelectedItemProps) => { +const SelectedItem = ({ item, itemType }: SelectedItemProps) => { switch (itemType) { case LibraryItem.ALBUM: - return ; + return ; default: return null; } diff --git a/src/renderer/components/item-list/helpers/item-list-controls.ts b/src/renderer/components/item-list/helpers/item-list-controls.ts index 3791516a5..04a838500 100644 --- a/src/renderer/components/item-list/helpers/item-list-controls.ts +++ b/src/renderer/components/item-list/helpers/item-list-controls.ts @@ -8,6 +8,7 @@ import { ContextMenuController } from '/@/renderer/features/context-menu/context import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { useSetFavorite } from '/@/renderer/features/shared/hooks/use-set-favorite'; 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 { Play, TableColumn } from '/@/shared/types/types'; @@ -277,19 +278,27 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs } }, - onExpand: ({ internalState, item }: DefaultItemControlProps) => { - if (!item || !internalState) { - return; - } + onExpand: ({ item, itemType }: DefaultItemControlProps) => { + if (!item) 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 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: ({ diff --git a/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx b/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx index 73f2a11b8..88556033d 100644 --- a/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx +++ b/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import throttle from 'lodash/throttle'; -import { AnimatePresence, motion } from 'motion/react'; +import { motion } from 'motion/react'; import { useOverlayScrollbars } from 'overlayscrollbars-react'; import React, { CSSProperties, @@ -31,15 +31,12 @@ import { ItemCard, ItemCardProps, } 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 { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls'; import { ItemListStateActions, ItemListStateItemWithRequiredProperties, useItemListState, - useItemListStateSubscription, } from '/@/renderer/components/item-list/helpers/item-list-state'; import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys'; import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types'; @@ -829,10 +826,6 @@ const BaseItemGridList = ({ /> )} - - - {/* {enableSelectionDialog && } */} - ); }; @@ -903,25 +896,3 @@ const ListComponent = memo((props: ListChildComponentProps) => { export const ItemGridList = memo(BaseItemGridList); ItemGridList.displayName = 'ItemGridList'; - -const ExpandedContainer = ({ - internalState, - itemType, -}: { - internalState: ItemListStateActions; - itemType: LibraryItem; -}) => { - const hasExpanded = useItemListStateSubscription(internalState, (state) => - state ? state.expanded.size > 0 : false, - ); - - return ( - - {hasExpanded && ( - - - - )} - - ); -}; diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx index c256fcc4d..4d297ecd0 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx @@ -1,7 +1,7 @@ // Component adapted from https://github.com/bvaughn/react-window/issues/826 import clsx from 'clsx'; -import { AnimatePresence, motion } from 'motion/react'; +import { motion } from 'motion/react'; import React, { type JSXElementConstructor, memo, @@ -18,15 +18,12 @@ import { type CellComponentProps, Grid } from 'react-window-v2'; 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 { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls'; import { ItemListStateActions, ItemListStateItemWithRequiredProperties, useItemListState, - useItemListStateSubscription, } from '/@/renderer/components/item-list/helpers/item-list-state'; import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns'; import { useListHotkeys } from '/@/renderer/components/item-list/helpers/use-list-hotkeys'; @@ -1651,8 +1648,6 @@ const BaseItemTableList = ({ totalColumnCount={totalColumnCount} totalRowCount={totalRowCount} /> - - {/* {enableSelectionDialog && } */} @@ -1661,26 +1656,4 @@ const 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 ( - - {hasExpanded && ( - - - - )} - - ); -}; - ItemTableList.displayName = 'ItemTableList'; diff --git a/src/renderer/features/albums/components/album-grid-carousel.tsx b/src/renderer/features/albums/components/album-grid-carousel.tsx index 509461078..28f99c55d 100644 --- a/src/renderer/features/albums/components/album-grid-carousel.tsx +++ b/src/renderer/features/albums/components/album-grid-carousel.tsx @@ -20,7 +20,6 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) { const controls = useDefaultItemListControls(); const cards = useMemo(() => { - // Filter out excluded IDs if provided const filteredItems = excludeIds ? data.filter((album) => !excludeIds.includes(album.id)) : data; @@ -31,6 +30,7 @@ export function AlbumGridCarousel(props: AlbumGridCarouselProps) { controls={controls} data={album} enableDrag + enableExpansion itemType={LibraryItem.ALBUM} rows={rows} type="poster" diff --git a/src/renderer/features/albums/components/album-infinite-carousel.tsx b/src/renderer/features/albums/components/album-infinite-carousel.tsx index 8e87bd5cb..02be4e9ea 100644 --- a/src/renderer/features/albums/components/album-infinite-carousel.tsx +++ b/src/renderer/features/albums/components/album-infinite-carousel.tsx @@ -58,7 +58,6 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[] const controls = useDefaultItemListControls(); const cards = useMemo(() => { - // Flatten all pages and filter excluded IDs const allItems = albums?.pages.flatMap((page: AlbumListResponse) => page.items) || []; const filteredItems = excludeIds ? allItems.filter((album) => !excludeIds.includes(album.id)) @@ -70,6 +69,7 @@ const BaseAlbumInfiniteCarousel = (props: AlbumCarouselProps & { rows: DataRow[] controls={controls} data={album} enableDrag + enableExpansion itemType={LibraryItem.ALBUM} rows={rows} type="poster" diff --git a/src/renderer/features/albums/components/expanded-album-list-item.tsx b/src/renderer/features/albums/components/expanded-album-list-item.tsx index 162ee0738..5ca5014d9 100644 --- a/src/renderer/features/albums/components/expanded-album-list-item.tsx +++ b/src/renderer/features/albums/components/expanded-album-list-item.tsx @@ -22,6 +22,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group'; import { useFastAverageColor } from '/@/renderer/hooks'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; +import { useSetGlobalExpanded } from '/@/renderer/store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Group } from '/@/shared/components/group/group'; 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 { Text } from '/@/shared/components/text/text'; 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 { 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 { isDark?: boolean; serverId: string; @@ -46,11 +61,6 @@ interface AlbumTracksTableProps { }>; } -interface ExpandedAlbumListItemProps { - internalState?: ItemListStateActions; - item: ItemListStateItem; -} - interface TrackRowProps { controls: ReturnType; internalState: ItemListStateActions; @@ -60,6 +70,23 @@ interface TrackRowProps { songs: Song[]; } +const CloseExpandedButton = () => { + const setGlobalExpanded = useSetGlobalExpanded(); + return ( + setGlobalExpanded(null)} + radius="50%" + size="sm" + variant="default" + /> + ); +}; + const TrackRow = ({ controls, internalState, player, serverId, song, songs }: TrackRowProps) => { const rowId = internalState.extractRowId(song); const isSelected = useItemSelectionState(internalState, rowId); @@ -188,136 +215,165 @@ const AlbumTracksTable = ({ isDark, serverId, songs }: AlbumTracksTableProps) => ); }; -export const ExpandedAlbumListItem = ({ internalState, item }: ExpandedAlbumListItemProps) => { - const { data, isLoading } = useSuspenseQuery( - albumQueries.detail({ - query: { id: item.id }, - serverId: item._serverId, - }), - ); +interface ExpandedAlbumListItemContentProps { + albumData: ExpandedAlbumData; +} +const ExpandedAlbumListItemContent = ({ albumData }: ExpandedAlbumListItemContentProps) => { const player = usePlayer(); const imageUrl = useItemImageUrl({ - id: item.imageId || undefined, + id: albumData.imageId || undefined, itemType: LibraryItem.ALBUM, type: 'itemCard', }); const color = useFastAverageColor({ algorithm: 'sqrt', - id: item.id, + id: albumData.id, src: imageUrl, srcLoaded: true, }); const handlePlay = useCallback( (playType: Play) => { - if (!data) { - return; - } - - if (data.songs) { - player.addToQueueByData(data.songs, playType); + if (albumData.songs?.length) { + player.addToQueueByData(albumData.songs, playType); } }, - [data, player], + [albumData.songs, player], ); if (color.isLoading) { - return null; + return ; } + const songs = albumData.songs ?? null; + return ( - {isLoading && ( -
- -
- )} - -
-
-
-
- - {data?.name} - - {internalState && ( - { - const rowId = internalState.extractRowId(item); - if (rowId) { - internalState.clearExpanded(); - } - }} - radius="50%" - size="sm" - variant="default" - /> - )} -
- +
+
+
+ - {data?.albumArtists.map((artist, index) => ( - - - {artist.name} - - {index < data?.albumArtists.length - 1 && } - - ))} - + {albumData.name} + +
- -
-
-
- {data?.songs && data.songs.length > 0 && ( -
- -
- )} + + {albumData.albumArtists?.map((artist, index) => ( + + + {artist.name} + + {index < (albumData.albumArtists?.length ?? 0) - 1 && ( + + )} + + ))} +
+
- +
+
+ {songs && songs.length > 0 && ( +
+ +
+ )} +
+
); }; + +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 ; +}; + +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 ; + } + + if (props.item != null) { + const albumData = itemToExpandedAlbumData(props.item); + + if (albumData != null) { + return ; + } + + return ( + }> + + + ); + } + + return null; +}; diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index 904083da0..524df676b 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -1055,6 +1055,7 @@ interface AlbumSectionProps { albums: Album[]; controls: ItemControls; cq: ReturnType; + enableExpansion?: boolean; releaseType: string; rows: DataRow[] | undefined; title: React.ReactNode | string; @@ -1074,7 +1075,15 @@ const getItemsPerRow = (cq: ReturnType) => { 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 itemsPerRow = getItemsPerRow(cq); @@ -1199,6 +1208,7 @@ const AlbumSection = ({ albums, controls, cq, releaseType, rows, title }: AlbumS controls={controls} data={album} enableDrag + enableExpansion={enableExpansion ?? true} itemType={LibraryItem.ALBUM} rows={rows} type="poster" @@ -1376,7 +1386,6 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const routeId = (artistId || albumArtistId) as string; const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM); - const controls = useDefaultItemListControls(); const filteredAndSortedAlbums = useMemo(() => { const albums = albumsQuery.data?.items || []; @@ -1384,6 +1393,8 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { return sortAlbumList(searched, sortBy, sortOrder); }, [albumsQuery.data?.items, debouncedSearchTerm, sortBy, sortOrder]); + const controls = useDefaultItemListControls(); + const albumsByReleaseType = useMemo(() => { return groupAlbumsByReleaseType(filteredAndSortedAlbums, routeId, groupingType); }, [filteredAndSortedAlbums, routeId, groupingType]); @@ -1652,6 +1663,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { albums={albums} controls={controls} cq={cq} + enableExpansion key={releaseType} releaseType={releaseType} rows={rows} diff --git a/src/renderer/features/artists/components/album-artist-grid-carousel.tsx b/src/renderer/features/artists/components/album-artist-grid-carousel.tsx index eeb18623f..f94831d9e 100644 --- a/src/renderer/features/artists/components/album-artist-grid-carousel.tsx +++ b/src/renderer/features/artists/components/album-artist-grid-carousel.tsx @@ -20,7 +20,6 @@ export function AlbumArtistGridCarousel(props: AlbumArtistGridCarouselProps) { const controls = useDefaultItemListControls(); const cards = useMemo(() => { - // Filter out excluded IDs if provided const filteredItems = excludeIds ? data.filter((albumArtist) => !excludeIds.includes(albumArtist.id)) : data; diff --git a/src/renderer/layouts/default-layout/main-content.module.css b/src/renderer/layouts/default-layout/main-content.module.css index e7bb8ae83..d5754ea73 100644 --- a/src/renderer/layouts/default-layout/main-content.module.css +++ b/src/renderer/layouts/default-layout/main-content.module.css @@ -27,3 +27,17 @@ .main-content-container.sidebar-collapsed.right-expanded { 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; +} diff --git a/src/renderer/layouts/default-layout/main-content.tsx b/src/renderer/layouts/default-layout/main-content.tsx index e0598acbd..deb7f18e1 100644 --- a/src/renderer/layouts/default-layout/main-content.tsx +++ b/src/renderer/layouts/default-layout/main-content.tsx @@ -6,11 +6,18 @@ import { shallow } from 'zustand/shallow'; 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 { FullScreenVisualizerOverlay } from '/@/renderer/layouts/default-layout/full-screen-visualizer-overlay'; import { LeftSidebar } from '/@/renderer/layouts/default-layout/left-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 { 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 ( - }> - - + + + + ); +} + +function MainContentBody() { + return ( +
+
+ }> + + +
+ +
); } diff --git a/src/renderer/store/app.store.ts b/src/renderer/store/app.store.ts index f231116d5..6fa17f06c 100644 --- a/src/renderer/store/app.store.ts +++ b/src/renderer/store/app.store.ts @@ -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 { devtools, persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; @@ -17,6 +20,7 @@ export interface AppSlice extends AppState { setArtistSelectMode: (mode: 'multi' | 'single') => void; setGenreIdsMode: (mode: 'and' | 'or') => void; setGenreSelectMode: (mode: 'multi' | 'single') => void; + setGlobalExpanded: (value: GlobalExpandedState | null) => void; setPageSidebar: (key: string, value: boolean) => void; setPrivateMode: (enabled: boolean) => void; setShowTimeRemaining: (enabled: boolean) => void; @@ -38,6 +42,7 @@ export interface AppState { commandPalette: CommandPaletteProps; genreIdsMode: 'and' | 'or'; genreSelectMode: 'multi' | 'single'; + globalExpanded: GlobalExpandedState | null; isReorderingQueue: boolean; pageSidebar: Record; platform: Platform; @@ -47,6 +52,11 @@ export interface AppState { titlebar: TitlebarProps; } +export interface GlobalExpandedState { + item: ItemListStateItem; + itemType: LibraryItem; +} + type CommandPaletteProps = { close: () => void; open: () => void; @@ -120,6 +130,11 @@ export const useAppStore = createWithEqualityFn()( state.genreSelectMode = mode; }); }, + setGlobalExpanded: (value) => { + set((state) => { + state.globalExpanded = value; + }); + }, setPageSidebar: (key, value) => { set((state) => { state.pageSidebar[key] = value; @@ -175,6 +190,7 @@ export const useAppStore = createWithEqualityFn()( }, genreIdsMode: 'and', genreSelectMode: 'multi', + globalExpanded: null, isReorderingQueue: false, pageSidebar: { album: true, @@ -210,7 +226,12 @@ export const useAppStore = createWithEqualityFn()( return persistedState; }, 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]; }; + +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 }; +};