Drag and drop ready to go; Context menu not perfect, but better than current
This commit is contained in:
@ -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) {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user