From eb6e7bbcd51e52c261b22f8e5aa3eb9a3b02f636 Mon Sep 17 00:00:00 2001 From: Martin Muzikar Date: Sat, 15 Feb 2020 18:48:54 +0100 Subject: [PATCH] Suggestion providers added --- ui/src/components/CompletePrompt.tsx | 37 +++++++----- ui/src/components/TerminalInput.tsx | 90 +++++++++++++++------------- ui/src/interop/SuggestionManager.ts | 22 ------- ui/src/interop/stepManager.ts | 30 ++++++++++ 4 files changed, 102 insertions(+), 77 deletions(-) delete mode 100644 ui/src/interop/SuggestionManager.ts diff --git a/ui/src/components/CompletePrompt.tsx b/ui/src/components/CompletePrompt.tsx index ecb2ba1..deb4e04 100644 --- a/ui/src/components/CompletePrompt.tsx +++ b/ui/src/components/CompletePrompt.tsx @@ -5,13 +5,15 @@ import { keyDispatcher } from "./TerminalInput"; type Props = { value: string, show: boolean, - getData: () => Promise, + keys?: {name: string, weight: number}[] + getData: () => Promise | undefined, toggleSending: (arg0:boolean) => void, fillIn: (arg0:T) => void, render: (arg0:T) => JSX.Element }; type State = { data: T[], + loading: boolean, currentIndex: number }; @@ -24,17 +26,10 @@ export class CompletePrompt extends Component, State> { distance: 100, maxPatternLength: 32, minMatchCharLength: 1, - keys: [ - { - name: "pattern", - weight: 0.7 - }, { - name: "docs", - weight: 0.3 - } - ] + keys: this.props.keys } state = { + loading: false, data: [], currentIndex: -1 } @@ -73,28 +68,42 @@ export class CompletePrompt extends Component, State> { } this.props.toggleSending(outOfRange()); }) + this.updateValues(); } - componentDidUpdate(prevProps:Props){ - if (this.props.value !== prevProps.value){ - this.props.getData().then(val => { + updateValues(){ + const val = this.props.getData(); + if (val){ + this.setState({loading: true}); + val.then(val => { if (val){ const fuse = new Fuse(val, this.fuseOptions); const results = fuse.search(this.props.value); this.setState({ data: results as T[], - currentIndex: -1 + currentIndex: -1, + loading: false }); } }) } } + componentDidUpdate(prevProps:Props, prevState:State){ + if (this.props.value !== prevProps.value){ + console.debug("Prop value changed!"); + this.updateValues(); + } + } + render(){ const offset = (this.state.data.length * 20); if (!this.props.show){ return <>; } + if (this.state.loading){ + return
Loading...
+ } return
    {(this.state.data||[]).map((val:T, i) => setActive={(active) => { diff --git a/ui/src/components/TerminalInput.tsx b/ui/src/components/TerminalInput.tsx index 16ed221..3560107 100644 --- a/ui/src/components/TerminalInput.tsx +++ b/ui/src/components/TerminalInput.tsx @@ -57,29 +57,31 @@ export class TerminalInput extends Component<{}, State> { switch(input.key){ case "Enter": input.preventDefault(); - if (this.state.stepRef !== undefined && this.state.val.length > 0){ + if (input.shiftKey){ this.handleSubmit(document.getElementById("terminal-input")!); - //TODO: if user is pressing Enter repeatedly tell them they need to use Shift - } else if (input.shiftKey){ - this.handleSubmit(target); } + // if (this.state.stepRef !== undefined && this.state.val.length > 0){ + // this.handleSubmit(document.getElementById("terminal-input")!); + // //TODO: if user is pressing Enter repeatedly tell them they need to use Shift + // } else if (input.shiftKey){ + // this.handleSubmit(target); + // } break; case "Tab": - input.preventDefault(); - if (this.state.stepRef && this.state.stepRef !== undefined && this.getStepRef().args){ - let offset = this.state.val.length - this.getStepRef().pattern.length; - //TODO: ugh - let i = 0; - const args = this.getStepRef().args as Argument[]; - while(target.selectionStart! < args[i].start!){ - i++; - if (i > args.length){ - i = 0; - break; + if (this.state.parsedInput.length > 0){ + input.preventDefault(); + const active = document.activeElement; + if (active && active.classList.contains('arg')){ + const id = Number.parseInt(active.getAttribute('tabIndex')!); + const inputs = document.getElementsByClassName('arg'); + if (id + 1 >= inputs.length){ + (inputs.item(0)! as HTMLElement).focus(); + } else { + (inputs.item(id + 1)! as HTMLElement).focus(); } } } - break; + break; case "Escape": input.preventDefault(); this.setState({ @@ -88,21 +90,7 @@ export class TerminalInput extends Component<{}, State> { }); break; default: - if (this.state.stepRef){ - let i = 0; - let accum = 0; - const cursor = target.selectionStart; - for (let val of (this.state.parsedInput as string[])){ - accum += val.length; - console.debug(`cursor: ${cursor} accum: ${accum} for val ${val}`); - if (cursor! <= accum){ - break; - } - i++; - } - target.selectionStart = cursor; - console.log(`index: ${i} cursor: ${cursor}`); - } + break; } keyDispatcher.dispatch(input); @@ -143,21 +131,32 @@ export class TerminalInput extends Component<{}, State> { render(){ let val = this.state.parsedInput.length > 0 && false ? this.state.parsedInput.join("") : this.state.val; - let input = ; + let input = ; if (this.state.parsedInput.length > 0){ - input =
    + const focusedInput = document.activeElement as HTMLInputElement; + input = <> + value={focusedInput.value} + getData={() =>StepManager.get().getSuggestionForArg(this.state.stepRef!, focusedInput.tabIndex)} + fillIn={(val) => focusedInput.value = val.val} + render={(arg) => {arg.val}} + show={focusedInput !== undefined} + toggleSending={(arg) => this.setState({canSend: arg})} + keys={[{ + name: "val", + weight: 1 + }]} + /> + { this.state.parsedInput.map((val, i) => - typeof(val) === "string" ? {val} : { - if (event.key === "Enter"){ - this.handleSubmit(document.getElementById("terminal-input") as HTMLInputElement); - } - }} key={`var_${val}`} className='arg' id={`arg-${i}`} - autoFocus={val === 0} - /> + typeof(val) === "string" ? + {val} : + this.forceUpdate()} + /> ) } -
    ; + ; } return
    value={this.state.val} @@ -166,6 +165,15 @@ export class TerminalInput extends Component<{}, State> { fillIn={this.onSetStep} getData={() => StepManager.get().getSteps()} render={(step) => {step.pattern}} + keys={[ + { + name: "pattern", + weight: 0.7 + }, { + name: "docs", + weight: 0.3 + } + ]} /> {input} diff --git a/ui/src/interop/SuggestionManager.ts b/ui/src/interop/SuggestionManager.ts deleted file mode 100644 index 00469ed..0000000 --- a/ui/src/interop/SuggestionManager.ts +++ /dev/null @@ -1,22 +0,0 @@ - -export type Suggestion = { - -} - -export class SuggestionManager { - - private static instance: SuggestionManager; - - public static get(){ - if (!this.instance){ - this.instance = new SuggestionManager(); - } - return this.instance; - } - - public getSuggestions(suggProvider:string){ - - } - -} - diff --git a/ui/src/interop/stepManager.ts b/ui/src/interop/stepManager.ts index a75e259..d2da15f 100644 --- a/ui/src/interop/stepManager.ts +++ b/ui/src/interop/stepManager.ts @@ -54,6 +54,36 @@ export class StepManager { }) } + getSuggestionForArg(step:Step, i:number, stepArgs:string[] = []):Promise<{val: string}[]> | undefined{ + if (step.args){ + const args = step.args; + + const arg = args[i]; + if (arg.suggProvider !== ""){ + return new Promise((resolve) => { + if (i < 0 || i >= args.length){ + resolve([]); + } + else { + fetch(`${AppConfig.getServerUrl()}/suggestion`, { + method: "POST", + body: JSON.stringify({ + step: step.pattern, + args: stepArgs, + argId: i + }) + }).then((r => r.json())).then((suggs:string[]) => { + console.log(suggs.map((v) => ({val: v}))); + resolve(suggs.map((v) => ({val: v}))) + }) + } + }); + } + } else { + return undefined; + } + } + fetchSteps(callback:(value?:Step[]) => void | undefined){ fetch(`${AppConfig.getServerUrl()}/liststeps`).then((r) => r.json()).then((steps:Step[]) => { this.stepRepo = this.analyzeParams(steps);