Merge pull request #5485 from urbit/lf/recency-improvements

landscape: better leap searching
This commit is contained in:
Liam Fitzgerald 2022-01-10 11:10:51 -06:00 committed by GitHub
commit f1f57abec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 511 additions and 31980 deletions

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"formik": "^2.1.5", "formik": "^2.1.5",
"fuzzy": "^0.1.3",
"immer": "^9.0.2", "immer": "^9.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.1", "moment": "^2.29.1",

View File

@ -1,6 +1,7 @@
import { Box, Row, Text } from '@tlon/indigo-react'; import { Box, Row, Text } from '@tlon/indigo-react';
import { omit } from 'lodash'; import { omit } from 'lodash';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import fuzzy from 'fuzzy';
import _ from 'lodash'; import _ from 'lodash';
import f from 'lodash/fp'; import f from 'lodash/fp';
import React, { import React, {
@ -44,6 +45,19 @@ const SEARCHED_CATEGORIES = [
const settingsSel = (s: SettingsState) => s.leap; const settingsSel = (s: SettingsState) => s.leap;
const CAT_LIMIT = 6; const CAT_LIMIT = 6;
/**
* Flatten `catMap` according to ordering in `cats`
*/
function flattenCattegoryMap(cats: string[], catMap: Map<string, OmniboxItem[]>) {
let res = [] as OmniboxItem[];
cats.forEach(cat => {
res = res.concat(_.take(catMap.get(cat), CAT_LIMIT));
});
return res;
}
export function Omnibox(props: OmniboxProps): ReactElement { export function Omnibox(props: OmniboxProps): ReactElement {
const location = useLocation(); const location = useLocation();
const history = useHistory(); const history = useHistory();
@ -124,29 +138,28 @@ export function Omnibox(props: OmniboxProps): ReactElement {
); );
}, [index]); }, [index]);
const results = useMemo(() => { const [results, categoryOrder] = useMemo(
(): [Map<string, OmniboxItem[]>, string[]] => {
if (query.length <= 1) { if (query.length <= 1) {
return initialResults; return [initialResults, ['other']];
} }
const q = query.toLowerCase(); const q = query.toLowerCase();
const resultsMap = new Map<string, OmniboxItem[]>(); const resultsMap = new Map<string, OmniboxItem[]>();
let categoryMaxes: Record<string, number> = {};
SEARCHED_CATEGORIES.map((category) => { SEARCHED_CATEGORIES.map((category) => {
const categoryIndex = index.get(category); const categoryIndex = index.get(category);
resultsMap.set( const fuzzied = fuzzy
category, .filter(q, categoryIndex, { extract: res => res.title });
categoryIndex.filter((result) => { categoryMaxes[category] = fuzzied
return ( .map(a => a.score)
result.title.toLowerCase().includes(q) || .reduce((a,b) => Math.max(a,b), 0);
result.link.toLowerCase().includes(q) || resultsMap.set(category, fuzzied.map(a => a.original));
result.app.toLowerCase().includes(q) ||
(result.host !== null
? result.host.toLowerCase().includes(q)
: false)
);
})
);
}); });
return resultsMap; let order = Object.entries(categoryMaxes)
.sort(([,a],[,b]) => b - a)
.map(([id]) => id);
return [resultsMap, order];
}, [query, index]); }, [query, index]);
const navigate = useCallback( const navigate = useCallback(
@ -181,7 +194,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
); );
const setPreviousSelected = useCallback(() => { const setPreviousSelected = useCallback(() => {
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat(); const flattenedResults = flattenCattegoryMap(categoryOrder, results);
const totalLength = flattenedResults.length; const totalLength = flattenedResults.length;
if (selected.length) { if (selected.length) {
const currentIndex = flattenedResults.indexOf( const currentIndex = flattenedResults.indexOf(
@ -201,10 +214,10 @@ export function Omnibox(props: OmniboxProps): ReactElement {
const { app, link } = flattenedResults[totalLength - 1]; const { app, link } = flattenedResults[totalLength - 1];
setSelected([app, link]); setSelected([app, link]);
} }
}, [results, selected]); }, [results, categoryOrder, selected]);
const setNextSelected = useCallback(() => { const setNextSelected = useCallback(() => {
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat(); const flattenedResults = flattenCattegoryMap(categoryOrder, results);
if (selected.length) { if (selected.length) {
const currentIndex = flattenedResults.indexOf( const currentIndex = flattenedResults.indexOf(
// @ts-ignore unclear how to give this spread a return signature // @ts-ignore unclear how to give this spread a return signature
@ -223,7 +236,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
const { app, link } = flattenedResults[0]; const { app, link } = flattenedResults[0];
setSelected([app, link]); setSelected([app, link]);
} }
}, [selected, results]); }, [results, categoryOrder, selected]);
const setSelection = (app, link) => { const setSelection = (app, link) => {
setLeapCursor('pointer'); setLeapCursor('pointer');
@ -255,14 +268,15 @@ export function Omnibox(props: OmniboxProps): ReactElement {
} }
if (evt.key === 'Enter') { if (evt.key === 'Enter') {
evt.preventDefault(); evt.preventDefault();
let values = flattenCattegoryMap(categoryOrder, results);
if (selected.length) { if (selected.length) {
navigate(selected[0], selected[1], evt.shiftKey); navigate(selected[0], selected[1], evt.shiftKey);
} else if (Array.from(results.values()).flat().length === 0) { } else if (values.length === 0) {
return; return;
} else { } else {
navigate( navigate(
Array.from(results.values()).flat()[0].app, values[0].app,
Array.from(results.values()).flat()[0].link, values[0].link,
evt.shiftKey evt.shiftKey
); );
} }
@ -275,15 +289,16 @@ export function Omnibox(props: OmniboxProps): ReactElement {
query, query,
props.show, props.show,
results, results,
categoryOrder,
setPreviousSelected, setPreviousSelected,
setNextSelected setNextSelected
] ]
); );
useEffect(() => { useEffect(() => {
const flattenedResultLinks: [string, string][] = Array.from(results.values()) const flattenedResultLinks: [string, string][] =
.flat() flattenCattegoryMap(categoryOrder, results)
.map(result => [result.app, result.link]); .map(result => [result.app, result.link]);
if (!flattenedResultLinks.includes(selected as [string, string])) { if (!flattenedResultLinks.includes(selected as [string, string])) {
setSelected(flattenedResultLinks[0] || []); setSelected(flattenedResultLinks[0] || []);
} }
@ -319,10 +334,10 @@ export function Omnibox(props: OmniboxProps): ReactElement {
borderBottomLeftRadius={2} borderBottomLeftRadius={2}
borderBottomRightRadius={2} borderBottomRightRadius={2}
> >
{SEARCHED_CATEGORIES.map(category => {categoryOrder.map(category =>
({ ({
category, category,
categoryResults: _.take(results.get(category).sort(sortResults), CAT_LIMIT) categoryResults: _.take(results.get(category), CAT_LIMIT)
}) })
) )
.filter(category => category.categoryResults.length > 0) .filter(category => category.categoryResults.length > 0)