greatly simplify state + cleanup code
This commit is contained in:
parent
2d0deca127
commit
5964510036
11 changed files with 268 additions and 321 deletions
111
src/App.tsx
111
src/App.tsx
|
|
@ -1,11 +1,10 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import './App.css'
|
||||
import { Editor, type EditorState } from './Editor'
|
||||
import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations";
|
||||
import './App.css';
|
||||
import { CommandContext } from './CommandContext';
|
||||
import { deserialize, serialize } from './types';
|
||||
import { Editor, type EditorState } from './Editor';
|
||||
import { extendedEnv } from './EnvContext';
|
||||
import { useEffectBetter } from './util/use_effect_better';
|
||||
import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations";
|
||||
import { evalEditorBlock } from "./eval";
|
||||
|
||||
const commands: [string, string[], string][] = [
|
||||
["call" , ['c' ], "call" ],
|
||||
|
|
@ -19,54 +18,77 @@ const examples: [string, EditorState][] = [
|
|||
["push to list", nonEmptyEditorState],
|
||||
["function w/ 4 params", tripleFunctionCallEditorState]];
|
||||
|
||||
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() {
|
||||
// const [history, setHistory] = useState([initialEditorState]);
|
||||
// const [history, setHistory] = useState([nonEmptyEditorState]);
|
||||
// const [history, setHistory] = useState([tripleFunctionCallEditorState]);
|
||||
// const [future, setFuture] = useState<EditorState[]>([]);
|
||||
|
||||
// load from localStorage
|
||||
const [history, setHistory] = useState<EditorState[]>(
|
||||
localStorage["history"]
|
||||
? JSON.parse(localStorage["history"]).map(s => deserialize(s, extendedEnv))
|
||||
: [initialEditorState]
|
||||
);
|
||||
const [future, setFuture] = useState<EditorState[]>(
|
||||
localStorage["future"]
|
||||
? JSON.parse(localStorage["future"]).map(s => deserialize(s, extendedEnv))
|
||||
: []
|
||||
);
|
||||
const [appState, setAppState] = useState(loadFromLocalStorage());
|
||||
|
||||
useEffectBetter(() => {
|
||||
useEffect(() => {
|
||||
// persist accross reloads
|
||||
localStorage["history"] = JSON.stringify(history.map(serialize));
|
||||
localStorage["future"] = JSON.stringify(future.map(serialize));
|
||||
}, [history, future]);
|
||||
localStorage["appState"] = JSON.stringify(appState);
|
||||
}, [appState]);
|
||||
|
||||
const factoryReset = () => {
|
||||
setAppState(_ => defaultState);
|
||||
}
|
||||
|
||||
const pushHistory = (callback: (p: EditorState) => EditorState) => {
|
||||
const newState = callback(history.at(-1)!);
|
||||
setHistory(history.concat([newState]));
|
||||
setFuture([]);
|
||||
setAppState(({history}) => {
|
||||
const newState = callback(history.at(-1)!);
|
||||
return {
|
||||
history: history.concat([newState]),
|
||||
future: [],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const onUndo = () => {
|
||||
setFuture(future.concat(history.at(-1)!)); // add
|
||||
setHistory(history.slice(0,-1)); // remove
|
||||
setAppState(({history, future}) => ({
|
||||
history: history.slice(0,-1),
|
||||
future: future.concat(history.at(-1)!),
|
||||
}));
|
||||
};
|
||||
const onRedo = () => {
|
||||
setHistory(history.concat(future.at(-1)!)); // add
|
||||
setFuture(future.slice(0,-1)); // remove
|
||||
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 (future.length > 0) {
|
||||
if (appState.future.length > 0) {
|
||||
onRedo();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (history.length > 1) {
|
||||
if (appState.history.length > 1) {
|
||||
onUndo();
|
||||
}
|
||||
}
|
||||
|
|
@ -75,8 +97,8 @@ export function App() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
window['APP_STATE'] = history; // useful for debugging
|
||||
}, [history]);
|
||||
window['APP_STATE'] = appState.history; // useful for debugging
|
||||
}, [appState.history]);
|
||||
|
||||
useEffect(() => {
|
||||
window.onkeydown = onKeyDown;
|
||||
|
|
@ -95,14 +117,17 @@ export function App() {
|
|||
|
||||
const onSelectExample = (e: React.SyntheticEvent<HTMLSelectElement>) => {
|
||||
// @ts-ignore
|
||||
pushHistory(_ => examples[e.target.value][1]);
|
||||
if (e.target.value) {
|
||||
// @ts-ignore
|
||||
pushHistory(_ => examples[e.target.value][1]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<button disabled={history.length===1} onClick={onUndo}>Undo ({history.length-1}) <kbd>Ctrl</kbd>+<kbd>Z</kbd></button>
|
||||
<button disabled={future.length===0} onClick={onRedo}>Redo ({future.length}) <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></button>
|
||||
<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>
|
||||
{
|
||||
commands.map(([_, keys, descr], i) =>
|
||||
<span key={i} className={'command' + (highlighted[i] ? (' highlighted') : '')}>
|
||||
|
|
@ -111,22 +136,22 @@ export function App() {
|
|||
</span>)
|
||||
}
|
||||
<select onClick={onSelectExample}>
|
||||
<option>load example...</option>
|
||||
{
|
||||
examples.map(([name], i) => {
|
||||
return <option key={i} value={i}>{name}</option>;
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<button className="factoryReset" onClick={() => {
|
||||
setHistory(_ => [initialEditorState]);
|
||||
setFuture(_ => []);
|
||||
}}>FACTORY RESET</button>
|
||||
<button className="factoryReset" onClick={factoryReset}>
|
||||
FACTORY RESET
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main onKeyDown={onKeyDown}>
|
||||
<CommandContext value={{undo: onUndo, redo: onRedo, doHighlight}}>
|
||||
<Editor
|
||||
state={history.at(-1)!}
|
||||
state={appState.history.at(-1)!}
|
||||
setState={pushHistory}
|
||||
onCancel={() => {}}
|
||||
filter={() => true}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue