From 95eb8aef847cfa9dd5b8e8df2a9b8035696d3c55 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 12 May 2025 23:40:58 +0200 Subject: [PATCH] better-looking parameters --- src/App.tsx | 98 +++++----------------------------------- src/CallBlock.css | 50 +++++++++++++-------- src/CallBlock.tsx | 96 ++++++++++++++++++++------------------- src/Editor.css | 1 - src/Editor.tsx | 85 +++++++++++++++++++++-------------- src/InputBlock.tsx | 80 ++++++++++++++++----------------- src/LambdaBlock.tsx | 11 +++++ src/Value.tsx | 6 +-- src/configurations.ts | 102 ++++++++++++++++++++++++++++++++++++++++++ todo.txt | 6 +++ 10 files changed, 306 insertions(+), 229 deletions(-) create mode 100644 src/LambdaBlock.tsx create mode 100644 src/configurations.ts create mode 100644 todo.txt diff --git a/src/App.tsx b/src/App.tsx index f53f69e..9dddaec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,94 +1,16 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import './App.css' -import { Editor, initialEditorState, type EditorState } from './Editor' -import { trie, apply, Int } from "dope2"; - -const listPush = trie.get(initialEditorState.env.name2dyn)("list.push"); -const listEmptyList = trie.get(initialEditorState.env.name2dyn)("list.emptyList"); -const fourtyTwo = {i: 42n, t: Int}; - -const nonEmptyEditorState: EditorState = { - kind: "call", - env: initialEditorState.env, - fn: { - kind: "call", - env: initialEditorState.env, - fn: { - kind: "input", - env: initialEditorState.env, - text: "list.push", - resolved: listPush, - }, - input: { - kind: "input", - env: initialEditorState.env, - text: "list.emptyList", - resolved: listEmptyList, - }, - resolved: apply(listEmptyList)(listPush), - }, - input: { - kind: "input", - env: initialEditorState.env, - text: "42", - resolved: fourtyTwo, - }, - resolved: apply(fourtyTwo)(apply(listEmptyList)(listPush)), -}; - -const functionWith3Params = trie.get(initialEditorState.env.name2dyn)("functionWith3Params"); -const fourtyThree = {i: 43n, t: Int}; -const fourtyFour = {i: 44n, t: Int}; - -const tripleFunctionCallEditorState: EditorState = { - kind: "call", - env: initialEditorState.env, - fn: { - kind: "call", - env: initialEditorState.env, - fn: { - kind: "call", - env: initialEditorState.env, - fn: { - kind: "input", - env: initialEditorState.env, - text: "functionWith3Params", - resolved: functionWith3Params, - }, - input: { - kind: "input", - env: initialEditorState.env, - text: "42", - resolved: fourtyTwo, - }, - resolved: apply(fourtyTwo)(functionWith3Params), - }, - input: { - kind: "input", - env: initialEditorState.env, - text: "43", - resolved: fourtyThree, - }, - resolved: apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params)), - }, - input: { - kind: "input", - env: initialEditorState.env, - text: "44", - resolved: fourtyFour, - }, - resolved: apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params))), -} +import { Editor, type EditorState } from './Editor' +import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; export function App() { - // const [history, setHistory] = useState([initialEditorState]); + const [history, setHistory] = useState([initialEditorState]); // const [history, setHistory] = useState([nonEmptyEditorState]); - const [history, setHistory] = useState([tripleFunctionCallEditorState]); + // const [history, setHistory] = useState([tripleFunctionCallEditorState]); const [future, setFuture] = useState([]); const pushHistory = (s: EditorState) => { - console.log('pushHistory'); setHistory(history.concat([s])); setFuture([]); }; @@ -118,10 +40,8 @@ export function App() { } }; - useEffect(() => { - window['APP_STATE'] = history; - // console.log("EDITOR STATE:", state); + window['APP_STATE'] = history; // useful for debugging }, [history]); useEffect(() => { @@ -139,8 +59,10 @@ export function App() { {console.log("toplevel resolved")}} - onCancel={() => {console.log("toplevel canceled")}} + onResolve={() => {}} + onCancel={() => {}} + filter={() => true} + focus={true} /> diff --git a/src/CallBlock.css b/src/CallBlock.css index 11d369d..5488178 100644 --- a/src/CallBlock.css +++ b/src/CallBlock.css @@ -1,7 +1,8 @@ .functionBlock { border: solid 1px darkgray; display: inline-block; - margin: 1px; + margin: 4px; + color: black; } .functionBlock.unifyError { @@ -18,37 +19,43 @@ /* background-color: pink; */ } - .inputParam:after { content: ""; position: absolute; - border: solid 10px transparent; - border-left-color: rgb(242, 253, 146); - margin-left: 0px; - /* z-index: 1; */ + border: solid transparent; + border-width: 10px; + right: -19px; + top: 0; + bottom:0; } .inputParam { - /* height: 20px; */ margin-right: 20px; - display: inline-block; + display: inline-flex; + vertical-align: middle; + position: relative; /* to ensure the :after (which is absolute) is relative to ourselves */ + flex-grow: 1; +} + +/* Count nested level AFTER .outputParam (resets the depth) */ +.outputParam > .inputParam:after { + border-left-color: rgb(242, 253, 146); +} +.outputParam > .inputParam { background-color: rgb(242, 253, 146); } - - -.inputParam .inputParam { +.outputParam > .inputParam > .inputParam { background-color: rgb(180, 248, 214); } -.inputParam .inputParam:after { +.outputParam > .inputParam > .inputParam:after { border-left-color: rgb(180, 248, 214); } -.inputParam .inputParam .inputParam { +.outputParam > .inputParam > .inputParam > .inputParam { background-color: rgb(153, 212, 214); } -.inputParam .inputParam .inputParam:after { +.outputParam > .inputParam > .inputParam > .inputParam:after { border-left-color: rgb(153, 212, 214); } - .typeAnnot { display: inline-block; vertical-align: top; @@ -67,14 +74,21 @@ width: 100%; } -.functionBlock.unifyError .inputParam { +.functionBlock.unifyError > .functionParams > .outputParam > .inputParam { background-color: darkred; color: white; } -.functionBlock.unifyError .inputParam:after { +.functionBlock.unifyError > .functionParams > .outputParam > .inputParam:after { border-left-color: darkred; } +.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam { + background-color: rgb(95, 4, 4); + color: white; +} +.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam:after { + border-left-color: rgb(95, 4, 4); +} -.functionBlock.unifyError .outputParam { +.functionBlock.unifyError > .functionParams > .outputParam { background-color: pink; } \ No newline at end of file diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index d2034a0..f93dced 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { apply, UnifyError } from "dope2"; +import { apply, UnifyError, assignFn, getType } from "dope2"; import { Editor, type EditorState } from "./Editor"; import { Value } from "./Value"; @@ -17,6 +17,7 @@ export interface CallBlockState< fn: FnState; input: InputState; resolved: undefined | Dynamic; + // focus: boolean; } interface CallBlockProps< @@ -26,25 +27,24 @@ interface CallBlockProps< onResolve: (resolved: EditorState) => void; } -function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState, onResolve}: CallBlockProps) { +function headlessCallBlock({state, setState, onResolve}: CallBlockProps) { const [unifyError, setUnifyError] = useState(undefined); + const {fn, input } = state; const setFn = (fn: EditorState) => { - setState({kind, env, fn, input, resolved}); + setState({...state, fn}); } const setInput = (input: EditorState) => { - setState({kind, env, fn, input, resolved}); + setState({...state, input}); } const setResolved = (resolved?: Dynamic) => { - setState({kind, env, fn, input, resolved}); + setState({...state, resolved}); } const makeTheCall = (input, fn) => { - console.log('makeTheCall...') try { const outputResolved = apply(input.resolved)(fn.resolved); setResolved(outputResolved); - console.log("onResolve callblock..") onResolve({ - kind, env, fn, input, resolved: outputResolved + ...state, resolved: outputResolved }); setUnifyError(undefined); } @@ -54,12 +54,11 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState, } setUnifyError(e); onResolve({ - kind, env, fn, input, resolved: undefined + ...state, resolved: undefined }) } }; const onFnResolve = (fnState) => { - console.log('my fn resolved') if (input.resolved) { makeTheCall(input, fnState); } @@ -67,20 +66,18 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState, // setFn(fnState); setResolved(undefined); onResolve({ - kind, env, fn: fnState, input, resolved: undefined + ...state, resolved: undefined }); } }; const onInputResolve = (inputState) => { - console.log('my input resolved') if (fn.resolved) { makeTheCall(inputState, fn); } else { - // setInput(inputState); setResolved(undefined); onResolve({ - kind, env, fn, input: inputState, resolved: undefined + ...state, resolved: undefined }); } }; @@ -90,24 +87,11 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState, const onInputCancel = () => { setState(fn); } - // const filterCompatibleInputs = ([_name, dynamic]: [string, Dynamic]) => { - // if (fn.resolved) { - // try { - // assignFn(getType(fn.resolved), getType(dynamic)); - // } catch (e) { - // if (!(e instanceof UnifyError)) { - // throw e; - // } - // return false; - // } - // } - // return true; - // } return {unifyError, setFn, setInput, onFnResolve, onInputResolve, onFnCancel, onInputCancel}; } export function CallBlock({ state, setState, onResolve }: CallBlockProps) { - const {unifyError, setFn, setInput, onFnResolve, onInputResolve, onFnCancel, onInputCancel} + const {unifyError, setFn, setInput, onFnResolve, onInputResolve, onInputCancel} = headlessCallBlock({ state, setState, onResolve }); return @@ -153,38 +138,59 @@ function FunctionHeader({ fn, setFn, onFnResolve }) { {/*todo*/}}/> + onCancel={() => {/*todo*/}} + filter={() => true} /> ; } } -function InputParams({ fn, setFn, input, setInput, onFnResolve, onInputResolve, onInputCancel }) { - const { - onInputResolve: onFnInputResolve, - onFnResolve : onFnFnResolve - } = headlessCallBlock({state: fn, setState: setFn, onResolve: onFnResolve}); +function InputParams({ fn, setFn, input, setInput, onFnResolve, onInputResolve, onInputCancel, focus }) { + const filterCompatibleInputs = ([_name, dynamic]: [string, Dynamic]) => { + if (fn.resolved) { + try { + assignFn(getType(fn.resolved), getType(dynamic)); + } catch (e) { + if (!(e instanceof UnifyError)) { + throw e; + } + return false; + } + } + return true; + } return
{(fn.kind === "call") && // if the function we're calling is itself the result of a function call, // then we render its input parameter nested in our own input parameter box, which is way more readable // Input(s) of the function we're calling: - setFn({...fn, fn: fnFn})} - input={fn.input} - setInput={fnInput => setFn({...fn, input: fnInput})} - onFnResolve={onFnFnResolve} - onInputResolve={onFnInputResolve} - onInputCancel={() => {/*todo*/}}/> + } - {/* Our own input */} + onCancel={onInputCancel} + filter={filterCompatibleInputs} + focus={focus} />
; -}; +} + +function NestedInputParams({fn, setFn, onFnResolve}) { + const { + onInputResolve: onFnInputResolve, + onFnResolve : onFnFnResolve, + } = headlessCallBlock({state: fn, setState: setFn, onResolve: onFnResolve}); + return setFn({...fn, fn: fnFn})} + input={fn.input} + setInput={fnInput => setFn({...fn, input: fnInput})} + onFnResolve={onFnFnResolve} + onInputResolve={onFnInputResolve} + onInputCancel={() => {/*todo*/}} + focus={false}/>; +} diff --git a/src/Editor.css b/src/Editor.css index 1e324d9..0d8b78b 100644 --- a/src/Editor.css +++ b/src/Editor.css @@ -1,6 +1,5 @@ .typeSignature { display: inline-block; - /* vertical-align:; */ } .command { diff --git a/src/Editor.tsx b/src/Editor.tsx index 2bb7f09..a2eb6f8 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -1,22 +1,16 @@ -import { getSymbol, getType, module2Env, ModuleStd, symbolFunction, getDefaultTypeParser } from "dope2"; +import { getSymbol, getType, symbolFunction } from "dope2"; -import { InputBlock, type InputBlockState } from "./InputBlock"; -import { type Dynamic, type State2Props } from "./util/extra"; +import { useEffect, useReducer, useRef, useState } from "react"; import { CallBlock, type CallBlockState } from "./CallBlock"; -import { useEffect, useState } from "react"; +import { InputBlock, type InputBlockState } from "./InputBlock"; import { Type } from "./Type"; +import { type Dynamic, type State2Props } from "./util/extra"; -import "./Editor.css" -import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; +import "./Editor.css"; import type { LetInBlockState } from "./LetInBlock"; - -interface LambdaBlockState { - kind: "lambda"; - env: any; - paramName: string; - expr: EditorState; - resolved: undefined | Dynamic; -} +import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; +import type { LambdaBlockState } from "./LambdaBlock"; +import { initialEditorState } from "./configurations"; export type EditorState = InputBlockState @@ -24,33 +18,42 @@ export type EditorState = | LetInBlockState | LambdaBlockState; -const mkType = getDefaultTypeParser(); -export const initialEditorState: EditorState = { - kind: "input", - env: module2Env(ModuleStd.concat([ - ["functionWith3Params", {i: i=>j=>k=>i+j+k, t: mkType("Int->Int->Int->Int")}], - ])), - text: "", - resolved: undefined, -}; - interface EditorProps extends State2Props { + focus: boolean; + filter: (suggestion: [string, Dynamic]) => boolean; onResolve: (state: EditorState) => void; onCancel: () => void; } -const dontFilter = () => true; - function getCommands(type) { - const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'Tab', 'l', '=']; + const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=']; if (getSymbol(type) === symbolFunction) { commands.push('c'); } return commands; } -export function Editor({state, setState, onResolve, onCancel}: EditorProps) { +function removeFocus(state: EditorState): EditorState { + if (state.kind === "input") { + return {...state, focus: false}; + } + if (state.kind === "call") { + return {...state, + fn: removeFocus(state.fn), + input: removeFocus(state.input), + }; + } + return state; +} + +export function Editor({state, setState, onResolve, onCancel, filter, focus}: EditorProps) { const [needCommand, setNeedCommand] = useState(false); + const commandInputRef = useRef(null); + useEffect(() => { + if (needCommand) { + commandInputRef.current?.focus(); + } + }, [needCommand]); const onMyResolve = (editorState: EditorState) => { setState(editorState); if (editorState.resolved) { @@ -82,10 +85,11 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) { setState({ kind: "call", env: state.env, - fn: state, + fn: removeFocus(state), input: initialEditorState, resolved: undefined, }); + // focusNextElement(); return; } // t -> Transform @@ -95,7 +99,7 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) { kind: "call", env: state.env, fn: initialEditorState, - input: state, + input: removeFocus(state), resolved: undefined, }); return; @@ -104,6 +108,10 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) { focusPrevElement(); return; } + if (e.key === "ArrowRight") { + focusNextElement(); + return; + } // l -> Let ... in ... // = -> assign to name if (e.key === 'l' || e.key === '=') { @@ -123,9 +131,17 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) { const renderBlock = () => { switch (state.kind) { case "input": - return ; + return ; case "call": - return ; + return ; case "let": return <>; case "lambda": @@ -140,12 +156,13 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) { :: { (needCommand) ? + value={""} + onChange={() => {}} /> /* gets rid of React warning */ : <> } diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index f1afa5d..fea4c4c 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -1,5 +1,5 @@ import { Double, getType, Int, newDynamic, trie } from "dope2"; -import { focusNextElement, focusPrevElement, getCaretPosition, setRightMostCaretPosition } from "./util/dom_trickery"; +import { focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery"; import { parseDouble, parseInt } from "./util/parse"; import { useEffect, useRef, useState } from "react"; @@ -7,13 +7,13 @@ import { Type } from "./Type"; import "./InputBlock.css"; import type { Dynamic, State2Props } from "./util/extra"; -import type { EditorState } from "./Editor"; export interface InputBlockState { kind: "input"; env: any; text: string; resolved: undefined | Dynamic; + focus: boolean } interface InputBlockProps extends State2Props { @@ -22,70 +22,73 @@ interface InputBlockProps extends State2Props { onCancel: () => void; } -export function InputBlock({ state: {kind, env, text, resolved}, setState, filter, onResolve, onCancel }: InputBlockProps) { - const ref = useRef(null); - useEffect(() => { - ref.current?.focus(); - }, []); - - const [i, setI] = useState(0); // selected suggestion - const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not - - const setText = (text: string) => { - setState({kind, env, text, resolved}); - } - const setResolved = (resolved: Dynamic) => { - setState({kind, env, text, resolved}); - } - - const singleSuggestion = trie.growPrefix(env.name2dyn)(text); - +const computeSuggestions = (text, env, filter) => { const asDouble = parseDouble(text); const asInt = parseInt(text); - const suggestions = [ + const ls = [ ... (asDouble ? [[asDouble.toString(), newDynamic(asDouble)(Double)]] : []), ... (asInt ? [[asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []), + ... trie.suggest(env.name2dyn)(text)(10), + ] + return [ + ...ls.filter(filter), // ones that match filter come first + ...ls.filter(s => !filter(s)), + ]; +} - ... (text !== '') ? trie.suggest(env.name2dyn)(text)(Infinity) : [], - ].filter(filter); +export function InputBlock({ state, setState, filter, onResolve, onCancel }: InputBlockProps) { + const {env, text, resolved, focus} = state; + const inputRef = useRef(null); + const [i, setI] = useState(0); // selected suggestion idx + const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not + + const setText = (text: string) => { + setState({...state, text}); + } + + const singleSuggestion = trie.growPrefix(env.name2dyn)(text); + const suggestions = computeSuggestions(text, env, filter); useEffect(() => { setI(0); // reset - if (ref.current) { - ref.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`; + if (inputRef.current) { + inputRef.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`; } }, [text]); + useEffect(() => { + if (focus) { + inputRef.current?.focus(); + } + }, [focus]); + const onSelectSuggestion = ([name, dynamic]) => { - // setText(name); - // ref.current.textContent = name; - // setRightMostCaretPosition(ref.current); - // setI(0); - // setResolved(dynamic); - console.log("onResolve inputblock..") onResolve({ kind: "input", env, text: name, resolved: dynamic, + focus: false, }); }; const onInput = e => { setText(e.target.value); if (resolved) { + // un-resolve onResolve({ kind: "input", env, text: e.target.value, resolved: undefined, + focus: true, }); } }; const getCaretPosition = () => { - return ref.current.selectionStart; + return inputRef.current?.selectionStart || -1; } const onKeyDown = (e: React.KeyboardEvent) => { @@ -100,8 +103,7 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte if (singleSuggestion.length > 0) { const newText = text + singleSuggestion; setText(newText); - // ref.current.textContent = newText; - setRightMostCaretPosition(ref.current); + setRightMostCaretPosition(inputRef.current); e.preventDefault(); } else { @@ -156,7 +158,7 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
} {/* Input box */} - 0) ? + return <>{(suggestions.length > 0) &&
{suggestions.map(([name, dynamic], j) =>
{name} :: -
) - } +
)} - : <>; + }; } \ No newline at end of file diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx new file mode 100644 index 0000000..e255de8 --- /dev/null +++ b/src/LambdaBlock.tsx @@ -0,0 +1,11 @@ +import type { EditorState } from "./Editor"; +import type { Dynamic } from "./util/extra"; + + +export interface LambdaBlockState { + kind: "lambda"; + env: any; + paramName: string; + expr: EditorState; + resolved: undefined | Dynamic; +} diff --git a/src/Value.tsx b/src/Value.tsx index 8e05bed..89eb9f7 100644 --- a/src/Value.tsx +++ b/src/Value.tsx @@ -32,7 +32,7 @@ export function Value({dynamic}) { // case symbolSet: // return ; case symbolList: - return ; + return ; default: return <>don't know how to show value; @@ -51,8 +51,8 @@ function ValueFunction() { // function Sum({val, elemType}) { // return // } -function List({val, elemType}) { - return [{val.map((v, i) => )}]; +function ValueList({val, elemType}) { + return [{val.map((v, i) => )}]; } function ValueSum({val, leftType, rightType}) { return match(val) diff --git a/src/configurations.ts b/src/configurations.ts new file mode 100644 index 0000000..20bd600 --- /dev/null +++ b/src/configurations.ts @@ -0,0 +1,102 @@ +import { getDefaultTypeParser, module2Env, ModuleStd, trie, Int, apply } from "dope2"; +import type { EditorState } from "./Editor"; + +const mkType = getDefaultTypeParser(); +export const extendedEnv = module2Env(ModuleStd.concat([ + ["functionWith3Params", { i: i => j => k => i + j + k, t: mkType("Int->Int->Int->Int") }], +])); + +export const initialEditorState: EditorState = { + kind: "input", + env: extendedEnv, + text: "", + resolved: undefined, + focus: true, +}; + +const listPush = trie.get(initialEditorState.env.name2dyn)("list.push"); +const listEmptyList = trie.get(initialEditorState.env.name2dyn)("list.emptyList"); +const fourtyTwo = { i: 42n, t: Int }; + +export const nonEmptyEditorState: EditorState = { + kind: "call", + env: initialEditorState.env, + fn: { + kind: "call", + env: initialEditorState.env, + fn: { + kind: "input", + env: initialEditorState.env, + text: "list.push", + resolved: listPush, + focus: false, + }, + input: { + kind: "input", + env: initialEditorState.env, + text: "list.emptyList", + resolved: listEmptyList, + focus: false, + }, + resolved: apply(listEmptyList)(listPush), + // focus: , + }, + input: { + kind: "input", + env: initialEditorState.env, + text: "42", + resolved: fourtyTwo, + focus: false, + }, + // resolved: apply(fourtyTwo)(apply(listEmptyList)(listPush)), + resolved: undefined, +}; + +const functionWith3Params = trie.get(initialEditorState.env.name2dyn)("functionWith3Params"); +const fourtyThree = { i: 43n, t: Int }; +const fourtyFour = { i: 44n, t: Int }; + +export const tripleFunctionCallEditorState: EditorState = { + kind: "call", + env: initialEditorState.env, + fn: { + kind: "call", + env: initialEditorState.env, + fn: { + kind: "call", + env: initialEditorState.env, + fn: { + kind: "input", + env: initialEditorState.env, + text: "functionWith3Params", + resolved: functionWith3Params, + focus: false, + }, + input: { + kind: "input", + env: initialEditorState.env, + text: "42", + resolved: fourtyTwo, + focus: false, + }, + resolved: apply(fourtyTwo)(functionWith3Params), + }, + input: { + kind: "input", + env: initialEditorState.env, + text: "43", + resolved: fourtyThree, + focus: false, + }, + resolved: apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params)), + }, + input: { + kind: "input", + env: initialEditorState.env, + text: "44", + resolved: fourtyFour, + focus: false, + }, + resolved: apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params))), +}; + diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..db4ee51 --- /dev/null +++ b/todo.txt @@ -0,0 +1,6 @@ +- [DONE] figure out autofocus of text inputs to make app more usable + - maybe in a small example, see if focus can be controlled by react state? + +- add 'let ... in...' block + +- draw flow of events on a piece of paper to see if things can be simplified