diff --git a/src/js/App.js b/src/js/App.js index 11842dd9..d097b168 100755 --- a/src/js/App.js +++ b/src/js/App.js @@ -53,7 +53,7 @@ const Content = () => ( }/> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/js/components/SearchResults.js b/src/js/components/SearchResults.js index 663bcd5b..3043a2a3 100755 --- a/src/js/components/SearchResults.js +++ b/src/js/components/SearchResults.js @@ -1,5 +1,6 @@ import React from 'react'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; import { sortItems } from '../util/arrays'; import URILink from './URILink'; import Icon from './Icon'; @@ -11,38 +12,21 @@ import { makeSearchResultsSelector, getSortSelector } from '../util/selectors'; const SearchResults = ({ type, - query, - sortField, - sortReverse: sortReverseProp, - uri_schemes_priority, all, - results: rawResults, }) => { - const encodedTerm = encodeURIComponent(query.term); - let results = rawResults; - let sortReverse = sortReverseProp; - - if (!results) return null; - - let sort_map = null; - switch (sortField) { - case 'uri': - sort_map = uri_schemes_priority; - break; - case 'followers': - // Followers (aka popularlity works in reverse-numerical order) - // Ie "more popular" is a bigger number - sortReverse = !sortReverse; - break; - default: - break; - } + const { term } = useParams(); + const { sortField, sortReverse } = useSelector( + (state) => getSortSelector(state, 'search_results'), + ); + const searchResultsSelector = makeSearchResultsSelector(term, type); + const rawResults = useSelector(searchResultsSelector); + const encodedTerm = encodeURIComponent(term); + let results = [...rawResults]; results = sortItems( results, (type === 'tracks' && sortField === 'followers' ? 'popularity' : sortField), sortReverse, - sort_map, ); const resultsCount = results.length; @@ -50,7 +34,7 @@ const SearchResults = ({ results = results.slice(0, 6); } - if (results.length <= 0) return null; + if (all && !results.length) return null; return (
@@ -72,56 +56,59 @@ const SearchResults = ({ )} -
- {type === 'artists' && } - {type === 'albums' && } - {type === 'playlists' && } - {type === 'tracks' && ( - - )} - {/* */} + {results.length > 0 && ( +
+ {type === 'artists' && } + {type === 'albums' && } + {type === 'playlists' && } + {type === 'tracks' && ( + + )} + {/* */} - {resultsCount > results.length && ( - - )} -
+ {resultsCount > results.length && ( + + )} +
+ )}
); }; -const mapStateToProps = (state, ownProps) => { - const { - query: { - term, - }, - type, - } = ownProps; - const { - ui: { - uri_schemes_priority = [], - }, - } = state; - const searchResultsSelector = makeSearchResultsSelector(term, type); - const { sortField, sortReverse } = getSortSelector(state, 'search_results'); +const AllSearchResults = () => ( + <> +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +); - return { - results: searchResultsSelector(state), - uri_schemes_priority, - sortField, - sortReverse, - }; -}; - -const mapDispatchToProps = () => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchResults); +export { + SearchResults, + AllSearchResults, +} \ No newline at end of file diff --git a/src/js/views/Search.js b/src/js/views/Search.js index 76c429df..c969cc4a 100755 --- a/src/js/views/Search.js +++ b/src/js/views/Search.js @@ -1,294 +1,94 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; +import React, { useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import Header from '../components/Header'; import Icon from '../components/Icon'; import DropdownField from '../components/Fields/DropdownField'; import SearchForm from '../components/Fields/SearchForm'; -import SearchResults from '../components/SearchResults'; -import * as coreActions from '../services/core/actions'; -import * as uiActions from '../services/ui/actions'; -import * as mopidyActions from '../services/mopidy/actions'; -import * as spotifyActions from '../services/spotify/actions'; -import { titleCase } from '../util/helpers'; -import { withRouter } from '../util'; +import { AllSearchResults, SearchResults } from '../components/SearchResults'; +import { startSearch } from '../services/core/actions'; +import { + set, + hideContextMenu, + setWindowTitle, +} from '../services/ui/actions'; import { i18n } from '../locale'; +import { getSortSelector } from '../util/selectors'; -class Search extends React.Component { - constructor(props) { - super(props); - this.state = { term: props.term || '' }; - } +const Search = () => { + const { term, type = 'all' } = useParams(); + const dispatch = useDispatch(); + const navigate = useNavigate(); + const lastQuery = useSelector((state) => state.core?.search_results?.query); + const { sortField, sortReverse } = useSelector( + (state) => getSortSelector(state, 'search_results'), + ); - componentDidMount = () => { - const { - uiActions: { - setWindowTitle, - }, - } = this.props; - - setWindowTitle('Search'); - - // Auto-focus on the input field + useEffect(() => { + dispatch(setWindowTitle('Search')); $(document).find('.search-form input').focus(); - this.digestUri(); - } + }, []); - componentDidUpdate = ({ term: prevTerm }) => { - const { term: termProp } = this.props; - if (prevTerm !== termProp) { - this.search(); + useEffect(() => { + if (term && type && term !== lastQuery?.term) { + dispatch(setWindowTitle(i18n('search.title_window', { term: decodeURIComponent(term) }))); + dispatch(startSearch({ term, type })); } + }, [term, type]) + + const onSubmit = (nextTerm) => { + const encodedTerm = encodeURIComponent(nextTerm); + navigate(`/search/${type}/${encodedTerm}`); } - onSubmit = (term) => { - const { navigate, type } = this.props; - const encodedTerm = encodeURIComponent(term); + const onReset = () => navigate('/search'); - this.setState( - { term }, - () => { - navigate(`/search/${type}/${encodedTerm}`); - }, - ); + const onSortChange = (value) => { + dispatch(set({ uri_schemes_search_enabled: value })); + dispatch(hideContextMenu()); } - onReset = () => { - const { navigate } = this.props; - navigate('/search'); - } + const sortOptions = [ + { value: 'followers', label: i18n('common.popularity') }, + { value: 'name', label: i18n('common.name') }, + { value: 'artist', label: i18n('common.artist') }, + { value: 'duration', label: i18n('common.duration') }, + ]; - onSortChange = (value) => { - const { uiActions: { hideContextMenu } } = this.props; - this.setSort(value); - hideContextMenu(); - } + const options = ( + + ); - onSourceChange = (value) => { - const { - uiActions: { - set, - hideContextMenu, - }, - } = this.props; - set({ uri_schemes_search_enabled: value }); - hideContextMenu(); - } + return ( +
+
+ +
- onSourceClose = () => { - this.search(true); - }; + - digestUri = () => { - const { term } = this.props; - if (term) { - this.setState({ term }, this.search); - } else { - this.clearSearch(); - } - } - - clearSearch = () => { - const { - uiActions: { - setWindowTitle, - }, - } = this.props; - - setWindowTitle(i18n('search.title')); - this.setState({ term: '' }); - } - - search = (force = false) => { - const { - coreActions: { - startSearch, - }, - uiActions: { - setWindowTitle, - }, - search_results_query: { - type: existingType, - term: existingTerm, - }, - type, - } = this.props; - const { term } = this.state; - - setWindowTitle(i18n('search.title_window', { term: decodeURIComponent(term) })); - - if ((type && term && (force || existingType !== type || existingTerm !== term))) { - startSearch({ type, term }); - } - } - - setSort = (value) => { - const { - sort, - sort_reverse, - uiActions: { - set, - }, - } = this.props; - - let reverse = false; - if (sort === value) reverse = !sort_reverse; - - const data = { - search_results_sort_reverse: reverse, - search_results_sort: value, - }; - set(data); - } - - render = () => { - const { term } = this.state; - const { - uri_schemes, - sort, - sort_reverse, - uri_schemes_search_enabled, - uiActions, - type, - } = this.props; - - const sort_options = [ - { value: 'followers', label: i18n('common.popularity') }, - { value: 'name', label: i18n('common.name') }, - { value: 'artist', label: i18n('common.artist') }, - { value: 'duration', label: i18n('common.duration') }, - ]; - - const provider_options = uri_schemes.map((item) => ({ - value: item, - label: titleCase(item.replace(':', '').replace('+', ' ')), - })); - - const options = ( - <> - - - - ); - - let searchResults; - - switch (type) { - case 'artists': - searchResults = ; - break; - case 'albums': - searchResults = ; - break; - case 'playlists': - searchResults = ; - break; - case 'tracks': - searchResults = - break; - default: - searchResults = ( - <> -
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
- - - ); - } - - return ( -
-
- -
- - - -
- {searchResults} -
+
+ {type != 'all' ? ( + + ) : ( + + )}
- ); - } +
+ ); } -const mapStateToProps = (state, ownProps) => { - const { - params: { - type, - term, - }, - navigation, - } = 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: type || 'all', - term, - navigation, - uri_schemes, - uri_schemes_search_enabled, - sort, - sort_reverse: !!search_results_sort_reverse, - search_results_query, - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - coreActions: bindActionCreators(coreActions, dispatch), - uiActions: bindActionCreators(uiActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch), -}); - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Search)); +export default Search; diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss index ab544217..b7d7d1f6 100755 --- a/src/scss/components/_grid.scss +++ b/src/scss/components/_grid.scss @@ -11,6 +11,10 @@ border-bottom: 0 !important; cursor: pointer; + a { + text-decoration: none !important; + } + &__wrapper { display: inline-block; } diff --git a/src/scss/components/_images.scss b/src/scss/components/_images.scss index 5046c3e4..00f17953 100755 --- a/src/scss/components/_images.scss +++ b/src/scss/components/_images.scss @@ -111,6 +111,7 @@ cursor: pointer; color: colour('white'); border: 0 !important; + text-decoration: none !important; margin: 0 5px; &:hover { diff --git a/src/scss/components/_sub-tabs.scss b/src/scss/components/_sub-tabs.scss index f9338a00..29d35282 100755 --- a/src/scss/components/_sub-tabs.scss +++ b/src/scss/components/_sub-tabs.scss @@ -18,6 +18,7 @@ display: block; box-sizing: border-box; border: none !important; + text-decoration: none !important; cursor: pointer; &__inner { diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss index 58430cdd..95fe15cb 100755 --- a/src/scss/global/_core.scss +++ b/src/scss/global/_core.scss @@ -195,10 +195,8 @@ main { cursor: pointer; &:not(.control):not(.action):not(.button) { - border-bottom: 1px solid transparent; - &:hover { - border-color: colour('mid_grey'); + text-decoration: underline; } } } diff --git a/src/scss/views/_search.scss b/src/scss/views/_search.scss index d8bfa5fc..102be9f9 100755 --- a/src/scss/views/_search.scss +++ b/src/scss/views/_search.scss @@ -5,7 +5,7 @@ position: absolute; top: 30px; left: 90px; - right: 270px; + right: 170px; input { @include feature_font();