Browse Source

refine drag-n-drop functionality

- add currentSongUniqueId in playqueue reducer
- add quick single drag/drop
- split out logic for single vs multi drag/drop
- add drag/drop label to row # column
- adjust drag placement styling
- set cursor types when drag/dropping
- fix current song/player indices when rearranging order
master
jeffvli 3 years ago
parent
commit
a7e56a21f6
  1. 1
      src/components/library/AlbumView.tsx
  2. 1
      src/components/library/ArtistView.tsx
  3. 16
      src/components/player/NowPlayingMiniView.tsx
  4. 29
      src/components/player/NowPlayingView.tsx
  5. 1
      src/components/playlist/PlaylistView.tsx
  6. 18
      src/components/settings/ListViewColumns.ts
  7. 1
      src/components/starred/StarredView.tsx
  8. 73
      src/components/viewtypes/ListViewTable.tsx
  9. 4
      src/components/viewtypes/ListViewType.tsx
  10. 17
      src/components/viewtypes/styled.tsx
  11. 37
      src/redux/multiSelectSlice.ts
  12. 88
      src/redux/playQueueSlice.ts

1
src/components/library/AlbumView.tsx

@ -86,6 +86,7 @@ const AlbumView = ({ ...rest }: any) => {
entries: data.song,
currentIndex: e.index,
currentSongId: e.id,
uniqueSongId: e.uniqueId,
})
);
dispatch(setStatus('PLAYING'));

1
src/components/library/ArtistView.tsx

@ -89,6 +89,7 @@ const ArtistView = ({ ...rest }: any) => {
entries: data.album,
currentIndex: e.index,
currentSongId: e.id,
uniqueSongId: e.uniqueId,
})
);
dispatch(setStatus('PLAYING'));

16
src/components/player/NowPlayingMiniView.tsx

