Merge branch 'develop'
This commit is contained in:
@ -1 +1 @@
|
||||
3.68.0
|
||||
3.69.0
|
||||
|
||||
@ -3,7 +3,7 @@ import pathlib
|
||||
|
||||
from mopidy import config, ext
|
||||
|
||||
__version__ = "3.68.0"
|
||||
__version__ = "3.69.0"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class IrisSystemPermissionError(IrisSystemError):
|
||||
|
||||
|
||||
class IrisSystemThread(Thread):
|
||||
_USE_SUDO = True
|
||||
_USE_SUDO = os.environ.get("IRIS_USE_SUDO", True)
|
||||
|
||||
def __init__(self, action, ioloop, callback):
|
||||
Thread.__init__(self)
|
||||
|
||||
@ -28,7 +28,10 @@ elif [[ $1 = "restart" ]]; then
|
||||
elif [[ $1 = "local_scan" ]]; then
|
||||
START=$(date +%s)
|
||||
if $IS_CONTAINER; then
|
||||
SCAN=$(mopidy --config /config/mopidy.conf local scan)
|
||||
if [ -n "$IRIS_CONFIG_LOCATION" ]; then
|
||||
SCAN=$(mopidy --config $IRIS_CONFIG_LOCATION local scan)
|
||||
else
|
||||
SCAN=$(mopidy --config /config/mopidy.conf local scan)
|
||||
else
|
||||
SCAN=$(sudo mopidyctl local scan)
|
||||
fi
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mopidy-iris",
|
||||
"version": "3.68.0",
|
||||
"version": "3.69.0",
|
||||
"description": "Mopidy HTTP interface",
|
||||
"repository": "https://github.com/jaedb/iris",
|
||||
"author": "James Barnsley <james@barnsley.nz>",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = Mopidy-Iris
|
||||
version = 3.68.0
|
||||
version = 3.69.0
|
||||
url = https://github.com/jaedb/iris
|
||||
author = James Barnsley
|
||||
author_email = james@barnsley.nz
|
||||
|
||||
@ -54,7 +54,7 @@ const Content = () => (
|
||||
<Route path="settings/debug" element={<Debug />} />
|
||||
<Route path="settings/*" element={<Settings />} />
|
||||
<Route path="search" element={<Search />} />
|
||||
<Route path="search/:type/:term" element={<Search />} />
|
||||
<Route path="search/:type/:providers/:term" element={<Search />} />
|
||||
<Route path="artist/:uri/*" element={<Artist />} />
|
||||
<Route path="album/:uri/" element={<Album />} />
|
||||
<Route path="album/:uri/:name" element={<Album />} />
|
||||
|
||||
@ -9,6 +9,7 @@ import { Grid } from './Grid';
|
||||
import { I18n } from '../locale';
|
||||
import Button from './Button';
|
||||
import { makeSearchResultsSelector, getSortSelector } from '../util/selectors';
|
||||
import useSearchQuery from '../util/useSearchQuery';
|
||||
|
||||
const SORT_KEY = 'search_results';
|
||||
|
||||
@ -16,11 +17,12 @@ const SearchResults = ({
|
||||
type,
|
||||
all,
|
||||
}) => {
|
||||
const { term } = useParams();
|
||||
const { term, providers } = useSearchQuery();
|
||||
const encodedProviders = providers.join(',').replace(/:/g,'');
|
||||
const [sortField, sortReverse] = useSelector(
|
||||
(state) => getSortSelector(state, SORT_KEY, 'name'),
|
||||
);
|
||||
const searchResultsSelector = makeSearchResultsSelector(term, type);
|
||||
const searchResultsSelector = makeSearchResultsSelector(providers, term, type);
|
||||
const rawResults = useSelector(searchResultsSelector);
|
||||
const encodedTerm = encodeURIComponent(term);
|
||||
let results = [...rawResults];
|
||||
@ -43,17 +45,17 @@ const SearchResults = ({
|
||||
<h4>
|
||||
{!all && (
|
||||
<span>
|
||||
<URILink uri={`iris:search:all:${encodedTerm}`} uriType="search" unencoded>
|
||||
<URILink uri={`iris:search:all:${encodedProviders}:${encodedTerm}`} uriType="search" unencoded>
|
||||
<I18n path="search.title" />
|
||||
</URILink>
|
||||
{' '}
|
||||
<span style={{ display: 'inline-block', width: 10 }} />
|
||||
<Icon type="fontawesome" name="angle-right" />
|
||||
{' '}
|
||||
<span style={{ display: 'inline-block', width: 10 }} />
|
||||
<I18n path={`search.${type}.title`} />
|
||||
</span>
|
||||
)}
|
||||
{all && (
|
||||
<URILink uri={`iris:search:${type}:${encodedTerm}`} uriType="search" unencoded>
|
||||
<URILink uri={`iris:search:${type}:${encodedProviders}:${encodedTerm}`} uriType="search" unencoded>
|
||||
<I18n path={`search.${type}.title`} />
|
||||
</URILink>
|
||||
)}
|
||||
@ -66,7 +68,7 @@ const SearchResults = ({
|
||||
{type === 'tracks' && (
|
||||
<TrackList
|
||||
source={{
|
||||
uri: `iris:search:${type}:${encodedTerm}`,
|
||||
uri: `iris:search:${type}:${encodedProviders}:${encodedTerm}`,
|
||||
name: 'Search results',
|
||||
type: 'search',
|
||||
}}
|
||||
@ -77,7 +79,7 @@ const SearchResults = ({
|
||||
{/* <LazyLoadListener enabled={this.props.artists_more && spotify_search_enabled} loadMore={loadMore} /> */}
|
||||
|
||||
{resultsCount > results.length && (
|
||||
<Button uri={`iris:search:${type}:${encodedTerm}`} uriType="search" unencoded>
|
||||
<Button uri={`iris:search:${type}:${encodedProviders}:${encodedTerm}`} uriType="search" unencoded>
|
||||
<I18n path={`search.${type}.more`} count={resultsCount} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -47,7 +47,7 @@ export default memo(({
|
||||
|
||||
case 'search':
|
||||
var exploded = uri.split(':');
|
||||
to = `/search/${exploded[2]}/${exploded[3]}`;
|
||||
to = `/search/${exploded[2]}/${exploded[3]}/${exploded[4]}`;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@ -337,7 +337,7 @@ search:
|
||||
title_window: 'Search: %{term}'
|
||||
context_actions:
|
||||
sort: Sort
|
||||
source: 'Sources (%{count})'
|
||||
source: Sources
|
||||
placeholder: Search
|
||||
all:
|
||||
title: All
|
||||
|
||||
@ -100,11 +100,10 @@ export function startSearch(query) {
|
||||
};
|
||||
}
|
||||
|
||||
export function searchResultsLoaded(query, resultType, results) {
|
||||
export function searchResultsLoaded(key, results) {
|
||||
return {
|
||||
type: 'SEARCH_RESULTS_LOADED',
|
||||
query,
|
||||
resultType,
|
||||
key,
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
@ -177,79 +177,36 @@ const CoreMiddleware = (function () {
|
||||
|
||||
case 'START_SEARCH': {
|
||||
const { query } = action;
|
||||
const { term = '', providers = [], type = 'all' } = query;
|
||||
const {
|
||||
ui: {
|
||||
allow_reporting,
|
||||
},
|
||||
mopidy: {
|
||||
uri_schemes = [],
|
||||
} = {},
|
||||
} = store.getState();
|
||||
|
||||
if (allow_reporting) {
|
||||
ReactGA.event({
|
||||
category: 'Search',
|
||||
action: 'Started',
|
||||
label: `${query.type}: ${query.term}`,
|
||||
label: `${type}: ${term}`,
|
||||
});
|
||||
}
|
||||
|
||||
console.info(`Searching for ${query.type} matching "${query.term}"`);
|
||||
console.info(`Searching ${providers.length} providers for ${type} matching "${term}"`);
|
||||
|
||||
// Trigger reducer immediately; this will hose out any previous results
|
||||
next(action);
|
||||
|
||||
if (uri_schemes.includes('spotify:')) {
|
||||
if (providers.includes('spotify')) {
|
||||
store.dispatch(spotifyActions.getSearchResults(query));
|
||||
}
|
||||
store.dispatch(mopidyActions.getSearchResults(
|
||||
query,
|
||||
100,
|
||||
uri_schemes.filter((i) => i !== 'spotify:'), // Omit Spotify; handled above
|
||||
providers.filter((i) => i !== 'spotify'), // Omit Spotify; handled above
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SEARCH_RESULTS_LOADED': {
|
||||
const {
|
||||
query: {
|
||||
term,
|
||||
type,
|
||||
},
|
||||
resultType,
|
||||
results,
|
||||
} = action;
|
||||
const {
|
||||
core: {
|
||||
search_results: {
|
||||
query: {
|
||||
term: prevTerm,
|
||||
type: prevType,
|
||||
} = {},
|
||||
...allResults
|
||||
} = {},
|
||||
} = {},
|
||||
} = store.getState();
|
||||
|
||||
// Add to our existing results, so long as the search term is the same
|
||||
const search_results = {
|
||||
query: { term, type },
|
||||
...(term === prevTerm && type === prevType ? allResults : {}),
|
||||
};
|
||||
|
||||
// Merge our new results with the existing (if any)
|
||||
search_results[resultType] = [
|
||||
...(search_results[resultType] || []),
|
||||
...results,
|
||||
];
|
||||
|
||||
next({
|
||||
...action,
|
||||
search_results,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PLAYLIST_TRACKS_ADDED': {
|
||||
const {
|
||||
key,
|
||||
@ -546,7 +503,6 @@ const CoreMiddleware = (function () {
|
||||
|
||||
case 'LOAD_LIBRARY':
|
||||
store.dispatch(uiActions.startLoading(action.uri, action.uri));
|
||||
console.debug(action);
|
||||
const fetchLibrary = () => {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
|
||||
@ -176,24 +176,15 @@ export default function reducer(core = {}, action) {
|
||||
/**
|
||||
* Search results
|
||||
* */
|
||||
case 'START_SEARCH':
|
||||
|
||||
case 'SEARCH_RESULTS_LOADED': {
|
||||
return {
|
||||
...core,
|
||||
search_results: {
|
||||
query: action.query,
|
||||
artists: [],
|
||||
albums: [],
|
||||
playlists: [],
|
||||
tracks: [],
|
||||
...core?.search_results || {},
|
||||
[action.key]: action.results,
|
||||
},
|
||||
};
|
||||
|
||||
case 'SEARCH_RESULTS_LOADED': {
|
||||
const { search_results } = action;
|
||||
return {
|
||||
...core,
|
||||
search_results,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@ -591,12 +591,12 @@ export function clearSearchResults() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getSearchResults(query, limit = 100, uri_schemes) {
|
||||
export function getSearchResults(query, providers, limit = 100) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_SEARCH_RESULTS',
|
||||
query,
|
||||
providers,
|
||||
limit,
|
||||
uri_schemes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
uriSource,
|
||||
setFavicon,
|
||||
titleCase,
|
||||
getSearchResultKey,
|
||||
} from '../../util/helpers';
|
||||
import {
|
||||
digestMopidyImages,
|
||||
@ -31,6 +32,7 @@ import {
|
||||
indexToArray,
|
||||
} from '../../util/arrays';
|
||||
import { getProvider, getSortSelector } from '../../util/selectors';
|
||||
import { iterate } from 'localforage';
|
||||
|
||||
const mopidyActions = require('./actions.js');
|
||||
const coreActions = require('../core/actions.js');
|
||||
@ -263,31 +265,44 @@ const MopidyMiddleware = (function () {
|
||||
type,
|
||||
term,
|
||||
requestType,
|
||||
uri_scheme,
|
||||
provider,
|
||||
method = 'library.search',
|
||||
data,
|
||||
} = queue.shift();
|
||||
const processKey = 'MOPIDY_GET_SEARCH_RESULTS';
|
||||
const processor = store.getState().ui.processes[processKey];
|
||||
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS'));
|
||||
return;
|
||||
}
|
||||
|
||||
const iterateNext = (store, queue) => {
|
||||
if (queue.length) {
|
||||
processSearchQueue(store, queue);
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished(processKey));
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
processKey,
|
||||
{
|
||||
content: i18n(
|
||||
'services.mopidy.searching',
|
||||
{
|
||||
provider: titleCase(uri_scheme.replace(':', '')),
|
||||
provider: titleCase(provider),
|
||||
type: requestType,
|
||||
},
|
||||
),
|
||||
remaining: queue.length,
|
||||
},
|
||||
));
|
||||
|
||||
const resultKey = getSearchResultKey({ provider, type, term });
|
||||
if (resultKey in store.getState().core.search_results) {
|
||||
iterateNext(store, queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Each type has a different method of formatting and destructuring.
|
||||
const processResults = {
|
||||
@ -348,7 +363,7 @@ const MopidyMiddleware = (function () {
|
||||
playlists: (response) => {
|
||||
const playlists = response.filter(
|
||||
(item) => {
|
||||
if (!item.uri.includes(uri_scheme)) return false;
|
||||
if (!item.uri.includes(provider)) return false;
|
||||
return item.name.toLowerCase().includes(term.toLowerCase());
|
||||
},
|
||||
);
|
||||
@ -371,17 +386,11 @@ const MopidyMiddleware = (function () {
|
||||
(response) => {
|
||||
if (response.length > 0) {
|
||||
store.dispatch(coreActions.searchResultsLoaded(
|
||||
{ term, type },
|
||||
requestType,
|
||||
resultKey,
|
||||
processResults[requestType](response),
|
||||
));
|
||||
}
|
||||
|
||||
if (queue.length) {
|
||||
processSearchQueue(store, queue);
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished(processKey));
|
||||
}
|
||||
iterateNext(store, queue);
|
||||
},
|
||||
);
|
||||
};
|
||||
@ -969,7 +978,6 @@ const MopidyMiddleware = (function () {
|
||||
}
|
||||
|
||||
case 'MOPIDY_ENQUEUE_URIS': {
|
||||
console.debug(action)
|
||||
if (!action.uris || action.uris.length <= 0) {
|
||||
this.props.uiActions.createNotification({
|
||||
content: 'No URIs to enqueue',
|
||||
@ -1221,35 +1229,35 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
case 'MOPIDY_GET_SEARCH_RESULTS': {
|
||||
const {
|
||||
uri_schemes = [],
|
||||
query = {},
|
||||
query: { term, type: queryType } = {},
|
||||
providers,
|
||||
} = action;
|
||||
const types = query.type === 'all'
|
||||
const types = queryType === 'all'
|
||||
? ['artists', 'albums', 'tracks', 'playlists']
|
||||
: [query.type];
|
||||
: [queryType];
|
||||
|
||||
const queue = [];
|
||||
uri_schemes.forEach(
|
||||
(uri_scheme) => types.forEach(
|
||||
providers.forEach(
|
||||
(provider) => types.forEach(
|
||||
(type) => {
|
||||
const item = {
|
||||
type: query.type,
|
||||
term: query.term,
|
||||
type,
|
||||
term,
|
||||
provider,
|
||||
requestType: type,
|
||||
uri_scheme,
|
||||
data: {
|
||||
uris: [uri_scheme],
|
||||
uris: [`${provider}:`],
|
||||
},
|
||||
};
|
||||
switch (type) {
|
||||
case 'tracks':
|
||||
item.data.query = { any: [query.term] };
|
||||
item.data.query = { any: [term] };
|
||||
break;
|
||||
case 'artists':
|
||||
item.data.query = { artist: [query.term] };
|
||||
item.data.query = { artist: [term] };
|
||||
break;
|
||||
case 'albums':
|
||||
item.data.query = { album: [query.term] };
|
||||
item.data.query = { album: [term] };
|
||||
break;
|
||||
case 'playlists':
|
||||
// Searching for playlists is not supported, so we get a simple
|
||||
@ -1818,13 +1826,13 @@ const MopidyMiddleware = (function () {
|
||||
if (item.__model__ === 'Track') {
|
||||
tracks.push(formatTrack(item));
|
||||
} else if (item.__model__ === 'Ref' && item.type === 'track') {
|
||||
tracks.push(formatTrack({ ...item, loading: true }));
|
||||
tracks.push(formatTrack(item));
|
||||
trackUrisToLoad.push(item.uri);
|
||||
} else if (item.__model__ === 'Ref' && item.type === 'album') {
|
||||
subdirectories.push(formatAlbum({ ...item, loading: true }));
|
||||
subdirectories.push(formatAlbum(item));
|
||||
subdirectoryImagesToLoad.push(item.uri);
|
||||
} else if (item.__model__ === 'Ref' && item.type === 'artist') {
|
||||
subdirectories.push(formatArtist({ ...item, loading: true }));
|
||||
subdirectories.push(formatArtist(item));
|
||||
subdirectoryImagesToLoad.push(item.uri);
|
||||
} else if (item.__model__ === 'Ref' && item.type === 'playlist') {
|
||||
// Tidal moods and genres incorrectly marked as Playlist type
|
||||
@ -1837,7 +1845,7 @@ const MopidyMiddleware = (function () {
|
||||
type: 'directory',
|
||||
});
|
||||
} else {
|
||||
subdirectories.push(formatPlaylist({ ...item, loading: true }));
|
||||
subdirectories.push(formatPlaylist(item));
|
||||
subdirectoryImagesToLoad.push(item.uri);
|
||||
playlistsToLoad.push(item.uri);
|
||||
}
|
||||
|
||||
@ -103,33 +103,6 @@ export default function reducer(mopidy = {}, action) {
|
||||
directory: { ...mopidy.directory, ...action.directory },
|
||||
};
|
||||
|
||||
/**
|
||||
* Searching
|
||||
* */
|
||||
case 'MOPIDY_CLEAR_SEARCH_RESULTS':
|
||||
return { ...mopidy, search_results: {} };
|
||||
|
||||
case 'MOPIDY_SEARCH_RESULTS_LOADED':
|
||||
// Fetch or create our container
|
||||
if (mopidy.search_results) {
|
||||
var search_results = { ...mopidy.search_results };
|
||||
} else {
|
||||
var search_results = {};
|
||||
}
|
||||
|
||||
search_results = {
|
||||
...search_results,
|
||||
query: action.query,
|
||||
};
|
||||
|
||||
if (search_results[action.context]) {
|
||||
search_results[action.context] = [...search_results[action.context], ...action.results];
|
||||
} else {
|
||||
search_results[action.context] = action.results;
|
||||
}
|
||||
|
||||
return { ...mopidy, search_results };
|
||||
|
||||
default:
|
||||
return mopidy;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
getFromUri,
|
||||
uriType,
|
||||
upgradeSpotifyPlaylistUris,
|
||||
getSearchResultKey,
|
||||
} from '../../util/helpers';
|
||||
import {
|
||||
formatPlaylistGroup,
|
||||
@ -613,7 +614,8 @@ export function getMore(endpoint, core_action = null, custom_action = null, extr
|
||||
};
|
||||
}
|
||||
|
||||
export function getSearchResults({ type, term }, limit = 50, offset = 0) {
|
||||
export function getSearchResults(query, limit = 50, offset = 0) {
|
||||
const { type, term } = query;
|
||||
const processKey = 'SPOTIFY_GET_SEARCH_RESULTS';
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
@ -641,24 +643,21 @@ export function getSearchResults({ type, term }, limit = 50, offset = 0) {
|
||||
(response) => {
|
||||
if (response.tracks !== undefined) {
|
||||
dispatch(coreActions.searchResultsLoaded(
|
||||
{ term, type },
|
||||
'tracks',
|
||||
getSearchResultKey({ provider: 'spotify', type: 'tracks', term }),
|
||||
formatTracks(response.tracks.items),
|
||||
));
|
||||
}
|
||||
|
||||
if (response.artists !== undefined) {
|
||||
dispatch(coreActions.searchResultsLoaded(
|
||||
{ term, type },
|
||||
'artists',
|
||||
getSearchResultKey({ provider: 'spotify', type: 'artists', term }),
|
||||
formatArtists(response.artists.items),
|
||||
));
|
||||
}
|
||||
|
||||
if (response.albums !== undefined) {
|
||||
dispatch(coreActions.searchResultsLoaded(
|
||||
{ term, type },
|
||||
'albums',
|
||||
getSearchResultKey({ provider: 'spotify', type: 'albums', term }),
|
||||
formatAlbums(response.albums.items),
|
||||
));
|
||||
}
|
||||
@ -669,8 +668,7 @@ export function getSearchResults({ type, term }, limit = 50, offset = 0) {
|
||||
can_edit: (meId === item.owner.id),
|
||||
}));
|
||||
dispatch(coreActions.searchResultsLoaded(
|
||||
{ term, type },
|
||||
'playlists',
|
||||
getSearchResultKey({ provider: 'spotify', type: 'playlists', term }),
|
||||
playlists,
|
||||
));
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ let initialState = {
|
||||
tracks: {},
|
||||
items: {},
|
||||
libraries: {},
|
||||
search_results: {},
|
||||
},
|
||||
ui: {
|
||||
language: 'en',
|
||||
|
||||
@ -603,6 +603,8 @@ const upgradeSpotifyPlaylistUri = function (uri) {
|
||||
return upgradeSpotifyPlaylistUris([uri])[0];
|
||||
};
|
||||
|
||||
const getSearchResultKey = ({ provider, type, term }) => [provider, type, term].join(':');
|
||||
|
||||
export {
|
||||
debounce,
|
||||
throttle,
|
||||
@ -626,6 +628,7 @@ export {
|
||||
upgradeSpotifyPlaylistUris,
|
||||
upgradeSpotifyPlaylistUri,
|
||||
iconFromKeyword,
|
||||
getSearchResultKey,
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -651,4 +654,5 @@ export default {
|
||||
upgradeSpotifyPlaylistUris,
|
||||
upgradeSpotifyPlaylistUri,
|
||||
iconFromKeyword,
|
||||
getSearchResultKey,
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { indexToArray } from './arrays';
|
||||
import { isLoading } from './helpers';
|
||||
import { getSearchResultKey, isLoading } from './helpers';
|
||||
import { i18n } from '../locale';
|
||||
|
||||
const getItem = (state, uri) => state.core.items[uri];
|
||||
@ -68,13 +68,14 @@ const makeLibrarySelector = (name, filtered = true) => createSelector(
|
||||
},
|
||||
);
|
||||
|
||||
const makeSearchResultsSelector = (term, type) => createSelector(
|
||||
const makeSearchResultsSelector = (providers, term, type) => createSelector(
|
||||
[getSearchResults],
|
||||
(searchResults) => {
|
||||
if (!searchResults || searchResults.query.term !== term) return [];
|
||||
return searchResults[type] || [];
|
||||
},
|
||||
);
|
||||
(searchResults) => providers.reduce(
|
||||
(acc, curr) => [
|
||||
...acc,
|
||||
...searchResults[getSearchResultKey({ provider: curr, term, type })] || [],
|
||||
], []),
|
||||
);
|
||||
|
||||
const makeProcessProgressSelector = (keys) => createSelector(
|
||||
[getProcesses],
|
||||
|
||||
27
src/js/util/useSearchQuery.js
Normal file
27
src/js/util/useSearchQuery.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { useParams } from 'react-router';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
const useSearchQuery = () => {
|
||||
const {
|
||||
term = '',
|
||||
type = 'all',
|
||||
providers: rawProviders = 'all',
|
||||
} = useParams();
|
||||
const allProviders = useSelector(
|
||||
({ mopidy: { uri_schemes } }) => uri_schemes || []
|
||||
).map((str) => str.replace(/:/g,''));
|
||||
const providers = rawProviders == 'all'
|
||||
? [...allProviders]
|
||||
: rawProviders.split(',').filter((str) => allProviders.indexOf(str) > -1);
|
||||
const providersString = providers.join(',');
|
||||
|
||||
return {
|
||||
term,
|
||||
type,
|
||||
providers,
|
||||
allProviders,
|
||||
providersString,
|
||||
}
|
||||
};
|
||||
|
||||
export default useSearchQuery;
|
||||
@ -197,8 +197,6 @@ class FeaturedPlaylists extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
console.debug({ source, view })
|
||||
|
||||
const options = (
|
||||
<>
|
||||
<FilterField
|
||||
|
||||
@ -223,7 +223,6 @@ class Recommendations extends React.Component {
|
||||
if (!uri) return;
|
||||
|
||||
const seeds = uri.replace('iris:discover:', '').split(',').map((seed) => decodeUri(seed));
|
||||
console.debug({ uri, uriProp, seeds })
|
||||
loadUris(seeds);
|
||||
this.setState(
|
||||
{ seeds },
|
||||
|
||||
@ -14,43 +14,58 @@ import {
|
||||
} from '../services/ui/actions';
|
||||
import { i18n } from '../locale';
|
||||
import { getSortSelector } from '../util/selectors';
|
||||
import useSearchQuery from '../util/useSearchQuery';
|
||||
|
||||
const SORT_KEY = 'search_results';
|
||||
|
||||
const Search = () => {
|
||||
const { term, type = 'all' } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const lastQuery = useSelector((state) => state.core?.search_results?.query);
|
||||
const {
|
||||
term,
|
||||
type,
|
||||
providers,
|
||||
allProviders,
|
||||
providersString,
|
||||
} = useSearchQuery();
|
||||
const [sortField, sortReverse] = useSelector(
|
||||
(state) => getSortSelector(state, SORT_KEY, 'name'),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setWindowTitle('Search'));
|
||||
$(document).find('.search-form input').focus();
|
||||
$(document).find('.search-form input').trigger('focus');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (term && type && term !== lastQuery?.term) {
|
||||
if (term) {
|
||||
dispatch(setWindowTitle(i18n('search.title_window', { term: decodeURIComponent(term) })));
|
||||
dispatch(startSearch({ term, type }));
|
||||
dispatch(startSearch({ term, type, providers }));
|
||||
}
|
||||
}, [term, type])
|
||||
}, [providersString, type, term])
|
||||
|
||||
const onSubmit = (nextTerm) => {
|
||||
const encodedTerm = encodeURIComponent(nextTerm);
|
||||
navigate(`/search/${type}/${encodedTerm}`);
|
||||
const onSubmit = (term) => {
|
||||
updateSearchQuery(term, providers);
|
||||
}
|
||||
|
||||
const updateSearchQuery = (term, providers) => {
|
||||
const encodedTerm = encodeURIComponent(term);
|
||||
navigate(`/search/${type}/${providers.join(',')}/${encodedTerm || ''}`);
|
||||
}
|
||||
|
||||
const onReset = () => navigate('/search');
|
||||
|
||||
const onSortChange = (field) => {
|
||||
const onProvidersChange = (providers) => {
|
||||
updateSearchQuery(term, providers)
|
||||
dispatch(hideContextMenu());
|
||||
}
|
||||
|
||||
const onSortChange = (value) => {
|
||||
let reverse = false;
|
||||
if (field !== null && sortField === field) {
|
||||
if (value !== null && sortField === value) {
|
||||
reverse = !sortReverse;
|
||||
}
|
||||
dispatch(setSort(SORT_KEY, field, reverse));
|
||||
dispatch(setSort(SORT_KEY, value, reverse));
|
||||
dispatch(hideContextMenu());
|
||||
}
|
||||
|
||||
@ -62,16 +77,27 @@ const Search = () => {
|
||||
{ value: 'duration', label: i18n('common.duration') },
|
||||
];
|
||||
|
||||
const providerOptions = allProviders.map((value) => ({ value, label: value}))
|
||||
|
||||
const options = (
|
||||
<DropdownField
|
||||
icon="swap_vert"
|
||||
name={i18n('common.sort')}
|
||||
value={sortField}
|
||||
options={sortOptions}
|
||||
selected_icon={sortField ? (sortReverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={onSortChange}
|
||||
valueAsLabel
|
||||
/>
|
||||
<>
|
||||
<DropdownField
|
||||
icon="swap_vert"
|
||||
name={i18n('common.sort')}
|
||||
value={sortField}
|
||||
options={sortOptions}
|
||||
selected_icon={sortField ? (sortReverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={onSortChange}
|
||||
valueAsLabel
|
||||
/>
|
||||
<DropdownField
|
||||
icon="cloud"
|
||||
name={i18n('search.context_actions.source')}
|
||||
value={providers}
|
||||
options={providerOptions}
|
||||
handleChange={onProvidersChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -741,7 +741,7 @@ footer {
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@ -773,7 +773,7 @@ footer {
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
|
||||
@ -123,7 +123,7 @@
|
||||
|
||||
.sub-views {
|
||||
margin-left: 5px;
|
||||
padding-top: 10px;
|
||||
padding-top: 30px;
|
||||
|
||||
.option {
|
||||
margin: 0 8px;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 90px;
|
||||
right: 150px;
|
||||
right: 250px;
|
||||
|
||||
input {
|
||||
@include feature_font();
|
||||
@ -81,6 +81,10 @@
|
||||
}
|
||||
|
||||
@include responsive($bp_medium) {
|
||||
header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
top: 10px;
|
||||
left: 40px;
|
||||
@ -97,7 +101,7 @@
|
||||
}
|
||||
|
||||
.search-result-sections {
|
||||
padding: 10px 10px 0;
|
||||
// padding: 10px 10px 0;
|
||||
|
||||
section {
|
||||
width: auto;
|
||||
|
||||
Reference in New Issue
Block a user