mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
initial implementation of play queue for new list
This commit is contained in:
@@ -27,7 +27,7 @@ export const DrawerPlayQueue = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Flex bg="var(--theme-colors-background)" h="100%" mb="0.6rem">
|
<Flex bg="var(--theme-colors-background)" h="100%" mb="0.6rem">
|
||||||
<PlayQueue ref={queueRef} searchTerm={search} type="sideQueue" />
|
<PlayQueue ref={queueRef} searchTerm={search} listKey="sideQueue" />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import isElectron from 'is-electron';
|
import { type MutableRefObject } from 'react';
|
||||||
import { type MutableRefObject, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
import { usePlayerContext } from '/@/renderer/features/player/context/player-context';
|
||||||
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||||
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
import { usePlaybackType } from '/@/renderer/store/settings.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 { Popover } from '/@/shared/components/popover/popover';
|
|
||||||
import { Song } from '/@/shared/types/domain-types';
|
import { Song } from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
|
||||||
|
|
||||||
interface PlayQueueListOptionsProps {
|
interface PlayQueueListOptionsProps {
|
||||||
handleSearch: (value: string) => void;
|
handleSearch: (value: string) => void;
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
@@ -41,54 +38,49 @@ export const PlayQueueListControls = ({
|
|||||||
|
|
||||||
// const { pause } = usePlayerControls();
|
// const { pause } = usePlayerControls();
|
||||||
|
|
||||||
|
const player = usePlayerContext();
|
||||||
|
|
||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
// const setCurrentTime = useSetCurrentTime();
|
// const setCurrentTime = useSetCurrentTime();
|
||||||
|
|
||||||
const handleMoveToNext = () => {
|
const handleMoveToNext = () => {
|
||||||
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
// const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
// const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
if (!uniqueIds?.length) return;
|
// if (!uniqueIds?.length) return;
|
||||||
|
// // const playerData = moveToNextOfQueue(uniqueIds);
|
||||||
// const playerData = moveToNextOfQueue(uniqueIds);
|
// // if (playbackType === PlaybackType.LOCAL) {
|
||||||
|
// // setQueueNext(playerData);
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
// // }
|
||||||
// setQueueNext(playerData);
|
// player.moveSelectedToNext(selectedRows);
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveToBottom = () => {
|
const handleMoveToBottom = () => {
|
||||||
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
// const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
// const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
if (!uniqueIds?.length) return;
|
// if (!uniqueIds?.length) return;
|
||||||
|
|
||||||
// const playerData = moveToBottomOfQueue(uniqueIds);
|
// const playerData = moveToBottomOfQueue(uniqueIds);
|
||||||
|
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
// if (playbackType === PlaybackType.LOCAL) {
|
||||||
// setQueueNext(playerData);
|
// setQueueNext(playerData);
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveToTop = () => {
|
const handleMoveToTop = () => {
|
||||||
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
// const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
// const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
if (!uniqueIds?.length) return;
|
// if (!uniqueIds?.length) return;
|
||||||
|
|
||||||
// const playerData = moveToTopOfQueue(uniqueIds);
|
// const playerData = moveToTopOfQueue(uniqueIds);
|
||||||
|
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
// if (playbackType === PlaybackType.LOCAL) {
|
||||||
// setQueueNext(playerData);
|
// setQueueNext(playerData);
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveSelected = () => {
|
const handleRemoveSelected = () => {
|
||||||
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
// const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
// const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
if (!uniqueIds?.length) return;
|
// if (!uniqueIds?.length) return;
|
||||||
|
|
||||||
// const currentSong = usePlayerStore.getState().current.song;
|
// const currentSong = usePlayerStore.getState().current.song;
|
||||||
// const playerData = removeFromQueue(uniqueIds);
|
// const playerData = removeFromQueue(uniqueIds);
|
||||||
// const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId);
|
// const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId);
|
||||||
|
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
// if (playbackType === PlaybackType.LOCAL) {
|
||||||
// if (isCurrentSongRemoved) {
|
// if (isCurrentSongRemoved) {
|
||||||
// setQueue(playerData);
|
// setQueue(playerData);
|
||||||
@@ -96,7 +88,6 @@ export const PlayQueueListControls = ({
|
|||||||
// setQueueNext(playerData);
|
// setQueueNext(playerData);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if (isCurrentSongRemoved) {
|
// if (isCurrentSongRemoved) {
|
||||||
// updateSong(playerData.current.song);
|
// updateSong(playerData.current.song);
|
||||||
// }
|
// }
|
||||||
@@ -123,25 +114,9 @@ export const PlayQueueListControls = ({
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchTerm = useCallback(
|
|
||||||
(term: string) => {
|
|
||||||
handleSearch(term);
|
|
||||||
tableRef.current?.grid.api.redrawRows();
|
|
||||||
},
|
|
||||||
[handleSearch, tableRef],
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasSearch = !!searchTerm;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group justify="space-between" px="1rem" py="1rem" w="100%">
|
||||||
justify="space-between"
|
<Group gap="xs">
|
||||||
px="1rem"
|
|
||||||
py="1rem"
|
|
||||||
style={{ alignItems: 'center' }}
|
|
||||||
w="100%"
|
|
||||||
>
|
|
||||||
<Group gap="sm">
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="mediaShuffle"
|
icon="mediaShuffle"
|
||||||
iconProps={{ size: 'lg' }}
|
iconProps={{ size: 'lg' }}
|
||||||
@@ -150,7 +125,7 @@ export const PlayQueueListControls = ({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
disabled={hasSearch}
|
// disabled={hasSearch}
|
||||||
icon="mediaPlayNext"
|
icon="mediaPlayNext"
|
||||||
iconProps={{ size: 'lg' }}
|
iconProps={{ size: 'lg' }}
|
||||||
onClick={handleMoveToNext}
|
onClick={handleMoveToNext}
|
||||||
@@ -158,7 +133,7 @@ export const PlayQueueListControls = ({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
disabled={hasSearch}
|
// disabled={hasSearch}
|
||||||
icon="arrowDownToLine"
|
icon="arrowDownToLine"
|
||||||
iconProps={{ size: 'lg' }}
|
iconProps={{ size: 'lg' }}
|
||||||
onClick={handleMoveToBottom}
|
onClick={handleMoveToBottom}
|
||||||
@@ -166,7 +141,7 @@ export const PlayQueueListControls = ({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
disabled={hasSearch}
|
// disabled={hasSearch}
|
||||||
icon="arrowUpToLine"
|
icon="arrowUpToLine"
|
||||||
iconProps={{ size: 'lg' }}
|
iconProps={{ size: 'lg' }}
|
||||||
onClick={handleMoveToTop}
|
onClick={handleMoveToTop}
|
||||||
@@ -189,27 +164,12 @@ export const PlayQueueListControls = ({
|
|||||||
tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
<SearchInput
|
|
||||||
onChange={(e) => handleSearchTerm(e.target.value)}
|
|
||||||
value={searchTerm}
|
|
||||||
/>
|
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
<Popover position="top-end" transitionProps={{ transition: 'fade' }}>
|
<ListConfigMenu
|
||||||
<Popover.Target>
|
listKey={ItemListKey.SIDE_QUEUE}
|
||||||
<ActionIcon
|
tableColumnsData={SONG_TABLE_COLUMNS}
|
||||||
icon="settings"
|
/>
|
||||||
iconProps={{ size: 'lg' }}
|
|
||||||
tooltip={{
|
|
||||||
label: t('common.configure', { postProcess: 'sentenceCase' }),
|
|
||||||
}}
|
|
||||||
variant="subtle"
|
|
||||||
/>
|
|
||||||
</Popover.Target>
|
|
||||||
<Popover.Dropdown>
|
|
||||||
<TableConfigDropdown type={type} />
|
|
||||||
</Popover.Dropdown>
|
|
||||||
</Popover>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,51 +1,25 @@
|
|||||||
import type { RowClassRules, RowDragEvent, RowNode } from '@ag-grid-community/core';
|
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
import type { Ref } from 'react';
|
import type { Ref } from 'react';
|
||||||
|
|
||||||
import '@ag-grid-community/styles/ag-theme-alpine.css';
|
import { forwardRef, useMemo } from 'react';
|
||||||
import { useMergedRef } from '@mantine/hooks';
|
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
|
|
||||||
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback';
|
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { useListSettings, usePlayerQueue } from '/@/renderer/store';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
|
|
||||||
import { useAppFocus } from '/@/renderer/hooks';
|
|
||||||
import {
|
|
||||||
useAppStoreActions,
|
|
||||||
usePlayerQueue,
|
|
||||||
usePlayerSong,
|
|
||||||
usePlayerStatus,
|
|
||||||
} from '/@/renderer/store';
|
|
||||||
import {
|
|
||||||
useSettingsStore,
|
|
||||||
useSettingsStoreActions,
|
|
||||||
useTableSettings,
|
|
||||||
} from '/@/renderer/store/settings.store';
|
|
||||||
import { searchSongs } from '/@/renderer/utils/search-songs';
|
import { searchSongs } from '/@/renderer/utils/search-songs';
|
||||||
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
type QueueProps = {
|
type QueueProps = {
|
||||||
type: ItemListKey;
|
listKey: ItemListKey;
|
||||||
|
searchTerm: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref<any>) => {
|
export const PlayQueue = forwardRef(({ listKey, searchTerm }: QueueProps, ref: Ref<any>) => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const { table } = useListSettings(listKey);
|
||||||
const mergedRef = useMergedRef(ref, tableRef);
|
|
||||||
const queue = usePlayerQueue();
|
|
||||||
const currentSong = usePlayerSong();
|
|
||||||
const status = usePlayerStatus();
|
|
||||||
const { setSettings } = useSettingsStoreActions();
|
|
||||||
const { setAppStore } = useAppStoreActions();
|
|
||||||
const tableConfig = useTableSettings(type);
|
|
||||||
const [gridApi, setGridApi] = useState<AgGridReactType | undefined>();
|
|
||||||
const isFocused = useAppFocus();
|
|
||||||
const isFocusedRef = useRef<boolean>(isFocused);
|
|
||||||
|
|
||||||
const songs = useMemo(() => {
|
const queue = usePlayerQueue();
|
||||||
|
|
||||||
|
const data: QueueSong[] = useMemo(() => {
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
return searchSongs(queue, searchTerm);
|
return searchSongs(queue, searchTerm);
|
||||||
}
|
}
|
||||||
@@ -53,230 +27,21 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref<
|
|||||||
return queue;
|
return queue;
|
||||||
}, [queue, searchTerm]);
|
}, [queue, searchTerm]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (tableRef.current) {
|
|
||||||
setGridApi(tableRef.current);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
get grid() {
|
|
||||||
return gridApi;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const columnDefs = useMemo(
|
|
||||||
() => getColumnDefs(tableConfig.columns, false, 'generic'),
|
|
||||||
[tableConfig.columns],
|
|
||||||
);
|
|
||||||
|
|
||||||
// const handleDoubleClick = (e: CellDoubleClickedEvent) => {
|
|
||||||
// const playerData = setCurrentTrack(e.data.uniqueId);
|
|
||||||
// updateSong(playerData.current.song);
|
|
||||||
|
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
|
||||||
// mpvPlayer!.volume(volume);
|
|
||||||
// setQueue(playerData, false);
|
|
||||||
// } else {
|
|
||||||
// const player =
|
|
||||||
// playerData.current.player === 1
|
|
||||||
// ? PlayersRef.current?.player1
|
|
||||||
// : PlayersRef.current?.player2;
|
|
||||||
// const underlying = player?.getInternalPlayer();
|
|
||||||
// if (underlying) {
|
|
||||||
// underlying.currentTime = 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// play();
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleDragStart = () => {
|
|
||||||
if (type === 'sideDrawerQueue') {
|
|
||||||
setAppStore({ isReorderingQueue: true });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeout: any;
|
|
||||||
const handleDragEnd = (e: RowDragEvent<QueueSong>) => {
|
|
||||||
if (!e.nodes.length) return;
|
|
||||||
const selectedUniqueIds = e.nodes
|
|
||||||
.map((node) => node.data?._uniqueId)
|
|
||||||
.filter((e) => e !== undefined);
|
|
||||||
|
|
||||||
// const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId);
|
|
||||||
|
|
||||||
// if (playbackType === PlaybackType.LOCAL) {
|
|
||||||
// setQueueNext(playerData);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (type === 'sideDrawerQueue') {
|
|
||||||
setAppStore({ isReorderingQueue: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { api } = tableRef?.current || {};
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => api?.redrawRows(), 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGridReady = () => {
|
|
||||||
const { api } = tableRef?.current || {};
|
|
||||||
|
|
||||||
if (currentSong?._uniqueId) {
|
|
||||||
const currentNode = api?.getRowNode(currentSong?._uniqueId);
|
|
||||||
|
|
||||||
if (!currentNode) return;
|
|
||||||
api?.ensureNodeVisible(currentNode, 'middle');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleColumnChange = () => {
|
|
||||||
const { columnApi } = tableRef?.current || {};
|
|
||||||
const columnsOrder = columnApi?.getAllGridColumns();
|
|
||||||
if (!columnsOrder) return;
|
|
||||||
|
|
||||||
const columnsInSettings = useSettingsStore.getState().lists[type].columns;
|
|
||||||
|
|
||||||
const updatedColumns: PersistedTableColumn[] = [];
|
|
||||||
for (const column of columnsOrder) {
|
|
||||||
const columnInSettings = columnsInSettings.find(
|
|
||||||
(c) => c.column === column.getColDef().colId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (columnInSettings) {
|
|
||||||
updatedColumns.push({
|
|
||||||
...columnInSettings,
|
|
||||||
...(!useSettingsStore.getState().lists[type].autoFit && {
|
|
||||||
width: column.getActualWidth(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettings({
|
|
||||||
lists: {
|
|
||||||
...useSettingsStore.getState().lists,
|
|
||||||
[type]: {
|
|
||||||
...useSettingsStore.getState().lists[type],
|
|
||||||
columns: updatedColumns,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const debouncedColumnChange = debounce(handleColumnChange, 250);
|
|
||||||
|
|
||||||
const handleGridSizeChange = () => {
|
|
||||||
if (tableConfig.autoFit) {
|
|
||||||
tableRef?.current?.api?.sizeColumnsToFit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowClassRules = useMemo<RowClassRules | undefined>(() => {
|
|
||||||
return {
|
|
||||||
'current-song': (params) => {
|
|
||||||
return params.data.uniqueId === currentSong?._uniqueId;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [currentSong?._uniqueId]);
|
|
||||||
|
|
||||||
const previousSongRef = useRef<QueueSong | undefined>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentSong) {
|
|
||||||
previousSongRef.current = currentSong;
|
|
||||||
}
|
|
||||||
}, [currentSong]);
|
|
||||||
|
|
||||||
// Redraw the current song row when the previous song changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (tableRef?.current) {
|
|
||||||
const { api, columnApi } = tableRef?.current || {};
|
|
||||||
if (api == null || columnApi == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentNode = currentSong?._uniqueId
|
|
||||||
? api.getRowNode(currentSong._uniqueId)
|
|
||||||
: undefined;
|
|
||||||
const previousNode = previousSongRef.current?._uniqueId
|
|
||||||
? api.getRowNode(previousSongRef.current?._uniqueId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const rowNodes = [currentNode, previousNode].filter(
|
|
||||||
(e) => e !== undefined,
|
|
||||||
) as RowNode<any>[];
|
|
||||||
|
|
||||||
if (rowNodes) {
|
|
||||||
api.redrawRows({ rowNodes });
|
|
||||||
if (tableConfig.followCurrentSong) {
|
|
||||||
if (!currentNode) return;
|
|
||||||
api.ensureNodeVisible(currentNode, 'middle');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [currentSong, previousSongRef, tableConfig.followCurrentSong, status]);
|
|
||||||
|
|
||||||
// As a separate rule, update the current row when focus changes. This is
|
|
||||||
// to prevent queue scrolling when the application loses and then gains focus.
|
|
||||||
// The body should only fire when focus changes, even though it depends on current song
|
|
||||||
useEffect(() => {
|
|
||||||
if (isFocused !== isFocusedRef.current && tableRef?.current) {
|
|
||||||
const { api, columnApi } = tableRef.current;
|
|
||||||
if (api == null || columnApi == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentNode = currentSong?._uniqueId
|
|
||||||
? api.getRowNode(currentSong._uniqueId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (currentNode) {
|
|
||||||
api.redrawRows({ rowNodes: [currentNode] });
|
|
||||||
}
|
|
||||||
|
|
||||||
isFocusedRef.current = isFocused;
|
|
||||||
}
|
|
||||||
}, [currentSong, isFocused]);
|
|
||||||
|
|
||||||
const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, QUEUE_CONTEXT_MENU_ITEMS);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ItemTableList
|
||||||
<VirtualGridAutoSizerContainer>
|
CellComponent={ItemTableListColumn}
|
||||||
<VirtualTable
|
columns={table.columns}
|
||||||
alwaysShowHorizontalScroll
|
data={data || []}
|
||||||
autoFitColumns={tableConfig.autoFit}
|
enableAlternateRowColors={table.enableAlternateRowColors}
|
||||||
columnDefs={columnDefs}
|
enableExpansion={false}
|
||||||
context={{
|
enableHeader={true}
|
||||||
currentSong,
|
enableHorizontalBorders={table.enableHorizontalBorders}
|
||||||
// handleDoubleClick,
|
enableRowHoverHighlight={table.enableRowHoverHighlight}
|
||||||
isFocused,
|
enableSelection={true}
|
||||||
isQueue: true,
|
enableVerticalBorders={table.enableVerticalBorders}
|
||||||
itemType: LibraryItem.SONG,
|
itemType={LibraryItem.ALBUM}
|
||||||
onCellContextMenu,
|
ref={ref}
|
||||||
status,
|
size={table.size}
|
||||||
}}
|
/>
|
||||||
deselectOnClickOutside={type === 'fullScreen'}
|
|
||||||
getRowId={(data) => data.data.uniqueId}
|
|
||||||
onCellContextMenu={onCellContextMenu}
|
|
||||||
// onCellDoubleClicked={handleDoubleClick}
|
|
||||||
onColumnMoved={handleColumnChange}
|
|
||||||
onColumnResized={debouncedColumnChange}
|
|
||||||
onDragStarted={handleDragStart}
|
|
||||||
onGridReady={handleGridReady}
|
|
||||||
onGridSizeChanged={handleGridSizeChange}
|
|
||||||
onRowDragEnd={handleDragEnd}
|
|
||||||
ref={mergedRef}
|
|
||||||
rowBuffer={50}
|
|
||||||
rowClassRules={rowClassRules}
|
|
||||||
rowData={songs}
|
|
||||||
rowDragEntireRow
|
|
||||||
rowDragMultiRow
|
|
||||||
rowHeight={tableConfig.rowHeight || 40}
|
|
||||||
suppressCellFocus={type === 'fullScreen'}
|
|
||||||
/>
|
|
||||||
</VirtualGridAutoSizerContainer>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
|
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import { useWindowSettings } from '/@/renderer/store/settings.store';
|
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
||||||
import { Song } from '/@/shared/types/domain-types';
|
import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls';
|
||||||
import { Platform } from '/@/shared/types/types';
|
import { Flex } from '/@/shared/components/flex/flex';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const SidebarPlayQueue = () => {
|
export const SidebarPlayQueue = () => {
|
||||||
const queueRef = useRef<null | { grid: AgGridReactType<Song> }>(null);
|
const tableRef = useRef<null>(null);
|
||||||
|
// const queueRef = useRef<null | { grid: AgGridReactType<Song> }>(null);
|
||||||
const [search, setSearch] = useState<string | undefined>(undefined);
|
const [search, setSearch] = useState<string | undefined>(undefined);
|
||||||
const { windowBarStyle } = useWindowSettings();
|
|
||||||
|
|
||||||
const isWeb = windowBarStyle === Platform.WEB;
|
return (
|
||||||
|
<Flex direction="column" h="100%">
|
||||||
return null;
|
<PlayQueueListControls
|
||||||
|
handleSearch={setSearch}
|
||||||
|
searchTerm={search}
|
||||||
|
tableRef={tableRef}
|
||||||
|
type={ItemListKey.SIDE_QUEUE}
|
||||||
|
/>
|
||||||
|
<PlayQueue listKey={ItemListKey.SIDE_QUEUE} searchTerm={search} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '/@/renderer/store/full-screen-player.store';
|
} from '/@/renderer/store/full-screen-player.store';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { PlayerType } from '/@/shared/types/types';
|
import { ItemListKey, PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const Visualizer = lazy(() =>
|
const Visualizer = lazy(() =>
|
||||||
import('/@/renderer/features/player/components/visualizer').then((module) => ({
|
import('/@/renderer/features/player/components/visualizer').then((module) => ({
|
||||||
@@ -99,7 +99,7 @@ export const FullScreenPlayerQueue = () => {
|
|||||||
</Group>
|
</Group>
|
||||||
{activeTab === 'queue' ? (
|
{activeTab === 'queue' ? (
|
||||||
<div className={styles.queueContainer}>
|
<div className={styles.queueContainer}>
|
||||||
<PlayQueue type="fullScreen" />
|
<PlayQueue listKey={ItemListKey.FULL_SCREEN} searchTerm={undefined} />
|
||||||
</div>
|
</div>
|
||||||
) : activeTab === 'related' ? (
|
) : activeTab === 'related' ? (
|
||||||
<div className={styles.queueContainer}>
|
<div className={styles.queueContainer}>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
.right-sidebar-container {
|
.right-sidebar-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
grid-area: right-sidebar;
|
grid-area: right-sidebar;
|
||||||
height: 100%;
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
border-left: 1px solid alpha(var(--theme-colors-border), 0.3);
|
border-left: 1px solid alpha(var(--theme-colors-border), 0.3);
|
||||||
|
|
||||||
.current-song-cell:not(.current-playlist-song-cell) svg {
|
.current-song-cell:not(.current-playlist-song-cell) svg {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
import { Song } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const searchSongs = (songs: Song[], searchTerm: string) => {
|
export const searchSongs = (songs: QueueSong[], searchTerm: string) => {
|
||||||
const fuse = new Fuse(songs, {
|
const fuse = new Fuse(songs, {
|
||||||
fieldNormWeight: 1,
|
fieldNormWeight: 1,
|
||||||
ignoreLocation: true,
|
ignoreLocation: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user