mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 22:07:32 -04:00
New: Header search for quick manual search
This commit is contained in:
parent
867b61e4aa
commit
82eb6495d4
4 changed files with 7 additions and 184 deletions
|
@ -1,4 +1,3 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Autosuggest from 'react-autosuggest';
|
import Autosuggest from 'react-autosuggest';
|
||||||
|
@ -7,7 +6,6 @@ import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import FuseWorker from './fuse.worker';
|
|
||||||
import MovieSearchResult from './MovieSearchResult';
|
import MovieSearchResult from './MovieSearchResult';
|
||||||
import styles from './MovieSearchInput.css';
|
import styles from './MovieSearchInput.css';
|
||||||
|
|
||||||
|
@ -22,7 +20,6 @@ class MovieSearchInput extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this._autosuggest = null;
|
this._autosuggest = null;
|
||||||
this._worker = null;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: '',
|
||||||
|
@ -42,15 +39,6 @@ class MovieSearchInput extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getWorker() {
|
|
||||||
if (!this._worker) {
|
|
||||||
this._worker = new FuseWorker();
|
|
||||||
this._worker.addEventListener('message', this.onSuggestionsReceived, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._worker;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
|
@ -105,11 +93,6 @@ class MovieSearchInput extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
goToMovie(item) {
|
|
||||||
this.setState({ value: '' });
|
|
||||||
this.props.onGoToMovie(item.item.titleSlug);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: '',
|
value: '',
|
||||||
|
@ -149,8 +132,7 @@ class MovieSearchInput extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
highlightedSectionIndex,
|
highlightedSectionIndex
|
||||||
highlightedSuggestionIndex
|
|
||||||
} = this._autosuggest.state;
|
} = this._autosuggest.state;
|
||||||
|
|
||||||
if (!suggestions.length || highlightedSectionIndex) {
|
if (!suggestions.length || highlightedSectionIndex) {
|
||||||
|
@ -161,15 +143,6 @@ class MovieSearchInput extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an suggestion is not selected go to the first movie,
|
|
||||||
// otherwise go to the selected movie.
|
|
||||||
|
|
||||||
if (highlightedSuggestionIndex == null) {
|
|
||||||
this.goToMovie(suggestions[0]);
|
|
||||||
} else {
|
|
||||||
this.goToMovie(suggestions[highlightedSuggestionIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._autosuggest.input.blur();
|
this._autosuggest.input.blur();
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
@ -178,71 +151,6 @@ class MovieSearchInput extends Component {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionsFetchRequested = ({ value }) => {
|
|
||||||
if (!this.state.loading) {
|
|
||||||
this.setState({
|
|
||||||
loading: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestSuggestions(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
requestSuggestions = _.debounce((value) => {
|
|
||||||
if (!this.state.loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestLoading = this.state.requestLoading;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
requestValue: value,
|
|
||||||
requestLoading: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!requestLoading) {
|
|
||||||
const payload = {
|
|
||||||
value,
|
|
||||||
movies: this.props.movies
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getWorker().postMessage(payload);
|
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
onSuggestionsReceived = (message) => {
|
|
||||||
const {
|
|
||||||
value,
|
|
||||||
suggestions
|
|
||||||
} = message.data;
|
|
||||||
|
|
||||||
if (!this.state.loading) {
|
|
||||||
this.setState({
|
|
||||||
requestValue: null,
|
|
||||||
requestLoading: false
|
|
||||||
});
|
|
||||||
} else if (value === this.state.requestValue) {
|
|
||||||
this.setState({
|
|
||||||
suggestions,
|
|
||||||
requestValue: null,
|
|
||||||
requestLoading: false,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
suggestions,
|
|
||||||
requestLoading: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
value: this.state.requestValue,
|
|
||||||
movies: this.props.movies
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getWorker().postMessage(payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuggestionsClearRequested = () => {
|
onSuggestionsClearRequested = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
|
@ -253,8 +161,6 @@ class MovieSearchInput extends Component {
|
||||||
onSuggestionSelected = (event, { suggestion }) => {
|
onSuggestionSelected = (event, { suggestion }) => {
|
||||||
if (suggestion.type === ADD_NEW_TYPE) {
|
if (suggestion.type === ADD_NEW_TYPE) {
|
||||||
this.props.onGoToAddNewMovie(this.state.value);
|
this.props.onGoToAddNewMovie(this.state.value);
|
||||||
} else {
|
|
||||||
this.goToMovie(suggestion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,23 +169,13 @@ class MovieSearchInput extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
value,
|
value
|
||||||
loading,
|
|
||||||
suggestions
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const suggestionGroups = [];
|
const suggestionGroups = [];
|
||||||
|
|
||||||
if (suggestions.length || loading) {
|
|
||||||
suggestionGroups.push({
|
|
||||||
title: translate('ExistingMovies'),
|
|
||||||
loading,
|
|
||||||
suggestions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
suggestionGroups.push({
|
suggestionGroups.push({
|
||||||
title: translate('AddNewMovie'),
|
title: translate('SearchIndexers'),
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
type: ADD_NEW_TYPE,
|
type: ADD_NEW_TYPE,
|
||||||
|
@ -328,8 +224,6 @@ class MovieSearchInput extends Component {
|
||||||
getSuggestionValue={this.getSuggestionValue}
|
getSuggestionValue={this.getSuggestionValue}
|
||||||
renderSuggestion={this.renderSuggestion}
|
renderSuggestion={this.renderSuggestion}
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -337,8 +231,6 @@ class MovieSearchInput extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieSearchInput.propTypes = {
|
MovieSearchInput.propTypes = {
|
||||||
movies: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onGoToMovie: PropTypes.func.isRequired,
|
|
||||||
onGoToAddNewMovie: PropTypes.func.isRequired,
|
onGoToAddNewMovie: PropTypes.func.isRequired,
|
||||||
bindShortcut: PropTypes.func.isRequired
|
bindShortcut: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { setSearchDefault } from 'Store/Actions/releaseActions';
|
||||||
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
||||||
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
|
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
|
@ -58,12 +59,9 @@ function createMapStateToProps() {
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
onGoToMovie(titleSlug) {
|
|
||||||
dispatch(push(`${window.Prowlarr.urlBase}/movie/${titleSlug}`));
|
|
||||||
},
|
|
||||||
|
|
||||||
onGoToAddNewMovie(query) {
|
onGoToAddNewMovie(query) {
|
||||||
dispatch(push(`${window.Prowlarr.urlBase}/add/new?term=${encodeURIComponent(query)}`));
|
dispatch(setSearchDefault({ searchQuery: query, searchIndexerIds: [-1, -2] }));
|
||||||
|
dispatch(push(`${window.Prowlarr.urlBase}search`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import Fuse from 'fuse.js';
|
|
||||||
|
|
||||||
const fuseOptions = {
|
|
||||||
shouldSort: true,
|
|
||||||
includeMatches: true,
|
|
||||||
ignoreLocation: true,
|
|
||||||
threshold: 0.3,
|
|
||||||
minMatchCharLength: 1,
|
|
||||||
keys: [
|
|
||||||
'title',
|
|
||||||
'alternateTitles.title',
|
|
||||||
'tags.label'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
function getSuggestions(movies, value) {
|
|
||||||
const limit = 10;
|
|
||||||
let suggestions = [];
|
|
||||||
|
|
||||||
if (value.length === 1) {
|
|
||||||
for (let i = 0; i < movies.length; i++) {
|
|
||||||
const s = movies[i];
|
|
||||||
if (s.firstCharacter === value.toLowerCase()) {
|
|
||||||
suggestions.push({
|
|
||||||
item: movies[i],
|
|
||||||
indices: [
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
matches: [
|
|
||||||
{
|
|
||||||
value: s.title,
|
|
||||||
key: 'title'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
arrayIndex: 0
|
|
||||||
});
|
|
||||||
if (suggestions.length > limit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const fuse = new Fuse(movies, fuseOptions);
|
|
||||||
suggestions = fuse.search(value, { limit });
|
|
||||||
}
|
|
||||||
|
|
||||||
return suggestions;
|
|
||||||
}
|
|
||||||
|
|
||||||
onmessage = function(e) {
|
|
||||||
if (!e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
movies,
|
|
||||||
value
|
|
||||||
} = e.data;
|
|
||||||
|
|
||||||
const suggestions = getSuggestions(movies, value);
|
|
||||||
|
|
||||||
const results = {
|
|
||||||
value,
|
|
||||||
suggestions
|
|
||||||
};
|
|
||||||
|
|
||||||
self.postMessage(results);
|
|
||||||
};
|
|
|
@ -321,6 +321,7 @@
|
||||||
"TestAllClients": "Test All Clients",
|
"TestAllClients": "Test All Clients",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"Title": "Title",
|
"Title": "Title",
|
||||||
|
"SearchIndexers": "Search all Indexers",
|
||||||
"Today": "Today",
|
"Today": "Today",
|
||||||
"Tomorrow": "Tomorrow",
|
"Tomorrow": "Tomorrow",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue