2018-03-26 23:32:43 +02:00
|
|
|
// @flow
|
2019-11-20 21:57:38 +01:00
|
|
|
import { URL, URL_LOCAL, URL_DEV } from 'config';
|
2019-03-28 17:53:13 +01:00
|
|
|
import * as PAGES from 'constants/pages';
|
2018-11-26 02:21:25 +01:00
|
|
|
import * as ICONS from 'constants/icons';
|
2017-12-21 22:08:54 +01:00
|
|
|
import React from 'react';
|
2018-03-26 23:32:43 +02:00
|
|
|
import Icon from 'component/common/icon';
|
2020-12-03 18:29:47 +01:00
|
|
|
import { isURIValid, normalizeURI, parseURI } from 'lbry-redux';
|
|
|
|
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox';
|
|
|
|
import '@reach/combobox/styles.css';
|
|
|
|
import useLighthouse from 'effects/use-lighthouse';
|
|
|
|
import { Form } from 'component/common/form';
|
|
|
|
import Button from 'component/button';
|
|
|
|
import WunderbarTopSuggestion from 'component/wunderbarTopSuggestion';
|
|
|
|
import WunderbarSuggestion from 'component/wunderbarSuggestion';
|
|
|
|
import { useHistory } from 'react-router';
|
|
|
|
import { formatLbryUrlForWeb } from 'util/url';
|
|
|
|
import useThrottle from 'effects/use-throttle';
|
|
|
|
|
2019-11-20 21:57:38 +01:00
|
|
|
const WEB_DEV_PREFIX = `${URL_DEV}/`;
|
|
|
|
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
|
|
|
|
const WEB_PROD_PREFIX = `${URL}/`;
|
2019-08-19 06:49:54 +02:00
|
|
|
const SEARCH_PREFIX = `$/${PAGES.SEARCH}q=`;
|
2020-09-10 19:11:31 +02:00
|
|
|
const INVALID_URL_ERROR = "Invalid LBRY URL entered. Only A-Z, a-z, 0-9, and '-' allowed.";
|
|
|
|
|
|
|
|
const L_KEY_CODE = 76;
|
|
|
|
const ESC_KEY_CODE = 27;
|
2018-10-04 07:59:47 +02:00
|
|
|
|
2018-03-26 23:32:43 +02:00
|
|
|
type Props = {
|
2019-03-28 17:53:13 +01:00
|
|
|
searchQuery: ?string,
|
2019-02-18 18:24:56 +01:00
|
|
|
onSearch: string => void,
|
2020-12-03 18:29:47 +01:00
|
|
|
navigateToSearchPage: string => void,
|
|
|
|
doResolveUris: string => void,
|
2019-04-19 20:05:55 +02:00
|
|
|
doShowSnackBar: string => void,
|
2020-12-03 18:29:47 +01:00
|
|
|
showMature: boolean,
|
2018-03-26 23:32:43 +02:00
|
|
|
};
|
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
export default function WunderBar(props: Props) {
|
|
|
|
const { navigateToSearchPage, doShowSnackBar, doResolveUris, showMature } = props;
|
|
|
|
const inputRef = React.useRef();
|
|
|
|
const {
|
|
|
|
push,
|
|
|
|
location: { search },
|
|
|
|
} = useHistory();
|
|
|
|
const urlParams = new URLSearchParams(search);
|
|
|
|
const queryFromUrl = urlParams.get('q') || '';
|
|
|
|
const [term, setTerm] = React.useState(queryFromUrl);
|
|
|
|
const throttledTerm = useThrottle(term, 500) || '';
|
|
|
|
const { results } = useLighthouse(throttledTerm, showMature);
|
|
|
|
const nameFromQuery = throttledTerm
|
|
|
|
.trim()
|
|
|
|
.replace(/\s+/g, '')
|
|
|
|
.replace(/:/g, '#');
|
|
|
|
const uriFromQuery = `lbry://${nameFromQuery}`;
|
|
|
|
let uriFromQueryIsValid = false;
|
|
|
|
let channelUrlForTopTest;
|
|
|
|
try {
|
|
|
|
const { isChannel } = parseURI(uriFromQuery);
|
|
|
|
uriFromQueryIsValid = true;
|
|
|
|
if (!isChannel) {
|
|
|
|
channelUrlForTopTest = `lbry://@${uriFromQuery}`;
|
|
|
|
}
|
|
|
|
} catch (e) {}
|
2019-03-28 17:53:13 +01:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
const topUrisToTest = [uriFromQuery];
|
|
|
|
if (channelUrlForTopTest) {
|
|
|
|
topUrisToTest.push(uriFromQuery);
|
|
|
|
}
|
2017-05-04 05:44:08 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
function handleSelect(value) {
|
|
|
|
const includesLbryTvProd = value.includes(WEB_PROD_PREFIX);
|
|
|
|
const includesLbryTvLocal = value.includes(WEB_LOCAL_PREFIX);
|
|
|
|
const includesLbryTvDev = value.includes(WEB_DEV_PREFIX);
|
|
|
|
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd;
|
|
|
|
const isLbryUrl = value.startsWith('lbry://');
|
2019-03-28 17:53:13 +01:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
if (inputRef.current) {
|
|
|
|
inputRef.current.blur();
|
|
|
|
}
|
2018-10-04 07:59:47 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
if (wasCopiedFromWeb) {
|
|
|
|
let prefix = WEB_PROD_PREFIX;
|
|
|
|
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
|
|
|
|
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
|
2018-10-04 07:59:47 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
let query = value.slice(prefix.length).replace(/:/g, '#');
|
2018-01-09 02:15:44 +01:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
if (query.includes(SEARCH_PREFIX)) {
|
|
|
|
query = query.slice(SEARCH_PREFIX.length);
|
|
|
|
navigateToSearchPage(query);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const lbryUrl = `lbry://${query}`;
|
|
|
|
parseURI(lbryUrl);
|
|
|
|
const formattedLbryUrl = formatLbryUrlForWeb(lbryUrl);
|
|
|
|
push(formattedLbryUrl);
|
|
|
|
|
|
|
|
return;
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isLbryUrl) {
|
|
|
|
navigateToSearchPage(value);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
if (isURIValid(value)) {
|
|
|
|
const uri = normalizeURI(value);
|
|
|
|
const normalizedWebUrl = formatLbryUrlForWeb(uri);
|
|
|
|
push(normalizedWebUrl);
|
|
|
|
} else {
|
|
|
|
doShowSnackBar(INVALID_URL_ERROR);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
navigateToSearchPage(value);
|
|
|
|
}
|
2018-01-09 02:15:44 +01:00
|
|
|
}
|
2020-12-03 18:29:47 +01:00
|
|
|
}
|
2017-05-04 05:44:08 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
React.useEffect(() => {
|
|
|
|
function handleKeyDown(event) {
|
|
|
|
const { ctrlKey, metaKey, keyCode } = event;
|
2018-10-04 07:59:47 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
if (!inputRef.current) {
|
2019-01-19 19:54:06 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-10-04 07:59:47 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
if (inputRef.current === document.activeElement && keyCode === ESC_KEY_CODE) {
|
|
|
|
inputRef.current.blur();
|
|
|
|
}
|
|
|
|
|
2019-03-28 17:53:13 +01:00
|
|
|
// @if TARGET='app'
|
2019-01-19 19:54:06 +01:00
|
|
|
const shouldFocus =
|
2019-05-07 23:38:29 +02:00
|
|
|
process.platform === 'darwin' ? keyCode === L_KEY_CODE && metaKey : keyCode === L_KEY_CODE && ctrlKey;
|
2019-01-19 19:54:06 +01:00
|
|
|
if (shouldFocus) {
|
2020-12-03 18:29:47 +01:00
|
|
|
inputRef.current.focus();
|
2019-01-19 19:54:06 +01:00
|
|
|
}
|
2019-03-28 17:53:13 +01:00
|
|
|
// @endif
|
2018-10-04 07:59:47 +02:00
|
|
|
}
|
2018-01-09 02:15:44 +01:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
|
|
}, [inputRef]);
|
2020-09-10 19:11:31 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
const stringifiedResults = JSON.stringify(results);
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (stringifiedResults) {
|
|
|
|
const arrayResults = JSON.parse(stringifiedResults);
|
|
|
|
if (arrayResults && arrayResults.length > 0) {
|
|
|
|
doResolveUris(arrayResults);
|
2018-10-11 07:56:47 +02:00
|
|
|
}
|
2020-09-10 19:11:31 +02:00
|
|
|
}
|
2020-12-03 18:29:47 +01:00
|
|
|
}, [doResolveUris, stringifiedResults]);
|
2020-09-10 19:11:31 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
return (
|
|
|
|
<Form className="wunderbar__wrapper" onSubmit={() => handleSelect(term)}>
|
|
|
|
<Combobox className="wunderbar" onSelect={handleSelect}>
|
2018-11-26 02:21:25 +01:00
|
|
|
<Icon icon={ICONS.SEARCH} />
|
2020-12-03 18:29:47 +01:00
|
|
|
<ComboboxInput
|
|
|
|
ref={inputRef}
|
|
|
|
className="wunderbar__input"
|
|
|
|
placeholder={__('Search')}
|
|
|
|
onChange={e => setTerm(e.target.value)}
|
|
|
|
value={term}
|
2017-06-06 23:19:12 +02:00
|
|
|
/>
|
2017-05-04 05:44:08 +02:00
|
|
|
|
2020-12-03 18:29:47 +01:00
|
|
|
{results && results.length > 0 && (
|
|
|
|
<ComboboxPopover portal={false} className="wunderbar__suggestions">
|
|
|
|
<ComboboxList>
|
|
|
|
{uriFromQueryIsValid ? <WunderbarTopSuggestion query={nameFromQuery} /> : null}
|
|
|
|
|
2020-12-03 22:06:54 +01:00
|
|
|
<div className="wunderbar__label">{__('Search Results')}</div>
|
2020-12-03 18:29:47 +01:00
|
|
|
{results.slice(0, 5).map(uri => (
|
|
|
|
<WunderbarSuggestion key={uri} uri={uri} />
|
|
|
|
))}
|
|
|
|
<ComboboxOption value={term} className="wunderbar__more-results">
|
|
|
|
<Button button="link" label={__('View All Results')} />
|
|
|
|
</ComboboxOption>
|
|
|
|
</ComboboxList>
|
|
|
|
</ComboboxPopover>
|
|
|
|
)}
|
|
|
|
</Combobox>
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
}
|