Search fully functional; Loading images properly, and dependent URIs

This commit is contained in:
James Barnsley
2020-10-14 21:19:21 +13:00
parent 73d24270a8
commit fdcd15fbd3
10 changed files with 374 additions and 442 deletions

View File

@ -55,6 +55,21 @@ class DebugInfo extends React.Component {
render = () => {
const localStorageUsage = this.localStorageSize();
const {
core: {
items = {},
},
ui: {
notifications = {},
processes = {},
slim_mode,
test_mode,
selected_tracks = [],
},
mopidy: {
enqueue_uris_batches = [],
},
} = this.props;
return (
<div className="debug-info">
@ -75,95 +90,45 @@ class DebugInfo extends React.Component {
<div className="debug-info-section">
<h5>State</h5>
<div className="debug-info-item">
Items:
{' '}
{this.props.core.items ? Object.keys(this.props.core.items).length : '0'}
{`Items: ${Object.keys(items).length}`}
</div>
<div className="debug-info-item">
Coldstore items:
{' '}
{this.state.localForageLength}
{`Coldstore items: ${this.state.localForageLength}`}
</div>
<div className="debug-info-item">
Albums:
{' '}
{this.props.core.albums ? Object.keys(this.props.core.albums).length : '0'}
{`Notifications: ${Object.keys(notifications).length}`}
</div>
<div className="debug-info-item">
Artists:
{' '}
{this.props.core.artists ? Object.keys(this.props.core.artists).length : '0'}
{`Processes: ${Object.keys(processes).length}`}
</div>
<div className="debug-info-item">
Playlists:
{' '}
{this.props.core.playlists ? Object.keys(this.props.core.playlists).length : '0'}
{`Enqueue batches: ${enqueue_uris_batches.length}`}
</div>
<div className="debug-info-item">
Tracks:
{' '}
{this.props.core.tracks ? Object.keys(this.props.core.tracks).length : '0'}
</div>
<div className="debug-info-item">
Users:
{' '}
{this.props.core.users ? Object.keys(this.props.core.users).length : '0'}
</div>
<div className="debug-info-item">
Notifications:
{' '}
{this.props.ui.notifications ? Object.keys(this.props.ui.notifications).length : '0'}
</div>
<div className="debug-info-item">
Processes:
{' '}
{this.props.ui.processes ? Object.keys(this.props.ui.processes).length : '0'}
</div>
<div className="debug-info-item">
Enqueue batches:
{' '}
{this.props.mopidy.enqueue_uris_batches ? this.props.mopidy.enqueue_uris_batches.length : '0'}
</div>
<div className="debug-info-item">
Cached URLs:
{' '}
{Object.keys(getStorage('cache')).length}
{`Cached URLs: ${Object.keys(getStorage('cache')).length}`}
</div>
</div>
<div className="debug-info-section">
<h5>Config</h5>
<div className="debug-info-item">
Slim mode:
{' '}
{this.props.ui.slim_mode ? 'on' : 'off'}
{`Slim mode: ${slim_mode ? 'on' : 'off'}`}
</div>
<div className="debug-info-item">
Test mode:
{' '}
{this.props.ui.test_mode ? 'on' : 'off'}
{`Test mode: ${test_mode ? 'on' : 'off'}`}
</div>
<div className="debug-info-item">
Touch:
{' '}
{isTouchDevice() ? 'on' : 'off'}
{`Touch: ${isTouchDevice() ? 'on' : 'off'}`}
</div>
<div className="debug-info-item">
LocalStorage usage:
{' '}
{localStorageUsage.used}
kb (~
{localStorageUsage.percent}
%)
{`LocalStorage usage: ${localStorageUsage.used}kb (~${localStorageUsage.percent}%)`}
</div>
<div className="debug-info-item">
Selected tracks:
{' '}
{this.props.ui.selected_tracks.length}
{`Selected tracks: ${selected_tracks.length}`}
<br />
{
this.props.ui.selected_tracks.map((track_key, index) => (
<div key={`${track_key}_${index}`}>{track_key}</div>
selected_tracks.map((track_key, index) => (
<div key={`${track_key}_${index}`}>{track_key}</div>
))
}
</div>

View File

@ -9,11 +9,6 @@ import {
formatTracks,
formatTrack,
formatSimpleObject,
formatAlbum,
formatArtist,
formatPlaylist,
formatUser,
formatSimpleObjects,
} from '../../util/format';
import { handleException } from './actions';
@ -66,10 +61,12 @@ const ensureLoaded = ({
if (!parent) return [];
return [...dependents, ...fullDependents].reduce(
(acc, dependent) => [
...acc,
...(dependent.match(new RegExp('/(.*)_uri(.*)')) ? parent[dependent] : []),
],
(acc, dependent) => {
return [
...acc,
...(dependent.match(new RegExp('(.*)_uri(.*)')) ? parent[dependent] : []),
];
},
[],
);
};
@ -86,10 +83,7 @@ const ensureLoaded = ({
if (getMissingDependents(item).length === 0) {
console.info(`"${uri}" and all dependents already in index`);
store.dispatch(uiActions.stopLoading(uri));
getDependentUris(item).forEach(
(dependent) => store.dispatch(coreActions.loadItems(item[dependent])),
);
coreActions.loadItems(getDependentUris(item));
return;
}
}
@ -327,14 +321,6 @@ const CoreMiddleware = (function () {
...(term === prevTerm && type === prevType ? allResults : {}),
};
console.log({
term,
type,
prevTerm,
prevType,
allResults,
});
// Merge our new results with the existing (if any)
search_results[resultType] = [
...(search_results[resultType] || []),
@ -352,98 +338,89 @@ const CoreMiddleware = (function () {
location.reload();
break;
/**
* Playlist manipulation
* */
case 'PLAYLIST_TRACKS':
const playlist_tracks = formatTracks(action.tracks);
store.dispatch({
type: 'TRACKS_LOADED',
tracks: playlist_tracks,
});
next({ ...action, tracks_uris: arrayOf('uri', playlist_tracks) });
break;
case 'PLAYLIST_TRACKS_ADDED':
const asset = store.getState().core.playlists[action.key];
case 'PLAYLIST_TRACKS_ADDED': {
const {
key,
tracks_uris,
} = action;
const {
core: {
items: {
[key]: asset,
},
},
} = store.getState();
store.dispatch(uiActions.createNotification({
content: <span>Added {action.tracks_uris.length} tracks to <URILink uri={action.key}>{asset ? asset.name : 'playlist'}</URILink></span>,
content: (
<span>
Added {tracks_uris.length} tracks to <URILink uri={key}>{asset ? asset.name : 'playlist'}</URILink>
</span>
),
}));
switch (uriSource(action.key)) {
switch (uriSource(key)) {
case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.key));
store.dispatch(spotifyActions.getPlaylist(key));
break;
case 'm3u':
store.dispatch(mopidyActions.getPlaylist(action.key));
store.dispatch(mopidyActions.getPlaylist(key));
break;
default:
break;
}
next(action);
break;
}
case 'PLAYLIST_TRACKS_REORDERED':
var playlists = { ...core.playlists };
var playlist = { ...playlists[action.key] };
var tracks_uris = Object.assign([], playlist.tracks_uris);
case 'PLAYLIST_TRACKS_REORDERED': {
const {
key,
snapshot_id,
} = action;
let { insert_before } = action;
const playlist = { ...core.items[key] };
const tracks = Object.assign([], playlist.tracks);
// handle insert_before offset if we're moving BENEATH where we're slicing tracks
var { insert_before } = action;
if (insert_before > action.range_start) {
insert_before -= action.range_length;
}
// cut our moved tracks into a new array
var tracks_to_move = tracks_uris.splice(action.range_start, action.range_length);
const tracks_to_move = tracks.splice(action.range_start, action.range_length);
tracks_to_move.reverse();
for (i = 0; i < tracks_to_move.length; i++) {
tracks_uris.splice(insert_before, 0, tracks_to_move[i]);
for (let i = 0; i < tracks_to_move.length; i++) {
tracks.splice(insert_before, 0, tracks_to_move[i]);
}
var snapshot_id = null;
if (action.snapshot_id) {
snapshot_id = action.snapshot_id;
}
// Update our playlist
playlist.tracks_uris = tracks_uris;
playlist.snapshot_id = snapshot_id;
// Trigger normal playlist updating
store.dispatch({
type: 'PLAYLISTS_LOADED',
playlists: [playlist],
});
store.dispatch(coreActions.itemLoaded({
...playlist,
tracks,
snapshot_id,
}));
break;
}
case 'PLAYLIST_TRACKS_REMOVED':
var playlists = { ...core.playlists };
var playlist = { ...playlists[action.key] };
var tracks_uris = Object.assign([], playlist.tracks_uris);
var indexes = action.tracks_indexes.reverse();
for (var i = 0; i < indexes.length; i++) {
tracks_uris.splice(indexes[i], 1);
case 'PLAYLIST_TRACKS_REMOVED': {
const {
key,
snapshot_id,
} = action;
const playlist = { ...core.items[key] };
const tracks = Object.assign([], playlist.tracks);
const indexes = action.tracks_indexes.reverse();
for (let i = 0; i < indexes.length; i++) {
tracks.splice(indexes[i], 1);
}
var snapshot_id = null;
if (action.snapshot_id) {
snapshot_id = action.snapshot_id;
}
// Update our playlist
playlist.tracks_uris = tracks_uris;
playlist.snapshot_id = snapshot_id;
// Trigger normal playlist updating
store.dispatch({
type: 'PLAYLISTS_LOADED',
playlists: [playlist],
});
store.dispatch(coreActions.itemLoaded({
...playlist,
tracks,
snapshot_id,
}));
break;
}
/**
* Asset Load commands

View File

@ -2,7 +2,7 @@
import ReactGA from 'react-ga';
import Mopidy from 'mopidy';
import { sha256 } from 'js-sha256';
import { sampleSize, uniqBy } from 'lodash';
import { sampleSize, compact } from 'lodash';
import { i18n } from '../../locale';
import {
generateGuid,
@ -1147,18 +1147,23 @@ const MopidyMiddleware = (function () {
term: query.term,
requestType: type,
uri_scheme,
data: {
uris: [uri_scheme],
},
};
switch (type) {
case 'tracks':
item.data = { query: { track_name: [query.term] }, uris: [uri_scheme] };
item.data.query = { any: [query.term] };
break;
case 'artists':
item.data = { query: { artist: [query.term] }, uris: [uri_scheme] };
item.data.query = { artist: [query.term] };
break;
case 'albums':
item.data = { query: { album: [query.term] }, uris: [uri_scheme] };
item.data.query = { album: [query.term] };
break;
case 'playlists':
// Searching for playlists is not supported, so we get a simple
// list of names and perform a client-side regex match
item.method = 'playlists.asList';
item.data = {};
break;
@ -1489,22 +1494,19 @@ const MopidyMiddleware = (function () {
);
break;
case 'MOPIDY_CURRENT_TRACK_LOADED':
// Let the UI know we're finished transition
case 'MOPIDY_CURRENT_TRACK_LOADED': {
store.dispatch(uiActions.setCurrentTrackTransition(false));
var track = formatTrack(action.tl_track);
const track = formatTrack(action.tl_track);
if (track.uri) {
store.dispatch({
type: 'CURRENT_TRACK_LOADED',
track,
uri: track.uri,
});
store.dispatch(coreActions.loadItem(track.uri, { full: true }));
}
break;
}
case 'MOPIDY_GET_NEXT_TRACK':
request(store, 'tracklist.getNextTlid')
@ -1539,10 +1541,11 @@ const MopidyMiddleware = (function () {
.then(
(_response) => {
if (!_response) return;
const tracks = compact(indexToArray(_response).map(
(results) => (results.length ? formatTrack(results[0]) : null),
));
const tracks = indexToArray(_response);
store.dispatch(coreActions.itemsLoaded(formatTracks(tracks)));
store.dispatch(coreActions.itemsLoaded(tracks));
if (action.get_images) {
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));
@ -1588,15 +1591,10 @@ const MopidyMiddleware = (function () {
);
break;
/**
* =============================================================== IMAGES ===============
* ======================================================================================
* */
case 'MOPIDY_GET_IMAGES':
case 'MOPIDY_GET_IMAGES': {
const { uris } = action;
if (action.uris) {
request(store, 'library.getImages', { uris: action.uris })
request(store, 'library.getImages', { uris })
.then((response) => {
const itemsWithImages = [];
Object.keys(response).forEach((uri) => {
@ -1620,6 +1618,7 @@ const MopidyMiddleware = (function () {
next(action);
break;
}
/**

View File

@ -1509,23 +1509,28 @@ export function addTracksToPlaylist(uri, tracks_uris) {
export function deleteTracksFromPlaylist(uri, snapshot_id, tracks_indexes) {
return (dispatch, getState) => {
request(dispatch, getState, `playlists/${getFromUri('playlistid', uri)}/tracks`, 'DELETE', { snapshot_id, positions: tracks_indexes })
.then(
(response) => {
dispatch({
type: 'PLAYLIST_TRACKS_REMOVED',
key: uri,
tracks_indexes,
snapshot_id: response.snapshot_id,
});
},
(error) => {
dispatch(coreActions.handleException(
'Could not remove tracks from playlist',
error,
));
},
);
request(
dispatch,
getState,
`playlists/${getFromUri('playlistid', uri)}/tracks`,
'DELETE',
{ snapshot_id, positions: tracks_indexes },
).then(
(response) => {
dispatch({
type: 'PLAYLIST_TRACKS_REMOVED',
key: uri,
tracks_indexes,
snapshot_id: response.snapshot_id,
});
},
(error) => {
dispatch(coreActions.handleException(
'Could not remove tracks from playlist',
error,
));
},
);
};
}

View File

@ -89,11 +89,11 @@ const SpotifyMiddleware = (function () {
store.dispatch(spotifyActions.createPlaylist(action.name, action.description, action.is_private, action.is_collaborative));
break;
case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS':
var playlist = { ...store.getState().core.playlists[action.key] };
store.dispatch(spotifyActions.deleteTracksFromPlaylist(playlist.uri, playlist.snapshot_id, action.tracks_indexes));
case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': {
const { snapshot_id } = store.getState().core.items[action.key] || {};
store.dispatch(spotifyActions.deleteTracksFromPlaylist(action.key, snapshot_id, action.tracks_indexes));
break;
}
case 'SPOTIFY_ADD_PLAYLIST_TRACKS':
store.dispatch(spotifyActions.addTracksToPlaylist(action.key, action.tracks_uris));

View File

@ -96,7 +96,7 @@ class Search extends React.Component {
}
onSourceClose = () => {
this.search();
this.search(true);
};
digestUri = () => {
@ -125,7 +125,7 @@ class Search extends React.Component {
this.setState({ term: '' });
}
search = () => {
search = (force = false) => {
const {
coreActions: {
startSearch,
@ -133,6 +133,10 @@ class Search extends React.Component {
uiActions: {
setWindowTitle,
},
search_results_query: {
type: existingType,
term: existingTerm,
},
} = this.props;
const {
type,
@ -141,27 +145,11 @@ class Search extends React.Component {
setWindowTitle(i18n('search.title_window', { term: decodeURIComponent(term) }));
/**
* TODO: Searches are being triggered when navigating to type subviews, despite already having
* results. This might be a consequence of having providers merging their results into one array
*/
if (type && term) {
if ((type && term && (force || existingType !== type || existingTerm !== term))) {
startSearch({ type, term });
/*
mopidyActions.getSearchResults(type, term);
if (uri_schemes_search_enabled.includes('spotify:')) {
spotifyActions.getSearchResults(type, term);
}*/
}
}
loadMore = (type) => {
alert(`load more: ${type}`);
// this.props.spotifyActions.getURL(this.props['spotify_'+type+'_more'], 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_'+type.toUpperCase());
}
setSort = (value) => {
const {
sort,
@ -291,14 +279,41 @@ class Search extends React.Component {
}
}
const mapStateToProps = (state, ownProps) => ({
type: ownProps.match.params.type,
term: ownProps.match.params.term,
uri_schemes_search_enabled: state.ui.uri_schemes_search_enabled || [],
uri_schemes: state.mopidy.uri_schemes || [],
sort: state.ui.search_results_sort || 'followers.total',
sort_reverse: (!!state.ui.search_results_sort_reverse),
});
const mapStateToProps = (state, ownProps) => {
const {
match: {
params: {
type,
term,
},
},
} = ownProps;
const {
mopidy: {
uri_schemes = [],
},
ui: {
uri_schemes_search_enabled = [],
search_results_sort: sort = 'followers.total',
search_results_sort_reverse,
},
core: {
search_results: {
query: search_results_query = {},
} = {},
},
} = state;
return {
type,
term,
uri_schemes,
uri_schemes_search_enabled,
sort,
sort_reverse: !!search_results_sort_reverse,
search_results_query,
};
};
const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),