Drag and drop ready to go; Context menu not perfect, but better than current

This commit is contained in:
James Barnsley
2022-02-01 21:12:38 +13:00
parent e787a339f2
commit 73e2bb2cc3
18 changed files with 188 additions and 233 deletions

View File

@ -24,13 +24,13 @@ const zones = [
action: 'add_to_playlist',
title: i18n('actions.add_to_playlist'),
icon: 'playlist_add',
accept: ['TRACK', 'ALBUM', 'ARTIST'],
accept: ['TRACK'],
},
{
action: 'create_playlist_and_add',
title: i18n('modal.edit_playlist.create_playlist'),
icon: 'playlist_add',
accept: ['TRACK', 'ALBUM', 'ARTIST'],
accept: ['TRACK'],
},
];
@ -39,10 +39,8 @@ const Dropzones = () => {
isDragging: monitor.isDragging(),
}));
if (!isDragging) return null;
return (
<div className="dropzones">
<div className={`dropzones ${isDragging ? 'dropzones--dragging' : ''}`}>
{zones.map((zone) => <Dropzone key={zone.action} {...zone} />)}
</div>
);
@ -58,12 +56,13 @@ const Dropzone = ({
const history = useHistory();
const [{ handlerId, isOver, canDrop }, drop] = useDrop({
accept,
canDrop: () => true,
collect: (monitor) => ({
handlerId: monitor.getHandlerId(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
collect: (monitor) => {
return {
handlerId: monitor.getHandlerId(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
};
},
drop: ({ item, items, context }) => {
const uris = item ? [item.uri] : items.map(({ item: { uri } }) => uri);
switch (action) {

View File

@ -7,6 +7,7 @@ import Thumbnail from './Thumbnail';
import LinksSentence from './LinksSentence';
import { I18n } from '../locale';
import { encodeUri } from '../util/format';
import { isTouchDevice } from '../util/helpers';
import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
@ -71,7 +72,7 @@ const GridItem = ({
const grid_glow_enabled = useSelector((state) => state.ui.grid_glow_enabled);
const spotify_available = useSelector((state) => state.spotify.access_token);
const [_, drag] = useDrag({
type: item.type.toUpperCase(),
type: item?.type?.toUpperCase() || 'UNKNOWN',
item: { item, context: item },
});
@ -120,7 +121,10 @@ const GridItem = ({
}
return (
<div ref={drag} className={`grid__item grid__item--${itemProp.type}`}>
<div
ref={isTouchDevice() ? undefined : drag}
className={`grid__item grid__item--${itemProp.type}`}
>
<Link
to={to}
onContextMenu={onContextMenu}

View File

@ -27,9 +27,9 @@ const GridItemBatchIndex = ({
{inViewport || isFirst ? (
<ErrorBoundary>
{
items.map((item, index) => (
items.map((item) => (
<GridItem
key={`${index}_${item.uri || item.name}`}
key={`uri-${item.uri}__name-${item.name}__tlid-${item.tlid}`}
item={item}
getLink={getLink}
/>

View File

@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { useDrag } from 'react-dnd';
import LinksSentence from './LinksSentence';
import { dater } from './Dater';
import { nice_number } from './NiceNumber';
@ -10,12 +11,13 @@ import Icon, { SourceIcon } from './Icon';
import Thumbnail from './Thumbnail';
import Popularity from './Popularity';
import { I18n } from '../locale';
import { encodeUri, formatSimpleObject } from '../util/format';
import { encodeUri } from '../util/format';
import { updateScrollPosition } from './Link';
import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as spotifyActions from '../services/spotify/actions';
import { isTouchDevice } from '../util/helpers';
const getValue = (item = {}, name = '') => {
const { [name]: value } = item;
@ -91,6 +93,10 @@ const ListItem = ({
const spotify_available = useSelector((state) => state.spotify.access_token);
const history = useHistory();
const location = useLocation();
const [_, drag] = useDrag({
type: item?.type?.toUpperCase() || 'UNKNOWN',
item: { item, context: item },
});
// Load images
useEffect(() => {
@ -159,6 +165,7 @@ const ListItem = ({
className={className}
onContextMenu={onContextMenu}
onClick={onClick}
ref={isTouchDevice() ? undefined : drag}
>
{
right_column && !nocontext && (

View File

@ -83,7 +83,7 @@ const PlaybackControls = () => {
<Link className="thumbnail-wrapper" to="/modal/kiosk-mode" tabIndex="-1">
<Thumbnail size="small" images={currentTrack.images} type="track" />
</Link>
<div className="text">
<Link className="text" to="/queue">
<div className="title">
{streamTitle && <span>{streamTitle}</span>}
{!streamTitle && currentTrack && <span>{currentTrack.name}</span>}
@ -92,12 +92,12 @@ const PlaybackControls = () => {
<div className="artist">
{
(currentTrack && currentTrack.artists
&& <LinksSentence items={currentTrack.artists} type="artist" />)
&& <LinksSentence items={currentTrack.artists} type="artist" nolinks />)
|| (streamTitle && <span className="links-sentence">{streamTitle}</span>)
|| <LinksSentence />
}
</div>
</div>
</Link>
</div>
) : (
<div

View File

@ -43,7 +43,7 @@ const SmartListBatch = handleViewport(
{
items.map((item, index) => (
<ItemComponent
key={`${index}_${item.uri || item.name}`}
key={`uri-${item.uri}__name-${item.name}__tlid-${item.tlid}`}
item={item}
getItemIndex={() => itemIndex(batchIndex, index)}
{...itemProps}

View File

@ -507,7 +507,7 @@ library:
modal:
add_to_playlist:
title: Add to playlist
subtitle: 'Select playlist to add %{count} track%{plural} to'
subtitle: 'Select playlist to add %{count} item%{plural} to'
no_playlists: No playlists available
no_editable_playlists: No editable playlists
add_to_queue:
@ -522,7 +522,7 @@ modal:
edit_playlist:
title: Edit playlist
title_create: Create playlist
subtitle: 'And add %{count} track%{plural}'
subtitle: 'And add %{count} item%{plural}'
name_required: Name is required
name: Name
description: Description

View File

@ -1349,7 +1349,6 @@ const MopidyMiddleware = (function () {
}
case 'MOPIDY_ADD_PLAYLIST_TRACKS':
console.debug({ action })
request(store, 'playlists.lookup', { uri: action.key })
.then((response) => {
if (!response) {
@ -1514,7 +1513,7 @@ const MopidyMiddleware = (function () {
store.dispatch(uiActions.createNotification({
content: i18n('actions.deleted', { name: i18n('playlist.title') }),
}));
store.dispatch(coreActions.removeFromLibrary('mopidy:library:playlists', action.uri));
store.dispatch(coreActions.removeFromLibrary('m3u:playlists', action.uri));
});
break;

View File

@ -515,8 +515,8 @@ const PusherMiddleware = (function () {
break;
case 'PUSHER_ADD_PINNED':
var pinned = [...pusher.pinned, action.item];
store.dispatch(pusherActions.setPinned(pinned));
store.dispatch(pusherActions.setPinned([...pusher.pinned, action.item]));
store.dispatch(coreActions.itemLoaded({ uri: action.item.uri, is_pinned: true }));
break;
case 'PUSHER_SET_PINNED':
@ -536,8 +536,9 @@ const PusherMiddleware = (function () {
next(action);
break;
case 'PUSHER_REMOVE_PINNED':
var pinned = pusher.pinned.filter((item) => item.uri !== action.uri);
case 'PUSHER_REMOVE_PINNED': {
const pinned = pusher.pinned.filter((item) => item.uri !== action.uri);
store.dispatch(coreActions.itemLoaded({ uri: action.uri, is_pinned: false }));
request(store, 'set_pinned', { pinned })
.then(
@ -554,6 +555,7 @@ const PusherMiddleware = (function () {
next(action);
break;
}
/**
* Commands

View File

@ -1,9 +1,11 @@
.dropzones {
@include animate(0.1s);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateX(-100%);
z-index: 98;
background: darken(colour('dark_grey'), 1%);
@ -11,6 +13,10 @@
background: colour('faint_grey');
}
&--dragging {
transform: translateX(0);
}
&__item {
@include animate();
@include gradient_overlay(3px,0);

View File

@ -41,70 +41,12 @@
z-index: 1;
text-decoration: none;
@keyframes slide_left {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(-15vw);
opacity: 0;
}
}
@keyframes slide_right {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(15vw);
opacity: 0;
}
}
@keyframes slide_from_left {
0% {
transform: translateX(15vw);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slide_from_right {
0% {
transform: translateX(-15vw);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
.current-track {
width: 100%;
display: flex;
position: relative;
align-items: center;
&__outgoing {
z-index: 2;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
.text {
@include animate(0.15s);
opacity: 0;
}
}
.thumbnail {
cursor: pointer;
width: 44px;
@ -161,6 +103,8 @@
line-height: 1.1rem;
font-size: 0.9rem;
min-width: 0;
color: inherit;
text-decoration: none;
.title {
@include one_line_text;