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 { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "../statecharts/parser";
|
import { parseStatechart } from "../statecharts/parser";
|
||||||
import { BottomPanel } from "./BottomPanel";
|
import { BottomPanel } from "./BottomPanel/BottomPanel";
|
||||||
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar";
|
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar/SideBar";
|
||||||
import { InsertMode } from "./TopPanel/InsertModes";
|
import { InsertMode } from "./TopPanel/InsertModes";
|
||||||
import { TopPanel } from "./TopPanel/TopPanel";
|
import { TopPanel } from "./TopPanel/TopPanel";
|
||||||
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
import { VisualEditor, VisualEditorState } from "./VisualEditor/VisualEditor";
|
||||||
import { makeIndividualSetters } from "./makePartialSetter";
|
import { makeAllSetters } from "./makePartialSetter";
|
||||||
import { useEditor } from "./useEditor";
|
import { useEditor } from "./hooks/useEditor";
|
||||||
import { useSimulator } from "./useSimulator";
|
import { useSimulator } from "./hooks/useSimulator";
|
||||||
import { useUrlHashState } from "./useUrlHashState";
|
import { useUrlHashState } from "../hooks/useUrlHashState";
|
||||||
import { plants } from "./plants";
|
import { plants } from "./plants";
|
||||||
|
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||||
|
import { ModalOverlay } from "./Modals/ModalOverlay";
|
||||||
|
|
||||||
export type EditHistory = {
|
export type EditHistory = {
|
||||||
current: VisualEditorState,
|
current: VisualEditorState,
|
||||||
|
|
@ -56,9 +58,12 @@ export function App() {
|
||||||
|
|
||||||
const persist = useUrlHashState<VisualEditorState | AppState & {editorState: VisualEditorState}>(
|
const persist = useUrlHashState<VisualEditorState | AppState & {editorState: VisualEditorState}>(
|
||||||
recoveredState => {
|
recoveredState => {
|
||||||
|
if (recoveredState === null) {
|
||||||
|
setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
||||||
|
}
|
||||||
// we support two formats
|
// we support two formats
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (recoveredState.nextID) {
|
else if (recoveredState.nextID) {
|
||||||
// old format
|
// old format
|
||||||
setEditHistory(() => ({current: recoveredState as VisualEditorState, history: [], future: []}));
|
setEditHistory(() => ({current: recoveredState as VisualEditorState, history: [], future: []}));
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +82,7 @@ export function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
if (editorState !== null) {
|
if (editorState !== null) {
|
||||||
|
console.log('persisting state to url');
|
||||||
persist({editorState, ...appState});
|
persist({editorState, ...appState});
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
@ -104,7 +110,15 @@ export function App() {
|
||||||
|
|
||||||
const simulator = useSimulator(ast, plant, plantConns, scrollDownSidebar);
|
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 syntaxErrors = parsed && parsed[1] || [];
|
||||||
const currentTraceItem = simulator.trace && simulator.trace.trace[simulator.trace.idx];
|
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];
|
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 */}
|
{/* left-to-right: main -> sidebar */}
|
||||||
{modal && <div
|
<div className="stackHorizontal" style={{flexGrow:1, overflow: "auto"}}>
|
||||||
className="modalOuter"
|
|
||||||
onMouseDown={() => setModal(null)}>
|
|
||||||
<div className="modalInner">
|
|
||||||
<span onMouseDown={e => e.stopPropagation()}>
|
|
||||||
{modal}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/* top-to-bottom: everything -> bottom panel */}
|
{/* top-to-bottom: top bar, editor */}
|
||||||
<div className="stackVertical" style={{height:'100%'}}>
|
<div className="stackVertical" style={{flexGrow:1, overflow: "auto"}}>
|
||||||
|
{/* Top bar */}
|
||||||
{/* left-to-right: main -> sidebar */}
|
<div
|
||||||
<div className="stackHorizontal" style={{flexGrow:1, overflow: "auto"}}>
|
className="shadowBelow"
|
||||||
|
style={{flex: '0 0 content'}}
|
||||||
{/* top-to-bottom: top bar, editor */}
|
>
|
||||||
<div className="stackVertical" style={{flexGrow:1, overflow: "auto"}}>
|
{editHistory && <TopPanel
|
||||||
{/* Top bar */}
|
{...{onUndo, onRedo, onRotate, setModal, editHistory, ...simulator, ...setters, ...appState}}
|
||||||
<div
|
/>}
|
||||||
className="shadowBelow"
|
</div>
|
||||||
style={{flex: '0 0 content'}}
|
{/* Editor */}
|
||||||
>
|
<div style={{flexGrow: 1, overflow: "auto"}}>
|
||||||
{editHistory && <TopPanel
|
{editorState && conns && syntaxErrors &&
|
||||||
{...{onUndo, onRedo, onRotate, setModal, editHistory, ...simulator, ...setters, ...appState}}
|
<VisualEditor {...{state: editorState, setState: setEditorState, conns, syntaxErrors: allErrors, highlightActive, highlightTransitions, setModal, makeCheckPoint, ...appState}}/>}
|
||||||
/>}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Editor */}
|
|
||||||
<div style={{flexGrow: 1, overflow: "auto"}}>
|
{/* Right: sidebar */}
|
||||||
{editorState && conns && syntaxErrors &&
|
<div style={{
|
||||||
<VisualEditor {...{state: editorState, setState: setEditorState, conns, syntaxErrors: allErrors, highlightActive, highlightTransitions, setModal, makeCheckPoint, ...appState}}/>}
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: sidebar */}
|
{/* Bottom panel */}
|
||||||
<div style={{
|
<div style={{flex: '0 0 content'}}>
|
||||||
flex: '0 0 content',
|
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
|
</ModalOverlay>;
|
||||||
{/* Bottom panel */}
|
|
||||||
<div style={{flex: '0 0 content'}}>
|
|
||||||
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TraceableError } from "../statecharts/parser";
|
import { TraceableError } from "../../statecharts/parser";
|
||||||
|
|
||||||
import "./BottomPanel.css";
|
import "./BottomPanel.css";
|
||||||
|
|
||||||
import logo from "../../artwork/logo-playful.svg";
|
import logo from "../../../artwork/logo-playful.svg";
|
||||||
import { PersistentDetailsLocalStorage } from "./PersistentDetails";
|
import { PersistentDetailsLocalStorage } from "../PersistentDetails";
|
||||||
|
|
||||||
export function BottomPanel(props: {errors: TraceableError[]}) {
|
export function BottomPanel(props: {errors: TraceableError[]}) {
|
||||||
const [greeting, setGreeting] = useState(
|
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";
|
import { DetailsHTMLAttributes, Dispatch, PropsWithChildren, SetStateAction } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useAudioContext } from "@/App/useAudioContext";
|
import { useAudioContext } from "@/hooks/useAudioContext";
|
||||||
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "@/statecharts/parser";
|
import { parseStatechart } from "@/statecharts/parser";
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { memo, useEffect } from "react";
|
import { memo, useEffect } from "react";
|
||||||
|
|
||||||
import "./Microwave.css";
|
import "./Microwave.css";
|
||||||
import { useAudioContext } from "../../useAudioContext";
|
import { useAudioContext } from "../../../hooks/useAudioContext";
|
||||||
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "@/statecharts/parser";
|
import { parseStatechart } from "@/statecharts/parser";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { Statechart } from "@/statecharts/abstract_syntax";
|
||||||
import { EventTrigger } from "@/statecharts/label_ast";
|
import { EventTrigger } from "@/statecharts/label_ast";
|
||||||
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
import { BigStep, RaisedEvent, RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
import { statechartExecution, TimedReactive } from "@/statecharts/timed_reactive";
|
||||||
import { setsEqual } from "@/util/util";
|
|
||||||
|
|
||||||
export type PlantRenderProps<StateType> = {
|
export type PlantRenderProps<StateType> = {
|
||||||
state: StateType,
|
state: StateType,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { ConcreteSyntax } from "@/App/VisualEditor/VisualEditor";
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { detectConnections } from "@/statecharts/detect_connections";
|
||||||
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
import { makeStatechartPlant, PlantRenderProps, StatechartPlantSpec } from "../Plant";
|
||||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { useAudioContext } from "@/App/useAudioContext";
|
import { useAudioContext } from "@/hooks/useAudioContext";
|
||||||
import { memo, useEffect } from "react";
|
import { memo, useEffect } from "react";
|
||||||
import { objectsEqual } from "@/util/util";
|
import { objectsEqual } from "@/util/util";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Dispatch, memo, SetStateAction, useCallback } from "react";
|
import { Dispatch, memo, SetStateAction, useCallback } from "react";
|
||||||
import { Statechart, stateDescription } from "../statecharts/abstract_syntax";
|
import { Statechart, stateDescription } from "../../statecharts/abstract_syntax";
|
||||||
import { Mode, RaisedEvent, RT_Event } from "../statecharts/runtime_types";
|
import { Mode, RaisedEvent, RT_Event } from "../../statecharts/runtime_types";
|
||||||
import { formatTime } from "../util/util";
|
import { formatTime } from "../../util/util";
|
||||||
import { TimeMode, timeTravel } from "../statecharts/time";
|
import { TimeMode, timeTravel } from "../../statecharts/time";
|
||||||
import { BigStepCause, TraceItem, TraceState } from "./App";
|
|
||||||
import { Environment } from "@/statecharts/environment";
|
import { Environment } from "@/statecharts/environment";
|
||||||
|
import { BigStepCause, TraceItem, TraceState } from "../hooks/useSimulator";
|
||||||
|
|
||||||
type RTHistoryProps = {
|
type RTHistoryProps = {
|
||||||
trace: TraceState|null,
|
trace: TraceState|null,
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { ConcreteState, UnstableState, stateDescription, Transition } from "../statecharts/abstract_syntax";
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
import { Action, EventTrigger, Expression } from "../statecharts/label_ast";
|
import { memo, useEffect } from "react";
|
||||||
|
import { usePersistentState } from "../../hooks/usePersistentState";
|
||||||
import "./AST.css";
|
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}) {
|
export function ShowTransition(props: {transition: Transition}) {
|
||||||
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
||||||
|
|
@ -46,10 +48,6 @@ export const ShowAST = memo(function ShowASTx(props: {root: ConcreteState | Unst
|
||||||
</li>;
|
</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}) {
|
export function ShowInputEvents({inputEvents, onRaise, disabled, showKeys}: {inputEvents: EventTrigger[], onRaise: (e: string, p: any) => void, disabled: boolean, showKeys: boolean}) {
|
||||||
const raiseHandlers = inputEvents.map(({event}) => {
|
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 { Dispatch, Ref, SetStateAction, useEffect, useRef, useState } from 'react';
|
||||||
import { Statechart } from '@/statecharts/abstract_syntax';
|
import { Statechart } from '@/statecharts/abstract_syntax';
|
||||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from './ShowAST';
|
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from './ShowAST';
|
||||||
import { Plant } from './Plant/Plant';
|
import { Plant } from '../Plant/Plant';
|
||||||
import { checkProperty, PropertyCheckResult } from './check_property';
|
import { checkProperty, PropertyCheckResult } from './check_property';
|
||||||
import { Setters } from './makePartialSetter';
|
import { Setters } from '../makePartialSetter';
|
||||||
import { RTHistory } from './RTHistory';
|
import { RTHistory } from './RTHistory';
|
||||||
import { BigStepCause, TraceState } from './useSimulator';
|
import { BigStepCause, TraceState } from '../hooks/useSimulator';
|
||||||
import { plants, UniversalPlantState } from './plants';
|
import { plants, UniversalPlantState } from '../plants';
|
||||||
import { TimeMode } from '@/statecharts/time';
|
import { TimeMode } from '@/statecharts/time';
|
||||||
import { PersistentDetails } from './PersistentDetails';
|
import { PersistentDetails } from '../PersistentDetails';
|
||||||
|
import "./SideBar.css";
|
||||||
|
|
||||||
type SavedTraces = [string, BigStepCause[]][];
|
type SavedTraces = [string, BigStepCause[]][];
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { RT_Statechart } from "@/statecharts/runtime_types";
|
import { RT_Statechart } from "@/statecharts/runtime_types";
|
||||||
import { Plant } from "./Plant/Plant";
|
import { Plant } from "../Plant/Plant";
|
||||||
import { TraceItem } from "./useSimulator";
|
import { TraceItem } from "../hooks/useSimulator";
|
||||||
|
|
||||||
// const endpoint = "http://localhost:15478/check_property";
|
// const endpoint = "http://localhost:15478/check_property";
|
||||||
const endpoint = "https://deemz.org/apis/mtl-aas/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 SkipNextIcon from '@mui/icons-material/SkipNext';
|
||||||
import StopIcon from '@mui/icons-material/Stop';
|
import StopIcon from '@mui/icons-material/Stop';
|
||||||
import { InsertModes } from "./InsertModes";
|
import { InsertModes } from "./InsertModes";
|
||||||
import { usePersistentState } from "@/App/persistent_state";
|
import { usePersistentState } from "@/hooks/usePersistentState";
|
||||||
import { RotateButtons } from "./RotateButtons";
|
import { RotateButtons } from "./RotateButtons";
|
||||||
import { SpeedControl } from "./SpeedControl";
|
import { SpeedControl } from "./SpeedControl";
|
||||||
import { TraceState } from "../useSimulator";
|
import { TraceState } from "../hooks/useSimulator";
|
||||||
|
|
||||||
export type TopPanelProps = {
|
export type TopPanelProps = {
|
||||||
trace: TraceState | null,
|
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 { Mode } from "@/statecharts/runtime_types";
|
||||||
import { arraysEqual, objectsEqual, setsEqual } from "@/util/util";
|
import { arraysEqual, objectsEqual, setsEqual } from "@/util/util";
|
||||||
import { Arrow, ArrowPart, Diamond, History, RectSide, Rountangle, Text } from "../../statecharts/concrete_syntax";
|
import { Arrow, ArrowPart, Diamond, History, RectSide, Rountangle, Text } from "../../statecharts/concrete_syntax";
|
||||||
import { Connections } from "../../statecharts/detect_connections";
|
import { Connections } from "../../statecharts/detect_connections";
|
||||||
import { TraceableError } from "../../statecharts/parser";
|
import { TraceableError } from "../../statecharts/parser";
|
||||||
import { ArcDirection, arcDirection } from "../../util/geometry";
|
import { ArcDirection, arcDirection } from "../../util/geometry";
|
||||||
|
import { InsertMode } from "../TopPanel/InsertModes";
|
||||||
import { ArrowSVG } from "./ArrowSVG";
|
import { ArrowSVG } from "./ArrowSVG";
|
||||||
import { DiamondSVG } from "./DiamondSVG";
|
import { DiamondSVG } from "./DiamondSVG";
|
||||||
import { HistorySVG } from "./HistorySVG";
|
import { HistorySVG } from "./HistorySVG";
|
||||||
import { RountangleSVG } from "./RountangleSVG";
|
import { RountangleSVG } from "./RountangleSVG";
|
||||||
import { TextSVG } from "./TextSVG";
|
import { TextSVG } from "./TextSVG";
|
||||||
import { useCopyPaste } from "./useCopyPaste";
|
|
||||||
|
|
||||||
import "./VisualEditor.css";
|
import "./VisualEditor.css";
|
||||||
import { useMouse } from "./useMouse";
|
import { useCopyPaste } from "./hooks/useCopyPaste";
|
||||||
|
import { useMouse } from "./hooks/useMouse";
|
||||||
|
|
||||||
export type ConcreteSyntax = {
|
export type ConcreteSyntax = {
|
||||||
rountangles: Rountangle[];
|
rountangles: Rountangle[];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Arrow, Diamond, Rountangle, Text, History } from "@/statecharts/concrete_syntax";
|
import { Arrow, Diamond, Rountangle, Text, History } from "@/statecharts/concrete_syntax";
|
||||||
import { ClipboardEvent, Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
import { ClipboardEvent, Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
||||||
import { Selection, VisualEditorState } from "./VisualEditor";
|
import { Selection, VisualEditorState } from "../VisualEditor";
|
||||||
import { addV2D } from "@/util/geometry";
|
import { addV2D } from "@/util/geometry";
|
||||||
|
|
||||||
// const offset = {x: 40, y: 40};
|
// 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 { addV2D, area, isEntirelyWithin, normalizeRect, scaleV2D, subtractV2D, transformLine, transformRect } from "@/util/geometry";
|
||||||
import { getBBoxInSvgCoords } from "@/util/svg_helper";
|
import { getBBoxInSvgCoords } from "@/util/svg_helper";
|
||||||
import { Dispatch, useCallback, useEffect, useState } from "react";
|
import { Dispatch, useCallback, useEffect, useState } from "react";
|
||||||
import { MIN_ROUNTANGLE_SIZE } from "../parameters";
|
import { MIN_ROUNTANGLE_SIZE } from "../../parameters";
|
||||||
import { InsertMode } from "../TopPanel/InsertModes";
|
import { InsertMode } from "../../TopPanel/InsertModes";
|
||||||
import { Selecting, SelectingState } from "./Selection";
|
import { Selecting, SelectingState } from "../Selection";
|
||||||
import { Selection, VisualEditorState } from "./VisualEditor";
|
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) {
|
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);
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { addV2D, rotateLine90CCW, rotateLine90CW, rotatePoint90CCW, rotatePoint90CW, rotateRect90CCW, rotateRect90CW, scaleV2D, subtractV2D, Vec2D } from "@/util/geometry";
|
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 { Dispatch, SetStateAction, useCallback, useEffect } from "react";
|
||||||
import { EditHistory } from "./App";
|
import { EditHistory } from "../App";
|
||||||
import { VisualEditorState } from "./VisualEditor/VisualEditor";
|
|
||||||
|
|
||||||
export function useEditor(setEditHistory: Dispatch<SetStateAction<EditHistory|null>>) {
|
export function useEditor(setEditHistory: Dispatch<SetStateAction<EditHistory|null>>) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -3,9 +3,9 @@ import { RuntimeError } from "@/statecharts/interpreter";
|
||||||
import { BigStep, RaisedEvent } from "@/statecharts/runtime_types";
|
import { BigStep, RaisedEvent } from "@/statecharts/runtime_types";
|
||||||
import { Conns, coupledExecution, statechartExecution } from "@/statecharts/timed_reactive";
|
import { Conns, coupledExecution, statechartExecution } from "@/statecharts/timed_reactive";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
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 { getSimTime, getWallClkDelay, TimeMode } from "@/statecharts/time";
|
||||||
import { UniversalPlantState } from "./plants";
|
import { UniversalPlantState } from "../plants";
|
||||||
|
|
||||||
type CoupledState = {
|
type CoupledState = {
|
||||||
sc: BigStep,
|
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)
|
// timer elapse events are triggered by a change of the simulated time (possibly as a scheduled JS event loop timeout)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// console.log('time effect:', time, currentTraceItem);
|
||||||
let timeout: NodeJS.Timeout | undefined;
|
let timeout: NodeJS.Timeout | undefined;
|
||||||
if (currentTraceItem !== null && cE !== null) {
|
if (currentTraceItem !== null && cE !== null) {
|
||||||
if (currentTraceItem.kind === "bigstep") {
|
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]>> {
|
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])) => {
|
return (newValueOrCallback: T[K] | ((newValue: T[K]) => T[K])) => {
|
||||||
fullSetter(oldFullValue => {
|
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 {
|
return {
|
||||||
...oldFullValue,
|
...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]>>;
|
[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>>,
|
fullSetter: Dispatch<SetStateAction<T>>,
|
||||||
keys: (keyof T)[],
|
keys: (keyof T)[],
|
||||||
): Setters<T> {
|
): Setters<T> {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return useMemo(() =>
|
return useMemo(() => {
|
||||||
|
console.log('creating setters for App');
|
||||||
// @ts-ignore
|
// @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)];
|
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
|
// 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
|
// 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...');
|
console.log('recovering state...');
|
||||||
const compressedState = window.location.hash.slice(1);
|
const compressedState = window.location.hash.slice(1);
|
||||||
if (compressedState.length === 0) {
|
if (compressedState.length === 0) {
|
||||||
// empty URL hash
|
// empty URL hash
|
||||||
console.log("no state to recover");
|
console.log("no state to recover");
|
||||||
// setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
return recoverCallback(null);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let compressedBuffer;
|
let compressedBuffer;
|
||||||
try {
|
try {
|
||||||
|
|
@ -19,8 +19,7 @@ export function useUrlHashState<T>(recoverCallback: (recoveredState: T) => void)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// probably invalid base64
|
// probably invalid base64
|
||||||
console.error("failed to recover state:", e);
|
console.error("failed to recover state:", e);
|
||||||
// setEditHistory(() => ({current: emptyState, history: [], future: []}));
|
return recoverCallback(null);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const ds = new DecompressionStream("deflate");
|
const ds = new DecompressionStream("deflate");
|
||||||
const writer = ds.writable.getWriter();
|
const writer = ds.writable.getWriter();
|
||||||
|
|
@ -29,12 +28,13 @@ export function useUrlHashState<T>(recoverCallback: (recoveredState: T) => void)
|
||||||
new Response(ds.readable).arrayBuffer()
|
new Response(ds.readable).arrayBuffer()
|
||||||
.then(decompressedBuffer => {
|
.then(decompressedBuffer => {
|
||||||
const recoveredState = JSON.parse(new TextDecoder().decode(decompressedBuffer));
|
const recoveredState = JSON.parse(new TextDecoder().decode(decompressedBuffer));
|
||||||
|
console.log('successfully recovered state');
|
||||||
recoverCallback(recoveredState);
|
recoverCallback(recoveredState);
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
// any other error: invalid JSON, or decompression failed.
|
// any other error: invalid JSON, or decompression failed.
|
||||||
console.error("failed to recover state:", e);
|
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