mirror of
https://github.com/mue/mue.git
synced 2026-07-14 20:43:54 +02:00
feat: debounced search
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
|
||||
import { memo, createRef, useEffect, useState } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { MdSearch, MdMic, MdScreenSearchDesktop } from 'react-icons/md';
|
||||
import { BsGoogle } from 'react-icons/bs';
|
||||
import { SiDuckduckgo, SiMicrosoftbing, SiBaidu } from 'react-icons/si';
|
||||
@@ -14,26 +14,87 @@ import './search.scss';
|
||||
|
||||
import searchEngines from './search_engines.json';
|
||||
|
||||
export default class Search extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
url: '',
|
||||
query: '',
|
||||
microphone: null,
|
||||
suggestions: [],
|
||||
searchDropdown: false,
|
||||
classList:
|
||||
localStorage.getItem('widgetStyle') === 'legacy' ? 'searchIcons old' : 'searchIcons',
|
||||
function Search() {
|
||||
const [url, setURL] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [microphone, setMicrophone] = useState(null);
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [searchDropdown, setSearchDropdown] = useState(false);
|
||||
const [classList] = useState(localStorage.getItem('widgetStyle') === 'legacy' ? 'searchIcons old' : 'searchIcons');
|
||||
const [currentSearch, setCurrentSearch] = useState('');
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'search') {
|
||||
init();
|
||||
}
|
||||
});
|
||||
init();
|
||||
if (localStorage.getItem('searchFocus') === 'true') {
|
||||
const element = document.getElementById('searchtext');
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
EventBus.off('refresh');
|
||||
};
|
||||
this.micIcon = createRef();
|
||||
});
|
||||
|
||||
let micIcon = createRef();
|
||||
|
||||
|
||||
const customText = variables
|
||||
.getMessage('modals.main.settings.sections.search.custom')
|
||||
.split(' ')[0];
|
||||
|
||||
function init() {
|
||||
let _url;
|
||||
let _query = 'q';
|
||||
|
||||
const setting = localStorage.getItem('searchEngine');
|
||||
const info = searchEngines.find((i) => i.settingsName === setting);
|
||||
|
||||
if (info !== undefined) {
|
||||
_url = info.url;
|
||||
if (info.query) {
|
||||
_query = info.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (setting === 'custom') {
|
||||
const custom = localStorage.getItem('customSearchEngine');
|
||||
if (custom !== null) {
|
||||
_url = custom;
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem('voiceSearch') === 'true') {
|
||||
setMicrophone((
|
||||
<button
|
||||
className="navbarButton"
|
||||
onClick={startSpeechRecognition}
|
||||
ref={micIcon}
|
||||
aria-label="Microphone Search"
|
||||
>
|
||||
<MdMic className="micIcon" />
|
||||
</button>
|
||||
));
|
||||
}
|
||||
|
||||
setURL(_url);
|
||||
setQuery(_query);
|
||||
setCurrentSearch(info ? info.name : 'Custom');
|
||||
}
|
||||
|
||||
startSpeechRecognition = () => {
|
||||
|
||||
function startSpeechRecognition() {
|
||||
const voiceSearch = new window.webkitSpeechRecognition();
|
||||
voiceSearch.start();
|
||||
|
||||
this.micIcon.current.classList.add('micActive');
|
||||
micIcon.current.classList.add('micActive');
|
||||
|
||||
const searchText = document.getElementById('searchtext');
|
||||
|
||||
@@ -42,82 +103,44 @@ export default class Search extends PureComponent {
|
||||
};
|
||||
|
||||
voiceSearch.onend = () => {
|
||||
this.micIcon.current.classList.remove('micActive');
|
||||
micIcon.current.classList.remove('micActive');
|
||||
if (searchText.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
variables.stats.postEvent('feature', 'Voice search');
|
||||
window.location.href = this.state.url + `?${this.state.query}=` + searchText.value;
|
||||
window.location.href = url + `?${query}=` + searchText.value;
|
||||
}, 1000);
|
||||
};
|
||||
};
|
||||
|
||||
searchButton = (e) => {
|
||||
function searchButton(e) {
|
||||
e.preventDefault();
|
||||
const value = e.target.value || document.getElementById('searchtext').value || 'mue fast';
|
||||
variables.stats.postEvent('feature', 'Search');
|
||||
window.location.href = this.state.url + `?${this.state.query}=` + value;
|
||||
window.location.href = url + `?${query}=` + value;
|
||||
};
|
||||
|
||||
async getSuggestions(input) {
|
||||
window.setResults = (results) => {
|
||||
window.searchResults = results;
|
||||
};
|
||||
|
||||
async function getSuggestions(input) {
|
||||
if (input === '') {
|
||||
setSuggestions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await (await fetch(`${variables.constants.API_URL}/search/autocomplete?q=${input}`)).json();
|
||||
|
||||
try {
|
||||
this.setState({
|
||||
suggestions: results.suggestions.splice(0, 5),
|
||||
});
|
||||
setSuggestions(results.suggestions.splice(0, 5));
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// ignore error if empty
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
let url, microphone;
|
||||
let query = 'q';
|
||||
|
||||
const setting = localStorage.getItem('searchEngine');
|
||||
const info = searchEngines.find((i) => i.settingsName === setting);
|
||||
|
||||
if (info !== undefined) {
|
||||
url = info.url;
|
||||
if (info.query) {
|
||||
query = info.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (setting === 'custom') {
|
||||
const custom = localStorage.getItem('customSearchEngine');
|
||||
if (custom !== null) {
|
||||
url = custom;
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem('voiceSearch') === 'true') {
|
||||
microphone = (
|
||||
<button
|
||||
className="navbarButton"
|
||||
onClick={this.startSpeechRecognition}
|
||||
ref={this.micIcon}
|
||||
aria-label="Microphone Search"
|
||||
>
|
||||
<MdMic className="micIcon" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
url,
|
||||
query,
|
||||
microphone,
|
||||
currentSearch: info ? info.name : 'Custom',
|
||||
});
|
||||
}
|
||||
const getSuggestionsDebounced = useDebouncedCallback(getSuggestions, 100);
|
||||
// const getSuggestionsDebounced = getSuggestions;
|
||||
|
||||
/**
|
||||
* If the user selects a search engine from the dropdown menu, the function will set the state of the
|
||||
@@ -125,64 +148,42 @@ export default class Search extends PureComponent {
|
||||
* @param {string} name - The name of the search engine
|
||||
* @param {boolean} custom - If the search engine is custom
|
||||
*/
|
||||
setSearch(name, custom) {
|
||||
let url;
|
||||
let query = 'q';
|
||||
function setSearch(name, custom) {
|
||||
let _url;
|
||||
let _query = 'q';
|
||||
const info = searchEngines.find((i) => i.name === name);
|
||||
|
||||
if (info !== undefined) {
|
||||
url = info.url;
|
||||
_url = info.url;
|
||||
if (info.query) {
|
||||
query = info.query;
|
||||
_query = info.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
const customSetting = localStorage.getItem('customSearchEngine');
|
||||
if (customSetting !== null) {
|
||||
url = customSetting;
|
||||
_url = customSetting;
|
||||
} else {
|
||||
url = this.state.url;
|
||||
_url = url;
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem('searchEngine', info.settingsName);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
url,
|
||||
query,
|
||||
currentSearch: name,
|
||||
searchDropdown: false,
|
||||
});
|
||||
setURL(_url);
|
||||
setQuery(_query);
|
||||
setCurrentSearch(name);
|
||||
setSearchDropdown(false);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'search') {
|
||||
this.init();
|
||||
}
|
||||
});
|
||||
|
||||
this.init();
|
||||
|
||||
if (localStorage.getItem('searchFocus') === 'true') {
|
||||
const element = document.getElementById('searchtext');
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon for the search engine dropdown.
|
||||
* @param {string} name - The name of the search engine.
|
||||
* @returns A React component.
|
||||
*/
|
||||
getSearchDropdownicon(name) {
|
||||
function getSearchDropdownicon(name) {
|
||||
switch (name) {
|
||||
case 'Google':
|
||||
return <BsGoogle />;
|
||||
@@ -202,84 +203,82 @@ export default class Search extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const customText = variables
|
||||
.getMessage('modals.main.settings.sections.search.custom')
|
||||
.split(' ')[0];
|
||||
|
||||
return (
|
||||
<div className="searchComponents">
|
||||
<div className="searchMain">
|
||||
<div className={this.state.classList}>
|
||||
{localStorage.getItem('searchDropdown') === 'true' ? (
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.search.search_engine')}
|
||||
>
|
||||
<button
|
||||
className="navbarButton"
|
||||
aria-label="Search Engine"
|
||||
onClick={() => this.setState({ searchDropdown: !this.state.searchDropdown })}
|
||||
>
|
||||
{this.getSearchDropdownicon(this.state.currentSearch)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
return (
|
||||
<div className="searchComponents">
|
||||
<div className="searchMain">
|
||||
<div className={classList}>
|
||||
{localStorage.getItem('searchDropdown') === 'true' ? (
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.search.voice_search')}
|
||||
title={variables.getMessage('modals.main.settings.sections.search.search_engine')}
|
||||
>
|
||||
{this.state.microphone}
|
||||
<button
|
||||
className="navbarButton"
|
||||
aria-label="Search Engine"
|
||||
onClick={() => setSearchDropdown(!searchDropdown)}
|
||||
>
|
||||
{getSearchDropdownicon(currentSearch)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.search.voice_search')}
|
||||
>
|
||||
{microphone}
|
||||
</Tooltip>
|
||||
</div>
|
||||
<form onSubmit={searchButton} className="searchBar">
|
||||
<div className={classList}>
|
||||
<Tooltip title={variables.getMessage('widgets.search')}>
|
||||
<button className="navbarButton" onClick={searchButton} aria-label="Search">
|
||||
<MdSearch />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<form onSubmit={this.searchButton} className="searchBar">
|
||||
<div className={this.state.classList}>
|
||||
<Tooltip title={variables.getMessage('widgets.search')}>
|
||||
<button className="navbarButton" onClick={this.searchButton} aria-label="Search">
|
||||
<MdSearch />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AutocompleteInput
|
||||
placeholder={variables.getMessage('widgets.search')}
|
||||
id="searchtext"
|
||||
suggestions={this.state.suggestions}
|
||||
onChange={(e) => this.getSuggestions(e)}
|
||||
onClick={this.searchButton}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
{localStorage.getItem('searchDropdown') === 'true' &&
|
||||
this.state.searchDropdown === true && (
|
||||
<div className="searchDropdown">
|
||||
{searchEngines.map(({ name }, key) => {
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(this.state.currentSearch === name ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => this.setSearch(name)}
|
||||
key={key}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(this.state.currentSearch === customText ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => this.setSearch(customText, 'custom')}
|
||||
>
|
||||
{customText}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<AutocompleteInput
|
||||
placeholder={variables.getMessage('widgets.search')}
|
||||
id="searchtext"
|
||||
suggestions={suggestions}
|
||||
onChange={(e) => getSuggestionsDebounced(e)}
|
||||
onClick={searchButton}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div>
|
||||
{localStorage.getItem('searchDropdown') === 'true' &&
|
||||
searchDropdown === true && (
|
||||
<div className="searchDropdown">
|
||||
{searchEngines.map(({ name }, key) => {
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(currentSearch === name ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => setSearch(name)}
|
||||
key={key}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<span
|
||||
className={
|
||||
'searchDropdownList' +
|
||||
(currentSearch === customText ? ' searchDropdownListActive' : '')
|
||||
}
|
||||
onClick={() => setSearch(customText, 'custom')}
|
||||
>
|
||||
{customText}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const MemoizedSearch = memo(Search);
|
||||
export { MemoizedSearch as default, MemoizedSearch as Search };
|
||||
|
||||
Reference in New Issue
Block a user