remove dependency on MUI

This commit is contained in:
Joeri Exelmans 2025-10-24 11:09:21 +02:00
parent 74f4c3bead
commit 3e192f8e26
8 changed files with 72 additions and 44 deletions

View file

@ -4,10 +4,8 @@
"": { "": {
"name": "bun-react-template", "name": "bun-react-template",
"dependencies": { "dependencies": {
"@emotion/styled": "^11.14.1",
"@fontsource/roboto": "^5.2.8", "@fontsource/roboto": "^5.2.8",
"@mui/icons-material": "^7.3.4", "@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"react": "^19", "react": "^19",
"react-dom": "^19", "react-dom": "^19",
}, },

View file

@ -11,10 +11,8 @@
"start": "NODE_ENV=production bun src/index.tsx" "start": "NODE_ENV=production bun src/index.tsx"
}, },
"dependencies": { "dependencies": {
"@emotion/styled": "^11.14.1",
"@fontsource/roboto": "^5.2.8", "@fontsource/roboto": "^5.2.8",
"@mui/icons-material": "^7.3.4", "@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"react": "^19", "react": "^19",
"react-dom": "^19" "react-dom": "^19"
}, },

View file

@ -94,3 +94,13 @@ button.active {
max-height: 100vh; max-height: 100vh;
overflow: auto; overflow: auto;
} }
div.stackVertical {
display: flex;
flex-direction: column;
}
div.stackHorizontal {
display: flex;
flex-direction: row;
}

View file

