Add queue controls

This commit is contained in:
jeffvli
2022-12-10 05:41:56 -08:00
parent f48560ef60
commit b6c81183e9
9 changed files with 362 additions and 139 deletions
@@ -1,6 +1,6 @@
import type { Ref } from 'react'; import type { Ref } from 'react';
import { forwardRef, useRef } from 'react'; import { forwardRef, useRef } from 'react';
import { useClickOutside, useMergedRef } from '@mantine/hooks'; import { useMergedRef } from '@mantine/hooks';
import type { import type {
ICellRendererParams, ICellRendererParams,
ValueGetterParams, ValueGetterParams,
@@ -175,17 +175,8 @@ export const VirtualTable = forwardRef(
const mergedRef = useMergedRef(ref, tableRef); const mergedRef = useMergedRef(ref, tableRef);
const tableContainerRef = useClickOutside(() => {
if (tableRef?.current) {
tableRef?.current.api.deselectAll();
}
});
return ( return (
<TableWrapper <TableWrapper className="ag-theme-alpine-dark">
ref={tableContainerRef}
className="ag-theme-alpine-dark"
>
<AgGridReact <AgGridReact
ref={mergedRef} ref={mergedRef}
suppressMoveWhenRowDragging suppressMoveWhenRowDragging
@@ -1,19 +1,12 @@
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { Stack } from '@mantine/core'; import { Stack } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import type { Variants } from 'framer-motion';
import { motion } from 'framer-motion';
import { RiListSettingsLine } from 'react-icons/ri';
import styled from 'styled-components';
import { Button } from '/@/components/button';
import { Popover } from '/@/components/popover';
import { MultiSelect } from '/@/components/select';
import { Slider } from '/@/components/slider'; import { Slider } from '/@/components/slider';
import { Switch } from '/@/components/switch'; import { Switch } from '/@/components/switch';
import { Text } from '/@/components/text'; import { Text } from '/@/components/text';
import { useSettingsStore } from '/@/store/settings.store'; import { useSettingsStore } from '/@/store/settings.store';
import type { TableType } from '/@/types'; import type { TableType } from '/@/types';
import { TableColumn } from '/@/types'; import { TableColumn } from '/@/types';
import { MultiSelect } from '/@/components/select';
export const tableColumns = [ export const tableColumns = [
{ label: 'Row Index', value: TableColumn.ROW_INDEX }, { label: 'Row Index', value: TableColumn.ROW_INDEX },
@@ -38,13 +31,6 @@ export const tableColumns = [
{ label: 'Date Added', value: TableColumn.DATE_ADDED }, { label: 'Date Added', value: TableColumn.DATE_ADDED },
]; ];
const Container = styled(motion.div)`
position: absolute;
right: 0;
bottom: 0;
z-index: 500;
`;
interface TableConfigDropdownProps { interface TableConfigDropdownProps {
type: TableType; type: TableType;
} }
@@ -52,15 +38,6 @@ interface TableConfigDropdownProps {
export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
const setSettings = useSettingsStore((state) => state.setSettings); const setSettings = useSettingsStore((state) => state.setSettings);
const tableConfig = useSettingsStore((state) => state.tables); const tableConfig = useSettingsStore((state) => state.tables);
const [opened, handlers] = useDisclosure(false);
const containerVariants: Variants = {
animate: {
opacity: 0.2,
},
initial: {
opacity: 0,
},
};
const handleAddOrRemoveColumns = (values: TableColumn[]) => { const handleAddOrRemoveColumns = (values: TableColumn[]) => {
const existingColumns = tableConfig[type].columns; const existingColumns = tableConfig[type].columns;
@@ -146,27 +123,6 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
}; };
return ( return (
<Container
animate="animate"
initial="initial"
variants={containerVariants}
whileHover={{ opacity: 1 }}
>
<Popover
opened={opened}
position="top-start"
withArrow={false}
>
<Popover.Target>
<Button
compact
variant="subtle"
onClick={() => handlers.toggle()}
>
<RiListSettingsLine size={20} />
</Button>
</Popover.Target>
<Popover.Dropdown>
<Stack <Stack
p="1rem" p="1rem"
spacing="xl" spacing="xl"
@@ -207,8 +163,5 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
/> />
</Stack> </Stack>
</Stack> </Stack>
</Popover.Dropdown>
</Popover>
</Container>
); );
}; };
@@ -0,0 +1,24 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Stack } from '@mantine/core';
import { PlayQueue } from '/@/features/now-playing/components/play-queue';
import { PlayQueueListControls } from './play-queue-list-controls';
import { useRef } from 'react';
import type { Song } from '/@/api/types';
export const DrawerPlayQueue = () => {
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
return (
<Stack
pb="1rem"
sx={{ height: '100%' }}
>
<PlayQueue type="sideQueue" />
<PlayQueueListControls
gridApi={queueRef.current?.grid.api}
gridColumnApi={queueRef.current?.grid?.columnApi}
type="sideQueue"
/>
</Stack>
);
};
@@ -0,0 +1,137 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Group } from '@mantine/core';
import {
RiArrowDownLine,
RiArrowUpLine,
RiShuffleLine,
RiDeleteBinLine,
RiListSettingsLine,
RiEraserLine,
} from 'react-icons/ri';
import type { Song } from '/@/api/types';
import { TableConfigDropdown, Button, Popover } from '/@/components';
import { useQueueControls } from '/@/store';
import type { TableType } from '/@/types';
import { mpvPlayer } from '#preload';
interface PlayQueueListOptionsProps {
gridApi?: AgGridReactType<Song>['api'];
gridColumnApi?: AgGridReactType<Song>['columnApi'];
type: TableType;
}
export const PlayQueueListControls = ({ type, gridApi }: PlayQueueListOptionsProps) => {
const { clearQueue, moveToBottomOfQueue, moveToTopOfQueue, shuffleQueue, removeFromQueue } =
useQueueControls();
const handleMoveToBottom = () => {
const selectedRows = gridApi?.getSelectedRows();
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
if (!uniqueIds?.length) return;
const playerData = moveToBottomOfQueue(uniqueIds);
mpvPlayer.setQueueNext(playerData);
};
const handleMoveToTop = () => {
const selectedRows = gridApi?.getSelectedRows();
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
if (!uniqueIds?.length) return;
const playerData = moveToTopOfQueue(uniqueIds);
mpvPlayer.setQueueNext(playerData);
};
const handleRemoveSelected = () => {
const selectedRows = gridApi?.getSelectedRows();
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
if (!uniqueIds?.length) return;
const playerData = removeFromQueue(uniqueIds);
mpvPlayer.setQueueNext(playerData);
};
const handleClearQueue = () => {
const playerData = clearQueue();
mpvPlayer.setQueue(playerData);
mpvPlayer.stop();
};
const handleShuffleQueue = () => {
const playerData = shuffleQueue();
mpvPlayer.setQueueNext(playerData);
};
return (
<Group
position="apart"
px="1rem"
sx={{ alignItems: 'center' }}
>
<Group>
<Button
compact
size="sm"
tooltip={{ label: 'Shuffle queue' }}
variant="default"
onClick={handleShuffleQueue}
>
<RiShuffleLine size={15} />
</Button>
<Button
compact
size="sm"
tooltip={{ label: 'Move selected to top' }}
variant="default"
onClick={handleMoveToTop}
>
<RiArrowUpLine size={15} />
</Button>
<Button
compact
size="sm"
tooltip={{ label: 'Move selected to bottom' }}
variant="default"
onClick={handleMoveToBottom}
>
<RiArrowDownLine size={15} />
</Button>
<Button
compact
size="sm"
tooltip={{ label: 'Remove selected' }}
variant="default"
onClick={handleRemoveSelected}
>
<RiEraserLine size={15} />
</Button>
<Button
compact
size="sm"
tooltip={{ label: 'Clear queue' }}
variant="default"
onClick={handleClearQueue}
>
<RiDeleteBinLine size={15} />
</Button>
</Group>
<Group>
<Popover>
<Popover.Target>
<Button
compact
size="sm"
tooltip={{ label: 'Configure' }}
variant="default"
>
<RiListSettingsLine size={15} />
</Button>
</Popover.Target>
<Popover.Dropdown>
<TableConfigDropdown type={type} />
</Popover.Dropdown>
</Popover>
</Group>
</Group>
);
};
@@ -1,9 +1,10 @@
import { useEffect, useMemo, useRef } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import type { import type {
CellDoubleClickedEvent, CellDoubleClickedEvent,
ColDef, ColDef,
RowClassRules, RowClassRules,
RowDragEvent, RowDragEvent,
RowNode,
} from '@ag-grid-community/core'; } from '@ag-grid-community/core';
import '@ag-grid-community/styles/ag-theme-alpine.css'; import '@ag-grid-community/styles/ag-theme-alpine.css';
import { VirtualGridAutoSizerContainer, VirtualGridContainer, getColumnDefs } from '/@/components'; import { VirtualGridAutoSizerContainer, VirtualGridContainer, getColumnDefs } from '/@/components';
@@ -16,17 +17,19 @@ import {
} from '/@/store'; } from '/@/store';
import { useSettingsStore } from '/@/store/settings.store'; import { useSettingsStore } from '/@/store/settings.store';
import type { QueueSong, TableType } from '/@/types'; import type { QueueSong, TableType } from '/@/types';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import { mpvPlayer } from '#preload'; import { mpvPlayer } from '#preload';
import { VirtualTable } from '/@/components/virtual-table'; import { VirtualTable } from '/@/components/virtual-table';
import { ErrorFallback } from '/@/features/action-required'; import { ErrorFallback } from '/@/features/action-required';
import type { Song } from '/@/api/types';
type QueueProps = { type QueueProps = {
type: TableType; type: TableType;
}; };
export const PlayQueue = ({ type }: QueueProps) => { export const PlayQueue = forwardRef(({ type }: QueueProps, ref: any) => {
const gridRef = useRef<any>(null); const gridRef = useRef<AgGridReactType<Song> | null | any>(null);
const queue = useDefaultQueue(); const queue = useDefaultQueue();
const { reorderQueue, setCurrentTrack } = useQueueControls(); const { reorderQueue, setCurrentTrack } = useQueueControls();
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
@@ -35,6 +38,12 @@ export const PlayQueue = ({ type }: QueueProps) => {
const { setAppStore } = useAppStoreActions(); const { setAppStore } = useAppStoreActions();
const tableConfig = useSettingsStore((state) => state.tables[type]); const tableConfig = useSettingsStore((state) => state.tables[type]);
useImperativeHandle(ref, () => ({
get grid() {
return gridRef?.current;
},
}));
const columnDefs = useMemo(() => getColumnDefs(tableConfig.columns), [tableConfig.columns]); const columnDefs = useMemo(() => getColumnDefs(tableConfig.columns), [tableConfig.columns]);
const defaultColumnDefs: ColDef = useMemo(() => { const defaultColumnDefs: ColDef = useMemo(() => {
return { return {
@@ -71,19 +80,22 @@ export const PlayQueue = ({ type }: QueueProps) => {
const { api } = gridRef?.current || {}; const { api } = gridRef?.current || {};
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => api.redrawRows(), 250); timeout = setTimeout(() => api?.redrawRows(), 250);
}; };
const handleGridReady = () => { const handleGridReady = () => {
const { api } = gridRef?.current || {}; const { api } = gridRef?.current || {};
const currentNode = api.getRowNode(currentSong?.uniqueId); if (currentSong?.uniqueId) {
api.ensureNodeVisible(currentNode, 'middle'); const currentNode = api?.getRowNode(currentSong?.uniqueId);
api?.ensureNodeVisible(currentNode, 'middle');
}
}; };
const handleColumnChange = () => { const handleColumnChange = () => {
const { columnApi } = gridRef?.current || {}; const { columnApi } = gridRef?.current || {};
const columnsOrder = columnApi.getAllGridColumns(); const columnsOrder = columnApi?.getAllGridColumns();
if (!columnsOrder) return;
const columnsInSettings = useSettingsStore.getState().tables[type].columns; const columnsInSettings = useSettingsStore.getState().tables[type].columns;
@@ -114,7 +126,7 @@ export const PlayQueue = ({ type }: QueueProps) => {
const handleGridSizeChange = () => { const handleGridSizeChange = () => {
if (tableConfig.autoFit) { if (tableConfig.autoFit) {
gridRef?.current.api.sizeColumnsToFit(); gridRef?.current?.api.sizeColumnsToFit();
} }
}; };
@@ -134,10 +146,12 @@ export const PlayQueue = ({ type }: QueueProps) => {
return; return;
} }
const currentNode = api.getRowNode(currentSong?.uniqueId); const currentNode = currentSong?.uniqueId ? api.getRowNode(currentSong.uniqueId) : undefined;
const previousNode = api.getRowNode(previousSong?.uniqueId); const previousNode = previousSong?.uniqueId
? api.getRowNode(previousSong?.uniqueId)
: undefined;
const rowNodes = [currentNode, previousNode]; const rowNodes = [currentNode, previousNode].filter((e) => e !== undefined) as RowNode<any>[];
if (rowNodes) { if (rowNodes) {
api.redrawRows({ rowNodes }); api.redrawRows({ rowNodes });
@@ -186,8 +200,6 @@ export const PlayQueue = ({ type }: QueueProps) => {
rowData={queue} rowData={queue}
rowHeight={tableConfig.rowHeight || 40} rowHeight={tableConfig.rowHeight || 40}
rowSelection="multiple" rowSelection="multiple"
// onCellClicked={(e) => console.log('clicked', e)}
// onCellContextMenu={(e) => console.log(e)}
onCellDoubleClicked={handlePlayByRowClick} onCellDoubleClicked={handlePlayByRowClick}
onColumnMoved={handleColumnChange} onColumnMoved={handleColumnChange}
onColumnResized={handleColumnChange} onColumnResized={handleColumnChange}
@@ -198,7 +210,6 @@ export const PlayQueue = ({ type }: QueueProps) => {
/> />
</VirtualGridAutoSizerContainer> </VirtualGridAutoSizerContainer>
</VirtualGridContainer> </VirtualGridContainer>
{/* <TableConfigDropdown type={type} /> */}
</ErrorBoundary> </ErrorBoundary>
); );
}; });
@@ -0,0 +1,28 @@
import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Stack } from '@mantine/core';
import { PlayQueue } from '/@/features/now-playing/components/play-queue';
import { PlayQueueListControls } from './play-queue-list-controls';
import type { Song } from '/@/api/types';
export const SidebarPlayQueue = () => {
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
return (
<Stack
pb="1rem"
pt="2.5rem"
sx={{ height: '100%' }}
>
<PlayQueue
ref={queueRef}
type="sideQueue"
/>
<PlayQueueListControls
gridApi={queueRef.current?.grid.api}
gridColumnApi={queueRef.current?.grid.columnApi}
type="sideQueue"
/>
</Stack>
);
};
@@ -1 +1,4 @@
export * from './components/play-queue'; export * from './components/play-queue';
export * from './components/sidebar-play-queue';
export * from './components/drawer-play-queue';
export * from './components/play-queue-list-controls';
@@ -15,7 +15,7 @@ import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/utils';
import { Playerbar } from '/@/features/player'; import { Playerbar } from '/@/features/player';
import { Sidebar } from '/@/features/sidebar/components/sidebar'; import { Sidebar } from '/@/features/sidebar/components/sidebar';
import { useAppStoreActions } from '/@/store/app.store'; import { useAppStoreActions } from '/@/store/app.store';
import { PlayQueue } from '/@/features/now-playing'; import { DrawerPlayQueue, SidebarPlayQueue } from '/@/features/now-playing';
if (!isElectron()) { if (!isElectron()) {
useSettingsStore.getState().setSettings({ useSettingsStore.getState().setSettings({
@@ -111,20 +111,6 @@ const QueueDrawerArea = styled(motion.div)`
user-select: none; user-select: none;
`; `;
const SideQueueContainer = styled.div`
width: 100%;
height: 100%;
.ag-root ::-webkit-scrollbar-track-piece {
background: var(--main-bg);
}
.ag-theme-alpine-dark {
--ag-background-color: var(--sidebar-bg) !important;
--ag-odd-row-background-color: var(--sidebar-bg) !important;
}
`;
interface DefaultLayoutProps { interface DefaultLayoutProps {
shell?: boolean; shell?: boolean;
} }
@@ -183,10 +169,9 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
x: '50vw', x: '50vw',
}, },
open: { open: {
boxShadow: '4px 4px 10px 0px rgba(0,0,0,.75)', boxShadow: '2px 2px 10px 10px rgba(0,0,0,.1)',
height: 'calc(100vh - 150px)', height: 'calc(100vh - 150px)',
minWidth: '400px', minWidth: '400px',
opacity: 0.98,
position: 'absolute', position: 'absolute',
right: '20px', right: '20px',
top: '50px', top: '50px',
@@ -315,9 +300,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
}, 50); }, 50);
}} }}
> >
<SideQueueContainer> <DrawerPlayQueue />
<PlayQueue type="sideDrawerQueue" />
</SideQueueContainer>
</QueueDrawer> </QueueDrawer>
)} )}
</AnimatePresence> </AnimatePresence>
@@ -344,9 +327,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
startResizing('right'); startResizing('right');
}} }}
/> />
<SideQueueContainer> <SidebarPlayQueue />
<PlayQueue type="sideQueue" />
</SideQueueContainer>
</RightSidebarContainer> </RightSidebarContainer>
)} )}
</AnimatePresence> </AnimatePresence>
+98 -3
View File
@@ -1,4 +1,5 @@
import map from 'lodash/map'; import map from 'lodash/map';
import merge from 'lodash/merge';
import shuffle from 'lodash/shuffle'; import shuffle from 'lodash/shuffle';
import { nanoid } from 'nanoid/non-secure'; import { nanoid } from 'nanoid/non-secure';
import create from 'zustand'; import create from 'zustand';
@@ -59,16 +60,18 @@ export interface PlayerSlice extends PlayerState {
autoNext: () => PlayerData; autoNext: () => PlayerData;
checkIsFirstTrack: () => boolean; checkIsFirstTrack: () => boolean;
checkIsLastTrack: () => boolean; checkIsLastTrack: () => boolean;
// getNextTrack: () => QueueSong; clearQueue: () => PlayerData;
// getPreviousTrack: () => QueueSong;
getPlayerData: () => PlayerData; getPlayerData: () => PlayerData;
getQueueData: () => QueueData; getQueueData: () => QueueData;
moveToBottomOfQueue: (uniqueIds: string[]) => PlayerData;
moveToTopOfQueue: (uniqueIds: string[]) => PlayerData;
next: () => PlayerData; next: () => PlayerData;
pause: () => void; pause: () => void;
play: () => void; play: () => void;
player1: () => QueueSong | undefined; player1: () => QueueSong | undefined;
player2: () => QueueSong | undefined; player2: () => QueueSong | undefined;
previous: () => PlayerData; previous: () => PlayerData;
removeFromQueue: (uniqueIds: string[]) => PlayerData;
reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => PlayerData; reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => PlayerData;
setCurrentIndex: (index: number) => PlayerData; setCurrentIndex: (index: number) => PlayerData;
setCurrentTime: (time: number) => void; setCurrentTime: (time: number) => void;
@@ -79,6 +82,7 @@ export interface PlayerSlice extends PlayerState {
setShuffledIndex: (index: number) => PlayerData; setShuffledIndex: (index: number) => PlayerData;
setStore: (data: Partial<PlayerState>) => void; setStore: (data: Partial<PlayerState>) => void;
setVolume: (volume: number) => void; setVolume: (volume: number) => void;
shuffleQueue: () => PlayerData;
}; };
} }
@@ -232,6 +236,19 @@ export const usePlayerStore = create<PlayerSlice>()(
return currentIndex === get().queue.default.length - 1; return currentIndex === get().queue.default.length - 1;
}, },
clearQueue: () => {
set((state) => {
state.queue.default = [];
state.queue.shuffled = [];
state.queue.sorted = [];
state.current.index = 0;
state.current.shuffledIndex = 0;
state.current.player = 1;
state.current.song = undefined;
});
return get().actions.getPlayerData();
},
getPlayerData: () => { getPlayerData: () => {
const queue = get().queue.default; const queue = get().queue.default;
const currentPlayer = get().current.player; const currentPlayer = get().current.player;
@@ -370,6 +387,46 @@ export const usePlayerStore = create<PlayerSlice>()(
previous: queue[get().current.index - 1], previous: queue[get().current.index - 1],
}; };
}, },
moveToBottomOfQueue: (uniqueIds) => {
const queue = get().queue.default;
const songsToMove = queue.filter((song) => uniqueIds.includes(song.uniqueId));
const songsToStay = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
const reorderedQueue = [...songsToStay, ...songsToMove];
const currentSongUniqueId = get().current.song?.uniqueId;
const newCurrentSongIndex = reorderedQueue.findIndex(
(song) => song.uniqueId === currentSongUniqueId,
);
set((state) => {
state.current.index = newCurrentSongIndex;
state.queue.default = reorderedQueue;
});
return get().actions.getPlayerData();
},
moveToTopOfQueue: (uniqueIds) => {
const queue = get().queue.default;
const songsToMove = queue.filter((song) => uniqueIds.includes(song.uniqueId));
const songsToStay = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
const reorderedQueue = [...songsToMove, ...songsToStay];
const currentSongUniqueId = get().current.song?.uniqueId;
const newCurrentSongIndex = reorderedQueue.findIndex(
(song) => song.uniqueId === currentSongUniqueId,
);
set((state) => {
state.current.index = newCurrentSongIndex;
state.queue.default = reorderedQueue;
});
return get().actions.getPlayerData();
},
next: () => { next: () => {
const isLastTrack = get().actions.checkIsLastTrack(); const isLastTrack = get().actions.checkIsLastTrack();
const repeat = get().repeat; const repeat = get().repeat;
@@ -472,6 +529,17 @@ export const usePlayerStore = create<PlayerSlice>()(
return get().actions.getPlayerData(); return get().actions.getPlayerData();
}, },
removeFromQueue: (uniqueIds) => {
const queue = get().queue.default;
const newQueue = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
set((state) => {
state.queue.default = newQueue;
});
return get().actions.getPlayerData();
},
reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => { reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => {
// Don't move if dropping on top of a selected row // Don't move if dropping on top of a selected row
if (afterUniqueId && rowUniqueIds.includes(afterUniqueId)) { if (afterUniqueId && rowUniqueIds.includes(afterUniqueId)) {
@@ -633,6 +701,22 @@ export const usePlayerStore = create<PlayerSlice>()(
state.volume = volume; state.volume = volume;
}); });
}, },
shuffleQueue: () => {
const queue = get().queue.default;
const shuffledQueue = shuffle(queue);
const currentSongUniqueId = get().current.song?.uniqueId;
const newCurrentSongIndex = shuffledQueue.findIndex(
(song) => song.uniqueId === currentSongUniqueId,
);
set((state) => {
state.current.index = newCurrentSongIndex;
state.queue.default = shuffledQueue;
});
return get().actions.getPlayerData();
},
}, },
current: { current: {
index: 0, index: 0,
@@ -658,7 +742,13 @@ export const usePlayerStore = create<PlayerSlice>()(
})), })),
{ name: 'store_player' }, { name: 'store_player' },
), ),
{ name: 'store_player' }, {
merge: (persistedState, currentState) => {
return merge(currentState, persistedState);
},
name: 'store_player',
version: 1,
},
), ),
); );
@@ -686,10 +776,15 @@ export const useQueueControls = () =>
usePlayerStore( usePlayerStore(
(state) => ({ (state) => ({
addToQueue: state.actions.addToQueue, addToQueue: state.actions.addToQueue,
clearQueue: state.actions.clearQueue,
moveToBottomOfQueue: state.actions.moveToBottomOfQueue,
moveToTopOfQueue: state.actions.moveToTopOfQueue,
removeFromQueue: state.actions.removeFromQueue,
reorderQueue: state.actions.reorderQueue, reorderQueue: state.actions.reorderQueue,
setCurrentIndex: state.actions.setCurrentIndex, setCurrentIndex: state.actions.setCurrentIndex,
setCurrentTrack: state.actions.setCurrentTrack, setCurrentTrack: state.actions.setCurrentTrack,
setShuffledIndex: state.actions.setShuffledIndex, setShuffledIndex: state.actions.setShuffledIndex,
shuffleQueue: state.actions.shuffleQueue,
}), }),
shallow, shallow,
); );