import { useEffect, useState } from 'react'; import './App.css'; import { CommandContext } from './CommandContext'; import { Editor, type EditorState } from './Editor'; import { extendedEnv } from './EnvContext'; import { biggerExample, initialEditorState, lambda2Params, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; import { evalEditorBlock } from "./eval"; const commands: [string, string[], string][] = [ ["call" , ['c' ], "call" ], ["eval" , ['e','Tab','Enter'], "eval" ], ["transform", ['t', '.' ], "transform" ], ["let" , ['l', '=' ], "let … in …" ], ["lambda" , ['a' ], "λx: …" ], ]; const examples: [string, EditorState][] = [ ["empty editor", initialEditorState], ["push to list", nonEmptyEditorState], ["function w/ 4 params", tripleFunctionCallEditorState], ["bigger example", biggerExample], ["lambda 2 params", lambda2Params], ]; type AppState = { history: EditorState[], future: EditorState[], } const defaultState = { history: [initialEditorState], future: [], }; function loadFromLocalStorage(): AppState { if (localStorage["appState"]) { try { const appState = JSON.parse(localStorage["appState"]); // may throw // if our state is corrupt, discover it eagerly: evalEditorBlock(appState.history.at(-1), extendedEnv); return appState; // all good } catch (e) { console.log('error recovering state from localStorage (resetting):', e); } } return defaultState; } export function App() { // load from localStorage const [appState, setAppState] = useState(loadFromLocalStorage()); const [syntacticSugar, setSyntacticSugar] = useState(true); useEffect(() => { // persist accross reloads localStorage["appState"] = JSON.stringify(appState); }, [appState]); const factoryReset = () => { setAppState(_ => defaultState); } const pushHistory = (callback: (p: EditorState) => EditorState) => { setAppState(({history}) => { const newState = callback(history.at(-1)!); return { history: history.concat([newState]), future: [], }; }); }; const onUndo = () => { setAppState(({history, future}) => ({ history: history.slice(0,-1), future: future.concat(history.at(-1)!), })); }; const onRedo = () => { setAppState(({history, future}) => ({ history: history.concat(future.at(-1)!), future: future.slice(0,-1), })); }; const onKeyDown = (e) => { if (e.key === "Z" && e.ctrlKey) { if (e.shiftKey) { if (appState.future.length > 0) { onRedo(); } } else { if (appState.history.length > 1) { onUndo(); } } e.preventDefault(); } }; useEffect(() => { window['APP_STATE'] = appState.history; // useful for debugging }, [appState.history]); useEffect(() => { window.onkeydown = onKeyDown; }, []); const [highlighted, setHighlighted] = useState( commands.map(() => false)); const doHighlight = Object.fromEntries(commands.map(([id], i) => { return [id, () => { setHighlighted(h => h.with(i, true)); setTimeout(() => setHighlighted(h => h.with(i, false)), 100); }]; })); const onSelectExample = (e: React.SyntheticEvent) => { // @ts-ignore if (e.target.value >= 0) { // @ts-ignore // @ts-ignore pushHistory(_ => examples[e.target.value][1]); } } return ( <>
{ commands.map(([_, keys, descr], i) => {keys.map((key, j) => {key})} {descr} ) }
{}} suggestionPriority={() => 1} />
) }