move everything
This commit is contained in:
parent
3ff7e76694
commit
9050581a10
25 changed files with 37 additions and 42 deletions
62
src/component/app/App.css
Normal file
62
src/component/app/App.css
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#root {
|
||||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"header header header"
|
||||
"content content content"
|
||||
"footer footer footer";
|
||||
|
||||
grid-template-columns: 100px 1fr 100px;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
/* grid-gap: 10px; */
|
||||
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
nav {
|
||||
grid-area: nav;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: content;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
aside {
|
||||
grid-area: side;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: right;
|
||||
grid-area: footer;
|
||||
|
||||
background-color: dodgerblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.command {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
padding: 0 6px 0 6px;
|
||||
margin: 0 4px 0 4px;
|
||||
}
|
||||
|
||||
.command.highlighted {
|
||||
background-color: dodgerblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.factoryReset {
|
||||
background-color: rgb(255, 0, 0);
|
||||
color: black;
|
||||
}
|
||||
181
src/component/app/App.tsx
Normal file
181
src/component/app/App.tsx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import './App.css';
|
||||
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
|
||||
import { GlobalContext } from '../../context/GlobalContext';
|
||||
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
||||
import { actionShortcuts } from './actions';
|
||||
// import { scoreResolved, type ResolvedType } from './eval';
|
||||
|
||||
|
||||
const examples: [string, ExprBlockState][] = [
|
||||
["empty editor" , initialEditorState ],
|
||||
["push to list" , nonEmptyEditorState ],
|
||||
["function w/ 4 params", tripleFunctionCallEditorState],
|
||||
["bigger example" , biggerExample ],
|
||||
["lambda 2 params" , lambda2Params ],
|
||||
["higher order" , higherOrder ],
|
||||
["higher order 2" , higherOrder2Params ],
|
||||
["push Bool" , pushBool ],
|
||||
["inc" , inc ],
|
||||
["empty set" , emptySet ],
|
||||
["factorial" , factorial ],
|
||||
];
|
||||
|
||||
type AppState = {
|
||||
history: ExprBlockState[],
|
||||
future: ExprBlockState[],
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// factoryReset();
|
||||
|
||||
const pushHistory = (callback: (p: ExprBlockState) => ExprBlockState) => {
|
||||
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(
|
||||
actionShortcuts.map(() => false));
|
||||
|
||||
const doHighlight = Object.fromEntries(actionShortcuts.map(([id], i) => {
|
||||
return [id, () => {
|
||||
setHighlighted(h => h.with(i, true));
|
||||
setTimeout(() => setHighlighted(h => h.with(i, false)), 100);
|
||||
}];
|
||||
}));
|
||||
|
||||
const onSelectExample = (e: React.SyntheticEvent<HTMLSelectElement>) => {
|
||||
// @ts-ignore
|
||||
if (e.target.value >= 0) {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
pushHistory(_ => examples[e.target.value][1]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<button disabled={appState.history.length===1} onClick={onUndo}>Undo ({appState.history.length-1}) <kbd>Ctrl</kbd>+<kbd>Z</kbd></button>
|
||||
<button disabled={appState.future.length===0} onClick={onRedo}>Redo ({appState.future.length}) <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></button>
|
||||
{
|
||||
actionShortcuts.map(([_, keys, descr], i) =>
|
||||
<span key={i} className={'command' + (highlighted[i] ? (' highlighted') : '')}>
|
||||
{keys.map((key, j) => <kbd key={j}>{key}</kbd>)}
|
||||
|
||||
{descr}
|
||||
</span>)
|
||||
}
|
||||
<select onChange={onSelectExample} value={-1}>
|
||||
<option value={-1}>load example...</option>
|
||||
{
|
||||
examples.map(([name], i) => {
|
||||
return <option key={i} value={i}>{name}</option>;
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<button className="factoryReset" onClick={factoryReset}>
|
||||
FACTORY RESET
|
||||
</button>
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
checked={syntacticSugar}
|
||||
onChange={e => setSyntacticSugar(e.target.checked)}/>
|
||||
syntactic sugar
|
||||
</label>
|
||||
</header>
|
||||
|
||||
<main onKeyDown={onKeyDown}>
|
||||
<GlobalContext value={{undo: onUndo, redo: onRedo, doHighlight, syntacticSugar}}>
|
||||
<ExprBlock
|
||||
state={appState.history.at(-1)!}
|
||||
setState={pushHistory}
|
||||
onCancel={() => {}}
|
||||
score={() => 0}
|
||||
/>
|
||||
</GlobalContext>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a href="https://deemz.org/git/joeri/dope2-webapp">Source code</a>
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
62
src/component/app/actions.ts
Normal file
62
src/component/app/actions.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { initialEditorState } from "./configurations";
|
||||
// import { removeFocus } from "./eval";
|
||||
|
||||
const removeFocus = state => state;
|
||||
|
||||
export const actionShortcuts: [string, string[], string][] = [
|
||||
["call" , ['c'], "expr ⌴" ],
|
||||
["transform", ['t'], "⌴ expr" ],
|
||||
["assign" , ['a'], "let (⌴ = expr) in ⌴"],
|
||||
["declare" , ['d'], "let (⌴ = ⌴) in expr"],
|
||||
["lambda" , ['l'], "λ⌴. expr" ],
|
||||
];
|
||||
|
||||
export function getActions(globalContext, setState) {
|
||||
return {
|
||||
c: () => {
|
||||
setState(state => ({
|
||||
kind: "call",
|
||||
fn: removeFocus(state),
|
||||
input: initialEditorState,
|
||||
}));
|
||||
globalContext?.doHighlight.call();
|
||||
},
|
||||
t: () => {
|
||||
setState(state => ({
|
||||
kind: "call",
|
||||
fn: initialEditorState,
|
||||
input: removeFocus(state),
|
||||
}));
|
||||
globalContext?.doHighlight.transform();
|
||||
},
|
||||
a: () => {
|
||||
setState(state => ({
|
||||
kind: "let",
|
||||
name: "",
|
||||
focus: true,
|
||||
value: removeFocus(state),
|
||||
inner: removeFocus(initialEditorState),
|
||||
}));
|
||||
globalContext?.doHighlight.assign();
|
||||
},
|
||||
d: () => {
|
||||
setState(state => ({
|
||||
kind: "let",
|
||||
name: "",
|
||||
focus: true,
|
||||
value: removeFocus(initialEditorState),
|
||||
inner: removeFocus(state),
|
||||
}));
|
||||
globalContext?.doHighlight.declare();
|
||||
},
|
||||
l: () => {
|
||||
setState(state => ({
|
||||
kind: "lambda",
|
||||
paramName: "",
|
||||
focus: true,
|
||||
expr: removeFocus(state),
|
||||
}));
|
||||
globalContext?.doHighlight.lambda();
|
||||
},
|
||||
};
|
||||
}
|
||||
144
src/component/app/configurations.ts
Normal file
144
src/component/app/configurations.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import type { ExprBlockState } from "../expr/ExprBlock";
|
||||
|
||||
export const initialEditorState: ExprBlockState = {
|
||||
kind: "input",
|
||||
text: "",
|
||||
value: { kind: "text" },
|
||||
focus: true,
|
||||
};
|
||||
|
||||
export const nonEmptyEditorState: ExprBlockState = {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "input",
|
||||
text: "list.push",
|
||||
value: { kind: "name" },
|
||||
focus: false,
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "list.emptyList",
|
||||
value: { kind: "name" },
|
||||
focus: false,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "42",
|
||||
value: { kind: "literal", type: "Int" },
|
||||
focus: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const tripleFunctionCallEditorState: ExprBlockState = {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "call",
|
||||
fn: {
|
||||
kind: "input",
|
||||
text: "functionWith4Params",
|
||||
value: { kind: "name" },
|
||||
focus: false,
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "42",
|
||||
value: { kind: "literal", type: "Int" },
|
||||
focus: false,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "43",
|
||||
value: { kind: "literal", type: "Int" },
|
||||
focus: false,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "44",
|
||||
value: { kind: "literal", type: "Int" },
|
||||
focus: false,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
kind: "input",
|
||||
text: "45",
|
||||
value: { kind: "literal", type: "Int" },
|
||||
focus: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const biggerExample: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};
|
||||
|
||||
export const lambda2Params: ExprBlockState = {
|
||||
"kind": "let",
|
||||
"inner": {
|
||||
"kind": "input",
|
||||
"text": "",
|
||||
"value": {
|
||||
"kind": "text"
|
||||
},
|
||||
"focus": false
|
||||
},
|
||||
"name": "myAddInt",
|
||||
"focus": false,
|
||||
"value": {
|
||||
"kind": "lambda",
|
||||
"paramName": "x",
|
||||
"focus": false,
|
||||
"expr": {
|
||||
"kind": "lambda",
|
||||
"paramName": "y",
|
||||
"focus": false,
|
||||
"expr": {
|
||||
"kind": "call",
|
||||
"fn": {
|
||||
"kind": "call",
|
||||
"fn": {
|
||||
"kind": "input",
|
||||
"text": "addInt",
|
||||
"value": {
|
||||
"kind": "name"
|
||||
},
|
||||
"focus": false
|
||||
},
|
||||
"input": {
|
||||
"kind": "input",
|
||||
"text": "x",
|
||||
"value": {
|
||||
"kind": "name"
|
||||
},
|
||||
"focus": false
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"kind": "input",
|
||||
"text": "y",
|
||||
"value": {
|
||||
"kind": "name"
|
||||
},
|
||||
"focus": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const higherOrder: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myBinaryApply","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"lambda","focus":false,"paramName":"fn","expr":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}}}};
|
||||
|
||||
export const higherOrder2Params: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"lambda","focus":false,"paramName":"y","expr":{"kind":"lambda","focus":false,"paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}};
|
||||
|
||||
export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}};
|
||||
|
||||
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};
|
||||
|
||||
export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"text"},"focus":true}};
|
||||
|
||||
export const factorial: ExprBlockState = {"kind":"lambda","paramName":"factorial","focus":true,"expr":{"kind":"lambda","paramName":"n","focus":true,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"leqZero","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"mulInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":true}},"input":{"kind":"call","fn":{"kind":"input","text":"factorial","value":{"kind":"name"},"focus":true},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"-1","value":{"kind":"literal","type":"Int"},"focus":false}}}}}}}};
|
||||
Loading…
Add table
Add a link
Reference in a new issue