From 868fc049637d4e4d617d1cc793f9b514f5ec8c7a Mon Sep 17 00:00:00 2001 From: Sophia Xu Date: Fri, 31 Dec 2021 18:00:30 -0500 Subject: [PATCH] feat(omnibar): auto command registration, keyboard navigation --- .../src/examples/bookmarks/Bookmarks.tsx | 7 +- .../src/examples/notes/init.tsx | 7 +- .../src/examples/todo/TodoList.tsx | 7 +- .../src/examples/todo/parseTodoObject.ts | 2 +- packages/unigraph-dev-explorer/src/init.ts | 31 ++- .../src/pages/SearchOverlay.tsx | 252 +++++++++++------- .../src/unigraph-react.tsx | 10 +- 7 files changed, 219 insertions(+), 97 deletions(-) diff --git a/packages/unigraph-dev-explorer/src/examples/bookmarks/Bookmarks.tsx b/packages/unigraph-dev-explorer/src/examples/bookmarks/Bookmarks.tsx index ca46148b..3b006b61 100644 --- a/packages/unigraph-dev-explorer/src/examples/bookmarks/Bookmarks.tsx +++ b/packages/unigraph-dev-explorer/src/examples/bookmarks/Bookmarks.tsx @@ -134,13 +134,18 @@ const quickAdder = async (inputStr: string, preview = true) => { }; export const init = () => { + const description = 'Add a bookmark'; const tt = () => (
For example, enter #tag1 https://example.com
); - registerQuickAdder({ bookmark: { adder: quickAdder, tooltip: tt }, bm: { adder: quickAdder, tooltip: tt } }); + registerQuickAdder({ + bookmark: { + adder: quickAdder, tooltip: tt, description, alias: ['bm'], + }, + }); registerDynamicViews({ '$/schema/web_bookmark': BookmarkItem }); }; diff --git a/packages/unigraph-dev-explorer/src/examples/notes/init.tsx b/packages/unigraph-dev-explorer/src/examples/notes/init.tsx index 105a7ee7..cd4605a1 100644 --- a/packages/unigraph-dev-explorer/src/examples/notes/init.tsx +++ b/packages/unigraph-dev-explorer/src/examples/notes/init.tsx @@ -28,7 +28,12 @@ export const init = () => { ); - registerQuickAdder({ n: { adder: quickAdder, tooltip: tt }, note: { adder: quickAdder, tooltip: tt } }); + const description = 'Add a note'; + registerQuickAdder({ + note: { + adder: quickAdder, tooltip: tt, description, alias: ['n'], + }, + }); registerContextMenuItems('$/schema/note_block', [(uid: any, object: any, handleClose: any, callbacks: any) => ( { diff --git a/packages/unigraph-dev-explorer/src/examples/todo/TodoList.tsx b/packages/unigraph-dev-explorer/src/examples/todo/TodoList.tsx index 8de9bb25..e1dc768d 100644 --- a/packages/unigraph-dev-explorer/src/examples/todo/TodoList.tsx +++ b/packages/unigraph-dev-explorer/src/examples/todo/TodoList.tsx @@ -128,8 +128,13 @@ const tt = () => ( ); export const init = () => { + const description = 'Add a new Todo object'; registerDynamicViews({ '$/schema/todo': TodoItem }); - registerQuickAdder({ todo: { adder: quickAdder, tooltip: tt }, td: { adder: quickAdder, tooltip: tt } }); + registerQuickAdder({ + todo: { + adder: quickAdder, tooltip: tt, description, alias: ['td'], + }, + }); }; export const TodoList = withUnigraphSubscription( diff --git a/packages/unigraph-dev-explorer/src/examples/todo/parseTodoObject.ts b/packages/unigraph-dev-explorer/src/examples/todo/parseTodoObject.ts index 18499ed8..deeda5c6 100644 --- a/packages/unigraph-dev-explorer/src/examples/todo/parseTodoObject.ts +++ b/packages/unigraph-dev-explorer/src/examples/todo/parseTodoObject.ts @@ -52,6 +52,6 @@ export const parseTodoObject: (arg0: string, refs?: any[]) => ATodoList = (todoS name: tagName, }, })), ...inlineRefsToChildren(refs)], - timeFrame, + time_frame: timeFrame, }; }; diff --git a/packages/unigraph-dev-explorer/src/init.ts b/packages/unigraph-dev-explorer/src/init.ts index c58e1f0b..79db03f1 100644 --- a/packages/unigraph-dev-explorer/src/init.ts +++ b/packages/unigraph-dev-explorer/src/init.ts @@ -31,12 +31,39 @@ window.reloadCommands = () => { const pageCommands = Object.entries(window.unigraph.getState('registry/pages').value).map(([k, v]: any) => ({ name: `Open: ${v.name}`, about: `Open the page ${v.name}`, - onClick: () => { + onClick: (ev: any, setInput: any, setClose: any) => { window.wsnavigator(`/${k}`); + setInput(''); setClose(); }, })); - commandsState.setValue(pageCommands); + const adderCommands = Object.entries(window.unigraph.getState('registry/quickAdder').value).map(([k, v]: any) => { + if ((v.alias || []).includes(k)) return false; + const matches = [k, ...(v.alias || [])].map((el: string) => `+${el}`).join(' / '); + return { + name: `${matches}: ${v.description}`, + about: 'Add a Unigraph object', + onClick: (ev: any, setInput: any) => { + ev.stopPropagation(); + ev.preventDefault(); + setInput(`+${k} `); + }, + group: 'adder', + }; + }).filter(Boolean); + + const searchCommand = { + name: '? : search Unigraph', + about: 'Search Unigraph', + onClick: (ev: any, setInput: any) => { + ev.stopPropagation(); + ev.preventDefault(); + setInput('?'); + }, + group: 'search', + }; + + commandsState.setValue([...adderCommands, searchCommand, ...pageCommands]); }; /** diff --git a/packages/unigraph-dev-explorer/src/pages/SearchOverlay.tsx b/packages/unigraph-dev-explorer/src/pages/SearchOverlay.tsx index f29cf215..8124c522 100644 --- a/packages/unigraph-dev-explorer/src/pages/SearchOverlay.tsx +++ b/packages/unigraph-dev-explorer/src/pages/SearchOverlay.tsx @@ -12,16 +12,100 @@ import { inlineTextSearch } from '../components/UnigraphCore/InlineSearchPopup'; import { parseQuery } from '../components/UnigraphCore/UnigraphSearch'; import { isElectron, setCaret } from '../utils'; -export function SearchOverlayTooltip() { +const groups = [ + { + title: 'Add an item', + key: 'adder', + }, + { + title: 'Search Unigraph', + key: 'search', + }, + { + title: 'Commands', + key: undefined, + }, +]; + +function AdderComponent({ + input, setInput, open, setClose, callback, summonerTooltip, +}: any) { + const parsedKey = input.substr(1, input.indexOf(' ') - 1); + const parsedValue = input.substr(input.indexOf(' ') + 1); + const [toAdd, setToAdd] = React.useState(null); + const [adderRefs, setAdderRefs] = React.useState([]); + const tf = React.useRef(null); + + React.useEffect(() => { + tf.current?.focus(); + }, [open]); + + React.useEffect(() => { + const allAdders = window.unigraph.getState('registry/quickAdder').value; + if (allAdders[parsedKey]) { + allAdders[parsedKey].adder(parsedValue).then((res: any) => { + const [object, type] = res; + window.unigraph.getSchemas().then((schemas: any) => { + try { + const padded = buildUnigraphEntity(JSON.parse(JSON.stringify(object)), type, schemas); + setToAdd( +
{ ev.stopPropagation(); }}> + +
, + ); + } catch (e) { + console.log(e); + } + }); + }); + } + }, [input]); + return ( -
- Add an item - +todo / +td : add todo item - +note / +n : create new note page and start editing - +bookmark / +bm : add bookmark from URL - Search Unigraph - ?<search query> : search Unigraph - Commands +
+ { + const newContent = ev.target.value; + const caret = ev.target.selectionStart || 0; + const hasMatch = inlineTextSearch( + ev.target.value, + tf, + caret, + async (match: any, newName: string, newUid: string) => { + const newStr = `${newContent?.slice?.(0, match.index)}[[${newName}]]${newContent?.slice?.(match.index + match[0].length)}`; + setInput(newStr); + setAdderRefs([...adderRefs, { key: newName, value: newUid }]); + window.unigraph.getState('global/searchPopup').setValue({ show: false }); + }, + ); + if (!hasMatch) window.unigraph.getState('global/searchPopup').setValue({ show: false }); + setInput(ev.target.value); + }} + onKeyPress={async (ev) => { + if (ev.key === 'Enter' && window.unigraph.getState('registry/quickAdder').value[parsedKey]) { + window.unigraph.getState('registry/quickAdder').value[parsedKey]?.adder(JSON.parse(JSON.stringify(parsedValue)), false, callback, adderRefs).then((uids: any[]) => { + if (callback && uids[0]) callback(uids[0]); + }); + setInput(''); + setClose(); + } + }} + /> +
+ {summonerTooltip ? {summonerTooltip} : []} + + {`Adding ${parsedKey} (Enter to add)`} + + {toAdd || []} +
+ {(window.unigraph.getState('registry/quickAdder').value[parsedKey]?.tooltip) + ? React.createElement(window.unigraph.getState('registry/quickAdder').value[parsedKey]?.tooltip) + : []} +
+
); } @@ -29,7 +113,6 @@ export function SearchOverlayTooltip() { export function SearchOverlay({ open, setClose, callback, summonerTooltip, defaultValue, }: any) { - const tf = React.useRef(null); const [input, setInput] = React.useState(defaultValue || ''); const [parsed, setParsed] = React.useState({}); const [query, setQuery] = React.useState([]); @@ -40,8 +123,10 @@ export function SearchOverlay({ const [entities, setEntities] = React.useState([]); const [response, setResponse] = React.useState(false); const [commands, setCommands] = React.useState([]); + const [finalCommands, setFinalCommands] = React.useState([]); + const [selectedIndex, setSelectedIndex] = React.useState(0); - const [adderRefs, setAdderRefs] = React.useState([]); + const tf = React.useRef(null); React.useEffect(() => { window.unigraph.getState('registry/commands').subscribe((res) => { @@ -53,6 +138,7 @@ export function SearchOverlay({ React.useEffect(() => { tf.current?.focus(); + console.log('open', open); }, [open]); React.useEffect(() => { @@ -60,22 +146,7 @@ export function SearchOverlay({ }, defaultValue); React.useEffect(() => { - if (input.startsWith('+')) { - const quickAdder = input.substr(1, input.indexOf(' ') - 1); - const value = input.substr(input.indexOf(' ') + 1); - const allAdders = window.unigraph.getState('registry/quickAdder').value; - if (allAdders[quickAdder]) { - setParsed({ - type: 'quickAdder', - key: quickAdder, - value, - }); - } else { - setParsed({}); - setEntries([]); - setEntities([]); - } - } else if (input.startsWith('?')) { + if (input.startsWith('?')) { const newQuery = input.slice(1); setParsed({ type: 'query', @@ -94,28 +165,39 @@ export function SearchOverlay({ setEntries([]); setEntities([]); } + setSelectedIndex(0); }, [input]); React.useEffect(() => { - if (parsed?.type === 'quickAdder') { - const allAdders = window.unigraph.getState('registry/quickAdder').value; - allAdders[parsed?.key].adder(parsed?.value).then((res: any) => { - const [object, type] = res; - console.log(JSON.stringify(object)); - window.unigraph.getSchemas().then((schemas: any) => { - try { - const padded = buildUnigraphEntity(JSON.parse(JSON.stringify(object)), type, schemas); - setEntries([ -
{ ev.stopPropagation(); }}> - -
, - ]); - } catch (e) { - console.log(e); - } - }); - }); - } else if (parsed?.type === 'query') { + const displayCommands = groups.map((grp: any) => [ + { type: 'group', element: ({grp.title}) }, + ...commands.filter((el) => el.group === grp.key).map((el: any) => ( + { + type: 'command', + element: ( +
{ + el.onClick(ev, setInput, setClose); + }} + style={{ + cursor: 'pointer', + display: 'flex', + }} + > + {el.name} + {el.about} +
+ ), + } + )), + ]).flat(); + console.log(displayCommands); + displayCommands.filter((el) => el.type === 'command').forEach((el, index) => { Object.assign(el, { index }); }); + setFinalCommands(displayCommands); + }, [commands]); + + React.useEffect(() => { + if (parsed?.type === 'query') { setQuery(parseQuery(parsed?.value)); } else if (parsed?.type === 'command') { // list all commands @@ -143,47 +225,34 @@ export function SearchOverlay({ search(query); }, [query]); - return ( + const defaultEl = (
{ - const newContent = ev.target.value; - const caret = ev.target.selectionStart || 0; - const hasMatch = inlineTextSearch( - ev.target.value, - tf, - caret, - async (match: any, newName: string, newUid: string) => { - const newStr = `${newContent?.slice?.(0, match.index)}[[${newName}]]${newContent?.slice?.(match.index + match[0].length)}`; - setInput(newStr); - setAdderRefs([...adderRefs, { key: newName, value: newUid }]); - window.unigraph.getState('global/searchPopup').setValue({ show: false }); - }, - ); - if (!hasMatch) window.unigraph.getState('global/searchPopup').setValue({ show: false }); setInput(ev.target.value); }} - placeholder="Enter: + to create; ? to search; to execute command" - onKeyPress={async (ev) => { - if (ev.key === 'Enter' && parsed?.type === 'quickAdder' && window.unigraph.getState('registry/quickAdder').value[parsed?.key]) { - window.unigraph.getState('registry/quickAdder').value[parsed?.key]?.adder(JSON.parse(JSON.stringify(parsed?.value)), false, callback, adderRefs).then((uids: any[]) => { - if (callback && uids[0]) callback(uids[0]); - }); - setInput(''); - setClose(); - } + onKeyDown={(ev) => { + console.log(ev); + if (ev.key === 'ArrowDown') { + console.log(ev); + setSelectedIndex((idx) => idx + 1); + } else if (ev.key === 'ArrowUp') { + setSelectedIndex((idx) => idx - 1); + } else if (ev.key === 'Enter') { + console.log('Hi'); + (document.getElementById('omnibarItem_current')?.children[0] as any)?.click(); + } else return; + ev.preventDefault(); + ev.stopPropagation(); }} + placeholder="Enter: + to create; ? to search; to execute command" />
{summonerTooltip ? {summonerTooltip} : []} - {parsed?.type === 'quickAdder' ? ( - - {`Adding ${parsed?.key} (Enter to add)`} - - ) : []} {entries} {entities.length > 0 ? ( ) : []} - {entries.length + entities.length === 0 && parsed?.type !== 'command' ? : []} - {(parsed?.type === 'command' || parsed?.type === '') ? commands.map((el: any) => ( + {(parsed?.type === 'command' || parsed?.type === '') ? finalCommands.map((el) => (
{ - el.onClick(); - setInput(''); setClose(); - }} style={{ - display: 'flex', marginTop: '8px', marginBottom: '8px', cursor: 'pointer', + backgroundColor: el.index === selectedIndex ? 'whitesmoke' : '', + padding: '4px', + borderRadius: '8px', }} + id={`omnibarItem_${el.index === selectedIndex ? 'current' : ''}`} > - {el.name} - {el.about} + {el.element}
)) : []} - {parsed?.type === 'quickAdder' ? ( -
- {React.createElement(window.unigraph.getState('registry/quickAdder').value[parsed?.key].tooltip)} -
- ) : []}
); + return (input.startsWith?.('+') + && window.unigraph.getState('registry/quickAdder').value[input.substr(1, input.indexOf(' ') - 1)]) ? ( + + ) : defaultEl; } type OmnibarSummonerType = { diff --git a/packages/unigraph-dev-explorer/src/unigraph-react.tsx b/packages/unigraph-dev-explorer/src/unigraph-react.tsx index 7c9fc3dc..e7213e95 100644 --- a/packages/unigraph-dev-explorer/src/unigraph-react.tsx +++ b/packages/unigraph-dev-explorer/src/unigraph-react.tsx @@ -98,7 +98,15 @@ export const registerDetailedDynamicViews = (views: Record): void = export const registerQuickAdder = (adders: Record): void => { const state = (window as any).unigraph.getState('registry/quickAdder'); - state.setValue({ ...state.value, ...adders }); + state.setValue({ + ...state.value, + ...adders, + ...Object.fromEntries( + Object.values(adders).map( + (el: any) => (el.alias || []).map((alias: string) => [alias, el]), + ).flat(), + ), + }); }; export const registerContextMenuItems = (schema: string, items: any[]): void => {