@ -8,8 +8,6 @@ import { getSimTime, getWallClkDelay, TimeMode } from "../statecharts/time";
import "../index.css"; import "../index.css";
import "./App.css"; import "./App.css";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { TopPanel } from "./TopPanel"; import { TopPanel } from "./TopPanel";
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST"; import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from "./ShowAST";
import { parseStatechart } from "../statecharts/parser"; import { parseStatechart } from "../statecharts/parser";
@ -92,6 +90,7 @@ export function App() {
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
console.log("no state to recover"); console.log("no state to recover");
setEditHistory(() => ({current: emptyState, history: [], future: []})); setEditHistory(() => ({current: emptyState, history: [], future: []}));
return; return;
@ -100,6 +99,7 @@ export function App() {
try { try {
compressedBuffer = Uint8Array.fromBase64(compressedState); // may throw compressedBuffer = Uint8Array.fromBase64(compressedState); // may throw
} catch (e) { } catch (e) {
// probably invalid base64
console.error("failed to recover state:", e); console.error("failed to recover state:", e);
setEditHistory(() => ({current: emptyState, history: [], future: []})); setEditHistory(() => ({current: emptyState, history: [], future: []}));
return; return;
@ -114,6 +114,7 @@ export function App() {
setEditHistory(() => ({current: recoveredState, history: [], future: []})); setEditHistory(() => ({current: recoveredState, history: [], future: []}));
}) })
.catch(e => { .catch(e => {
// 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: []}); setEditHistory({current: emptyState, history: [], future: []});
}); });
@ -370,16 +371,16 @@ export function App() {
</div> </div>
</div>} </div>}
<Stack sx={{height:'100%'}}> <div className="stackVertical" style={{height:'100%'}}>
<Stack direction="row" sx={{flexGrow:1, overflow: "auto"}}> <div className="stackHorizontal" style={{flexGrow:1, overflow: "auto"}}>
{/* Left: top bar and main editor */} {/* Left: top bar and main editor */}
<Box sx={{flexGrow:1, overflow: "auto"}}> <div style={{flexGrow:1, overflow: "auto"}}>
<Stack sx={{height:'100%'}}> <div style={{height:'100%'}}>
{/* Top bar */} {/* Top bar */}
<Box <div
className="shadowBelow" className="shadowBelow"
sx={{ style={{
display: "flex", display: "flex",
borderBottom: 1, borderBottom: 1,
borderColor: "divider", borderColor: "divider",
@ -390,16 +391,16 @@ export function App() {
{editHistory && <TopPanel {editHistory && <TopPanel
{...{trace, time, setTime, onUndo, onRedo, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}} {...{trace, time, setTime, onUndo, onRedo, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}}
/>} />}
</Box> </div>
{/* Below the top bar: Editor */} {/* Below the top bar: Editor */}
<Box sx={{flexGrow:1, overflow: "auto"}}> <div style={{flexGrow:1, overflow: "auto"}}>
{editorState && conns && syntaxErrors && <VisualEditor {...{state: editorState, setState: setEditorState, conns, trace, setTrace, syntaxErrors, insertMode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}}/>} {editorState && conns && syntaxErrors && <VisualEditor {...{state: editorState, setState: setEditorState, conns, trace, setTrace, syntaxErrors, insertMode, highlightActive, highlightTransitions, setModal, makeCheckPoint, zoom}}/>}
</Box> </div>
</Stack> </div>
</Box> </div>
{/* Right: sidebar */} {/* Right: sidebar */}
<Box sx={{ <div style={{
borderLeft: 1, borderLeft: 1,
borderColor: "divider", borderColor: "divider",
flex: '0 0 content', flex: '0 0 content',
@ -407,10 +408,10 @@ export function App() {
overflowX: "visible", overflowX: "visible",
maxWidth: 'min(300px, 30vw)', maxWidth: 'min(300px, 30vw)',
}}> }}>
<Stack sx={{height:'100%'}}> <div className="stackVertical" style={{height:'100%'}}>
<Box <div
className={showExecutionTrace ? "shadowBelow" : ""} className={showExecutionTrace ? "shadowBelow" : ""}
sx={{flex: '0 0 content', backgroundColor: ''}} style={{flex: '0 0 content', backgroundColor: ''}}
> >
<PersistentDetails localStorageKey="showStateTree" initiallyOpen={true}> <PersistentDetails localStorageKey="showStateTree" initiallyOpen={true}>
<summary>state tree</summary> <summary>state tree</summary>
@ -447,36 +448,34 @@ export function App() {
plant.render(trace.trace[trace.idx].plantState, event => onRaise(event.name, event.param))} plant.render(trace.trace[trace.idx].plantState, event => onRaise(event.name, event.param))}
</PersistentDetails> </PersistentDetails>
<details open={showExecutionTrace} onToggle={e => setShowExecutionTrace(e.newState === "open")}><summary>execution trace</summary></details> <details open={showExecutionTrace} onToggle={e => setShowExecutionTrace(e.newState === "open")}><summary>execution trace</summary></details>
</Box> </div>
{/* We cheat a bit, and render the execution trace depending on whether the <details> above is 'open' or not, rather than putting it as a child of the <details>. We do this because only then can we get the execution trace to scroll without the rest scrolling as well. */}
{showExecutionTrace && {showExecutionTrace &&
<Box sx={{ <div style={{
flexGrow:1, flexGrow:1,
overflow:'auto', overflow:'auto',
minHeight: '50vh', minHeight: '50vh',
// minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view // minHeight: '75%', // <-- allows us to always scroll down the sidebar far enough such that the execution history is enough in view
}}> }}>
{/* <PersistentDetails localStorageKey="showExecutionTrace" initiallyOpen={true}> */}
{/* <summary>execution trace</summary> */}
<div ref={refRightSideBar}> <div ref={refRightSideBar}>
{ast && <RTHistory {...{ast, trace, setTrace, setTime}}/>} {ast && <RTHistory {...{ast, trace, setTrace, setTime}}/>}
</div> </div>
{/* </PersistentDetails> */} </div>}
</Box>}
<Box sx={{flex: '0 0 content'}}> <div style={{flex: '0 0 content'}}>
</Box> </div>
</Stack> </div>
</Box> </div>
</Stack> </div>
{/* Bottom panel */} {/* Bottom panel */}
<Box sx={{flex: '0 0 content'}}> <div style={{flex: '0 0 content'}}>
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>} {syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
</Box> </div>
</Stack> </div>
</>; </>;
} }

View file

@ -3,11 +3,16 @@ import { TraceableError } from "../statecharts/parser";
import "./BottomPanel.css"; import "./BottomPanel.css";
import head from "../head.svg" ; import logo from "../../artwork/logo-playful.svg";
import { PersistentDetails } from "./PersistentDetails"; import { PersistentDetails } from "./PersistentDetails";
export function BottomPanel(props: {errors: TraceableError[]}) { export function BottomPanel(props: {errors: TraceableError[]}) {
const [greeting, setGreeting] = useState(<><b><img src={head} style={{transform: "scaleX(-1)"}}/>&emsp;"Welcome to StateBuddy, buddy!"</b><br/></>); const [greeting, setGreeting] = useState(
<div style={{textAlign:'center'}}>
<span style={{fontSize: 18, fontStyle: 'italic'}}>
Welcome to <img src={logo} style={{maxWidth:'100%'}}/>
</span>
</div>);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {

View file

@ -13,6 +13,7 @@ export type Connections = {
} }
export function detectConnections(state: VisualEditorState): Connections { export function detectConnections(state: VisualEditorState): Connections {
const startTime = performance.now();
// detect what is 'connected' // detect what is 'connected'
const arrow2SideMap = new Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>(); const arrow2SideMap = new Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>();
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>(); const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
@ -72,6 +73,11 @@ export function detectConnections(state: VisualEditorState): Connections {
} }
} }
const endTime = performance.now();
// rather slow, about 10ms for a large model:
// console.debug("connection detection took", endTime-startTime);
return { return {
arrow2SideMap, arrow2SideMap,
side2ArrowMap, side2ArrowMap,

View file

@ -78,6 +78,8 @@ export function parseStatechart(state: VisualEditorState, conns: Connections): [
// step 1: figure out state hierarchy // step 1: figure out state hierarchy
const startTime = performance.now();
// IMPORTANT ASSUMPTION: state.rountangles is sorted from big to small surface area: // IMPORTANT ASSUMPTION: state.rountangles is sorted from big to small surface area:
for (const rt of state.rountangles) { for (const rt of state.rountangles) {
const parent = findParent(rt); const parent = findParent(rt);
@ -132,6 +134,11 @@ export function parseStatechart(state: VisualEditorState, conns: Connections): [
historyStates.push(historyState); historyStates.push(historyState);
} }
const endTime = performance.now();
// currently seems to be quite fast:
// console.log('built state tree', endTime-startTime);
// step 2: figure out transitions // step 2: figure out transitions
const transitions = new Map<string, Transition[]>(); const transitions = new Map<string, Transition[]>();

View file

@ -27,8 +27,9 @@
TODO TODO
- digital watch: - testing
highlight when watch button pressed/released use STL for testing
https://github.com/mvcisback/py-metric-temporal-logic
- maybe support: - maybe support:
- explicit order of: - explicit order of:
@ -51,17 +52,21 @@ TODO
don't crash and show the error don't crash and show the error
- buttons to rotate selection 90 degrees - buttons to rotate selection 90 degrees
- performance:
maybe try this for rendering the execution trace:
https://legacy.reactjs.org/docs/optimizing-performance.html#virtualize-long-lists
- experimental features: - experimental features:
- multiverse execution history - multiverse execution history
stable tree layout? stable tree layout?
https://pub.dev/packages/ploeg_tree_layout https://pub.dev/packages/ploeg_tree_layout
- local scopes - local scopes
for the assignment: for the assignment:
*ALL* features *ALL* features
add history (look at original Harel paper) add history (look at original Harel paper)
add microwave oven
add traffic light
Publish StateBuddy paper(s): Publish StateBuddy paper(s):
compare CS approach to other tools, not only YAKINDU compare CS approach to other tools, not only YAKINDU