mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
Add queue controls
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { Ref } from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
import { useClickOutside, useMergedRef } from '@mantine/hooks';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import type {
|
||||
ICellRendererParams,
|
||||
ValueGetterParams,
|
||||
@@ -175,17 +175,8 @@ export const VirtualTable = forwardRef(
|
||||
|
||||
const mergedRef = useMergedRef(ref, tableRef);
|
||||
|
||||
const tableContainerRef = useClickOutside(() => {
|
||||
if (tableRef?.current) {
|
||||
tableRef?.current.api.deselectAll();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TableWrapper
|
||||
ref={tableContainerRef}
|
||||
className="ag-theme-alpine-dark"
|
||||
>
|
||||
<TableWrapper className="ag-theme-alpine-dark">
|
||||
<AgGridReact
|
||||
ref={mergedRef}
|
||||
suppressMoveWhenRowDragging
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
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 { Switch } from '/@/components/switch';
|
||||
import { Text } from '/@/components/text';
|
||||
import { useSettingsStore } from '/@/store/settings.store';
|
||||
import type { TableType } from '/@/types';
|
||||
import { TableColumn } from '/@/types';
|
||||
import { MultiSelect } from '/@/components/select';
|
||||
|
||||
export const tableColumns = [
|
||||
{ label: 'Row Index', value: TableColumn.ROW_INDEX },
|
||||
@@ -38,13 +31,6 @@ export const tableColumns = [
|
||||
{ label: 'Date Added', value: TableColumn.DATE_ADDED },
|
||||
];
|
||||
|
||||
const Container = styled(motion.div)`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 500;
|
||||
`;
|
||||
|
||||
interface TableConfigDropdownProps {
|
||||
type: TableType;
|
||||
}
|
||||
@@ -52,15 +38,6 @@ interface TableConfigDropdownProps {
|
||||
export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
|
||||
const setSettings = useSettingsStore((state) => state.setSettings);
|
||||
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 existingColumns = tableConfig[type].columns;
|
||||
@@ -146,69 +123,45 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container
|
||||
animate="animate"
|
||||
initial="initial"
|
||||
variants={containerVariants}
|
||||
whileHover={{ opacity: 1 }}
|
||||
<Stack
|
||||
p="1rem"
|
||||
spacing="xl"
|
||||
>
|
||||
<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
|
||||
p="1rem"
|
||||
spacing="xl"
|
||||
>
|
||||
<Stack spacing="xs">
|
||||
<Text>Table Columns</Text>
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={tableColumns}
|
||||
defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
|
||||
dropdownPosition="top"
|
||||
width={300}
|
||||
onChange={handleAddOrRemoveColumns}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Row Height</Text>
|
||||
<Slider
|
||||
defaultValue={tableConfig[type]?.rowHeight}
|
||||
max={100}
|
||||
min={25}
|
||||
sx={{ width: 150 }}
|
||||
onChangeEnd={handleUpdateRowHeight}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.autoFit}
|
||||
onChange={handleUpdateAutoFit}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Follow Current Song</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.followCurrentSong}
|
||||
onChange={handleUpdateFollow}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Container>
|
||||
<Stack spacing="xs">
|
||||
<Text>Table Columns</Text>
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={tableColumns}
|
||||
defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
|
||||
dropdownPosition="top"
|
||||
width={300}
|
||||
onChange={handleAddOrRemoveColumns}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Row Height</Text>
|
||||
<Slider
|
||||
defaultValue={tableConfig[type]?.rowHeight}
|
||||
max={100}
|
||||
min={25}
|
||||
sx={{ width: 150 }}
|
||||
onChangeEnd={handleUpdateRowHeight}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.autoFit}
|
||||
onChange={handleUpdateAutoFit}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Follow Current Song</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.followCurrentSong}
|
||||
onChange={handleUpdateFollow}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
CellDoubleClickedEvent,
|
||||
ColDef,
|
||||
RowClassRules,
|
||||
RowDragEvent,
|
||||
RowNode,
|
||||
} from '@ag-grid-community/core';
|
||||
import '@ag-grid-community/styles/ag-theme-alpine.css';
|
||||
import { VirtualGridAutoSizerContainer, VirtualGridContainer, getColumnDefs } from '/@/components';
|
||||
@@ -16,17 +17,19 @@ import {
|
||||
} from '/@/store';
|
||||
import { useSettingsStore } from '/@/store/settings.store';
|
||||
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 { mpvPlayer } from '#preload';
|
||||
import { VirtualTable } from '/@/components/virtual-table';
|
||||
import { ErrorFallback } from '/@/features/action-required';
|
||||
import type { Song } from '/@/api/types';
|
||||
|
||||
type QueueProps = {
|
||||
type: TableType;
|
||||
};
|
||||
|
||||
export const PlayQueue = ({ type }: QueueProps) => {
|
||||
const gridRef = useRef<any>(null);
|
||||
export const PlayQueue = forwardRef(({ type }: QueueProps, ref: any) => {
|
||||
const gridRef = useRef<AgGridReactType<Song> | null | any>(null);
|
||||
const queue = useDefaultQueue();
|
||||
const { reorderQueue, setCurrentTrack } = useQueueControls();
|
||||
const currentSong = useCurrentSong();
|
||||
@@ -35,6 +38,12 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
const { setAppStore } = useAppStoreActions();
|
||||
const tableConfig = useSettingsStore((state) => state.tables[type]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
get grid() {
|
||||
return gridRef?.current;
|
||||
},
|
||||
}));
|
||||
|
||||
const columnDefs = useMemo(() => getColumnDefs(tableConfig.columns), [tableConfig.columns]);
|
||||
const defaultColumnDefs: ColDef = useMemo(() => {
|
||||
return {
|
||||
@@ -71,19 +80,22 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
|
||||
const { api } = gridRef?.current || {};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => api.redrawRows(), 250);
|
||||
timeout = setTimeout(() => api?.redrawRows(), 250);
|
||||
};
|
||||
|
||||
const handleGridReady = () => {
|
||||
const { api } = gridRef?.current || {};
|
||||
|
||||
const currentNode = api.getRowNode(currentSong?.uniqueId);
|
||||
api.ensureNodeVisible(currentNode, 'middle');
|
||||
if (currentSong?.uniqueId) {
|
||||
const currentNode = api?.getRowNode(currentSong?.uniqueId);
|
||||
api?.ensureNodeVisible(currentNode, 'middle');
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnChange = () => {
|
||||
const { columnApi } = gridRef?.current || {};
|
||||
const columnsOrder = columnApi.getAllGridColumns();
|
||||
const columnsOrder = columnApi?.getAllGridColumns();
|
||||
if (!columnsOrder) return;
|
||||
|
||||
const columnsInSettings = useSettingsStore.getState().tables[type].columns;
|
||||
|
||||
@@ -114,7 +126,7 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
|
||||
const handleGridSizeChange = () => {
|
||||
if (tableConfig.autoFit) {
|
||||
gridRef?.current.api.sizeColumnsToFit();
|
||||
gridRef?.current?.api.sizeColumnsToFit();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,10 +146,12 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentNode = api.getRowNode(currentSong?.uniqueId);
|
||||
const previousNode = api.getRowNode(previousSong?.uniqueId);
|
||||
const currentNode = currentSong?.uniqueId ? api.getRowNode(currentSong.uniqueId) : undefined;
|
||||
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) {
|
||||
api.redrawRows({ rowNodes });
|
||||
@@ -186,8 +200,6 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
rowData={queue}
|
||||
rowHeight={tableConfig.rowHeight || 40}
|
||||
rowSelection="multiple"
|
||||
// onCellClicked={(e) => console.log('clicked', e)}
|
||||
// onCellContextMenu={(e) => console.log(e)}
|
||||
onCellDoubleClicked={handlePlayByRowClick}
|
||||
onColumnMoved={handleColumnChange}
|
||||
onColumnResized={handleColumnChange}
|
||||
@@ -198,7 +210,6 @@ export const PlayQueue = ({ type }: QueueProps) => {
|
||||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
</VirtualGridContainer>
|
||||
{/* <TableConfigDropdown type={type} /> */}
|
||||
</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/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 { Sidebar } from '/@/features/sidebar/components/sidebar';
|
||||
import { useAppStoreActions } from '/@/store/app.store';
|
||||
import { PlayQueue } from '/@/features/now-playing';
|
||||
import { DrawerPlayQueue, SidebarPlayQueue } from '/@/features/now-playing';
|
||||
|
||||
if (!isElectron()) {
|
||||
useSettingsStore.getState().setSettings({
|
||||
@@ -111,20 +111,6 @@ const QueueDrawerArea = styled(motion.div)`
|
||||
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 {
|
||||
shell?: boolean;
|
||||
}
|
||||
@@ -183,10 +169,9 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
|
||||
x: '50vw',
|
||||
},
|
||||
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)',
|
||||
minWidth: '400px',
|
||||
opacity: 0.98,
|
||||
position: 'absolute',
|
||||
right: '20px',
|
||||
top: '50px',
|
||||
@@ -315,9 +300,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
|
||||
}, 50);
|
||||
}}
|
||||
>
|
||||
<SideQueueContainer>
|
||||
<PlayQueue type="sideDrawerQueue" />
|
||||
</SideQueueContainer>
|
||||
<DrawerPlayQueue />
|
||||
</QueueDrawer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -344,9 +327,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
|
||||
startResizing('right');
|
||||
}}
|
||||
/>
|
||||
<SideQueueContainer>
|
||||
<PlayQueue type="sideQueue" />
|
||||
</SideQueueContainer>
|
||||
<SidebarPlayQueue />
|
||||
</RightSidebarContainer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import map from 'lodash/map';
|
||||
import merge from 'lodash/merge';
|
||||
import shuffle from 'lodash/shuffle';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import create from 'zustand';
|
||||
@@ -59,16 +60,18 @@ export interface PlayerSlice extends PlayerState {
|
||||
autoNext: () => PlayerData;
|
||||
checkIsFirstTrack: () => boolean;
|
||||
checkIsLastTrack: () => boolean;
|
||||
// getNextTrack: () => QueueSong;
|
||||
// getPreviousTrack: () => QueueSong;
|
||||
clearQueue: () => PlayerData;
|
||||
getPlayerData: () => PlayerData;
|
||||
getQueueData: () => QueueData;
|
||||
moveToBottomOfQueue: (uniqueIds: string[]) => PlayerData;
|
||||
moveToTopOfQueue: (uniqueIds: string[]) => PlayerData;
|
||||
next: () => PlayerData;
|
||||
pause: () => void;
|
||||
play: () => void;
|
||||
player1: () => QueueSong | undefined;
|
||||
player2: () => QueueSong | undefined;
|
||||
previous: () => PlayerData;
|
||||
removeFromQueue: (uniqueIds: string[]) => PlayerData;
|
||||
reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => PlayerData;
|
||||
setCurrentIndex: (index: number) => PlayerData;
|
||||
setCurrentTime: (time: number) => void;
|
||||
@@ -79,6 +82,7 @@ export interface PlayerSlice extends PlayerState {
|
||||
setShuffledIndex: (index: number) => PlayerData;
|
||||
setStore: (data: Partial<PlayerState>) => void;
|
||||
setVolume: (volume: number) => void;
|
||||
shuffleQueue: () => PlayerData;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -232,6 +236,19 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
|
||||
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: () => {
|
||||
const queue = get().queue.default;
|
||||
const currentPlayer = get().current.player;
|
||||
@@ -370,6 +387,46 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
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: () => {
|
||||
const isLastTrack = get().actions.checkIsLastTrack();
|
||||
const repeat = get().repeat;
|
||||
@@ -472,6 +529,17 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
|
||||
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) => {
|
||||
// Don't move if dropping on top of a selected row
|
||||
if (afterUniqueId && rowUniqueIds.includes(afterUniqueId)) {
|
||||
@@ -633,6 +701,22 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
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: {
|
||||
index: 0,
|
||||
@@ -658,7 +742,13 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||
})),
|
||||
{ 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(
|
||||
(state) => ({
|
||||
addToQueue: state.actions.addToQueue,
|
||||
clearQueue: state.actions.clearQueue,
|
||||
moveToBottomOfQueue: state.actions.moveToBottomOfQueue,
|
||||
moveToTopOfQueue: state.actions.moveToTopOfQueue,
|
||||
removeFromQueue: state.actions.removeFromQueue,
|
||||
reorderQueue: state.actions.reorderQueue,
|
||||
setCurrentIndex: state.actions.setCurrentIndex,
|
||||
setCurrentTrack: state.actions.setCurrentTrack,
|
||||
setShuffledIndex: state.actions.setShuffledIndex,
|
||||
shuffleQueue: state.actions.shuffleQueue,
|
||||
}),
|
||||
shallow,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user