More context menu items; Callbacks for love/library toggles

This commit is contained in:
James Barnsley
2021-12-12 21:06:42 +13:00
parent 1814284596
commit 94b2eedc5b
10 changed files with 387 additions and 88 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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,

View File

@ -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));
};

View File

@ -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],
});

View File

@ -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

View File

@ -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);
}
},
);

View File

@ -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));

View File

@ -208,6 +208,7 @@ const formatContext = function (data) {
'type',
'provider',
'context',
'can_edit',
];
for (const field of fields) {

View File

@ -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"