From 88dee7e3b9cec76678f83e75a10d7559e42070c1 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sun, 19 Oct 2025 20:30:56 +0200 Subject: [PATCH] custom dialog for editing text labels (no more window.prompt) --- src/App/App.tsx | 2 +- src/App/TextDialog.tsx | 26 ++++++++++++ src/App/TopPanel.tsx | 66 ++++++++++++++++--------------- src/App/shortcut_handler.ts | 42 ++++++++++---------- src/VisualEditor/TextSVG.tsx | 16 +++++--- src/VisualEditor/VisualEditor.tsx | 8 ++-- 6 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 src/App/TextDialog.tsx diff --git a/src/App/App.tsx b/src/App/App.tsx index 26e6927..47743f3 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -184,7 +184,7 @@ export function App() { flexGrow:1, overflow:'auto', }}> - + {/* right sidebar */} diff --git a/src/App/TextDialog.tsx b/src/App/TextDialog.tsx new file mode 100644 index 0000000..110a789 --- /dev/null +++ b/src/App/TextDialog.tsx @@ -0,0 +1,26 @@ +import { Dispatch, ReactElement, SetStateAction, useState } from "react"; + +export function TextDialog(props: {setModal: Dispatch>, text: string, done: (newText: string|undefined) => void}) { + const [text, setText] = useState(props.text); + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Enter") { + if (!e.shiftKey) { + e.preventDefault(); + props.done(text); + props.setModal(null); + } + } + if (e.key === "Escape") { + props.setModal(null); + e.stopPropagation(); + } + e.stopPropagation(); + } + return + +
+ + Tip: Shift+Enter to insert newline. + +
; +} \ No newline at end of file diff --git a/src/App/TopPanel.tsx b/src/App/TopPanel.tsx index 46ad441..721cdd7 100644 --- a/src/App/TopPanel.tsx +++ b/src/App/TopPanel.tsx @@ -75,38 +75,40 @@ export function TopPanel({rt, rtIdx, time, setTime, onInit, onClear, onRaise, on useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { - if (e.key === " ") { - e.preventDefault(); - if (rt) - onChangePaused(time.kind !== "paused", Math.round(performance.now())); - }; - if (e.key === "i") { - e.preventDefault(); - onInit(); - } - if (e.key === "c") { - e.preventDefault(); - onClear(); - } - if (e.key === "Tab") { - e.preventDefault(); - onSkip(); - } - if (e.key === "s") { - e.preventDefault(); - onSlower(); - } - if (e.key === "f") { - e.preventDefault(); - onFaster(); - } - if (e.key === "`") { - e.preventDefault(); - setShowKeys(show => !show); - } - if (e.key === "Backspace") { - e.preventDefault(); - onBack(); + if (!e.ctrlKey) { + if (e.key === " ") { + e.preventDefault(); + if (rt) + onChangePaused(time.kind !== "paused", Math.round(performance.now())); + }; + if (e.key === "i") { + e.preventDefault(); + onInit(); + } + if (e.key === "c") { + e.preventDefault(); + onClear(); + } + if (e.key === "Tab") { + e.preventDefault(); + onSkip(); + } + if (e.key === "s") { + e.preventDefault(); + onSlower(); + } + if (e.key === "f") { + e.preventDefault(); + onFaster(); + } + if (e.key === "`") { + e.preventDefault(); + setShowKeys(show => !show); + } + if (e.key === "Backspace") { + e.preventDefault(); + onBack(); + } } }; window.addEventListener("keydown", onKeyDown); diff --git a/src/App/shortcut_handler.ts b/src/App/shortcut_handler.ts index e74228c..824a245 100644 --- a/src/App/shortcut_handler.ts +++ b/src/App/shortcut_handler.ts @@ -3,26 +3,28 @@ import { InsertMode } from "../VisualEditor/VisualEditor"; export function getKeyHandler(setMode: Dispatch>) { return function onKeyDown(e: KeyboardEvent) { - if (e.key === "a") { - setMode("and"); - } - if (e.key === "o") { - setMode("or"); - } - if (e.key === "p") { - setMode("pseudo"); - } - if (e.key === "t") { - setMode("transition"); - } - if (e.key === "x") { - setMode("text"); - } - if (e.key === "h") { - setMode(oldMode => { - if (oldMode === "shallow") return "deep"; - return "shallow"; - }) + if (!e.ctrlKey) { + if (e.key === "a") { + setMode("and"); + } + if (e.key === "o") { + setMode("or"); + } + if (e.key === "p") { + setMode("pseudo"); + } + if (e.key === "t") { + setMode("transition"); + } + if (e.key === "x") { + setMode("text"); + } + if (e.key === "h") { + setMode(oldMode => { + if (oldMode === "shallow") return "deep"; + return "shallow"; + }) + } } } } diff --git a/src/VisualEditor/TextSVG.tsx b/src/VisualEditor/TextSVG.tsx index 2990e38..87b68cd 100644 --- a/src/VisualEditor/TextSVG.tsx +++ b/src/VisualEditor/TextSVG.tsx @@ -1,7 +1,9 @@ +import { TextDialog } from "@/App/TextDialog"; import { TraceableError } from "..//statecharts/parser"; import {Text} from "../statecharts/concrete_syntax"; +import { Dispatch, ReactElement, SetStateAction } from "react"; -export function TextSVG(props: {text: Text, error: TraceableError|undefined, selected: boolean, highlight: boolean, onEdit: (newText: string) => void}) { +export function TextSVG(props: {text: Text, error: TraceableError|undefined, selected: boolean, highlight: boolean, onEdit: (newText: string) => void, setModal: Dispatch>}) { const commonProps = { "data-uid": props.text.uid, "data-parts": "text", @@ -9,6 +11,7 @@ export function TextSVG(props: {text: Text, error: TraceableError|undefined, sel className: "draggableText" + (props.selected ? " selected":"") + (props.highlight ? " highlight":""), + style: {whiteSpace: "preserve"}, } let textNode; @@ -32,12 +35,13 @@ export function TextSVG(props: {text: Text, error: TraceableError|undefined, sel key={props.text.uid} transform={`translate(${props.text.topLeft.x} ${props.text.topLeft.y})`} onDoubleClick={() => { - const newText = prompt("", props.text.text); - if (newText) { - props.onEdit(newText); - } + props.setModal( { + if (newText) { + props.onEdit(newText); + } + }} />) }}> {textNode} - {props.text.text} + {props.text.text} ; } \ No newline at end of file diff --git a/src/VisualEditor/VisualEditor.tsx b/src/VisualEditor/VisualEditor.tsx index bb0e88e..f0cb80f 100644 --- a/src/VisualEditor/VisualEditor.tsx +++ b/src/VisualEditor/VisualEditor.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from "react"; +import { Dispatch, ReactElement, SetStateAction, useEffect, useMemo, useRef, useState } from "react"; import { Statechart } from "../statecharts/abstract_syntax"; import { Arrow, ArrowPart, Diamond, History, Rountangle, RountanglePart, Text, VisualEditorState, emptyState } from "../statecharts/concrete_syntax"; @@ -68,9 +68,10 @@ type VisualEditorProps = { mode: InsertMode, highlightActive: Set, highlightTransitions: string[], + setModal: Dispatch>, }; -export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highlightActive, highlightTransitions}: VisualEditorProps) { +export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highlightActive, highlightTransitions, setModal}: VisualEditorProps) { const [historyState, setHistoryState] = useState({current: emptyState, history: [], future: []}); const state = historyState.current; @@ -324,7 +325,7 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh setSelection([]); }; - const onMouseMove = (e: {pageX: number, pageY: number}) => { + const onMouseMove = (e: {pageX: number, pageY: number, movementX: number, movementY: number}) => { const currentPointer = getCurrentPointer(e); if (dragging) { // const pointerDelta = subtractV2D(currentPointer, dragging.lastMousePos); @@ -812,6 +813,7 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh selected={Boolean(selection.find(s => s.uid === txt.uid)?.parts?.length)} highlight={textsToHighlight.hasOwnProperty(txt.uid)} onEdit={newText => onEditText(txt, newText)} + setModal={setModal} /> })}