cleanup code a bit more
This commit is contained in:
parent
07b51dd2f2
commit
1f72542234
25 changed files with 146 additions and 122 deletions
116
src/App/App.tsx
116
src/App/App.tsx
|
|
@ -5,16 +5,18 @@ import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from
|
|||
|
||||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { parseStatechart } from "../statecharts/parser";
|
||||
import { BottomPanel } from "./BottomPanel";
|
||||
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar";
|
||||
import { BottomPanel } from "./BottomPanel/BottomPanel";
|
||||
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar/SideBar";
|
||||
import { InsertMode } from "./TopPanel/InsertModes";
|
||||
import { TopPanel } from "./TopPanel/TopPanel";
|
||||
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
||||
import { makeIndividualSetters } from "./makePartialSetter";
|
||||
import { useEditor } from "./useEditor";
|
||||
import { useSimulator } from "./useSimulator";
|
||||
import { useUrlHashState } from "./useUrlHashState";
|
||||
import { makeAllSetters } from "./makePartialSetter";
|
||||
import { useEditor } from "./hooks/useEditor";
|
||||
import { useSimulator } from "./hooks/useSimulator";
|
||||
import { useUrlHashState } from "../hooks/useUrlHashState";
|
||||
import { plants } from "./plants";
|
||||
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||
import { ModalOverlay } from "./Modals/ModalOverlay";
|
||||
|
||||
export type EditHistory = {
|
||||
current: VisualEditorState,
|
||||
|
|
@ -56,9 +58,12 @@ export function App() {
|
|||
|
||||
const persist = useUrlHashState<VisualEditorState | AppState & {editorState: VisualEditorState}>(
|
||||
recoveredState => {
|
||||
if (recoveredState === null) {
|
||||
setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
||||
}
|
||||
// we support two formats
|
||||
// @ts-ignore
|
||||
if (recoveredState.nextID) {
|
||||
else if (recoveredState.nextID) {
|
||||
// old format
|
||||
setEditHistory(() => ({current: recoveredState as VisualEditorState, history: [], future: []}));
|
||||
}
|
||||
|
|
@ -77,6 +82,7 @@ export function App() {
|
|||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (editorState !== null) {
|
||||
console.log('persisting state to url');
|
||||
persist({editorState, ...appState});
|
||||
}
|
||||
}, 100);
|
||||
|
|
@ -104,7 +110,15 @@ export function App() {
|
|||
|
||||
const simulator = useSimulator(ast, plant, plantConns, scrollDownSidebar);
|
||||
|
||||
const setters = makeIndividualSetters(setAppState, Object.keys(appState) as (keyof AppState)[]);
|
||||
// console.log('render app', {ast, plant, appState});
|
||||
// useDetectChange(ast, 'ast');
|
||||
// useDetectChange(plant, 'plant');
|
||||
// useDetectChange(scrollDownSidebar, 'scrollDownSidebar');
|
||||
// useDetectChange(appState, 'appState');
|
||||
// useDetectChange(simulator.time, 'simulator.time');
|
||||
// useDetectChange(simulator.trace, 'simulator.trace');
|
||||
|
||||
const setters = makeAllSetters(setAppState, Object.keys(appState) as (keyof AppState)[]);
|
||||
|
||||
const syntaxErrors = parsed && parsed[1] || [];
|
||||
const currentTraceItem = simulator.trace && simulator.trace.trace[simulator.trace.idx];
|
||||
|
|
@ -121,63 +135,51 @@ export function App() {
|
|||
|
||||
const plantState = currentBigStep && currentBigStep.state.plant || plant.execution.initial()[1];
|
||||
|
||||
return <>
|
||||
return <ModalOverlay modal={modal} setModal={setModal}>
|
||||
{/* top-to-bottom: everything -> bottom panel */}
|
||||
<div className="stackVertical" style={{height:'100%'}}>
|
||||
|
||||
{/* Modal dialog */}
|
||||
{modal && <div
|
||||
className="modalOuter"
|
||||
onMouseDown={() => setModal(null)}>
|
||||
<div className="modalInner">
|
||||
<span onMouseDown={e => e.stopPropagation()}>
|
||||
{modal}
|
||||
</span>
|
||||
</div>
|
||||
</div>}
|
||||
{/* left-to-right: main -> sidebar */}
|
||||
<div className="stackHorizontal" style={{flexGrow:1, overflow: "auto"}}>
|
||||
|
||||
{/* top-to-bottom: everything -> bottom panel */}
|
||||
<div className="stackVertical" style={{height:'100%'}}>
|
||||
|
||||
{/* left-to-right: main -> sidebar */}
|
||||
<div className="stackHorizontal" style={{flexGrow:1, overflow: "auto"}}>
|
||||
|
||||
{/* top-to-bottom: top bar, editor */}
|
||||
<div className="stackVertical" style={{flexGrow:1, overflow: "auto"}}>
|
||||
{/* Top bar */}
|
||||
<div
|
||||
className="shadowBelow"
|
||||
style={{flex: '0 0 content'}}
|
||||
>
|
||||
{editHistory && <TopPanel
|
||||
{...{onUndo, onRedo, onRotate, setModal, editHistory, ...simulator, ...setters, ...appState}}
|
||||
/>}
|
||||
{/* top-to-bottom: top bar, editor */}
|
||||
<div className="stackVertical" style={{flexGrow:1, overflow: "auto"}}>
|
||||
{/* Top bar */}
|
||||
<div
|
||||
className="shadowBelow"
|
||||
style={{flex: '0 0 content'}}
|
||||
>
|
||||
{editHistory && <TopPanel
|
||||
{...{onUndo, onRedo, onRotate, setModal, editHistory, ...simulator, ...setters, ...appState}}
|
||||
/>}
|
||||
</div>
|
||||
{/* Editor */}
|
||||
<div style={{flexGrow: 1, overflow: "auto"}}>
|
||||
{editorState && conns && syntaxErrors &&
|
||||
<VisualEditor {...{state: editorState, setState: setEditorState, conns, syntaxErrors: allErrors, highlightActive, highlightTransitions, setModal, makeCheckPoint, ...appState}}/>}
|
||||
</div>
|
||||
</div>
|
||||
{/* Editor */}
|
||||
<div style={{flexGrow: 1, overflow: "auto"}}>
|
||||
{editorState && conns && syntaxErrors &&
|
||||
<VisualEditor {...{state: editorState, setState: setEditorState, conns, syntaxErrors: allErrors, highlightActive, highlightTransitions, setModal, makeCheckPoint, ...appState}}/>}
|
||||
|
||||
{/* Right: sidebar */}
|
||||
<div style={{
|
||||
flex: '0 0 content',
|
||||
borderLeft: '1px solid lightgrey',
|
||||
overflowY: "auto",
|
||||
overflowX: "auto",
|
||||
maxWidth: 'min(400px, 50vw)',
|
||||
}}>
|
||||
<div className="stackVertical" style={{height:'100%'}}>
|
||||
<SideBar {...{...appState, refRightSideBar, ast, plantState, ...simulator, ...setters}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: sidebar */}
|
||||
<div style={{
|
||||
flex: '0 0 content',
|
||||
borderLeft: '1px solid lightgrey',
|
||||
overflowY: "auto",
|
||||
overflowX: "auto",
|
||||
maxWidth: 'min(400px, 50vw)',
|
||||
}}>
|
||||
<div className="stackVertical" style={{height:'100%'}}>
|
||||
<SideBar {...{...appState, refRightSideBar, ast, plantState, ...simulator, ...setters}} />
|
||||
</div>
|
||||
{/* Bottom panel */}
|
||||
<div style={{flex: '0 0 content'}}>
|
||||
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom panel */}
|
||||
<div style={{flex: '0 0 content'}}>
|
||||
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
</ModalOverlay>;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { TraceableError } from "../statecharts/parser";
|
||||
import { TraceableError } from "../../statecharts/parser";
|
||||
|
||||
import "./BottomPanel.css";
|
||||
|
||||
import logo from "../../artwork/logo-playful.svg";
|
||||
import { PersistentDetailsLocalStorage } from "./PersistentDetails";
|
||||
import logo from "../../../artwork/logo-playful.svg";
|
||||
import { PersistentDetailsLocalStorage } from "../PersistentDetails";
|
||||
|
||||
export function BottomPanel(props: {errors: TraceableError[]}) {
|
||||
const [greeting, setGreeting] = useState(
|
||||
17
src/App/Modals/ModalOverlay.tsx
Normal file
17
src/App/Modals/ModalOverlay.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Dispatch, PropsWithChildren, ReactElement, SetStateAction } from "react";
|
||||
|
||||
export function ModalOverlay(props: PropsWithChildren<{modal: ReactElement|null, setModal: Dispatch<SetStateAction<ReactElement|null>>}>) {
|
||||
return <>
|
||||
{props.modal && <div
|
||||
className="modalOuter"
|
||||
onMouseDown={() => props.setModal(null)}>
|
||||
<div className="modalInner">
|
||||
<span onMouseDown={e => e.stopPropagation()}>
|
||||
{props.modal}
|
||||
</span>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
{props.children}
|
||||
</>;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { usePersistentState } from "@/App/persistent_state"
|
||||
import { usePersistentState } from "@/hooks/usePersistentState"
|
||||
import { DetailsHTMLAttributes, Dispatch, PropsWithChildren, SetStateAction } from "react";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useAudioContext } from "@/App/useAudioContext";
|
||||
import { useAudioContext } from "@/hooks/useAudioContext";
|
||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { parseStatechart } from "@/statecharts/parser";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { RT_Statechart } from "@/statecharts/runtime_types";
|
|||
import { memo, useEffect } from "react";
|
||||
|
||||
import "./Microwave.css";
|
||||
import { useAudioContext } from "../../useAudioContext";
|
||||
import { useAudioContext } from "../../../hooks/useAudioContext";
|
||||
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { parseStatechart } from "@/statecharts/parser";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { Statechart } from "@/statecharts/abstract_syntax";
|
|||
import { EventTrigger } from "@/statecharts/label_ast";
|
||||
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
||||
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
||||
import { setsEqual } from "@/util/util";
|
||||
|
||||
export type PlantRenderProps<StateType> = {
|
||||
state: StateType,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
|||
import { detectConnections } from "@/statecharts/detect_connections";
|
||||
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||
import { useAudioContext } from "@/App/useAudioContext";
|
||||
import { useAudioContext } from "@/hooks/useAudioContext";
|
||||
import { memo, useEffect } from "react";
|
||||
import { objectsEqual } from "@/util/util";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Dispatch, memo, SetStateAction, useCallback } from "react";
|
||||
import { Statechart, stateDescription } from "../statecharts/abstract_syntax";
|
||||
import { Mode, RaisedEvent, RT_Event } from "../statecharts/runtime_types";
|
||||
import { formatTime } from "../util/util";
|
||||
import { TimeMode, timeTravel } from "../statecharts/time";
|
||||
import { BigStepCause, TraceItem, TraceState } from "./App";
|
||||
import { Statechart, stateDescription } from "../../statecharts/abstract_syntax";
|
||||
import { Mode, RaisedEvent, RT_Event } from "../../statecharts/runtime_types";
|
||||
import { formatTime } from "../../util/util";
|
||||
import { TimeMode, timeTravel } from "../../statecharts/time";
|
||||
import { Environment } from "@/statecharts/environment";
|
||||
import { BigStepCause, TraceItem, TraceState } from "../hooks/useSimulator";
|
||||
|
||||
type RTHistoryProps = {
|
||||
trace: TraceState|null,
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { ConcreteState, UnstableState, stateDescription, Transition } from "../statecharts/abstract_syntax";
|
||||
import { Action, EventTrigger, Expression } from "../statecharts/label_ast";
|
||||
|
||||
import "./AST.css";
|
||||
import BoltIcon from '@mui/icons-material/Bolt';
|
||||
import { memo, useEffect } from "react";
|
||||
import { usePersistentState } from "../../hooks/usePersistentState";
|
||||
import { ConcreteState, stateDescription, Transition, UnstableState } from "../../statecharts/abstract_syntax";
|
||||
import { Action, EventTrigger, Expression } from "../../statecharts/label_ast";
|
||||
import { KeyInfoHidden, KeyInfoVisible } from "../TopPanel/KeyInfo";
|
||||
|
||||
export function ShowTransition(props: {transition: Transition}) {
|
||||
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
||||
|
|
@ -46,10 +48,6 @@ export const ShowAST = memo(function ShowASTx(props: {root: ConcreteState | Unst
|
|||
</li>;
|
||||
});
|
||||
|
||||
import BoltIcon from '@mui/icons-material/Bolt';
|
||||
import { KeyInfoHidden, KeyInfoVisible } from "./TopPanel/KeyInfo";
|
||||
import { memo, useEffect } from "react";
|
||||
import { usePersistentState } from "./persistent_state";
|
||||
|
||||
export function ShowInputEvents({inputEvents, onRaise, disabled, showKeys}: {inputEvents: EventTrigger[], onRaise: (e: string, p: any) => void, disabled: boolean, showKeys: boolean}) {
|
||||
const raiseHandlers = inputEvents.map(({event}) => {
|
||||
|
|
@ -8,14 +8,15 @@ import { Conns } from '@/statecharts/timed_reactive';
|
|||
import { Dispatch, Ref, SetStateAction, useEffect, useRef, useState } from 'react';
|
||||
import { Statechart } from '@/statecharts/abstract_syntax';
|
||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from './ShowAST';
|
||||
import { Plant } from './Plant/Plant';
|
||||
import { Plant } from '../Plant/Plant';
|
||||
import { checkProperty, PropertyCheckResult } from './check_property';
|
||||
import { Setters } from './makePartialSetter';
|
||||
import { Setters } from '../makePartialSetter';
|
||||
import { RTHistory } from './RTHistory';
|
||||
import { BigStepCause, TraceState } from './useSimulator';
|
||||
import { plants, UniversalPlantState } from './plants';
|
||||
import { BigStepCause, TraceState } from '../hooks/useSimulator';
|
||||
import { plants, UniversalPlantState } from '../plants';
|
||||
import { TimeMode } from '@/statecharts/time';
|
||||
import { PersistentDetails } from './PersistentDetails';
|
||||
import { PersistentDetails } from '../PersistentDetails';
|
||||
import "./SideBar.css";
|
||||
|
||||
type SavedTraces = [string, BigStepCause[]][];
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||
import { Plant } from "./Plant/Plant";
|
||||
import { TraceItem } from "./useSimulator";
|
||||
import { Plant } from "../Plant/Plant";
|
||||
import { TraceItem } from "../hooks/useSimulator";
|
||||
|
||||
// const endpoint = "http://localhost:15478/check_property";
|
||||
const endpoint = "https://deemz.org/apis/mtl-aas/check_property";
|
||||
|
|
@ -18,10 +18,10 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
|||
import SkipNextIcon from '@mui/icons-material/SkipNext';
|
||||
import StopIcon from '@mui/icons-material/Stop';
|
||||
import { InsertModes } from "./InsertModes";
|
||||
import { usePersistentState } from "@/App/persistent_state";
|
||||
import { usePersistentState } from "@/hooks/usePersistentState";
|
||||
import { RotateButtons } from "./RotateButtons";
|
||||
import { SpeedControl } from "./SpeedControl";
|
||||
import { TraceState } from "../useSimulator";
|
||||
import { TraceState } from "../hooks/useSimulator";
|
||||
|
||||
export type TopPanelProps = {
|
||||
trace: TraceState | null,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
import { Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useRef } from "react";
|
||||
|
||||
import { InsertMode } from "../TopPanel/InsertModes";
|
||||
import { Mode } from "@/statecharts/runtime_types";
|
||||
import { arraysEqual, objectsEqual, setsEqual } from "@/util/util";
|
||||
import { Arrow, ArrowPart, Diamond, History, RectSide, Rountangle, Text } from "../../statecharts/concrete_syntax";
|
||||
import { Connections } from "../../statecharts/detect_connections";
|
||||
import { TraceableError } from "../../statecharts/parser";
|
||||
import { ArcDirection, arcDirection } from "../../util/geometry";
|
||||
import { InsertMode } from "../TopPanel/InsertModes";
|
||||
import { ArrowSVG } from "./ArrowSVG";
|
||||
import { DiamondSVG } from "./DiamondSVG";
|
||||
import { HistorySVG } from "./HistorySVG";
|
||||
import { RountangleSVG } from "./RountangleSVG";
|
||||
import { TextSVG } from "./TextSVG";
|
||||
import { useCopyPaste } from "./useCopyPaste";
|
||||
|
||||
import "./VisualEditor.css";
|
||||
import { useMouse } from "./useMouse";
|
||||
import { useCopyPaste } from "./hooks/useCopyPaste";
|
||||
import { useMouse } from "./hooks/useMouse";
|
||||
|
||||
export type ConcreteSyntax = {
|
||||
rountangles: Rountangle[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Arrow, Diamond, Rountangle, Text, History } from "@/statecharts/concrete_syntax";
|
||||
import { ClipboardEvent, Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
||||
import { Selection, VisualEditorState } from "./VisualEditor";
|
||||
import { Selection, VisualEditorState } from "../VisualEditor";
|
||||
import { addV2D } from "@/util/geometry";
|
||||
|
||||
// const offset = {x: 40, y: 40};
|
||||
|
|
@ -2,10 +2,10 @@ import { rountangleMinSize } from "@/statecharts/concrete_syntax";
|
|||
import { addV2D, area, isEntirelyWithin, normalizeRect, scaleV2D, subtractV2D, transformLine, transformRect } from "@/util/geometry";
|
||||
import { getBBoxInSvgCoords } from "@/util/svg_helper";
|
||||
import { Dispatch, useCallback, useEffect, useState } from "react";
|
||||
import { MIN_ROUNTANGLE_SIZE } from "../parameters";
|
||||
import { InsertMode } from "../TopPanel/InsertModes";
|
||||
import { Selecting, SelectingState } from "./Selection";
|
||||
import { Selection, VisualEditorState } from "./VisualEditor";
|
||||
import { MIN_ROUNTANGLE_SIZE } from "../../parameters";
|
||||
import { InsertMode } from "../../TopPanel/InsertModes";
|
||||
import { Selecting, SelectingState } from "../Selection";
|
||||
import { Selection, VisualEditorState } from "../VisualEditor";
|
||||
|
||||
export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoom: number, refSVG: {current: SVGSVGElement|null}, state: VisualEditorState, setState: Dispatch<(v: VisualEditorState) => VisualEditorState>, deleteSelection: () => void) {
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { addV2D, rotateLine90CCW, rotateLine90CW, rotatePoint90CCW, rotatePoint90CW, rotateRect90CCW, rotateRect90CW, scaleV2D, subtractV2D, Vec2D } from "@/util/geometry";
|
||||
import { HISTORY_RADIUS } from "./parameters";
|
||||
import { HISTORY_RADIUS } from "../parameters";
|
||||
import { Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
||||
import { EditHistory } from "./App";
|
||||
import { VisualEditorState } from "./VisualEditor/VisualEditor";
|
||||
import { EditHistory } from "../App";
|
||||
|
||||
export function useEditor(setEditHistory: Dispatch<SetStateAction<EditHistory|null>>) {
|
||||
useEffect(() => {
|
||||
|
|
@ -3,9 +3,9 @@ import { RuntimeError } from "@/statecharts/interpreter";
|
|||
import { BigStep, RaisedEvent } from "@/statecharts/runtime_types";
|
||||
import { Conns, coupledExecution, statechartExecution } from "@/statecharts/timed_reactive";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Plant } from "./Plant/Plant";
|
||||
import { Plant } from "../Plant/Plant";
|
||||
import { getSimTime, getWallClkDelay, TimeMode } from "@/statecharts/time";
|
||||
import { UniversalPlantState } from "./plants";
|
||||
import { UniversalPlantState } from "../plants";
|
||||
|
||||
type CoupledState = {
|
||||
sc: BigStep,
|
||||
|
|
@ -107,6 +107,7 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
|||
|
||||
// timer elapse events are triggered by a change of the simulated time (possibly as a scheduled JS event loop timeout)
|
||||
useEffect(() => {
|
||||
// console.log('time effect:', time, currentTraceItem);
|
||||
let timeout: NodeJS.Timeout | undefined;
|
||||
if (currentTraceItem !== null && cE !== null) {
|
||||
if (currentTraceItem.kind === "bigstep") {
|
||||
|
|
@ -3,16 +3,16 @@ import { Dispatch, SetStateAction, useCallback, useMemo } from "react";
|
|||
export function makePartialSetter<T, K extends keyof T>(fullSetter: Dispatch<SetStateAction<T>>, key: K): Dispatch<SetStateAction<T[typeof key]>> {
|
||||
return (newValueOrCallback: T[K] | ((newValue: T[K]) => T[K])) => {
|
||||
fullSetter(oldFullValue => {
|
||||
if (typeof newValueOrCallback === 'function') {
|
||||
const newValue = (typeof newValueOrCallback === 'function') ? (newValueOrCallback as (newValue: T[K]) => T[K])(oldFullValue[key] as T[K]) : newValueOrCallback as T[K];
|
||||
if (newValue === oldFullValue[key]) {
|
||||
return oldFullValue;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
...oldFullValue,
|
||||
[key]: (newValueOrCallback as (newValue: T[K]) => T[K])(oldFullValue[key] as T[K]),
|
||||
[key]: newValue,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...oldFullValue,
|
||||
[key]: newValueOrCallback as T[K],
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
@ -21,16 +21,16 @@ export type Setters<T extends {[key: string]: any}> = {
|
|||
[K in keyof T as `set${Capitalize<Extract<K, string>>}`]: Dispatch<SetStateAction<T[K]>>;
|
||||
}
|
||||
|
||||
export function makeIndividualSetters<T extends {[key: string]: any}>(
|
||||
export function makeAllSetters<T extends {[key: string]: any}>(
|
||||
fullSetter: Dispatch<SetStateAction<T>>,
|
||||
keys: (keyof T)[],
|
||||
): Setters<T> {
|
||||
// @ts-ignore
|
||||
return useMemo(() =>
|
||||
return useMemo(() => {
|
||||
console.log('creating setters for App');
|
||||
// @ts-ignore
|
||||
Object.fromEntries(keys.map((key: string) => {
|
||||
return Object.fromEntries(keys.map((key: string) => {
|
||||
return [`set${key.charAt(0).toUpperCase()}${key.slice(1)}`, makePartialSetter(fullSetter, key)];
|
||||
})),
|
||||
[fullSetter]
|
||||
);
|
||||
}));
|
||||
}, [fullSetter]);
|
||||
}
|
||||
|
|
|
|||
8
src/hooks/useDetectChange.ts
Normal file
8
src/hooks/useDetectChange.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
// useful for debugging
|
||||
export function useDetectChange(expr: any, name: string) {
|
||||
useEffect(() => {
|
||||
console.log(name, 'changed to:', expr);
|
||||
}, [expr]);
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
import { useEffect } from "react";
|
||||
import { useEffect, useLayoutEffect } from "react";
|
||||
|
||||
// persist state in URL hash
|
||||
export function useUrlHashState<T>(recoverCallback: (recoveredState: T) => void): (toPersist: T) => void {
|
||||
export function useUrlHashState<T>(recoverCallback: (recoveredState: (T|null)) => void): (toPersist: T) => void {
|
||||
|
||||
// recover editor state from URL - we need an effect here because decompression is asynchronous
|
||||
useEffect(() => {
|
||||
// layout effect because we want to run it before rendering the first frame
|
||||
useLayoutEffect(() => {
|
||||
console.log('recovering state...');
|
||||
const compressedState = window.location.hash.slice(1);
|
||||
if (compressedState.length === 0) {
|
||||
// empty URL hash
|
||||
console.log("no state to recover");
|
||||
// setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
||||
return;
|
||||
return recoverCallback(null);
|
||||
}
|
||||
let compressedBuffer;
|
||||
try {
|
||||
|
|
@ -19,8 +19,7 @@ export function useUrlHashState<T>(recoverCallback: (recoveredState: T) => void)
|
|||
} catch (e) {
|
||||
// probably invalid base64
|
||||
console.error("failed to recover state:", e);
|
||||
// setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
||||
return;
|
||||
return recoverCallback(null);
|
||||
}
|
||||
const ds = new DecompressionStream("deflate");
|
||||
const writer = ds.writable.getWriter();
|
||||
|
|
@ -29,12 +28,13 @@ export function useUrlHashState<T>(recoverCallback: (recoveredState: T) => void)
|
|||
new Response(ds.readable).arrayBuffer()
|
||||
.then(decompressedBuffer => {
|
||||
const recoveredState = JSON.parse(new TextDecoder().decode(decompressedBuffer));
|
||||
console.log('successfully recovered state');
|
||||
recoverCallback(recoveredState);
|
||||
})
|
||||
.catch(e => {
|
||||
// any other error: invalid JSON, or decompression failed.
|
||||
console.error("failed to recover state:", e);
|
||||
// setEditHistory({current: emptyState, history: [], future: []});
|
||||
recoverCallback(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue