Refactoring selection, now TrackList state-managed

This commit is contained in:
James Barnsley
2021-12-19 20:15:53 +13:00
parent 606a7d65ef
commit 99f8ad5a7e
15 changed files with 805 additions and 988 deletions

View File

@ -6,7 +6,7 @@ import * as Sentry from '@sentry/browser';
import Sidebar from './components/Sidebar';
import PlaybackControls from './components/PlaybackControls';
import ContextMenu from './components/ContextMenu';
import ContextMenu from './components/ContextMenu/ContextMenu';
import Dragger from './components/Dragger';
import Notifications from './components/Notifications';
import ResizeListener from './components/ResizeListener';

View File

@ -11,38 +11,92 @@ import {
isLoading,
throttle,
titleCase,
} from '../util/helpers';
} from '../../util/helpers';
import {
arrayOf,
sortItems,
} from '../util/arrays';
import Link from './Link';
import Icon from './Icon';
import Loader from './Loader';
import URILink from './URILink';
} from '../../util/arrays';
import Link from '../Link';
import Icon from '../Icon';
import Loader from '../Loader';
import URILink from '../URILink';
import * as coreActions from '../services/core/actions';
import * as uiActions from '../services/ui/actions';
import * as pusherActions from '../services/pusher/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as lastfmActions from '../services/lastfm/actions';
import * as spotifyActions from '../services/spotify/actions';
import { I18n } from '../locale';
import { encodeUri } from '../util/format';
import * as coreActions from '../../services/core/actions';
import * as uiActions from '../../services/ui/actions';
import * as pusherActions from '../../services/pusher/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import * as lastfmActions from '../../services/lastfm/actions';
import * as spotifyActions from '../../services/spotify/actions';
import { I18n } from '../../locale';
import { encodeUri } from '../../util/format';
import {
makeProcessProgressSelector,
makeProvidersSelector,
makeLibrarySelector,
} from '../util/selectors';
import AddedFrom from './AddedFrom';
import { ContextMenuItems } from './ContextMenu/ContextMenuItems';
import PlaylistSubmenu from './ContextMenu/PlaylistSubmenu';
} from '../../util/selectors';
import AddedFrom from '../AddedFrom';
import { ContextMenuItems } from './ContextMenuItems';
import PlaylistSubmenu from './PlaylistSubmenu';
const processKeys = [
'MOPIDY_GET_LIBRARY_PLAYLISTS',
'SPOTIFY_GET_LIBRARY_PLAYLISTS',
];
const Title = ({
context_menu: {
title,
item,
items,
type,
},
}) => {
if (items) {
return (
<div className="context-menu__title">
<div className="context-menu__title__text">
{`${items.length} ${type} selected`}
<span
className="context-menu__title__deselect"
onClick={() => {
// TODO: reset selected
}}
>
<Icon name="close" />
</span>
</div>
</div>
);
}
if (item) {
return (
<div className="context-menu__title">
<AddedFrom
from={item?.added_from}
by={item?.added_by}
className="context-menu__title__text"
inline
/>
</div>
);
}
if (type === 'custom') {
if (!title) return null;
return (
<div className="context-menu__title">
<div className="context-menu__title__text">
{title}
</div>
</div>
);
}
return null;
};
const ContextMenu = ({
context_menu,
loading_progress, // to move to submenu
@ -105,7 +159,9 @@ const ContextMenu = ({
return (
<div className={className} style={style} ref={ref}>
<div className="context-menu__section context-menu__section--items">
{/* {this.renderTitle()} */}
<Title
context_menu={context_menu}
/>
<ContextMenuItems
context_menu={context_menu}
onSubmenu={() => setSubmenu(true)}

View File

@ -1,7 +1,7 @@
import React, { memo } from 'react';
import Icon from './Icon';
import Button from './Button';
import { trackEvent } from './Trackable';
import Icon from '../Icon';
import Button from '../Button';
import { trackEvent } from '../Trackable';
export default memo(({ onTrigger }) => {
const handleClick = (e) => {

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react';
import { useDispatch } from 'react-redux';
import ContextMenuTrigger from './ContextMenuTrigger';
import ContextMenuTrigger from './ContextMenu/ContextMenuTrigger';
import { showContextMenu } from '../services/ui/actions';
export default memo(({

View File

@ -5,7 +5,7 @@ import LinksSentence from './LinksSentence';
import { dater } from './Dater';
import { nice_number } from './NiceNumber';
import URILink from './URILink';
import ContextMenuTrigger from './ContextMenuTrigger';
import ContextMenuTrigger from './ContextMenu/ContextMenuTrigger';
import Icon, { SourceIcon } from './Icon';
import Thumbnail from './Thumbnail';
import Popularity from './Popularity';

View File

@ -1,9 +1,9 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import Icon, { SourceIcon } from './Icon';
import LinksSentence from './LinksSentence';
import { Dater, dater } from './Dater';
import URILink from './URILink';
import ContextMenuTrigger from './ContextMenuTrigger';
import ContextMenuTrigger from './ContextMenu/ContextMenuTrigger';
import ErrorBoundary from './ErrorBoundary';
import { isTouchDevice } from '../util/helpers';
import { I18n, i18n } from '../locale';
@ -18,7 +18,7 @@ const MiddleColumn = ({
} = {},
}) => {
let content;
switch (context?.context) {
switch (context?.type) {
case 'history': {
content = (
<div className="list__item__column__item list__item__column__item--played_at">
@ -58,169 +58,172 @@ const Track = ({
stream_title,
play_state,
selected_tracks,
is_selected,
can_sort,
show_source_icon,
getItemIndex,
handleContextMenu,
handleDrag,
handleDrop,
handleClick,
handleDoubleClick,
handleDoubleTap,
handleTouchDrag,
handleTap,
dragger,
buildTrackKey,
onContextMenu,
onClick,
onDoubleClick,
}) => {
const [hover, setHover] = useState(false);
const key = buildTrackKey(item, getItemIndex());
const index = getItemIndex();
const [currentEvent, setCurrentEvent] = useState({
start_time: 0,
end_time: 0,
});
const onMouseEnter = () => setHover(true);
const onMouseLeave = () => setHover(false);
const onContextMenu = (e) => handleContextMenu(e, key);
const onDoubleClick = (e) => handleDoubleClick(e, key);
const updateCurrentEvent = (data) => setCurrentEvent((prev) => ({ ...prev, ...data }));
// const onMouseEnter = () => setHover(true);
// const onMouseLeave = () => setHover(false);
// const onDoubleClick = (e) => handleDoubleClick(e, key);
// const updateCurrentEvent = (data) => setCurrentEvent((prev) => ({ ...prev, ...data }));
const onMouseDown = (e) => {
const target = $(e.target);
const handleContextMenu = (e) => onContextMenu(item, index, e);
const handleClick = (e) => onClick(item, index, e);
const handleDoubleClick = (e) => onDoubleClick(item, index, e);
// Clicked a nested link (ie Artist name), so no dragging required
if (target.is('a')) {
return false;
}
// const onMouseDown = (e) => {
// const target = $(e.target);
// Only listen for left mouse clicks
if (e.button === 0) {
updateCurrentEvent({
start_position: {
x: e.pageX,
y: e.pageY,
},
});
// // Clicked a nested link (ie Artist name), so no dragging required
// if (target.is('a')) {
// return false;
// }
// Not left click, then ensure no dragging
} else {
updateCurrentEvent({ start_position: false });
}
};
// // Only listen for left mouse clicks
// if (e.button === 0) {
// updateCurrentEvent({
// start_position: {
// x: e.pageX,
// y: e.pageY,
// },
// });
const onMouseMove = (e) => {
if (handleDrag === undefined) return false;
// // Not left click, then ensure no dragging
// } else {
// updateCurrentEvent({ start_position: false });
// }
// };
if (currentEvent.start_position) {
const start_x = currentEvent.start_position.x;
const start_y = currentEvent.start_position.y;
const threshold = 5;
// const onMouseMove = (e) => {
// if (handleDrag === undefined) return false;
// Have we dragged outside of our threshold zone?
if (
e.pageX > start_x + threshold
|| e.pageX < start_x - threshold
|| e.pageY > start_y + threshold
|| e.pageY < start_y - threshold
) {
// Handover to parent for dragging. We can unset all our behaviour now.
handleDrag(e, key);
updateCurrentEvent({ start_position: false });
}
}
};
// if (currentEvent.start_position) {
// const start_x = currentEvent.start_position.x;
// const start_y = currentEvent.start_position.y;
// const threshold = 5;
const onMouseUp = (e) => {
const target = $(e.target);
// // Have we dragged outside of our threshold zone?
// if (
// e.pageX > start_x + threshold
// || e.pageX < start_x - threshold
// || e.pageY > start_y + threshold
// || e.pageY < start_y - threshold
// ) {
// // Handover to parent for dragging. We can unset all our behaviour now.
// handleDrag(e, key);
// updateCurrentEvent({ start_position: false });
// }
// }
// };
// Only listen for left clicks
if (e.button === 0) {
if (dragger) {
e.preventDefault();
// const onMouseUp = (e) => {
// const target = $(e.target);
if (handleDrop !== undefined) {
handleDrop(e, key);
}
} else if (!target.is('a') && target.closest('a').length <= 0) {
handleClick(e, key);
updateCurrentEvent({ start_position: false });
}
return;
}
// // Only listen for left clicks
// if (e.button === 0) {
// if (dragger) {
// e.preventDefault();
// Not left click, then ensure no dragging
updateCurrentEvent({ start_position: false });
}
// if (handleDrop !== undefined) {
// handleDrop(e, key);
// }
// } else if (!target.is('a') && target.closest('a').length <= 0) {
// handleClick(e, key);
// updateCurrentEvent({ start_position: false });
// }
// return;
// }
// // Not left click, then ensure no dragging
// updateCurrentEvent({ start_position: false });
// }
const onTouchStart = (e) => {
const target = $(e.target);
const timestamp = Math.floor(Date.now());
// const onTouchStart = (e) => {
// const target = $(e.target);
// const timestamp = Math.floor(Date.now());
if (target.hasClass('drag-zone')) {
handleTouchDrag(e, key);
e.preventDefault();
}
// if (target.hasClass('drag-zone')) {
// handleTouchDrag(e, key);
// e.preventDefault();
// }
// Save touch start details
updateCurrentEvent({
start_time: timestamp,
start_position: {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
},
});
return false;
}
// // Save touch start details
// updateCurrentEvent({
// start_time: timestamp,
// start_position: {
// x: e.touches[0].clientX,
// y: e.touches[0].clientY,
// },
// });
// return false;
// }
const onTouchEnd = (e) => {
const target = $(e.target);
const timestamp = Math.floor(Date.now());
const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap
const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap
const end_position = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY,
};
// const onTouchEnd = (e) => {
// const target = $(e.target);
// const timestamp = Math.floor(Date.now());
// const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap
// const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap
// const end_position = {
// x: e.changedTouches[0].clientX,
// y: e.changedTouches[0].clientY,
// };
// Too long between touchstart and touchend
if (currentEvent.start_time + tap_time_threshold < timestamp) {
return false;
}
// // Too long between touchstart and touchend
// if (currentEvent.start_time + tap_time_threshold < timestamp) {
// return false;
// }
// Make sure there's enough distance between start and end before we handle
// this event as a 'tap'
if (
currentEvent.start_position.x + tap_distance_threshold > end_position.x
&& currentEvent.start_position.x - tap_distance_threshold < end_position.x
&& currentEvent.start_position.y + tap_distance_threshold > end_position.y
&& currentEvent.start_position.y - tap_distance_threshold < end_position.y
) {
// Clicked a nested link (ie Artist name), so no dragging required
if (!target.is('a')) {
e.preventDefault();
}
// // Make sure there's enough distance between start and end before we handle
// // this event as a 'tap'
// if (
// currentEvent.start_position.x + tap_distance_threshold > end_position.x
// && currentEvent.start_position.x - tap_distance_threshold < end_position.x
// && currentEvent.start_position.y + tap_distance_threshold > end_position.y
// && currentEvent.start_position.y - tap_distance_threshold < end_position.y
// ) {
// // Clicked a nested link (ie Artist name), so no dragging required
// if (!target.is('a')) {
// e.preventDefault();
// }
// Context trigger
if (target.hasClass('touch-contextable')) {
// Update our selection. By not passing touch = true selection will work like a regular
// click this.props.handleSelection(e);
handleContextMenu(e, key);
return false;
}
// // Context trigger
// if (target.hasClass('touch-contextable')) {
// // Update our selection. By not passing touch = true selection will work like a regular
// // click this.props.handleSelection(e);
// handleContextMenu(e, key);
// return false;
// }
// We received a touchend within 300ms ago, so handle as double-tap
if ((timestamp - currentEvent.end_time) > 0 && (timestamp - currentEvent.end_time) <= 300) {
handleDoubleTap(e, key);
e.preventDefault();
return false;
}
// // We received a touchend within 300ms ago, so handle as double-tap
// if ((timestamp - currentEvent.end_time) > 0 && (timestamp - currentEvent.end_time) <= 300) {
// handleDoubleTap(e, key);
// e.preventDefault();
// return false;
// }
handleTap(e, key);
}
// handleTap(e, key);
// }
updateCurrentEvent({ end_time: timestamp });
}
// updateCurrentEvent({ end_time: timestamp });
// }
if (!item) return null;
@ -268,7 +271,7 @@ const Track = ({
}
const track_middle_column = <MiddleColumn context={context} item={item} />;
if (selected_tracks.includes(key)) className += ' list__item--selected';
if (is_selected(getItemIndex())) className += ' list__item--selected';
if (can_sort) className += ' list__item--can-sort';
if (item.type !== undefined) className += ` list__item--${item.type}`;
if (item.playing) className += ' list__item--playing';
@ -282,15 +285,18 @@ const Track = ({
<ErrorBoundary>
<div
className={className}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove}
onDoubleClick={onDoubleClick}
onContextMenu={onContextMenu}
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
// onMouseEnter={onMouseEnter}
// onMouseLeave={onMouseLeave}
// onMouseDown={onMouseDown}
// onMouseUp={onMouseUp}
// onMouseMove={onMouseMove}
// onDoubleClick={onDoubleClick}
// onContextMenu={onContextMenu}
// onTouchStart={onTouchStart}
// onTouchEnd={onTouchEnd}
>
<div className="list__item__column list__item__column--name">
<div className="list__item__column__item--name">

View File

@ -5,7 +5,7 @@ import Track from './Track';
import * as mopidyActions from '../services/mopidy/actions';
import * as uiActions from '../services/ui/actions';
import { isTouchDevice, uriSource } from '../util/helpers';
import { arrayOf } from '../util/arrays';
import { arrayOf, indexToArray } from '../util/arrays';
import { i18n } from '../locale';
import { SmartList } from './SmartList';
import { formatSimpleObject } from '../util/format';
@ -34,19 +34,50 @@ const TrackList = ({
playURIs,
},
}) => {
const [touchDraggingKeys, setTouchDraggingKeys] = useState(false);
const [selected, setSelected] = useState([]);
useEffect(() => {
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('touchmove', onTouchMove, false);
window.addEventListener('touchend', onTouchEnd, false);
// window.addEventListener('touchmove', onTouchMove, false);
// window.addEventListener('touchend', onTouchEnd, false);
return () => {
window.removeEventListener('keydown', onKeyDown, false);
window.removeEventListener('touchmove', onTouchMove, false);
window.removeEventListener('touchend', onTouchEnd, false);
// window.removeEventListener('touchmove', onTouchMove, false);
// window.removeEventListener('touchend', onTouchEnd, false);
}
}, [selected_tracks]);
const selectionIndexByItemIndex = (index) => (
selected && selected.length > 0
? selected.findIndex(({ index: selectedIndex }) => selectedIndex === index)
: -1
);
const nextSelected = (prev, item, index, e, sticky = false) => {
const alreadySelected = selectionIndexByItemIndex(index);
if (e.shiftKey) {
const { index: lastSelectedIndex } = selected[selected.length-1] || { index };
const next = [...prev, { index, item }];
const from = lastSelectedIndex < index ? lastSelectedIndex : index;
const to = lastSelectedIndex > index ? lastSelectedIndex : index;
for (let i = from; i < to; i++) {
next.push({ index: i, item: tracks[i] });
}
return next;
}
if (e.ctrlKey) {
if (alreadySelected >= 0 && !sticky) {
const next = [...prev];
next.splice(alreadySelected, 1);
return next;
}
return [...prev, { item, index }];
}
return [{ item, index }];
}
const onKeyDown = (e) => {
const {
key,
@ -69,156 +100,58 @@ const TrackList = ({
return;
}
const tracks_keys = digestTracksKeys();
switch (key.toLowerCase()) {
case 'enter':
if (tracks_keys && tracks_keys.length > 0) {
if (selected.length > 0) {
onPlayTracks();
}
e.preventDefault();
break;
case 'backspace':
case 'delete':
if (tracks_keys && tracks_keys.length > 0) {
if (selected.length > 0) {
onRemoveTracks();
}
e.preventDefault();
break;
case 'a':
var all_tracks = [];
for (let i = 0; i < this.props.tracks.length; i++) {
all_tracks.push(this.buildTrackKey(this.props.tracks[i], i));
}
this.props.uiActions.setSelectedTracks(all_tracks);
setSelected(tracks.map((item, index) => ({ item, index })));
e.preventDefault();
break;
default:
break;
}
}
const onDrag = (e, track_key) => {
let nextSelectedTracks = [];
const onClick = (item, index, e) => {
e.preventDefault();
e.persist();
// Dragging a non-selected track. We need to deselect everything
// else and select only this track
if (!selected_tracks.includes(track_key)) {
setSelectedTracks([track_key]);
nextSelectedTracks = digestTracksKeys([track_key]);
} else {
nextSelectedTracks = digestTracksKeys();
}
setSelected((prev) => nextSelected(prev, item, index, e));
};
const nextSelectedTracks_indexes = arrayOf('index', nextSelectedTracks);
const onDoubleClick = (item, index) => {
// if (context_menu) hideContextMenu();
setSelected([{ item, index }]);
playURIs([item.uri], context);
};
dragStart(
e,
context?.type,
uri,
nextSelectedTracks,
nextSelectedTracks_indexes,
);
}
const onDrop = (e, track_key) => {
if (dragger?.active && reorderTracks) {
const tracks = digestTracksKeys([track_key]);
return reorderTracks(dragger?.victims_indexes, tracks[0].index);
}
setTouchDraggingKeys(false);
}
const onTouchDrag = (e, track_key) => {
// Drag initiated on a selected track
if (selected_tracks.includes(track_key)) {
// They're all dragging
setTouchDraggingKeys(selected_tracks);
// Not already selected, so just dragging the one track
} else {
setTouchDraggingKeys([track_key]);
setSelectedTracks([track_key]);
}
}
const onTouchMove = (e) => {
if (touchDraggingKeys) {
const touch = e.touches[0];
let over = $(document.elementFromPoint(touch.clientX, touch.clientY));
if (!over.is('.track')) {
over = over.closest('.list__item--track');
}
$(document).find('.touch-drag-hover').removeClass('touch-drag-hover');
if (over.length > 0) {
over.addClass('touch-drag-hover');
}
e.returnValue = false;
e.cancelBubble = true;
e.preventDefault();
e.stopPropagation();
return false;
}
}
const onTouchEnd = (e) => {
if (touchDraggingKeys) {
const touch = e.changedTouches[0];
let over = $(document.elementFromPoint(touch.clientX, touch.clientY));
if (!over.is('.list__item--track')) {
over = over.closest('.list__item--track');
}
if (over.length > 0) {
const siblings = over.parent().children('.list__item--track');
const dropped_at = siblings.index(over);
if (reorderTracks !== undefined) {
reorderTracks(arrayOf('index', digestTracksKeys()), dropped_at);
setSelectedTracks([]);
}
}
$(document).find('.touch-drag-hover').removeClass('touch-drag-hover');
$('body').removeClass('touch-dragging');
setTouchDraggingKeys(false);
}
}
const onTap = (e, track_key) => {
updateSelection(e, track_key, true);
}
const onDoubleTap = (e, track_key) => {
onPlayTracks([track_key]);
updateSelection(e, track_key);
}
const onClick = (e, track_key) => {
updateSelection(e, track_key);
}
const onDoubleClick = (e, track_key) => {
if (context_menu) hideContextMenu();
onPlayTracks([track_key]);
updateSelection(e, track_key);
}
const onContextMenu = (e, track_key = null) => {
const onContextMenu = (item, index, e) => {
// Do our best to stop any flow-on events
e.preventDefault();
e.stopPropagation();
e.cancelBubble = true;
let nextSelectedTracks = [...selected_tracks];
// Not already selected, so select it prior to triggering menu
if (track_key && !nextSelectedTracks.includes(track_key)) {
nextSelectedTracks = [track_key];
setSelectedTracks(nextSelectedTracks);
const alreadySelected = selectionIndexByItemIndex(index);
let items = [];
if (alreadySelected > -1) {
items = selected;
} else {
items = nextSelected(selected, item, index, e);
setSelected(items);
}
items = items.map(({ item: selectedItem }) => selectedItem);
const items = digestTracksKeys(nextSelectedTracks);
showContextMenu({
e,
context,
@ -227,98 +160,19 @@ const TrackList = ({
: { type: 'tracks', items }
),
});
}
const updateSelection = (e, track_key, touched = false) => {
let nextSelectedTracks = [...selected_tracks];
if ((e.ctrlKey || e.metaKey) || touched) {
// Already selected, so unselect it
if (nextSelectedTracks.includes(track_key)) {
const index = nextSelectedTracks.indexOf(track_key);
nextSelectedTracks.splice(index, 1);
// Not selected, so add it
} else {
nextSelectedTracks.push(track_key);
}
} else if (e.shiftKey) {
const last_selected_track = digestTracksKeys(nextSelectedTracks[nextSelectedTracks.length - 1]);
const last_selected_track_index = last_selected_track.index;
const newly_selected_track = digestTracksKeys(track_key);
const newly_selected_track_index = newly_selected_track.index;
// We've selected a track further down the list,
// so proceed normally
if (last_selected_track_index < newly_selected_track_index) {
var start = last_selected_track_index + 1;
var end = newly_selected_track_index;
// Selected a track up the list, so
// our last selected is the END of our range
} else {
var start = newly_selected_track_index;
var end = last_selected_track_index - 1;
}
if (start !== false && start >= 0 && end !== false && end >= 0) {
for (let i = start; i <= end; i++) {
nextSelectedTracks.push(buildTrackKey(tracks[i], i));
}
}
// Regular, unmodified left click
} else {
nextSelectedTracks = [track_key];
}
setSelectedTracks(nextSelectedTracks);
}
const onPlayTracks = (tracks_keys = null) => {
const selectedKeys = tracks_keys !== null
? digestTracksKeys(tracks_keys)
: digestTracksKeys();
if (selectedKeys.length <= 0) {
createNotification({ content: i18n('errors.nothing_selected'), level: 'error' });
return;
}
if (playTracks) {
playTracks(selectedKeys);
return;
}
playURIs(arrayOf('uri', selectedKeys), uri);
}
};
const onRemoveTracks = () => {
const selectedKeys = digestTracksKeys();
if (!removeTracks) {
createNotification({ content: `Cannot delete ${selectedKeys.length > 1 ? 'these tracks' : 'this track'}`, level: 'error' });
createNotification({
content: `Cannot delete ${selected.length > 1 ? 'these tracks' : 'this track'}`,
level: 'error',
});
return;
}
const selected_tracks_indexes = arrayOf('index', selectedKeys);
removeTracks(selected_tracks_indexes);
}
removeTracks(selected.map(({ index }) => index));
};
/**
* Build the track key
* This is our unique reference to a track in a particular tracklist
*
* @param track = Track obj
* @param index = int (position of track in tracklist)
* @return string
* */
const buildTrackKey = (track, index) => {
let key = index;
key += `@@${track.tlid || 'none'}`;
key += `@@${track.uri}`;
key += `@@${uri || 'none'}`;
key += `@@${context?.type || 'none'}`;
return key;
}
/**
* Digest our selected tracks
@ -327,49 +181,54 @@ const TrackList = ({
* @param indexex_only = boolean (do we just want an array of indexes)
* @return mixed
* */
const digestTracksKeys = (keys = selected_tracks, indexes_only = false) => {
if (!keys) {
return false;
}
// const digestTracksKeys = (keys = selected_tracks, indexes_only = false) => {
// if (!keys) {
// return false;
// }
// Accommodate a single key
let singleton = false;
if (!(keys instanceof Array)) {
singleton = true;
keys = [keys];
}
// // Accommodate a single key
// let singleton = false;
// if (!(keys instanceof Array)) {
// singleton = true;
// keys = [keys];
// }
// Construct a basic track object, based on our unique track key
// This is enough to perform interactions (dragging, selecting, etc)
const array = [];
for (const key of keys) {
const key_components = key.split('@@');
// // Construct a basic track object, based on our unique track key
// // This is enough to perform interactions (dragging, selecting, etc)
// const array = [];
// for (const key of keys) {
// const key_components = key.split('@@');
if (indexes_only) {
array.push(key_components[0]);
} else {
array.push({
key,
index: parseInt(key_components[0]),
tlid: parseInt(key_components[1]),
uri: key_components[2],
provider: uriSource(key_components[2]),
context: key_components[3],
context_uri: key_components[4],
});
}
}
// if (indexes_only) {
// array.push(key_components[0]);
// } else {
// array.push({
// key,
// index: parseInt(key_components[0]),
// tlid: parseInt(key_components[1]),
// uri: key_components[2],
// provider: uriSource(key_components[2]),
// context: key_components[3],
// context_uri: key_components[4],
// });
// }
// }
if (singleton && array.length > 0) {
return array[0];
}
return array;
}
// if (singleton && array.length > 0) {
// return array[0];
// }
// return array;
// }
if (!tracks || Object.prototype.toString.call(tracks) !== '[object Array]') {
return null;
}
const is_selected = (index) => {
if (selected.length <= 0) return false;
return selected.filter(({ index: selIndex }) => index === selIndex).length > 0;
};
return (
<SmartList
className={`list list--tracks ${className}`}
@ -377,23 +236,16 @@ const TrackList = ({
itemComponent={Track}
itemProps={{
dragger,
buildTrackKey,
play_state,
show_source_icon,
context,
selected_tracks,
can_sort: (
context?.context === 'queue' || (context?.context === 'playlist' && context?.can_edit)
),
can_sort: context?.can_edit,
is_selected,
mini_zones: slim_mode || isTouchDevice(),
handleClick: onClick,
handleDoubleClick: onDoubleClick,
handleContextMenu: onContextMenu,
handleDrag: onDrag,
handleDrop: onDrop,
handleTap: onTap,
handleDoubleTap: onDoubleTap,
handleTouchDrag: onTouchDrag,
onClick,
onContextMenu,
onDoubleClick,
}}
/>
);

View File

@ -10,7 +10,7 @@ import Loader from '../components/Loader';
import FollowButton from '../components/Fields/FollowButton';
import { nice_number } from '../components/NiceNumber';
import { Dater } from '../components/Dater';
import ContextMenuTrigger from '../components/ContextMenuTrigger';
import ContextMenuTrigger from '../components/ContextMenu/ContextMenuTrigger';
import { SourceIcon } from '../components/Icon';
import DropdownField from '../components/Fields/DropdownField';
import FilterField from '../components/Fields/FilterField';

View File

@ -7,7 +7,7 @@ import Link from '../../components/Link';
import Thumbnail from '../../components/Thumbnail';
import Parallax from '../../components/Parallax';
import FollowButton from '../../components/Fields/FollowButton';
import ContextMenuTrigger from '../../components/ContextMenuTrigger';
import ContextMenuTrigger from '../../components/ContextMenu/ContextMenuTrigger';
import About from './About';
import Overview from './Overview';
import Tracks from './Tracks';

View File

@ -10,7 +10,7 @@ import Parallax from '../../components/Parallax';
import DropdownField from '../../components/Fields/DropdownField';
import AddSeedField from '../../components/Fields/AddSeedField';
import URILink from '../../components/URILink';
import ContextMenuTrigger from '../../components/ContextMenuTrigger';
import ContextMenuTrigger from '../../components/ContextMenu/ContextMenuTrigger';
import RelatedArtists from '../../components/RelatedArtists';
import Icon from '../../components/Icon';
import * as uiActions from '../../services/ui/actions';

View File

@ -11,7 +11,7 @@ import { nice_number } from '../components/NiceNumber';
import { Dater, dater } from '../components/Dater';
import FollowButton from '../components/Fields/FollowButton';
import Loader from '../components/Loader';
import ContextMenuTrigger from '../components/ContextMenuTrigger';
import ContextMenuTrigger from '../components/ContextMenu/ContextMenuTrigger';
import URILink from '../components/URILink';
import { SourceIcon } from '../components/Icon';
import DropdownField from '../components/Fields/DropdownField';

View File

@ -9,7 +9,7 @@ import LinksSentence from '../components/LinksSentence';
import LastfmLoveButton from '../components/Fields/LastfmLoveButton';
import { Dater } from '../components/Dater';
import SelectField from '../components/Fields/SelectField';
import ContextMenuTrigger from '../components/ContextMenuTrigger';
import ContextMenuTrigger from '../components/ContextMenu/ContextMenuTrigger';
import Icon from '../components/Icon';
import Loader from '../components/Loader';
import * as coreActions from '../services/core/actions';

View File

@ -21,7 +21,7 @@ import {
makeItemSelector,
makeLoadingSelector,
} from '../util/selectors';
import ContextMenuTrigger from '../components/ContextMenuTrigger';
import ContextMenuTrigger from '../components/ContextMenu/ContextMenuTrigger';
class User extends React.Component {
componentDidMount() {