Browse Source

Add move-selected to context menu

- Move local playlist data to redux store
- Refactor selected move logic to be reusable
master
jeffvli 3 years ago
parent
commit
594463d2d8
  1. 2
      src/components/library/AlbumView.tsx
  2. 37
      src/components/playlist/PlaylistView.tsx
  3. 206
      src/components/shared/ContextMenu.tsx
  4. 2
      src/components/shared/styled.ts
  5. 4
      src/components/starred/StarredView.tsx
  6. 3
      src/redux/miscSlice.ts
  7. 104
      src/redux/playQueueSlice.ts
  8. 61
      src/redux/playlistSlice.ts
  9. 2
      src/redux/store.ts
  10. 130
      src/shared/utils.ts

2
src/components/library/AlbumView.tsx

@ -197,7 +197,7 @@ const AlbumView = ({ ...rest }: any) => {
}}
listType="music"
isModal={rest.isModal}
disabledContextMenuOptions={['removeFromCurrent']}
disabledContextMenuOptions={['removeFromCurrent', 'moveSelectedTo']}
/>
</GenericPage>
);

37
src/components/playlist/PlaylistView.tsx

@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import settings from 'electron-settings';
@ -40,7 +41,6 @@ import {
errorMessages,
getRecoveryPath,
isFailedResponse,
moveToIndex,
} from '../../shared/utils';
import useSearchQuery from '../../hooks/useSearchQuery';
import GenericPage from '../layout/GenericPage';
@ -51,6 +51,7 @@ import { setStatus } from '../../redux/playerSlice';
import { notifyToast } from '../shared/toast';
import { addProcessingPlaylist, removeProcessingPlaylist } from '../../redux/miscSlice';
import { StyledButton, StyledCheckbox, StyledInputGroup } from '../shared/styled';
import { moveToIndex, setPlaylistData } from '../../redux/playlistSlice';
interface PlaylistParams {
id: string;
@ -59,6 +60,7 @@ interface PlaylistParams {
const PlaylistView = ({ ...rest }) => {
const [isModified, setIsModified] = useState(false);
const dispatch = useAppDispatch();
const playlist = useAppSelector((state) => state.playlist);
const playQueue = useAppSelector((state) => state.playQueue);
const multiSelect = useAppSelector((state) => state.multiSelect);
const misc = useAppSelector((state) => state.misc);
@ -78,11 +80,10 @@ const PlaylistView = ({ ...rest }) => {
const [editDescription, setEditDescription] = useState('');
const [editPublic, setEditPublic] = useState(false);
const [isSubmittingEdit, setIsSubmittingEdit] = useState(false);
const [localPlaylistData, setLocalPlaylistData] = useState(data);
const [searchQuery, setSearchQuery] = useState('');
const [recoveryPath, setRecoveryPath] = useState('');
const [needsRecovery, setNeedsRecovery] = useState(false);
const filteredData = useSearchQuery(searchQuery, localPlaylistData, ['title', 'artist', 'album']);
const filteredData = useSearchQuery(searchQuery, playlist.entry, ['title', 'artist', 'album']);
useEffect(() => {
const recoveryFilePath = path.join(getRecoveryPath(), `playlist_${data?.id}.json`);
@ -93,19 +94,19 @@ const PlaylistView = ({ ...rest }) => {
useEffect(() => {
// Set the local playlist data on any changes
setLocalPlaylistData(data?.song);
dispatch(setPlaylistData(data?.song));
setEditName(data?.name);
setEditDescription(data?.comment);
setEditPublic(data?.public);
}, [data]);
}, [data, dispatch]);
useEffect(() => {
if (data?.song !== localPlaylistData) {
if (!_.isEqual(data?.song, playlist.entry)) {
setIsModified(true);
} else {
setIsModified(false);
}
}, [data?.song, localPlaylistData]);
}, [data?.song, playlist.entry]);
let timeout: any = null;
const handleRowClick = (e: any, rowData: any) => {
@ -117,7 +118,7 @@ const PlaylistView = ({ ...rest }) => {
dispatch(toggleSelected(rowData));
} else if (e.shiftKey) {
dispatch(setRangeSelected(rowData));
dispatch(toggleRangeSelected(searchQuery !== '' ? filteredData : localPlaylistData));
dispatch(toggleRangeSelected(searchQuery !== '' ? filteredData : playlist.entry));
}
}, 100);
}
@ -130,7 +131,7 @@ const PlaylistView = ({ ...rest }) => {
dispatch(clearSelected());
dispatch(
setPlayQueueByRowClick({
entries: localPlaylistData,
entries: playlist.entry,
currentIndex: e.rowIndex,
currentSongId: e.id,
uniqueSongId: e.uniqueId,
@ -141,12 +142,12 @@ const PlaylistView = ({ ...rest }) => {
};
const handlePlay = () => {
dispatch(setPlayQueue({ entries: localPlaylistData }));
dispatch(setPlayQueue({ entries: playlist.entry }));
dispatch(setStatus('PLAYING'));
};
const handlePlayAppend = () => {
dispatch(appendPlayQueue({ entries: localPlaylistData }));
dispatch(appendPlayQueue({ entries: playlist.entry }));
if (playQueue.entry.length < 1) {
dispatch(setStatus('PLAYING'));
}
@ -159,7 +160,7 @@ const PlaylistView = ({ ...rest }) => {
let res;
const playlistData = recovery
? JSON.parse(fs.readFileSync(recoveryPath, { encoding: 'utf-8' }))
: localPlaylistData;
: playlist.entry;
// Smaller playlists can use the safe /createPlaylist method of saving
if (playlistData.length <= 400 && !recovery) {
@ -244,8 +245,8 @@ const PlaylistView = ({ ...rest }) => {
const handleDragEnd = () => {
if (multiSelect.isDragging) {
setLocalPlaylistData(
moveToIndex(localPlaylistData, multiSelect.selected, multiSelect.currentMouseOverId)
dispatch(
moveToIndex({ entries: multiSelect.selected, moveBeforeId: multiSelect.currentMouseOverId })
);
dispatch(setIsDragging(false));
}
@ -282,13 +283,13 @@ const PlaylistView = ({ ...rest }) => {
appearance="primary"
size="lg"
onClick={handlePlay}
disabled={localPlaylistData?.length < 1}
disabled={playlist.entry?.length < 1}
/>
<PlayAppendButton
appearance="primary"
size="lg"
onClick={handlePlayAppend}
disabled={localPlaylistData?.length < 1}
disabled={playlist.entry?.length < 1}
/>
<SaveButton
size="lg"
@ -307,7 +308,7 @@ const PlaylistView = ({ ...rest }) => {
disabled={
needsRecovery || !isModified || misc.isProcessingPlaylist.includes(data?.id)
}
onClick={() => setLocalPlaylistData(data?.song)}
onClick={() => dispatch(setPlaylistData(data?.song))}
/>
<Whisper
ref={editTriggerRef}
@ -387,7 +388,7 @@ const PlaylistView = ({ ...rest }) => {
}
>
<ListViewType
data={searchQuery !== '' ? filteredData : localPlaylistData}
data={searchQuery !== '' ? filteredData : playlist.entry}
tableColumns={settings.getSync('musicListColumns')}
handleRowClick={handleRowClick}
handleRowDoubleClick={handleRowDoubleClick}

206
src/components/shared/ContextMenu.tsx

@ -3,7 +3,7 @@ import React, { useRef, useState } from 'react';
import _ from 'lodash';
import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router';
import { Form, Input, Whisper } from 'rsuite';
import { Col, FlexboxGrid, Form, Grid, Icon, Input, Row, Whisper } from 'rsuite';
import {
getPlaylists,
updatePlaylistSongsLg,
@ -20,9 +20,20 @@ import {
import {
appendPlayQueue,
fixPlayer2Index,
moveDown,
moveToBottom,
moveToIndex,
moveToTop,
moveUp,
removeFromPlayQueue,
setStar,
} from '../../redux/playQueueSlice';
import {
moveToBottom as plMoveToBottom,
moveToTop as plMoveToTop,
moveUp as plMoveUp,
moveDown as plMoveDown,
} from '../../redux/playlistSlice';
import {
ContextMenuDivider,
ContextMenuWindow,
@ -31,15 +42,20 @@ import {
StyledButton,
StyledInputGroup,
StyledPopover,
StyledInputNumber,
StyledIconButton,
} from './styled';
import { notifyToast } from './toast';
import { errorMessages, isFailedResponse } from '../../shared/utils';
import { errorMessages, getCurrentEntryList, isFailedResponse } from '../../shared/utils';
export const ContextMenuButton = ({ text, children, ...rest }: any) => {
export const ContextMenuButton = ({ text, hotkey, children, ...rest }: any) => {
return (
<StyledContextMenuButton {...rest} appearance="subtle" size="sm" block>
{children}
{text}
<FlexboxGrid justify="space-between">
<FlexboxGrid.Item>{text}</FlexboxGrid.Item>
<FlexboxGrid.Item>{hotkey}</FlexboxGrid.Item>
</FlexboxGrid>
</StyledContextMenuButton>
);
};
@ -78,6 +94,7 @@ export const GlobalContextMenu = () => {
const [selectedPlaylistId, setSelectedPlaylistId] = useState('');
const [shouldCreatePlaylist, setShouldCreatePlaylist] = useState(false);
const [newPlaylistName, setNewPlaylistName] = useState('');
const [indexToMoveTo, setIndexToMoveTo] = useState(0);
const { data: playlists }: any = useQuery(['playlists', 'name'], () => getPlaylists('name'), {
refetchOnWindowFocus: false,
@ -120,7 +137,7 @@ export const GlobalContextMenu = () => {
<>
<p>
Added {sortedEntries.length} song(s) to playlist &quot;
{playlists.find((playlist: any) => playlist.id === localSelectedPlaylistId)?.name}
{playlists.find((pl: any) => pl.id === localSelectedPlaylistId)?.name}
&quot;
</p>
<StyledButton
@ -220,6 +237,59 @@ export const GlobalContextMenu = () => {
}
};
const handleMoveSelectedToIndex = () => {
const currentEntryList = getCurrentEntryList(playQueue);
const uniqueIdOfIndexToMoveTo = playQueue[currentEntryList][indexToMoveTo].uniqueId;
dispatch(moveToIndex({ entries: multiSelect.selected, moveBeforeId: uniqueIdOfIndexToMoveTo }));
};
const handleMoveToTop = () => {
if (misc.contextMenu.type === 'nowPlaying') {
dispatch(moveToTop({ selectedEntries: multiSelect.selected }));
if (playQueue.currentPlayer === 1) {
dispatch(fixPlayer2Index());
}
} else {
dispatch(plMoveToTop({ selectedEntries: multiSelect.selected }));
}
};
const handleMoveToBottom = () => {
if (misc.contextMenu.type === 'nowPlaying') {
dispatch(moveToBottom({ selectedEntries: multiSelect.selected }));
if (playQueue.currentPlayer === 1) {
dispatch(fixPlayer2Index());
}
} else {
dispatch(plMoveToBottom({ selectedEntries: multiSelect.selected }));
}
};
const handleMoveUpOne = () => {
if (misc.contextMenu.type === 'nowPlaying') {
dispatch(moveUp({ selectedEntries: multiSelect.selected }));
if (playQueue.currentPlayer === 1) {
dispatch(fixPlayer2Index());
}
} else {
dispatch(plMoveUp({ selectedEntries: multiSelect.selected }));
}
};
const handleMoveDownOne = () => {
if (misc.contextMenu.type === 'nowPlaying') {
dispatch(moveDown({ selectedEntries: multiSelect.selected }));
if (playQueue.currentPlayer === 1) {
dispatch(fixPlayer2Index());
}
} else {
dispatch(plMoveDown({ selectedEntries: multiSelect.selected }));
}
};
return (
<>
{misc.contextMenu.show && (
@ -227,10 +297,10 @@ export const GlobalContextMenu = () => {
xPos={misc.contextMenu.xPos}
yPos={misc.contextMenu.yPos}
width={190}
numOfButtons={6}
numOfDividers={3}
numOfButtons={7}
numOfDividers={4}
>
<ContextMenuButton text={`Selected: ${multiSelect.selected.length}`} />
<ContextMenuButton text={`Selected: ${multiSelect.selected.length}`} disabled />
<ContextMenuDivider />
<ContextMenuButton
text="Add to queue"
@ -251,24 +321,27 @@ export const GlobalContextMenu = () => {
trigger="none"
speaker={
<StyledPopover>
<StyledInputPicker
data={playlists}
placement="autoVerticalStart"
virtualized
labelKey="name"
valueKey="id"
width={200}
onChange={(e: any) => setSelectedPlaylistId(e)}
/>
<StyledButton
disabled={
!selectedPlaylistId || misc.isProcessingPlaylist.includes(selectedPlaylistId)
}
loading={misc.isProcessingPlaylist.includes(selectedPlaylistId)}
onClick={handleAddToPlaylist}
>
Add
</StyledButton>
<StyledInputGroup>
<StyledInputPicker
data={playlists}
placement="autoVerticalStart"
virtualized
labelKey="name"
valueKey="id"
width={200}
onChange={(e: any) => setSelectedPlaylistId(e)}
/>
<StyledButton
disabled={
!selectedPlaylistId || misc.isProcessingPlaylist.includes(selectedPlaylistId)
}
loading={misc.isProcessingPlaylist.includes(selectedPlaylistId)}
onClick={handleAddToPlaylist}
>
Add
</StyledButton>
</StyledInputGroup>
<div>
<StyledButton
appearance="link"
@ -327,6 +400,87 @@ export const GlobalContextMenu = () => {
onClick={handleUnfavorite}
disabled={misc.contextMenu.disabledOptions.includes('removeFromFavorites')}
/>
<ContextMenuDivider />
<Whisper
enterable
placement="autoHorizontalStart"
trigger="hover"
speaker={
<StyledPopover>
<Form>
<StyledInputGroup>
<StyledInputNumber
defaultValue={0}
min={0}
max={playQueue[getCurrentEntryList(playQueue)].length}
value={indexToMoveTo}
onChange={(e: number) => setIndexToMoveTo(e)}
/>
<StyledButton
type="submit"
onClick={handleMoveSelectedToIndex}
disabled={
indexToMoveTo > playQueue[getCurrentEntryList(playQueue)].length ||
indexToMoveTo < 0
}
>
Go
</StyledButton>
</StyledInputGroup>
</Form>
<Grid fluid>
<Row>
<Col xs={12}>
<StyledIconButton
icon={<Icon icon="angle-double-up" />}
onClick={handleMoveToTop}
block
>
Top
</StyledIconButton>
</Col>
<Col xs={12}>
<StyledIconButton
icon={<Icon icon="angle-up" />}
onClick={handleMoveUpOne}
block
>
Up
</StyledIconButton>
</Col>
</Row>
<Row>
<Col xs={12}>
<StyledIconButton
icon={<Icon icon="angle-double-down" />}
onClick={handleMoveToBottom}
block
>
Bottom
</StyledIconButton>
</Col>
<Col xs={12}>
<StyledIconButton
icon={<Icon icon="angle-down" />}
onClick={handleMoveDownOne}
block
>
Down
</StyledIconButton>
</Col>
</Row>
</Grid>
</StyledPopover>
}
>
<ContextMenuButton
text="Move selected to [...]"
disabled={misc.contextMenu.disabledOptions.includes('moveSelectedTo')}
/>
</Whisper>
</ContextMenu>
)}
</>

2
src/components/shared/styled.ts

@ -158,7 +158,7 @@ export const ContextMenuWindow = styled.div<{
top: ${(props) => `${props.yPos}px`};
left: ${(props) => `${props.xPos}px`};
height: ${(props) =>
`${props.numOfButtons * 30 + props.numOfDividers * 2 + (props.hasTitle ? 16 : 0)}px`};
`${props.numOfButtons * 30 + props.numOfDividers * 1.5 + (props.hasTitle ? 16 : 0)}px`};
width: ${(props) => `${props.width}px`};
margin: 0px;
white-space: normal;

4
src/components/starred/StarredView.tsx

@ -129,7 +129,7 @@ const StarredView = () => {
}}
listType="music"
virtualized
disabledContextMenuOptions={['removeFromCurrent']}
disabledContextMenuOptions={['removeFromCurrent', 'moveSelectedTo']}
/>
)}
{currentPage === 'Albums' && (
@ -149,7 +149,7 @@ const StarredView = () => {
}}
listType="album"
virtualized
disabledContextMenuOptions={['removeFromCurrent']}
disabledContextMenuOptions={['removeFromCurrent', 'moveSelectedTo']}
/>
)}
{viewType === 'grid' && (

3
src/redux/miscSlice.ts

@ -19,7 +19,8 @@ type ContextMenuOptions =
| 'removeFromCurrent'
| 'addToPlaylist'
| 'addToFavorites'
| 'removeFromFavorites';
| 'removeFromFavorites'
| 'moveSelectedTo';
export interface ContextMenu {
show: boolean;

104
src/redux/playQueueSlice.ts

@ -2,8 +2,12 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import _ from 'lodash';
import settings from 'electron-settings';
import { nanoid } from 'nanoid/non-secure';
import arrayMove from 'array-move';
import { areConsecutive, consecutiveRanges } from '../shared/utils';
import {
moveSelectedDown,
moveSelectedToBottom,
moveSelectedToTop,
moveSelectedUp,
} from '../shared/utils';
import { mockSettings } from '../shared/mockSettings';
const parsedSettings = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
@ -896,41 +900,32 @@ const playQueueSlice = createSlice({
state.currentIndex = newCurrentSongIndex;
},
moveUp: (state, action: PayloadAction<number[]>) => {
moveToTop: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
const currentEntry = entrySelect(state);
const newQueue = moveSelectedToTop(state[currentEntry], action.payload.selectedEntries);
state[currentEntry] = newQueue;
// Create a copy of the queue so we can mutate it in place with arrayMove.mutate
const tempQueue = state[currentEntry].slice();
// Ascending index is needed to move the indexes in order
const selectedIndexesAsc = action.payload.sort((a, b) => a - b);
const cr = consecutiveRanges(selectedIndexesAsc);
// Handle case when index hits 0
if (
!(
selectedIndexesAsc.includes(0) &&
areConsecutive(selectedIndexesAsc, selectedIndexesAsc.length)
)
) {
selectedIndexesAsc.map((index) => {
if (cr[0]?.includes(0)) {
if (!cr[0]?.includes(index) && index !== 0) {
return arrayMove.mutate(tempQueue, index, index - 1);
}
} else if (index !== 0) {
return arrayMove.mutate(tempQueue, index, index - 1);
}
// We'll need to fix the current player index after swapping the queue order
// This will be used in conjunction with fixPlayer2Index
const newCurrentSongIndex = getCurrentEntryIndexByUID(newQueue, state.currentSongUniqueId);
return undefined;
});
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
} else {
state.player2.index = newCurrentSongIndex;
}
state[currentEntry] = tempQueue;
state.currentIndex = newCurrentSongIndex;
},
moveToBottom: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
const currentEntry = entrySelect(state);
const newQueue = moveSelectedToBottom(state[currentEntry], action.payload.selectedEntries);
state[currentEntry] = newQueue;
// We'll need to fix the current player index after swapping the queue order
// This will be used in conjunction with fixPlayer2Index
const newCurrentSongIndex = getCurrentEntryIndexByUID(tempQueue, state.currentSongUniqueId);
const newCurrentSongIndex = getCurrentEntryIndexByUID(newQueue, state.currentSongUniqueId);
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
@ -941,41 +936,36 @@ const playQueueSlice = createSlice({
state.currentIndex = newCurrentSongIndex;
},
moveDown: (state, action: PayloadAction<number[]>) => {
moveUp: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
const currentEntry = entrySelect(state);
state[currentEntry] = moveSelectedUp(state[currentEntry], action.payload.selectedEntries);
// Create a copy of the queue so we can mutate it in place with arrayMove.mutate
const tempQueue = state[currentEntry].slice();
// Descending index is needed to move the indexes in order
const cr = consecutiveRanges(action.payload.sort((a, b) => a - b));
const selectedIndexesDesc = action.payload.sort((a, b) => b - a);
// Handle case when index hits the end
if (
!(
selectedIndexesDesc.includes(tempQueue.length - 1) &&
areConsecutive(selectedIndexesDesc, selectedIndexesDesc.length)
)
) {
selectedIndexesDesc.map((index) => {
if (cr[0]?.includes(tempQueue.length - 1)) {
if (!cr[0]?.includes(index) && index !== tempQueue.length - 1) {
return arrayMove.mutate(tempQueue, index, index + 1);
}
} else if (index !== tempQueue.length - 1) {
return arrayMove.mutate(tempQueue, index, index + 1);
}
// We'll need to fix the current player index after swapping the queue order
// This will be used in conjunction with fixPlayer2Index
const newCurrentSongIndex = getCurrentEntryIndexByUID(
state[currentEntry],
state.currentSongUniqueId
);
return undefined;
});
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
} else {
state.player2.index = newCurrentSongIndex;
}
state[currentEntry] = tempQueue;
state.currentIndex = newCurrentSongIndex;
},
moveDown: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
const currentEntry = entrySelect(state);
state[currentEntry] = moveSelectedDown(state[currentEntry], action.payload.selectedEntries);
// We'll need to fix the current player index after swapping the queue order
// This will be used in conjunction with fixPlayer2Index
const newCurrentSongIndex = getCurrentEntryIndexByUID(tempQueue, state.currentSongUniqueId);
const newCurrentSongIndex = getCurrentEntryIndexByUID(
state[currentEntry],
state.currentSongUniqueId
);
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
@ -1006,6 +996,8 @@ export const {
clearPlayQueue,
setIsLoading,
setIsLoaded,
moveToTop,
moveToBottom,
moveUp,
moveDown,
moveToIndex,

61
src/redux/playlistSlice.ts

@ -0,0 +1,61 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
moveSelectedDown,
moveSelectedToBottom,
moveSelectedToIndex,
moveSelectedToTop,
moveSelectedUp,
} from '../shared/utils';
import { Entry } from './playQueueSlice';
export interface Playlist {
entry: Entry[];
}
const initialState: Playlist = {
entry: [],
};
const playlistSlice = createSlice({
name: 'playlist',
initialState,
reducers: {
setPlaylistData: (state, action: PayloadAction<Entry[]>) => {
state.entry = action.payload;
},
moveToIndex: (state, action: PayloadAction<{ entries: Entry[]; moveBeforeId: string }>) => {
state.entry = moveSelectedToIndex(
state.entry,
action.payload.entries,
action.payload.moveBeforeId
);
},
moveUp: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
state.entry = moveSelectedUp(state.entry, action.payload.selectedEntries);
},
moveDown: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
state.entry = moveSelectedDown(state.entry, action.payload.selectedEntries);
},
moveToTop: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
state.entry = moveSelectedToTop(state.entry, action.payload.selectedEntries);
},
moveToBottom: (state, action: PayloadAction<{ selectedEntries: Entry[] }>) => {
state.entry = moveSelectedToBottom(state.entry, action.payload.selectedEntries);
},
},
});
export const {
setPlaylistData,
moveToIndex,
moveUp,
moveDown,
moveToBottom,
moveToTop,
} = playlistSlice.actions;
export default playlistSlice.reducer;

2
src/redux/store.ts

@ -4,6 +4,7 @@ import playerReducer from './playerSlice';
import playQueueReducer, { PlayQueue } from './playQueueSlice';
import multiSelectReducer from './multiSelectSlice';
import miscReducer from './miscSlice';
import playlistReducer from './playlistSlice';
export const store = configureStore<PlayQueue | any>({
reducer: {
@ -11,6 +12,7 @@ export const store = configureStore<PlayQueue | any>({
playQueue: playQueueReducer,
multiSelect: multiSelectReducer,
misc: miscReducer,
playlist: playlistReducer,
},
middleware: [forwardToMain],
});

130
src/shared/utils.ts

@ -2,6 +2,7 @@ import fs from 'fs';
import _ from 'lodash';
import path from 'path';
import moment from 'moment';
import arrayMove from 'array-move';
import settings from 'electron-settings';
import { mockSettings } from './mockSettings';
@ -216,9 +217,120 @@ export const consecutiveRanges = (a: number[]) => {
return list;
};
export const moveToIndex = (entryData: any, selectedEntries: any, moveBeforeId: string) => {
const uniqueIds: any[] = [];
selectedEntries.map((entry: any) => uniqueIds.push(entry.uniqueId));
export const moveSelectedUp = (entryData: any[], selectedEntries: any[]) => {
// Ascending index is needed to move the indexes in order
const selectedIndices = selectedEntries.map((selected: any) => {
return entryData.findIndex((item: any) => item.uniqueId === selected.uniqueId);
});
const selectedIndexesAsc = selectedIndices.sort((a: number, b: number) => a - b);
const cr = consecutiveRanges(selectedIndexesAsc);
// Handle case when index hits 0
if (
!(
selectedIndexesAsc.includes(0) &&
areConsecutive(selectedIndexesAsc, selectedIndexesAsc.length)
)
) {
selectedIndexesAsc.map((index: number) => {
if (cr[0]?.includes(0)) {
if (!cr[0]?.includes(index) && index !== 0) {
return arrayMove.mutate(entryData, index, index - 1);
}
} else if (index !== 0) {
return arrayMove.mutate(entryData, index, index - 1);
}
return undefined;
});
}
return entryData;
};
export const moveSelectedDown = (entryData: any[], selectedEntries: any[]) => {
// Descending index is needed to move the indexes in order
const selectedIndices = selectedEntries.map((selected: any) => {
return entryData.findIndex((item: any) => item.uniqueId === selected.uniqueId);
});
const cr = consecutiveRanges(selectedIndices.sort((a, b) => a - b));
const selectedIndexesDesc = selectedIndices.sort((a, b) => b - a);
// Handle case when index hits the end
if (
!(
selectedIndexesDesc.includes(entryData.length - 1) &&
areConsecutive(selectedIndexesDesc, selectedIndexesDesc.length)
)
) {
selectedIndexesDesc.map((index) => {
if (cr[0]?.includes(entryData.length - 1)) {
if (!cr[0]?.includes(index) && index !== entryData.length - 1) {
return arrayMove.mutate(entryData, index, index + 1);
}
} else if (index !== entryData.length - 1) {
return arrayMove.mutate(entryData, index, index + 1);
}
return undefined;
});
}
return entryData;
};
export const moveSelectedToTop = (entryData: any, selectedEntries: any) => {
const uniqueIds = _.map(selectedEntries, 'uniqueId');
// Remove the selected entries from the queue
const newList = entryData.filter((entry: any) => {
return !uniqueIds.includes(entry.uniqueId);
});
// Get the updated entry rowIndexes since dragging an entry multiple times will change the existing selected rowIndex
const updatedEntries = selectedEntries.map((entry: any) => {
const findIndex = entryData.findIndex((item: any) => item.uniqueId === entry.uniqueId);
return { ...entry, rowIndex: findIndex };
});
// Sort the entries by their rowIndex so that we can re-add them in the proper order
const sortedEntries = updatedEntries.sort((a: any, b: any) => a.rowIndex - b.rowIndex);
newList.splice(0, 0, ...sortedEntries);
return newList;
};
export const moveSelectedToBottom = (entryData: any, selectedEntries: any) => {
const uniqueIds = _.map(selectedEntries, 'uniqueId');
// Remove the selected entries from the queue
const newList = entryData.filter((entry: any) => {
return !uniqueIds.includes(entry.uniqueId);
});
// Get the updated entry rowIndexes since dragging an entry multiple times will change the existing selected rowIndex
const updatedEntries = selectedEntries.map((entry: any) => {
const findIndex = entryData.findIndex((item: any) => item.uniqueId === entry.uniqueId);
return { ...entry, rowIndex: findIndex };
});
// Sort the entries by their rowIndex so that we can re-add them in the proper order
const sortedEntries = updatedEntries.sort((a: any, b: any) => a.rowIndex - b.rowIndex);
newList.push(...sortedEntries);
return newList;
};
export const moveSelectedToIndex = (
entryData: any,
selectedEntries: any,
moveBeforeId: string | number
) => {
const uniqueIds = _.map(selectedEntries, 'uniqueId');
// Remove the selected entries from the queue
const newList = entryData.filter((entry: any) => {
@ -291,3 +403,15 @@ export const getUpdatedEntryRowIndex = (selectedEntries: any, entryData: any) =>
export const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
export const getCurrentEntryList = (playQueue: any) => {
if (playQueue.sortedEntry.length > 0) {
return 'sortedEntry';
}
if (playQueue.shuffle) {
return 'shuffledEntry';
}
return 'entry';
};

Loading…
Cancel
Save