a bit more progress
This commit is contained in:
parent
a9ae4f9888
commit
2b0d8bc2c6
8 changed files with 109 additions and 28 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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=""/>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
9
src/ShowIf.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// syntactic sugar
|
||||||
|
export function ShowIf({cond, children}) {
|
||||||
|
if (cond) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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="⨯" prefix="" suffix=""/>;
|
// return <BinaryType type={type} cssClass="productType" infix="⨯" 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="⇒" prefix="{" suffix="}"/>;
|
// return <BinaryType type={type} cssClass="dictType" infix="⇒" 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}}/>, <Value dynamic={{i:getRight(val), t:rightType}} />)</>;
|
||||||
|
}
|
||||||
|
|
@ -11,3 +11,8 @@ body {
|
||||||
"wdth" 100;
|
"wdth" 100;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:focus-within:not(body) {
|
||||||
|
/* outline: 2px solid black; */
|
||||||
|
/* background-color: aqua; */
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue