More context menu items; Callbacks for love/library toggles
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { I18n } from '../../locale';
|
||||
@ -9,15 +9,24 @@ import {
|
||||
loadArtist,
|
||||
loadTrack,
|
||||
loadTracks,
|
||||
removeTracksFromPlaylist,
|
||||
deletePlaylist,
|
||||
} from '../../services/core/actions';
|
||||
import {
|
||||
hideContextMenu,
|
||||
createNotification,
|
||||
} from '../../services/ui/actions';
|
||||
import {
|
||||
removeTracks,
|
||||
} from '../../services/mopidy/actions';
|
||||
import {
|
||||
following,
|
||||
} from '../../services/spotify/actions';
|
||||
import {
|
||||
getTrack,
|
||||
loveTrack,
|
||||
unloveTrack,
|
||||
} from '../../services/lastfm/actions';
|
||||
import {
|
||||
startRadio,
|
||||
addPinned,
|
||||
@ -40,9 +49,8 @@ const ContextMenuItems = ({
|
||||
items,
|
||||
context,
|
||||
} = context_menu;
|
||||
const {
|
||||
provider,
|
||||
} = context || item || {};
|
||||
const can_edit = context?.can_edit || item?.can_edit;
|
||||
const provider = context?.provider || item?.provider;
|
||||
|
||||
switch (type) {
|
||||
case 'album': {
|
||||
@ -95,15 +103,21 @@ const ContextMenuItems = ({
|
||||
<Library uri={item.uri} inLibrary={item.in_library} />
|
||||
<Pin item={item} isPinned={item.is_pinned} />
|
||||
<Divider />
|
||||
{item.user && <GoTo type="user" uri={item.user.uri} />}
|
||||
<Copy uris={[item.uri]} />
|
||||
{item.user && (
|
||||
<>
|
||||
<GoTo type="user" uri={item.user.uri} />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{item.can_edit && (
|
||||
<>
|
||||
<Edit uri={item.uri} type="playlist" />
|
||||
<Delete uri={item.uri} action={deletePlaylist} />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<Refresh uri={item.uri} action={loadTrack} />
|
||||
<Copy uris={[item.uri]} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -115,6 +129,8 @@ const ContextMenuItems = ({
|
||||
<Enqueue uris={[item.uri]} context={context} />
|
||||
<Divider />
|
||||
<AddToPlaylist uris={[item.uri]} onClick={onSubmenu} />
|
||||
{provider === 'spotify' && <Library uri={item.uri} />}
|
||||
<Love uri={item.uri} />
|
||||
<Divider />
|
||||
{provider === 'spotify' && (
|
||||
<>
|
||||
@ -124,6 +140,12 @@ const ContextMenuItems = ({
|
||||
</>
|
||||
)}
|
||||
<Copy uris={[item.uri]} />
|
||||
{can_edit && (
|
||||
<>
|
||||
<Divider />
|
||||
<Remove items={[item]} context={context} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -136,16 +158,22 @@ const ContextMenuItems = ({
|
||||
<Enqueue uris={uris} context={context} />
|
||||
<Divider />
|
||||
<AddToPlaylist uris={uris} onClick={onSubmenu} />
|
||||
{/* No support for adding/removing/checking library for multiple URIs (yet) */}
|
||||
<Divider />
|
||||
{provider === 'spotify' && (
|
||||
<>
|
||||
<Radio uris={uris} disabled={uris.length > 5} />
|
||||
<Divider />
|
||||
<Discover uris={uris} context={context} disabled={uris.length > 5} />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<Copy uris={uris} />
|
||||
{can_edit && (
|
||||
<>
|
||||
<Divider />
|
||||
<Remove items={items} context={context} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -210,15 +238,30 @@ const Discover = ({
|
||||
|
||||
const Library = ({
|
||||
uri,
|
||||
inLibrary,
|
||||
inLibrary: inLibraryProp,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [inLibrary, setInLibrary] = useState(inLibraryProp);
|
||||
const [loading, setLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
if (inLibraryProp === undefined) {
|
||||
setLoading(true);
|
||||
dispatch(following(
|
||||
uri,
|
||||
'GET',
|
||||
(result) => {
|
||||
setLoading(false);
|
||||
setInLibrary(result);
|
||||
},
|
||||
));
|
||||
}
|
||||
}, [uri]);
|
||||
const onClick = () => {
|
||||
dispatch(hideContextMenu());
|
||||
dispatch(following(uri, inLibrary ? 'DELETE' : 'PUT'));
|
||||
};
|
||||
return (
|
||||
<div className="context-menu__item">
|
||||
<div className={`context-menu__item ${loading && 'context-menu__item--disabled'}`}>
|
||||
<a className="context-menu__item__link" onClick={onClick}>
|
||||
<span className="context-menu__item__label">
|
||||
<I18n path={`actions.${inLibrary ? 'remove_from' : 'add_to'}_library`} />
|
||||
@ -228,6 +271,40 @@ const Library = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Love = ({
|
||||
uri,
|
||||
isLoved: isLovedProp,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [isLoved, setIsLoved] = useState(isLovedProp);
|
||||
const [loading, setLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
if (isLovedProp === undefined) {
|
||||
setLoading(true);
|
||||
dispatch(getTrack(
|
||||
uri,
|
||||
(result) => {
|
||||
setLoading(false);
|
||||
setIsLoved(result);
|
||||
},
|
||||
));
|
||||
}
|
||||
}, [uri]);
|
||||
const onClick = () => {
|
||||
dispatch(hideContextMenu());
|
||||
dispatch(isLoved ? unloveTrack(uri) : loveTrack(uri));
|
||||
};
|
||||
return (
|
||||
<div className={`context-menu__item ${loading && 'context-menu__item--disabled'}`}>
|
||||
<a className="context-menu__item__link" onClick={onClick}>
|
||||
<span className="context-menu__item__label">
|
||||
<I18n path={`context_menu.${isLoved ? 'un' : ''}love_track`} />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Pin = ({
|
||||
item,
|
||||
isPinned,
|
||||
@ -367,6 +444,40 @@ const AddToPlaylist = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Remove = ({
|
||||
items,
|
||||
context: {
|
||||
type,
|
||||
uri,
|
||||
},
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const onClick = () => {
|
||||
switch (type) {
|
||||
case 'playlist': {
|
||||
dispatch(removeTracksFromPlaylist(uri, arrayOf('index', items)));
|
||||
break;
|
||||
}
|
||||
case 'queue': {
|
||||
dispatch(removeTracks(arrayOf('tlid', items)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dispatch(hideContextMenu());
|
||||
};
|
||||
return (
|
||||
<div className="context-menu__item">
|
||||
<a className="context-menu__item__link" onClick={onClick}>
|
||||
<span className="context-menu__item__label">
|
||||
<I18n path="actions.remove" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Radio = ({
|
||||
uris,
|
||||
disabled,
|
||||
|
||||
@ -13,6 +13,9 @@ import {
|
||||
loadLibrary,
|
||||
addTracksToPlaylist,
|
||||
} from '../../services/core/actions';
|
||||
import {
|
||||
hideContextMenu,
|
||||
} from '../../services/ui/actions';
|
||||
|
||||
export default ({
|
||||
context_menu: {
|
||||
@ -36,6 +39,7 @@ export default ({
|
||||
const encodedUris = uris && uris.length > 0 ? encodeUri(uris.join(',')) : '';
|
||||
|
||||
const onClick = (playlist_uri) => {
|
||||
dispatch(hideContextMenu());
|
||||
dispatch(addTracksToPlaylist(playlist_uri, uris));
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { bindActionCreators } from 'redux';
|
||||
import Track from './Track';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import { isTouchDevice } from '../util/helpers';
|
||||
import { isTouchDevice, uriSource } from '../util/helpers';
|
||||
import { arrayOf } from '../util/arrays';
|
||||
import { i18n } from '../locale';
|
||||
import { SmartList } from './SmartList';
|
||||
@ -218,11 +218,14 @@ const TrackList = ({
|
||||
setSelectedTracks(nextSelectedTracks);
|
||||
}
|
||||
|
||||
const items = digestTracksKeys(nextSelectedTracks);
|
||||
showContextMenu({
|
||||
e,
|
||||
context,
|
||||
type: 'tracks',
|
||||
items: digestTracksKeys(nextSelectedTracks),
|
||||
...(items.length === 1
|
||||
? { type: 'track', item: items[0] }
|
||||
: { type: 'tracks', items }
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@ -350,6 +353,7 @@ const TrackList = ({
|
||||
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],
|
||||
});
|
||||
|
||||
@ -189,6 +189,7 @@ context_menu:
|
||||
unpin: Unpin
|
||||
go_to_artist: Go to artist
|
||||
go_to_user: Go to user
|
||||
go_to_track: Go to track
|
||||
track_info: Track info
|
||||
discover_similar: Discover similar
|
||||
start_radio: Start radio
|
||||
|
||||
@ -177,7 +177,7 @@ export function getMe() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getTrack(uri) {
|
||||
export function getTrack(uri, callback) {
|
||||
return (dispatch, getState) => {
|
||||
const selector = makeItemSelector(uri);
|
||||
const track = selector(getState());
|
||||
@ -200,15 +200,13 @@ export function getTrack(uri) {
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.track) {
|
||||
dispatch(
|
||||
coreActions.itemLoaded(
|
||||
formatTrack({
|
||||
uri: track.uri,
|
||||
...response.track,
|
||||
...track,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const result = formatTrack({
|
||||
uri: track.uri,
|
||||
...response.track,
|
||||
...track,
|
||||
});
|
||||
dispatch(coreActions.itemLoaded(result));
|
||||
if (callback) callback(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -754,7 +754,7 @@ export function clearAutocompleteResults(field_id = null) {
|
||||
};
|
||||
}
|
||||
|
||||
export function following(uri, method = 'GET') {
|
||||
export function following(uri, method = 'GET', callback) {
|
||||
return (dispatch, getState) => {
|
||||
const type = uriType(uri);
|
||||
let endpoint;
|
||||
@ -831,6 +831,7 @@ export function following(uri, method = 'GET') {
|
||||
} else {
|
||||
asset.in_library = is_following;
|
||||
}
|
||||
if (callback) callback(asset.in_library);
|
||||
|
||||
if (method === 'DELETE') {
|
||||
dispatch(coreActions.removeFromLibrary(`spotify:library:${type}s`, uri));
|
||||
|
||||
@ -208,6 +208,7 @@ const formatContext = function (data) {
|
||||
'type',
|
||||
'provider',
|
||||
'context',
|
||||
'can_edit',
|
||||
];
|
||||
|
||||
for (const field of fields) {
|
||||
|
||||
@ -234,8 +234,8 @@ const Queue = ({
|
||||
context={{
|
||||
uri: 'iris:queue',
|
||||
name: 'Queue',
|
||||
type: 'track',
|
||||
context: 'queue',
|
||||
type: 'queue',
|
||||
can_edit: true,
|
||||
}}
|
||||
show_source_icon
|
||||
className="queue-track-list"
|
||||
|
||||
Reference in New Issue
Block a user