a bit more progress

This commit is contained in:
Joeri Exelmans 2025-05-12 00:02:14 +02:00
parent a9ae4f9888
commit 2b0d8bc2c6
8 changed files with 109 additions and 28 deletions

View file

@ -5,7 +5,7 @@
} }
.functionName { .functionName {
text-align: center; /* text-align: center; */
} }
@ -20,7 +20,7 @@
} }
.inputParam { .inputParam {
height: 20px; /* height: 20px; */
margin-right: 20px; margin-right: 20px;
display: inline-block; display: inline-block;
background-color: rgba(242, 253, 146); background-color: rgba(242, 253, 146);

View file

@ -22,7 +22,7 @@ interface CallBlockProps extends State2Props<CallBlockState> {
} }
export function CallBlock({ state: {kind, env, fn, input, resolved, rollback }, setState, onResolve }: CallBlockProps) { export function CallBlock({ state: {kind, env, fn, input, resolved, rollback }, setState, onResolve }: CallBlockProps) {
const setResolved = (resolved: Dynamic) => { const setResolved = (resolved?: Dynamic) => {
setState({kind, env, fn, input, resolved, rollback}); setState({kind, env, fn, input, resolved, rollback});
} }
@ -53,7 +53,11 @@ export function CallBlock({ state: {kind, env, fn, input, resolved, rollback },
makeTheCall(input, fnState); makeTheCall(input, fnState);
} }
else { else {
setFn(fnState); // setFn(fnState);
setResolved(undefined);
onResolve({
kind, env, fn: fnState, input, resolved: undefined, rollback
});
} }
} }
const onInputResolve = (inputState) => { const onInputResolve = (inputState) => {

View file

@ -7,7 +7,7 @@ import { useEffect, useState } from "react";
import { Type } from "./Type"; import { Type } from "./Type";
import "./Editor.css" import "./Editor.css"
import { focusNextElement } from "./util/dom_trickery"; import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
interface LetInBlockState { interface LetInBlockState {
kind: "let"; kind: "let";
@ -49,22 +49,43 @@ interface EditorProps extends State2Props<EditorState> {
const dontFilter = () => true; const dontFilter = () => true;
function getCommands(type) {
const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'Tab'];
if (getSymbol(type) === symbolFunction) {
commands.push('c');
}
return commands;
}
export function Editor({state, setState, onResolve, onCancel}: EditorProps) { export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
const [proxyState, setProxyState] = useState<'unresolved'|'command'|'resolved'>('unresolved'); const [needCommand, setNeedCommand] = useState(false);
const onMyResolve = (editorState: EditorState) => { const onMyResolve = (editorState: EditorState) => {
setState(editorState); setState(editorState);
if (editorState.resolved) { if (editorState.resolved) {
setProxyState('command'); setNeedCommand(true);
} }
else { else {
setProxyState('unresolved'); // unresolved
setNeedCommand(false);
onResolve(editorState); // pass up the fact that we're unresolved
} }
} }
// const onMyCancel // const onMyCancel
const onCommand = (e: React.KeyboardEvent) => { const onCommand = (e: React.KeyboardEvent) => {
const type = getType(state.resolved); const type = getType(state.resolved);
if (e.key === "c" && getSymbol(type) === symbolFunction) { const commands = getCommands(type);
if (!commands.includes(e.key)) {
return;
}
e.preventDefault(); e.preventDefault();
setNeedCommand(false);
// u -> pass Up
if (e.key === "u" || e.key === "Enter" || e.key === "Tab") {
onResolve(state);
return;
}
// c -> Call
if (e.key === "c") {
// we become CallBlock // we become CallBlock
setState({ setState({
kind: "call", kind: "call",
@ -74,11 +95,24 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
resolved: undefined, resolved: undefined,
rollback: state, rollback: state,
}); });
setProxyState('resolved'); return;
} }
if (e.key === "u" || e.key === "Enter") { // t -> Transform
setProxyState('resolved'); if (e.key === "t") {
onResolve(state); // we become CallBlock
setState({
kind: "call",
env: state.env,
fn: initialEditorState,
input: state,
resolved: undefined,
rollback: state,
});
return;
}
if (e.key === "Backspace" || e.key === "ArrowLeft") {
focusPrevElement();
return;
} }
}; };
@ -100,7 +134,7 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
(state.resolved) (state.resolved)
? <div className="typeSignature"> ? <div className="typeSignature">
:: <Type type={getType(state.resolved)} /> :: <Type type={getType(state.resolved)} />
{ (proxyState === 'command') { (needCommand)
? <input autoFocus={true} className="editable command" placeholder="<enter command>" onKeyDown={onCommand} value=""/> ? <input autoFocus={true} className="editable command" placeholder="<enter command>" onKeyDown={onCommand} value=""/>
: <></> : <></>
} }

View file

@ -15,6 +15,10 @@
} }
.suggestions { .suggestions {
display: none;
}
.suggestions {
display: block;
text-align: left; text-align: left;
position: absolute; position: absolute;
border: solid 1px dodgerblue; border: solid 1px dodgerblue;

View file

@ -8,6 +8,7 @@ import { Type } from "./Type";
import "./InputBlock.css"; import "./InputBlock.css";
import type { Dynamic, State2Props } from "./util/extra"; import type { Dynamic, State2Props } from "./util/extra";
import type { EditorState } from "./Editor"; import type { EditorState } from "./Editor";
import { ShowIf } from "./ShowIf";
export interface InputBlockState { export interface InputBlockState {
kind: "input"; kind: "input";
@ -27,9 +28,6 @@ export function InputBlock({ state: {kind, env, text, resolved, rollback}, setSt
const ref = useRef<any>(null); const ref = useRef<any>(null);
useEffect(() => { useEffect(() => {
ref.current?.focus(); ref.current?.focus();
if (ref.current) {
// ref.current.textContent = text;
}
}, []); }, []);
const [i, setI] = useState(0); // selected suggestion const [i, setI] = useState(0); // selected suggestion
@ -111,6 +109,10 @@ export function InputBlock({ state: {kind, env, text, resolved, rollback}, setSt
setRightMostCaretPosition(ref.current); setRightMostCaretPosition(ref.current);
e.preventDefault(); e.preventDefault();
} }
else {
onSelectSuggestion(suggestions[i]);
e.preventDefault();
}
} }
}, },
ArrowDown: () => { ArrowDown: () => {
@ -149,21 +151,38 @@ export function InputBlock({ state: {kind, env, text, resolved, rollback}, setSt
return <span> return <span>
<span className=""> <span className="">
<input ref={ref} placeholder="start typing..." autoFocus={true} className="editable" value={text} onInput={onInput} onKeyDown={onKeyDown} onFocus={() => setHaveFocus(true)} onBlur={() => setHaveFocus(false)}/> <input ref={ref} placeholder="start typing..." className="editable" value={text} onInput={onInput} onKeyDown={onKeyDown} onFocus={() => setHaveFocus(true)} onBlur={() => setTimeout(() => setHaveFocus(false), 200)}/>
<span className="text-block suggest">{singleSuggestion}</span> <span className="text-block suggest">{singleSuggestion}</span>
</span> </span>
{ <ShowIf cond={haveFocus}>
(haveFocus) <Suggestions
? <Suggestions suggestions={suggestions} onSelect={onSelectSuggestion} i={i} setI={setI} /> suggestions={suggestions}
: <></> onSelect={onSelectSuggestion}
} i={i} setI={setI} />
</ShowIf>
</span>; </span>;
} }
function Suggestions({ suggestions, onSelect, i, setI }) { function Suggestions({ suggestions, onSelect, i, setI }) {
const onMouseEnter = j => () => {
setI(j);
};
const onMouseDown = j => () => {
setI(j);
onSelect(suggestions[i]);
};
return (suggestions.length > 0) ? return (suggestions.length > 0) ?
<div className="suggestions"> <div className={"suggestions"}>
{suggestions.map(([name, dynamic], j) => <div key={`${j}_${name}`} className={i === j ? "selected" : ""} onClick={() => onSelect(suggestions[i])}>{name} :: <Type type={getType(dynamic)} /></div>)} {suggestions.map(([name, dynamic], j) =>
<div
key={`${j}_${name}`}
className={i === j ? "selected" : ""}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
{name} :: <Type type={getType(dynamic)} />
</div>)
}
</div> </div>
: <></>; : <></>;
} }

9
src/ShowIf.tsx Normal file
View file

@ -0,0 +1,9 @@
// syntactic sugar
export function ShowIf({cond, children}) {
if (cond) {
return <>{children}</>;
}
else {
return <></>;
}
}

View file

@ -1,4 +1,4 @@
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match} from "dope2"; import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight} from "dope2";
import "./Value.css"; import "./Value.css";
@ -20,6 +20,9 @@ export function Value({dynamic}) {
// return <BinaryType type={type} cssClass="productType" infix="&#10799;" prefix="" suffix=""/>; // return <BinaryType type={type} cssClass="productType" infix="&#10799;" prefix="" suffix=""/>;
case symbolSum: case symbolSum:
return <ValueSum val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>; return <ValueSum val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
case symbolProduct:
return <ValueProduct val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
// case symbolDict: // case symbolDict:
// return <BinaryType type={type} cssClass="dictType" infix="&rArr;" prefix="{" suffix="}"/>; // return <BinaryType type={type} cssClass="dictType" infix="&rArr;" prefix="{" suffix="}"/>;
// case symbolSet: // case symbolSet:
@ -52,3 +55,6 @@ function ValueSum({val, leftType, rightType}) {
(l => <>L <Value dynamic={{i:l, t:leftType}}/></>) (l => <>L <Value dynamic={{i:l, t:leftType}}/></>)
(r => <>R <Value dynamic={{i:r, t:rightType}}/></>); (r => <>R <Value dynamic={{i:r, t:rightType}}/></>);
} }
function ValueProduct({val, leftType, rightType}) {
return <>(<Value dynamic={{i:getLeft(val), t:leftType}}/>,&nbsp;<Value dynamic={{i:getRight(val), t:rightType}} />)</>;
}

View file

@ -11,3 +11,8 @@ body {
"wdth" 100; "wdth" 100;
} }
:focus-within:not(body) {
/* outline: 2px solid black; */
/* background-color: aqua; */
}