Compare commits

..

2 commits

Author SHA1 Message Date
7b6d18bc6a forgot file 2025-05-31 11:11:39 +02:00
7fafa35b4b small usability changes 2025-05-28 12:51:37 +02:00
6 changed files with 55 additions and 46 deletions

View file

@ -1,17 +1,15 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { extendedEnv } from './environment';
import { GlobalContext } from '../../context/GlobalContext'; import { GlobalContext } from '../../context/GlobalContext';
import { deepEvalExpr } from '../../eval/deep_eval';
import { inferType, scoreTypeInfo } from '../../eval/infer_type'; import { inferType, scoreTypeInfo } from '../../eval/infer_type';
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock'; import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
import { TypeInfoBlock } from '../other/Type';
import { Value } from '../other/Value';
import { actionShortcuts } from './actions'; import { actionShortcuts } from './actions';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations"; import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations";
import { extendedEnv } from './environment';
import './App.css'; import './App.css';
import { evalExpr } from '../../eval/eval';
import { Value } from '../other/Value';
import { Type, TypeInfoBlock } from '../other/Type';
import { deepEvalExpr } from '../../eval/deep_eval';
const examples: [string, ExprBlockState][] = [ const examples: [string, ExprBlockState][] = [
["empty editor" , initialEditorState ], ["empty editor" , initialEditorState ],
@ -140,10 +138,11 @@ export function App() {
// static evalution // static evalution
const typeInfo = useMemo(() => inferType(currentState, extendedEnv), [currentState]); const typeInfo = useMemo(() => inferType(currentState, extendedEnv), [currentState]);
// dynamic evalutions
const evalResult = evalExpr(currentState, extendedEnv); // dynamic evalution
const deepEvalResult = deepEvalExpr(currentState, extendedEnv); const deepEvalResult = deepEvalExpr(currentState, extendedEnv);
console.log({deepEvalResult});
const err = typeInfo.err || deepEvalResult.err;
const onCopy = () => { const onCopy = () => {
const serialized = JSON.stringify(currentState); const serialized = JSON.stringify(currentState);
@ -216,7 +215,7 @@ export function App() {
/> />
= =
<Value dynamic={{ <Value dynamic={{
i: evalResult.val, i: err ? undefined : deepEvalResult.val,
t: typeInfo.type, t: typeInfo.type,
}}/> }}/>
:: ::

View file

@ -41,6 +41,7 @@ function nestedInputProperties({state, setState, score, typeInfo, evalResult}: C
setState(state => ({...state, input: callback(state.input)})); setState(state => ({...state, input: callback(state.input)}));
}; };
const onInputCancel = () => { const onInputCancel = () => {
setState(state => /*addFocusRightMost*/(state.fn)); // we become our function setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
}; };
const scoreInput = (inputSuggestion: ExprBlockState) => { const scoreInput = (inputSuggestion: ExprBlockState) => {
@ -52,7 +53,9 @@ function nestedInputProperties({state, setState, score, typeInfo, evalResult}: C
export function CallBlock(props: CallBlockProps) { export function CallBlock(props: CallBlockProps) {
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const addParam = getActions(globalContext, props.setState).c; const addParam = getActions(globalContext, props.setState).c;
return <span className={"functionBlock"}> const err = props.typeInfo.err || props.evalResult.err;
return <span className="functionBlock dropdownContainer">
<CallContext value={{addParam}}> <CallContext value={{addParam}}>
<FunctionHeader {...props} addParam={addParam} /> <FunctionHeader {...props} addParam={addParam} />
<div className="functionParams"> <div className="functionParams">
@ -66,6 +69,12 @@ export function CallBlock(props: CallBlockProps) {
</div> </div>
</div> </div>
</CallContext> </CallContext>
{(err !== undefined) &&
<span className="">
<div className="errorMessage">
{err.message.trim()}
</div>
</span>}
</span>; </span>;
} }

View file

@ -14,7 +14,7 @@
.errorMessage { .errorMessage {
color: darkred; color: darkred;
background-color: pink; background-color: pink;
margin-top: 4px; /* margin-top: 4px; */
z-index: 10; z-index: 10;
font-family: var(--my-monospace-font); font-family: var(--my-monospace-font);
white-space-collapse: preserve; white-space-collapse: preserve;

View file

@ -84,7 +84,7 @@ const computeSuggestions = (
return result; return result;
} }
export function InputBlock({ state, setState, score, onCancel, typeInfo }: InputBlockProps) { export function InputBlock({ state, setState, score, onCancel, typeInfo, evalResult }: InputBlockProps) {
const {text, focus} = state; const {text, focus} = state;
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const env = typeInfo.env; const env = typeInfo.env;
@ -144,7 +144,7 @@ export function InputBlock({ state, setState, score, onCancel, typeInfo }: Input
}, },
}; };
const err = typeInfo.err || evalExpr(state, env).err; const err = typeInfo.err || evalResult.err;
return <> return <>
<Input <Input

View file

@ -1,6 +1,7 @@
import { useEffect, useRef, type ReactNode, type KeyboardEvent, useState } from "react"; import { useEffect, useRef, type ReactNode, type KeyboardEvent, useState } from "react";
import "./Input.css"; import "./Input.css";
import { focusPrevElement, focusNextElement, setRightMostCaretPosition } from "../../util/dom_trickery";
interface InputProps { interface InputProps {
placeholder: string; placeholder: string;
@ -25,38 +26,6 @@ function getCaretPosition(ref: React.RefObject<HTMLInputElement| null>): number
return ref.current?.selectionStart || 0; return ref.current?.selectionStart || 0;
} }
// Move caret all the way to the right in the currently focused element
function setRightMostCaretPosition(elem) {
const range = document.createRange();
range.selectNode(elem);
if (elem.lastChild) { // if no text is entered, there is no lastChild
range.setStart(elem.lastChild, elem.textContent.length);
range.setEnd(elem.lastChild, elem.textContent.length);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
}
function focusNextElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const nextElem = editable[index+1];
if (nextElem) {
nextElem.focus();
}
}
function focusPrevElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const prevElem = editable[index-1]
if (prevElem) {
prevElem.focus();
setRightMostCaretPosition(prevElem);
}
}
export function Input({placeholder, text, suggestion, onTextChange, onEnter, onCancel, extraHandlers, children}: InputProps) { export function Input({placeholder, text, suggestion, onTextChange, onEnter, onCancel, extraHandlers, children}: InputProps) {
const ref = useRef<HTMLInputElement>(null); const ref = useRef<HTMLInputElement>(null);
const [focus, setFocus] = useState<"yes"|"hide"|"no">("no"); const [focus, setFocus] = useState<"yes"|"hide"|"no">("no");

32
src/util/dom_trickery.ts Normal file
View file

@ -0,0 +1,32 @@
// Move caret all the way to the right in the currently focused element
export function setRightMostCaretPosition(elem) {
const range = document.createRange();
range.selectNode(elem);
if (elem.lastChild) { // if no text is entered, there is no lastChild
range.setStart(elem.lastChild, elem.textContent.length);
range.setEnd(elem.lastChild, elem.textContent.length);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
}
export function focusNextElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const nextElem = editable[index + 1];
if (nextElem) {
nextElem.focus();
}
}
export function focusPrevElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const prevElem = editable[index - 1];
if (prevElem) {
prevElem.focus();
setRightMostCaretPosition(prevElem);
}
}