@ -8,6 +8,7 @@ import {
toggleRangeSelected,
setSelected,
clearSelected,
setIsDragging,
} from '../../redux/multiSelectSlice';
import {
setPlayerVolume,
@ -18,6 +19,7 @@ import {
clearPlayQueue,
shuffleInPlace,
toggleShuffle,
moveToIndex,
} from '../../redux/playQueueSlice';
import { resetPlayer, setStatus } from '../../redux/playerSlice';
import ListViewType from '../viewtypes/ListViewType';
@ -111,6 +113,19 @@ const NowPlayingMiniView = () => {
dispatch(moveDown(selectedIndexes));
};
const handleMouseUp = () => {
if (multiSelect.isDragging) {
dispatch(
moveToIndex({
entries: multiSelect.selected,
moveBeforeId: multiSelect.currentMouseOverId,
})
);
dispatch(setIsDragging(false));
dispatch(fixPlayer2Index());
}
};
return (
<>
{playQueue.displayQueue && (
@ -191,6 +206,7 @@ const NowPlayingMiniView = () => {
handleRowDoubleClick={handleRowDoubleClick}
handleUpClick={handleUpClick}
handleDownClick={handleDownClick}
handleMouseUp={handleMouseUp}
virtualized
rowHeight={Number(settings.getSync('miniListRowHeight'))}
fontSize={Number(settings.getSync('miniListFontSize'))}

29
src/components/player/NowPlayingView.tsx

@ -95,10 +95,6 @@ const NowPlayingView = () => {
dispatch(setStatus('PLAYING'));
};
if (!playQueue) {
return <PageLoader />;
}
const handleUpClick = () => {
const selectedIndexes: any[] = [];
multiSelect.selected.map((selected: any) => {
@ -119,16 +115,23 @@ const NowPlayingView = () => {
dispatch(moveDown(selectedIndexes));
};
const handleMouseUp = () => {
dispatch(
moveToIndex({
entries: multiSelect.selected,
moveBeforeId: multiSelect.currentMouseOverId,
})
);
dispatch(setIsDragging(false));
const handleDragEnd = () => {
if (multiSelect.isDragging) {
dispatch(
moveToIndex({
entries: multiSelect.selected,
moveBeforeId: multiSelect.currentMouseOverId,
})
);
dispatch(setIsDragging(false));
dispatch(fixPlayer2Index());
}
};
if (!playQueue) {
return <PageLoader />;
}
return (
<GenericPage
hideDivider
@ -199,7 +202,7 @@ const NowPlayingView = () => {
handleRowDoubleClick={handleRowDoubleClick}
handleUpClick={handleUpClick}
handleDownClick={handleDownClick}
handleMouseUp={handleMouseUp}
handleDragEnd={handleDragEnd}
virtualized
rowHeight={Number(settings.getSync('songListRowHeight'))}
fontSize={Number(settings.getSync('songListFontSize'))}

1
src/components/playlist/PlaylistView.tsx

@ -83,6 +83,7 @@ const PlaylistView = ({ ...rest }) => {
entries: data.song,
currentIndex: e.index,
currentSongId: e.id,
uniqueSongId: e.uniqueId,
})
);
dispatch(setStatus('PLAYING'));

18
src/components/settings/ListViewColumns.ts

@ -1,13 +1,13 @@
export const songColumnList = [
{
label: '#',
label: '# (Drag/Drop)',
value: {
id: '#',
dataKey: 'index',
alignment: 'center',
resizable: true,
width: 50,
label: '#',
label: '# (Drag/Drop)',
},
},
{
@ -156,7 +156,7 @@ export const songColumnList = [
];
export const songColumnPicker = [
{ label: '#' },
{ label: '# (Drag/Drop)' },
{ label: 'Album' },
{ label: 'Artist' },
{ label: 'Bitrate' },
@ -174,14 +174,14 @@ export const songColumnPicker = [
export const albumColumnList = [
{
label: '#',
label: '# (Drag/Drop)',
value: {
id: '#',
dataKey: 'index',
alignment: 'center',
resizable: true,
width: 50,
label: '#',
label: '# (Drag/Drop)',
},
},
{
@ -297,7 +297,7 @@ export const albumColumnList = [
];
export const albumColumnPicker = [
{ label: '#' },
{ label: '# (Drag/Drop)' },
{ label: 'Artist' },
{ label: 'CoverArt' },
{ label: 'Created' },
@ -313,14 +313,14 @@ export const albumColumnPicker = [
export const playlistColumnList = [
{
label: '#',
label: '# (Drag/Drop)',
value: {
id: '#',
dataKey: 'index',
alignment: 'center',
resizable: true,
width: 50,
label: '#',
label: '# (Drag/Drop)',
},
},
{
@ -447,7 +447,7 @@ export const playlistColumnList = [
];
export const playlistColumnPicker = [
{ label: '#' },
{ label: '# (Drag/Drop)' },
{ label: 'CoverArt' },
{ label: 'Created' },
{ label: 'Description' },

1
src/components/starred/StarredView.tsx

@ -88,6 +88,7 @@ const StarredView = () => {
entries: data.song,
currentIndex: e.index,
currentSongId: e.id,
uniqueSongId: e.uniqueId,
})
);
dispatch(setStatus('PLAYING'));

73
src/components/viewtypes/ListViewTable.tsx

@ -30,6 +30,7 @@ import { addModalPage } from '../../redux/miscSlice';
import {
setCurrentMouseOverId,
setIsDragging,
setSelectedSingle,
} from '../../redux/multiSelectSlice';
const ListViewTable = ({
@ -50,7 +51,7 @@ const ListViewTable = ({
isModal,
// onScroll,
nowPlaying,
handleMouseUp,
handleDragEnd,
}: any) => {
const history = useHistory();
const dispatch = useAppDispatch();
@ -165,9 +166,14 @@ const ListViewTable = ({
// rowIndex,
// })
// }
onMouseEnter={() => {
onMouseOver={() => {
if (multiSelect.isDragging) {
dispatch(setCurrentMouseOverId(rowData.uniqueId));
dispatch(
setCurrentMouseOverId({
uniqueId: rowData.uniqueId,
index: rowIndex,
})
);
}
}}
onMouseLeave={() => {
@ -175,17 +181,52 @@ const ListViewTable = ({
multiSelect.currentMouseOverId ||
multiSelect.isDragging
) {
dispatch(setCurrentMouseOverId(undefined));
dispatch(
setCurrentMouseOverId({
uniqueId: undefined,
index: undefined,
})
);
}
}}
onMouseDown={() => dispatch(setIsDragging(true))}
onMouseUp={() => handleMouseUp()}
mouseover={
onMouseDown={(e: any) => {
if (e.button === 0) {
const isSelected = multiSelect.selected.find(
(item: any) => item.uniqueId === rowData.uniqueId
);
// Handle cases where we want to quickly drag/drop single rows
if (multiSelect.selected.length <= 1 || !isSelected) {
dispatch(setSelectedSingle(rowData));
dispatch(
setCurrentMouseOverId({
uniqueId: rowData.uniqueId,
index: rowIndex,
})
);
dispatch(setIsDragging(true));
}
// Otherwise use regular multi-drag behavior
if (isSelected) {
dispatch(
setCurrentMouseOverId({
uniqueId: rowData.uniqueId,
index: rowIndex,
})
);
dispatch(setIsDragging(true));
}
}
}}
onMouseUp={() => handleDragEnd()}
dragover={
multiSelect.currentMouseOverId === rowData.uniqueId &&
multiSelect.isDragging
? 'true'
: 'false'
}
dragfield="true"
>
{rowIndex + 1}
{rowData['-empty']}
@ -218,6 +259,12 @@ const ListViewTable = ({
})
}
onMouseUp={() => dispatch(setIsDragging(false))}
dragover={
multiSelect.currentMouseOverId === rowData.uniqueId &&
multiSelect.isDragging
? 'true'
: 'false'
}
>
<Grid fluid>
<Row
@ -376,6 +423,12 @@ const ListViewTable = ({
}
height={rowHeight}
onMouseUp={() => dispatch(setIsDragging(false))}
dragover={
multiSelect.currentMouseOverId === rowData.uniqueId &&
multiSelect.isDragging
? 'true'
: 'false'
}
>
<LazyLoadImage
src={
@ -455,6 +508,12 @@ const ListViewTable = ({
}
}}
onMouseUp={() => dispatch(setIsDragging(false))}
dragover={
multiSelect.currentMouseOverId === rowData.uniqueId &&
multiSelect.isDragging
? 'true'
: 'false'
}
>
<div
style={{

4
src/components/viewtypes/ListViewType.tsx

@ -34,7 +34,7 @@ const ListViewType = (
children,
listType,
isModal,
handleMouseUp,
handleDragEnd,
...rest
}: any,
ref: any
@ -339,7 +339,7 @@ const ListViewType = (
listType={listType}
nowPlaying={rest.nowPlaying}
isModal={isModal}
handleMouseUp={handleMouseUp}
handleDragEnd={handleDragEnd}
// onScroll={(e) => setScrollY(tableRef.current.scrollY)}
/>
)}

17
src/components/viewtypes/styled.tsx

@ -28,16 +28,27 @@ export const TableCellWrapper = styled.div<{
rowselected: string;
playing?: string;
height?: number;
mouseover?: string;
dragover?: string;
dragselect?: string;
dragfield?: string;
}>`
background: ${(props) =>
props.rowselected === 'true' ? props.theme.primary.rowSelected : undefined};
color: ${(props) =>
props.playing === 'true' ? props.theme.primary.main : undefined};
line-height: ${(props) => (props.height ? `${props.height}px` : undefined)};
cursor: pointer;
border-top: ${(props) =>
props.mouseover === 'true' ? '1px blue solid' : undefined};
props.dragover === 'true'
? `1px ${props.theme.primary.main} solid`
: undefined};
cursor: ${(props) =>
props.dragover === 'true'
? 'grabbing'
: props.dragfield
? 'grab'
: props.dragselect === 'true'
? 'crosshair'
: 'pointer'};
`;
export const CombinedTitleTextWrapper = styled.span<{ playing: string }>`

37
src/redux/multiSelectSlice.ts

@ -1,4 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Entry } from './playQueueSlice';
interface MultiSelect {
lastSelected: Record<string, unknown>;
@ -7,6 +8,7 @@ interface MultiSelect {
lastRangeSelected: Record<string, unknown>;
};
selected: any[];
currentMouseOverIndex?: number;
currentMouseOverId?: string;
isDragging: boolean;
isSelectDragging: boolean;
@ -20,6 +22,7 @@ const initialState: MultiSelect = {
},
selected: [],
currentMouseOverId: undefined,
currentMouseOverIndex: undefined,
isDragging: false,
isSelectDragging: false,
};
@ -33,14 +36,18 @@ const multiSelectSlice = createSlice({
},
setIsSelectDragging: (state, action: PayloadAction<boolean>) => {
state.isDragging = action.payload;
state.isSelectDragging = action.payload;
},
setCurrentMouseOverId: (
state,
action: PayloadAction<string | undefined>
action: PayloadAction<{
uniqueId: string | undefined;
index: number | undefined;
}>
) => {
state.currentMouseOverId = action.payload;
state.currentMouseOverId = action.payload.uniqueId;
state.currentMouseOverIndex = action.payload.index;
},
setSelected: (state, action: PayloadAction<any>) => {
@ -61,6 +68,28 @@ const multiSelectSlice = createSlice({
}
},
setSelectedSingle: (state, action: PayloadAction<any>) => {
state.selected = [];
state.lastSelected = {};
state.lastRangeSelected = {
lastSelected: {},
lastRangeSelected: {},
};
state.lastSelected = action.payload;
state.selected.push(action.payload);
},
appendSelected: (state, action: PayloadAction<Entry>) => {
const alreadySelected = state.selected.find(
(item) => item.uniqueId === action.payload.uniqueId
);
if (!alreadySelected) {
state.selected.push(action.payload);
}
},
setRangeSelected: (state, action: PayloadAction<any>) => {
state.lastRangeSelected.lastSelected = state.lastSelected;
state.lastRangeSelected.lastRangeSelected = action.payload;
@ -115,6 +144,8 @@ const multiSelectSlice = createSlice({
export const {
setSelected,
setSelectedSingle,
appendSelected,
setRangeSelected,
toggleSelected,
toggleRangeSelected,

88
src/redux/playQueueSlice.ts

@ -35,6 +35,7 @@ export interface Entry {
type: string;
year: number;
uniqueId: string;
rowIndex: number;
}
export interface PlayQueue {
@ -60,6 +61,7 @@ export interface PlayQueue {
volumeFade: boolean;
currentIndex: number;
currentSongId: string;
currentSongUniqueId: string;
currentPlayer: number;
isFading: boolean;
autoIncremented: boolean;
@ -96,6 +98,7 @@ const initialState: PlayQueue = {
volumeFade: parsedSettings.volumeFade,
currentIndex: 0,
currentSongId: '',
currentSongUniqueId: '',
currentPlayer: 1,
isFading: false,
autoIncremented: false,
@ -185,6 +188,7 @@ const playQueueSlice = createSlice({
resetPlayerDefaults(state);
handleGaplessPlayback(state);
state.currentSongId = state[currentEntry][0].id;
state.currentSongUniqueId = state[currentEntry][0].uniqueId;
},
setPlaybackSetting: (
@ -440,6 +444,8 @@ const playQueueSlice = createSlice({
handleGaplessPlayback(state);
state.currentSongId = state[currentEntry][state.currentIndex].id;
state.currentSongUniqueId =
state[currentEntry][state.currentIndex].uniqueId;
},
incrementPlayerIndex: (state, action: PayloadAction<number>) => {
@ -512,6 +518,7 @@ const playQueueSlice = createSlice({
state.currentPlayer = 1;
state.currentIndex = findIndex;
state.currentSongId = action.payload.id;
state.currentSongUniqueId = action.payload.uniqueId;
},
setPlayerVolume: (
@ -546,6 +553,8 @@ const playQueueSlice = createSlice({
handleGaplessPlayback(state);
state.currentSongId = state[currentEntry][state.currentIndex].id;
state.currentSongUniqueId =
state[currentEntry][state.currentIndex].uniqueId;
}
},
@ -580,6 +589,7 @@ const playQueueSlice = createSlice({
state.currentIndex = findIndex;
state.currentSongId = action.payload.id;
state.currentSongUniqueId = action.payload.uniqueId;
},
setPlayQueue: (
@ -598,9 +608,11 @@ const playQueueSlice = createSlice({
const shuffledEntries = _.shuffle(action.payload.entries);
shuffledEntries.map((entry: any) => state.shuffledEntry.push(entry));
state.currentSongId = shuffledEntries[0].id;
state.currentSongUniqueId = shuffledEntries[0].uniqueId;
} else {
// If shuffle is disabled, add all entries in order
state.currentSongId = action.payload.entries[0].id;
state.currentSongUniqueId = action.payload.entries[0].uniqueId;
}
},
@ -610,6 +622,7 @@ const playQueueSlice = createSlice({
entries: Entry[];
currentIndex: number;
currentSongId: string;
uniqueSongId: string;
}>
) => {
/* Used with listview where you want to set the entry queue by double clicking on a row
@ -635,11 +648,13 @@ const playQueueSlice = createSlice({
state.currentIndex = 0;
state.player1.index = 0;
state.currentSongId = action.payload.currentSongId;
state.currentSongUniqueId = action.payload.uniqueSongId;
} else {
// Add all songs in order and set the current index to the selected row
state.currentIndex = action.payload.currentIndex;
state.player1.index = action.payload.currentIndex;
state.currentSongId = action.payload.currentSongId;
state.currentSongUniqueId = action.payload.uniqueSongId;
}
},
@ -686,16 +701,53 @@ const playQueueSlice = createSlice({
return !uniqueIds.includes(item.uniqueId);
});
// Find the slice index to add the selected entries to
const spliceIndex = getCurrentEntryIndexByUID(
// Used if dragging onto a selected row
const spliceIndexPre = getCurrentEntryIndexByUID(
tempQueue,
action.payload.moveBeforeId
);
// Used if dragging onto a non-selected row
const spliceIndexPost = getCurrentEntryIndexByUID(
newQueue,
action.payload.moveBeforeId
);
/* If we get a negative index, don't move the entry.
This can happen if you try to drag and drop too fast */
if (spliceIndexPre < 0 && spliceIndexPost < 0) {
return;
}
// Find the slice index to add the selected entries to
const spliceIndex =
spliceIndexPost >= 0 ? spliceIndexPost : spliceIndexPre;
// Sort the entries by their rowIndex so that we can add them in the proper order
const sortedEntries = action.payload.entries.sort(
(a, b) => a.rowIndex - b.rowIndex
);
// Splice the entries into the new queue array
newQueue.splice(spliceIndex, 0, ...action.payload.entries);
newQueue.splice(spliceIndex, 0, ...sortedEntries);
// Finally, set the modified entry
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(
newQueue,
state.currentSongUniqueId
);
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
} else {
state.player2.index = newCurrentSongIndex;
}
state.currentIndex = newCurrentSongIndex;
},
moveUp: (state, action: PayloadAction<number[]>) => {
@ -729,6 +781,21 @@ const playQueueSlice = createSlice({
}
state[currentEntry] = tempQueue;
// 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
);
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
} else {
state.player2.index = newCurrentSongIndex;
}
state.currentIndex = newCurrentSongIndex;
},
moveDown: (state, action: PayloadAction<number[]>) => {
@ -762,6 +829,21 @@ const playQueueSlice = createSlice({
}
state[currentEntry] = tempQueue;
// 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
);
if (state.currentPlayer === 1) {
state.player1.index = newCurrentSongIndex;
} else {
state.player2.index = newCurrentSongIndex;
}
state.currentIndex = newCurrentSongIndex;
},
},
});

Loading…
Cancel
Save