diff --git a/src/App/App.css b/src/App/App.css index 02789ac..2929afe 100644 --- a/src/App/App.css +++ b/src/App/App.css @@ -47,8 +47,7 @@ details:has(+ details) { } .toolbar input { - height: 26px; - box-sizing: border-box; + height: 22px; } .toolbar div { vertical-align: bottom; diff --git a/src/App/App.tsx b/src/App/App.tsx index 8390c59..727e22c 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -15,11 +15,10 @@ import { useEditor } from "./hooks/useEditor"; import { useSimulator } from "./hooks/useSimulator"; import { useUrlHashState } from "../hooks/useUrlHashState"; import { plants } from "./plants"; -import { initialEditorState } from "@/statecharts/concrete_syntax"; +import { emptyState } from "@/statecharts/concrete_syntax"; import { ModalOverlay } from "./Overlays/ModalOverlay"; import { FindReplace } from "./BottomPanel/FindReplace"; import { useCustomMemo } from "@/hooks/useCustomMemo"; -import { usePersistentState } from "@/hooks/usePersistentState"; export type EditHistory = { current: VisualEditorState, @@ -52,8 +51,6 @@ export function App() { const [editHistory, setEditHistory] = useState(null); const [modal, setModal] = useState(null); - const [[findText, replaceText], setFindReplaceText] = usePersistentState("findReplaceTxt", ["", ""]); - const {commitState, replaceState, onRedo, onUndo, onRotate} = useEditor(setEditHistory); const editorState = editHistory && editHistory.current; @@ -70,17 +67,9 @@ export function App() { ([prevState, prevConns], [nextState, nextConns]) => { if ((prevState === null) !== (nextState === null)) return false; if ((prevConns === null) !== (nextConns === null)) return false; - if (prevConns === null) { - return nextConns === null; - } - if (prevState === null) { - return nextState === null; - } - if (nextConns === null) return false; - if (nextState === null) return false; // the following check is much cheaper than re-rendering everything that depends on - return connectionsEqual(prevConns, nextConns) - && reducedConcreteSyntaxEqual(prevState, nextState); + return connectionsEqual(prevConns!, nextConns!) + && reducedConcreteSyntaxEqual(prevState!, nextState!); }); const ast = parsed && parsed[0]; @@ -89,7 +78,7 @@ export function App() { const persist = useUrlHashState( recoveredState => { if (recoveredState === null) { - setEditHistory(() => ({current: initialEditorState, history: [], future: []})); + setEditHistory(() => ({current: emptyState, history: [], future: []})); } // we support two formats // @ts-ignore @@ -172,7 +161,7 @@ export function App() {
{/* top-to-bottom: top bar, editor */} -
+
{/* Top bar */}
{editorState && conns && syntaxErrors && - } + }
- {editorState && appState.showFindReplace && -
- setters.setShowFindReplace(false)}/> + {appState.showFindReplace && +
+ setters.setShowFindReplace(false)}/>
} diff --git a/src/App/BottomPanel/BottomPanel.css b/src/App/BottomPanel/BottomPanel.css index 1725d16..098ef48 100644 --- a/src/App/BottomPanel/BottomPanel.css +++ b/src/App/BottomPanel/BottomPanel.css @@ -1,27 +1,12 @@ -.statusBar { - background-color: var(--statusbar-bg-color); - color: var(--statusbar-fg-color); -} - -.statusBar.error { - background-color: var(--statusbar-error-bg-color); - color: var(--statusbar-error-fg-color); -} - -.statusBar summary:hover { - background-color: color-mix(in srgb, var(--statusbar-bg-color) 60%, var(--background-color)); -} -.statusBar.error summary:hover { - background-color: color-mix(in srgb, var(--statusbar-error-bg-color) 70%, var(--text-color)); -} - -.statusBar a { - color: inherit; +.errorStatus { + /* background-color: rgb(230,0,0); */ + background-color: var(--error-color); + color: var(--background-color); } .greeter { /* border-top: 1px var(--separator-color) solid; */ - background-color: var(--background-color); + background-color: var(--greeter-bg-color); } .bottom { diff --git a/src/App/BottomPanel/BottomPanel.tsx b/src/App/BottomPanel/BottomPanel.tsx index bf1258d..d1f5e3a 100644 --- a/src/App/BottomPanel/BottomPanel.tsx +++ b/src/App/BottomPanel/BottomPanel.tsx @@ -6,6 +6,7 @@ import "./BottomPanel.css"; import { PersistentDetailsLocalStorage } from "../Components/PersistentDetails"; import { Logo } from "@/App/Logo/Logo"; import { AppState } from "../App"; +import { FindReplace } from "./FindReplace"; import { VisualEditorState } from "../VisualEditor/VisualEditor"; import { Setters } from "../makePartialSetter"; @@ -13,10 +14,9 @@ import gitRev from "@/git-rev.txt"; export function BottomPanel(props: {errors: TraceableError[], setEditorState: Dispatch<(state: VisualEditorState) => VisualEditorState>} & AppState & Setters) { const [greeting, setGreeting] = useState( -
setGreeting(<>)}> +
- Welcome to - + Welcome to
); @@ -26,35 +26,23 @@ export function BottomPanel(props: {errors: TraceableError[], setEditorState: Di }, 2000); }, []); - return
- {greeting} + return
{/* {props.showFindReplace &&
props.setShowFindReplace(false)}/>
} */} -
-
+
- {props.errors.length} errors -
- {props.errors.map(({message, shapeUid})=> -
- {shapeUid}: {message} -
)} -
-
-
-
- switch to  - {location.host === "localhost:3000" ? - production - : development - } -  mode -  |  - Rev: {gitRev.slice(0,8)} -
+ {props.errors.length} errors +
+ {props.errors.map(({message, shapeUid})=> +
+ {shapeUid}: {message} +
)} +
+
+ {greeting}
; } diff --git a/src/App/BottomPanel/FindReplace.tsx b/src/App/BottomPanel/FindReplace.tsx index 2b9b85b..4ea9326 100644 --- a/src/App/BottomPanel/FindReplace.tsx +++ b/src/App/BottomPanel/FindReplace.tsx @@ -1,82 +1,48 @@ -import { Dispatch, FormEvent, SetStateAction, useCallback } from "react"; +import { Dispatch, useCallback, useEffect } from "react"; import { VisualEditorState } from "../VisualEditor/VisualEditor"; +import { usePersistentState } from "@/hooks/usePersistentState"; import CloseIcon from '@mui/icons-material/Close'; -import SwapVertIcon from '@mui/icons-material/SwapVert'; +import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; +import { useShortcuts } from "@/hooks/useShortcuts"; type FindReplaceProps = { - findText: string, - replaceText: string, - setFindReplaceText: Dispatch>, - cs: VisualEditorState, setCS: Dispatch<(oldState: VisualEditorState) => VisualEditorState>, + // setModal: (modal: null) => void; hide: () => void, }; -export function FindReplace({findText, replaceText, setFindReplaceText, cs, setCS, hide}: FindReplaceProps) { +export function FindReplace({setCS, hide}: FindReplaceProps) { + const [findTxt, setFindText] = usePersistentState("findTxt", ""); + const [replaceTxt, setReplaceTxt] = usePersistentState("replaceTxt", ""); + const onReplace = useCallback(() => { setCS(cs => { return { ...cs, texts: cs.texts.map(txt => ({ ...txt, - text: txt.text.replaceAll(findText, replaceText) + text: txt.text.replaceAll(findTxt, replaceTxt) })), }; }); - }, [findText, replaceText, setCS]); + }, [findTxt, replaceTxt]); + + useShortcuts([ + {keys: ["Enter"], action: onReplace}, + ]) const onSwap = useCallback(() => { - setFindReplaceText(([findText, replaceText]) => [replaceText, findText]); - }, [findText, replaceText]); + setReplaceTxt(findTxt); + setFindText(replaceTxt); + }, [findTxt, replaceTxt]); - const onSubmit = useCallback((e: FormEvent) => { - e.preventDefault(); - e.stopPropagation(); - onReplace(); - // onSwap(); - }, [findText, replaceText, onSwap, onReplace]); - - const n = findText === "" ? 0 : cs.texts.reduce((count, txt) => count+(txt.text.indexOf(findText) !== -1 ? 1: 0), 0); - - return
-
-
- setFindReplaceText(([_, replaceText]) => [e.target.value, replaceText])} - style={{flexGrow: 1, minWidth: 20}}/> -
- setFindReplaceText(([findText, _]) => [findText, e.target.value]))} - style={{flexGrow: 1, minWidth: 20}}/> -
-
-
- - -
- -
-
-
; + return
+ setFindText(e.target.value)} style={{width:300}}/> + + setReplaceTxt(e.target.value))} style={{width:300}}/> +   + + +
; } \ No newline at end of file diff --git a/src/App/Logo/Logo.tsx b/src/App/Logo/Logo.tsx index e86a7fa..1aa429f 100644 --- a/src/App/Logo/Logo.tsx +++ b/src/App/Logo/Logo.tsx @@ -1,8 +1,6 @@ -import { SVGAttributes, SVGProps } from "react"; - // i couldn't find a better way to make the text in the logo adapt to light/dark mode... -export function Logo(props: SVGAttributes) { - return +export function Logo() { + return
; }) }, (prevProps, nextProps) => { + console.log('onRaise changed:', prevProps.onRaise === nextProps.onRaise, prevProps.onRaise, nextProps.onRaise); return prevProps.onRaise === nextProps.onRaise && prevProps.disabled === nextProps.disabled && jsonDeepEqual(prevProps.inputEvents, nextProps.inputEvents); diff --git a/src/App/SideBar/SideBar.tsx b/src/App/SideBar/SideBar.tsx index a32e373..a6e6048 100644 --- a/src/App/SideBar/SideBar.tsx +++ b/src/App/SideBar/SideBar.tsx @@ -196,7 +196,7 @@ export const SideBar = memo(function SideBar({showExecutionTrace, showConnection - setProperties(properties => properties.toSpliced(i, 1, e.target.value))} placeholder='write MTL property...'/> + setProperties(properties => properties.toSpliced(i, 1, e.target.value))}/> diff --git a/src/App/TopPanel/TopPanel.tsx b/src/App/TopPanel/TopPanel.tsx index 32ed918..32f8209 100644 --- a/src/App/TopPanel/TopPanel.tsx +++ b/src/App/TopPanel/TopPanel.tsx @@ -246,5 +246,11 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
+
+ {location.host === "localhost:3000" ? + production + : development + } +
; }); diff --git a/src/App/VisualEditor/ArrowSVG.tsx b/src/App/VisualEditor/ArrowSVG.tsx index 6e322b3..d09128d 100644 --- a/src/App/VisualEditor/ArrowSVG.tsx +++ b/src/App/VisualEditor/ArrowSVG.tsx @@ -32,7 +32,7 @@ export const ArrowSVG = memo(function(props: { arrow: Arrow; selected: ArrowPart data-parts="start end" /> {props.error && {props.rountangle.uid} {props.error && - {props.error}} + {props.error}} ) { - if (start !== -1 && start !== end) { - return - {text.slice(0, start)} - - {text.slice(start, end)} +export const TextSVG = memo(function TextSVG(props: {text: Text, error: TraceableError|undefined, selected: boolean, highlight: boolean, onEdit: (text: Text, newText: string) => void, setModal: Dispatch>}) { + const commonProps = { + "data-uid": props.text.uid, + "data-parts": "text", + textAnchor: "middle" as "middle", + className: "draggableText" + + (props.selected ? " selected":"") + + (props.highlight ? " highlight":""), + style: {whiteSpace: "preserve"}, + } + + let textNode; + if (props.error?.data?.location) { + const {start,end} = props.error.data.location; + textNode = <> + {props.text.text.slice(0, start.offset)} + + {props.text.text.slice(start.offset, end.offset)} + {start.offset === end.offset && <>_} - {text.slice(end)} - ; + {props.text.text.slice(end.offset)} + + {props.error.message}; } else { - return - {text} - + textNode = {props.text.text}; } -} - -export const TextSVG = memo(function TextSVG(props: {text: Text, error: TraceableError|undefined, selected: boolean, highlight: boolean, onEdit: (text: Text, newText: string) => void, setModal: Dispatch>, findText: string}) { - - const className = "draggableText" - + (props.selected ? " selected":"") - + (props.highlight ? " highlight":"") - + (props.error ? " error":""); - - const found = props.text.text.indexOf(props.findText); - const start = (found >= 0) ? found : -1 - const end = (found >= 0) ? found + props.findText.length : -1; - - const textNode = ; return {textNode} {props.text.text} - {props.error && - {props.error.message} - } ; }, (prevProps, newProps) => { return jsonDeepEqual(prevProps.text, newProps) @@ -65,5 +52,4 @@ export const TextSVG = memo(function TextSVG(props: {text: Text, error: Traceabl && prevProps.setModal === newProps.setModal && prevProps.error === newProps.error && prevProps.selected === newProps.selected - && prevProps.findText === newProps.findText }); diff --git a/src/App/VisualEditor/VisualEditor.css b/src/App/VisualEditor/VisualEditor.css index 2679255..4153853 100644 --- a/src/App/VisualEditor/VisualEditor.css +++ b/src/App/VisualEditor/VisualEditor.css @@ -112,6 +112,10 @@ line.selected, circle.selected { stroke-width: 4px; } +.draggableText.selected, .draggableText.selected:hover { + fill: var(--accent-border-color); + font-weight: 600; +} text.helper { fill: rgba(0,0,0,0); stroke: rgba(0,0,0,0); @@ -122,41 +126,24 @@ text.helper:hover { cursor: grab; } -.draggableText { +.draggableText, .draggableText.highlight { paint-order: stroke; fill: var(--text-color); stroke: var(--background-color); stroke-width: 4px; - text-anchor: middle; - white-space: preserve; -} -.draggableText.selected { - fill: var(--accent-border-color); - font-weight: 600; + stroke-linecap: butt; + stroke-linejoin: miter; + stroke-opacity: 1; + fill-opacity:1; } + .draggableText.highlight:not(.selected) { fill: var(--associated-color); -} -.draggableText.error:not(.selected) { - fill: var(--error-color); + font-weight: 600; } -text.errorHover { - display: none; - paint-order: stroke; - fill: var(--error-color); - stroke: var(--background-color); - stroke-width: 4px; - font-weight: 600; - white-space: preserve; -} -g:hover > text.errorHover { - display: inline; -} - -tspan.findText { - fill: var(--foundtext-color); - font-weight: 600; +.draggableText.selected { + fill: var(--accent-border-color); } .highlight:not(.selected):not(text) { @@ -165,7 +152,7 @@ tspan.findText { fill: none; } -.arrow.error:not(.selected) { +.arrow.error { stroke: var(--error-color); } .arrow.fired { @@ -185,13 +172,19 @@ tspan.findText { } } -/* .errorHover { + +text.error, tspan.error { + fill: var(--error-color); + font-weight: 600; +} + +.errorHover { display: none; } g:hover > .errorHover { display: inline; -} */ +} text.uid { fill: var(--separator-color); diff --git a/src/App/VisualEditor/VisualEditor.tsx b/src/App/VisualEditor/VisualEditor.tsx index e11e4ca..0a0b2d6 100644 --- a/src/App/VisualEditor/VisualEditor.tsx +++ b/src/App/VisualEditor/VisualEditor.tsx @@ -52,10 +52,9 @@ type VisualEditorProps = { highlightTransitions: string[], setModal: Dispatch>, zoom: number; - findText: string; }; -export const VisualEditor = memo(function VisualEditor({state, commitState, replaceState, conns, syntaxErrors: errors, insertMode, highlightActive, highlightTransitions, setModal, zoom, findText}: VisualEditorProps) { +export const VisualEditor = memo(function VisualEditor({state, commitState, replaceState, conns, syntaxErrors: errors, insertMode, highlightActive, highlightTransitions, setModal, zoom}: VisualEditorProps) { // While dragging, the editor is in a temporary state (a state that is not committed to the edit history). If the temporary state is not null, then this state will be what you see. // const [temporaryState, setTemporaryState] = useState(null); @@ -200,6 +199,8 @@ export const VisualEditor = memo(function VisualEditor({state, commitState, repl + {(rootErrors.length>0) && {rootErrors.join(' ')}} + @@ -234,9 +235,7 @@ export const VisualEditor = memo(function VisualEditor({state, commitState, repl } )} - - - {(rootErrors.length>0) && {rootErrors.join('\n')}} + {selectionRect} ; @@ -283,7 +282,7 @@ const Diamonds = memo(function Diamonds({diamonds, selection, sidesToHighlight, && arraysEqual(p.errors, n.errors); }); -const Texts = memo(function Texts({texts, selection, textsToHighlight, errors, onEditText, setModal, findText}: {texts: Text[], selection: Selection, textsToHighlight: {[key: string]: boolean}, errors: TraceableError[], onEditText: (text: Text, newText: string) => void, setModal: Dispatch>, findText: string}) { +const Texts = memo(function Texts({texts, selection, textsToHighlight, errors, onEditText, setModal}: {texts: Text[], selection: Selection, textsToHighlight: {[key: string]: boolean}, errors: TraceableError[], onEditText: (text: Text, newText: string) => void, setModal: Dispatch>}) { return <>{texts.map(txt => { return })}; }, (p, n) => { @@ -302,7 +300,6 @@ const Texts = memo(function Texts({texts, selection, textsToHighlight, errors, o && objectsEqual(p.textsToHighlight, n.textsToHighlight) && arraysEqual(p.errors, n.errors) && p.onEditText === n.onEditText - && p.setModal === n.setModal - && p.findText === n.findText; + && p.setModal === n.setModal; }); diff --git a/src/App/VisualEditor/hooks/useMouse.tsx b/src/App/VisualEditor/hooks/useMouse.tsx index 6c5ebcf..a89b08b 100644 --- a/src/App/VisualEditor/hooks/useMouse.tsx +++ b/src/App/VisualEditor/hooks/useMouse.tsx @@ -239,7 +239,7 @@ export function useMouse( ...t, topLeft: addV2D(t.topLeft, pointerDelta), } - }).toSorted((a,b) => a.topLeft.y - b.topLeft.y), + }), })); setDragging(true); } diff --git a/src/App/hooks/useSimulator.ts b/src/App/hooks/useSimulator.ts index 1e68792..1bd5b9f 100644 --- a/src/App/hooks/useSimulator.ts +++ b/src/App/hooks/useSimulator.ts @@ -138,6 +138,8 @@ export function useSimulator(ast: Statechart|null, plant: Plant { // console.log('time effect:', time, currentTraceItem); diff --git a/src/frontend.tsx b/src/frontend.tsx index e95c329..1e00a46 100644 --- a/src/frontend.tsx +++ b/src/frontend.tsx @@ -11,9 +11,9 @@ import { App } from "./App/App"; const elem = document.getElementById("root")!; const app = ( - + // - + // ); if (import.meta.hot) { diff --git a/src/hooks/usePersistentState.ts b/src/hooks/usePersistentState.ts index 137ea12..d59681e 100644 --- a/src/hooks/usePersistentState.ts +++ b/src/hooks/usePersistentState.ts @@ -3,7 +3,6 @@ import { Dispatch, SetStateAction, useCallback, useState } from "react"; // like useState, but it is persisted in localStorage // important: values must be JSON-(de-)serializable export function usePersistentState(key: string, initial: T): [T, Dispatch>] { - console.log('usePersistentState ---', key); const [state, setState] = useState(() => { const recovered = localStorage.getItem(key); let parsed; diff --git a/src/index.css b/src/index.css index 6444175..78b28ca 100644 --- a/src/index.css +++ b/src/index.css @@ -16,12 +16,6 @@ html, body { --light-accent-color: light-dark(rgba(0,0,255,0.2), rgba(78, 186, 248, 0.377)); --accent-border-color: light-dark(blue, rgb(64, 185, 255)); --accent-opaque-color: light-dark(#ccccff, #305b73); - - --statusbar-bg-color: light-dark(rgb(225, 229, 235), rgb(48, 48, 68)); - --statusbar-fg-color: light-dark(rgb(0, 0, 0), white); - --statusbar-error-bg-color: light-dark(rgb(163, 0, 0), rgb(255, 82, 82)); - --statusbar-error-fg-color: light-dark(white, black); - --separator-color: light-dark(lightgrey, rgb(58, 58, 58)); --inactive-bg-color: light-dark(#f7f7f7, rgb(29, 29, 29)); --inactive-fg-color: light-dark(grey, rgb(70, 70, 70)); @@ -46,7 +40,6 @@ html, body { --input-event-hover-bg-color: light-dark(rgb(195, 224, 176), rgb(59, 88, 40)); --input-event-active-bg-color: light-dark(rgb(176, 204, 158), rgb(77, 117, 53)); --output-event-bg-color: light-dark(rgb(230, 249, 255), rgb(28, 83, 104)); - --foundtext-color: light-dark(rgb(0, 189, 164), rgb(202, 149, 218)); background-color: var(--background-color); color: var(--text-color); @@ -57,16 +50,16 @@ input { border: 1px solid var(--separator-color); } -button, input[type="submit"] { +button { background-color: var(--button-bg-color); border: 1px var(--separator-color) solid; } -button:not(:disabled):hover, input[type="submit"]:not(:disabled):hover { +button:not(:disabled):hover { background-color: var(--light-accent-color); } -button:disabled, input[type="submit"]:disabled { +button:disabled { background-color: var(--inactive-bg-color); color: var(--inactive-fg-color); } diff --git a/src/statecharts/concrete_syntax.ts b/src/statecharts/concrete_syntax.ts index ffd1ca6..c74b9b9 100644 --- a/src/statecharts/concrete_syntax.ts +++ b/src/statecharts/concrete_syntax.ts @@ -40,23 +40,8 @@ export type ConcreteSyntax = { export type RectSide = "left" | "top" | "right" | "bottom"; export type ArrowPart = "start" | "end"; -export const initialEditorState: VisualEditorState = { - rountangles: [{ - uid:"0", - topLeft:{x:76.25,y:122.5}, - size:{x:133.75,y:103.75}, - kind:"and" - }], - diamonds:[], - history:[], - arrows:[{ - uid:"39", - start:{x:85,y:67.5}, - end:{x:116.25,y:116.25} - }], - texts:[], - nextID: 1, - selection: [], +export const emptyState: VisualEditorState = { + rountangles: [], texts: [], arrows: [], diamonds: [], history: [], nextID: 0, selection: [], }; // used to find which rountangle an arrow connects to (src/tgt) diff --git a/src/statecharts/parser.ts b/src/statecharts/parser.ts index 89d7c8a..a3287c4 100644 --- a/src/statecharts/parser.ts +++ b/src/statecharts/parser.ts @@ -203,7 +203,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co else if (state.initial.length === 0) { errors.push({ shapeUid: state.uid, - message: "needs initial state", + message: "no initial state", }); } } @@ -216,8 +216,8 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co // step 3: figure out labels - // ASSUMPTION: text is sorted by y-coordinate - for (const text of concreteSyntax.texts) { + const textsSorted = concreteSyntax.texts.toSorted((a,b) => a.topLeft.y - b.topLeft.y); + for (const text of textsSorted) { let parsed: ParsedText; try { parsed = cachedParseLabel(text.text); // may throw @@ -226,7 +226,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co if (e instanceof SyntaxError) { errors.push({ shapeUid: text.uid, - message: 'parser: ' + e.message, + message: e.message, data: e, }); parsed = { @@ -248,7 +248,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co // triggers if (parsed.trigger.kind === "event") { if (src.kind === "pseudo") { - errors.push({shapeUid: text.uid, message: "cannot have trigger"}); + errors.push({shapeUid: text.uid, message: "pseudo state outgoing transition must not have event trigger"}); } else { const {event} = parsed.trigger; @@ -262,7 +262,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co } else if (parsed.trigger.kind === "after") { if (src.kind === "pseudo") { - errors.push({shapeUid: text.uid, message: "cannot have trigger"}); + errors.push({shapeUid: text.uid, message: "pseudo state outgoing transition must not have after-trigger"}); } else { src.timers.push(parsed.trigger.durationMs); @@ -274,7 +274,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co } else if (parsed.trigger.kind === "triggerless") { if (src.kind !== "pseudo") { - errors.push({shapeUid: text.uid, message: "needs trigger"}); + errors.push({shapeUid: text.uid, message: "triggerless transitions only allowed on pseudo-states"}); } } } @@ -296,7 +296,7 @@ export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Co else { errors.push({ shapeUid: text.uid, - message: "must belong to transition", + message: "states can only have entry/exit triggers", data: {start: {offset: 0}, end: {offset: text.text.length}}, }); } diff --git a/todo.txt b/todo.txt index 9f5b2d3..e0f1cd1 100644 --- a/todo.txt +++ b/todo.txt @@ -79,9 +79,6 @@ TODO https://pub.dev/packages/ploeg_tree_layout - local variable scopes -Breaking changes (after mosis25 is finished): - - state names start with # (not with //) - Publish StateBuddy paper(s): compare CS approach to other tools, not only YAKINDU z \ No newline at end of file