diff --git a/index.html b/index.html index e9ed36ce..7ea4b7f4 100644 --- a/index.html +++ b/index.html @@ -89,9 +89,12 @@ singleCursorHeightPerLine: false, }); - const updateDiagram = debounce((content) => { + const updateDiagram = debounce((content, cursor) => { const config = createConfig({ onContentChange: (code) => editor.setValue(code), + cursor: cursor, + enableCurrentElementScrollIntoView: true, + enableCurrentElementHighlight: true, }); window.zenUml.render(content, config).then((r) => { @@ -105,13 +108,18 @@ waitUntil( () => window.zenUml, () => { - updateDiagram(cm.getValue()); + updateDiagram(cm.getValue(), cm.indexFromPos(cm.getCursor())); // Save to localStorage localStorage.setItem("zenuml-cm-code", cm.getValue()); }, ); }); + editor.on("cursorActivity", function (cm) { + if (!window.zenUml) return; + updateDiagram(cm.getValue(), cm.indexFromPos(cm.getCursor())); + }); + // Load saved code from localStorage const savedCode = localStorage.getItem("zenuml-cm-code"); if (savedCode) { diff --git a/src/assets/tailwind.css b/src/assets/tailwind.css index 03feee66..bc46b985 100644 --- a/src/assets/tailwind.css +++ b/src/assets/tailwind.css @@ -19,6 +19,26 @@ serif; } +.zenuml .highlight { + background-color: rgba(255, 255, 0, 0.3); +} + +.zenuml .flash-highlight { + animation: flash-animation 2s 1; +} + +@keyframes flash-animation { + 0% { + background-color: transparent; + } + 50% { + background-color: gray; + } + 100% { + background-color: transparent; + } +} + @layer base { :root { --color-bg-base: #ffffff; diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Creation/Creation.tsx b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Creation/Creation.tsx index 7e010f12..e64fafc3 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Creation/Creation.tsx +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Creation/Creation.tsx @@ -8,11 +8,17 @@ import { } from "@/positioning/Constants"; import CommentClass from "@/components/Comment/Comment"; import { useAtomValue } from "jotai"; -import { cursorAtom, onElementClickAtom } from "@/store/Store"; +import { + onElementClickAtom, + cursorAtom, + enableCurrentElementHighlightAtom, + enableCurrentElementScrollIntoViewAtom, +} from "@/store/Store"; import { Comment } from "../Comment/Comment"; import { useEffect, useMemo, useRef, useState } from "react"; import { useArrow } from "../useArrow"; import { EventBus } from "@/EventBus"; +import { handleScrollAndHighlight } from "@/parser/IsCurrent"; export const Creation = (props: { context: any; @@ -22,9 +28,15 @@ export const Creation = (props: { number?: string; className?: string; }) => { - const messageContainerRef = useRef(null); - const cursor = useAtomValue(cursorAtom); + const msgRef = useRef(null); const onElementClick = useAtomValue(onElementClickAtom); + const cursor = useAtomValue(cursorAtom); + const enableCurrentElementHighlight = useAtomValue( + enableCurrentElementHighlightAtom, + ); + const enableCurrentElementScrollIntoView = useAtomValue( + enableCurrentElementScrollIntoViewAtom, + ); const [participantWidth, setParticipantWidth] = useState(0); const creation = props.context?.creation(); const target = creation?.Owner(); @@ -75,14 +87,27 @@ export const Creation = (props: { console.log(`Init or update message container for ${target}`); }, [target, participantWidth]); + useEffect(() => { + return handleScrollAndHighlight({ + ref: msgRef, + isCurrent, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + }); + }, [ + isCurrent, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + ]); + return (
{props.comment && }
{ - const cursor = useAtomValue(cursorAtom); const messageTextStyle = props.commentObj?.messageStyle; const messageClassNames = props.commentObj?.messageClassNames; const message = props.context?.message(); const statements = message?.Statements(); const assignee = message?.Assignment()?.getText() || ""; const signature = message?.SignatureText(); - const isCurrent = message?.isCurrent(cursor); const source = message?.From() || _STARTER_; const target = props.context?.message()?.Owner() || _STARTER_; const isSelf = source === target; + const msgRef = useRef(null); + const cursor = useAtomValue(cursorAtom); + const enableCurrentElementHighlight = useAtomValue( + enableCurrentElementHighlightAtom, + ); + const enableCurrentElementScrollIntoView = useAtomValue( + enableCurrentElementScrollIntoViewAtom, + ); + const isCurrent = message?.isCurrent(cursor); const { translateX, @@ -42,17 +54,30 @@ export const Interaction = (props: { target, }); + useEffect(() => { + // return handleScrollAndHighlight({ + // ref: msgRef, + // isCurrent, + // enableCurrentElementScrollIntoView, + // enableCurrentElementHighlight, + // }); + }, [ + isCurrent, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + ]); + return (
e.stopPropagation()} data-to={target} data-origin={origin} diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/InteractionAsync/Interaction-async.tsx b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/InteractionAsync/Interaction-async.tsx index 3bce6243..6a5cce71 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/InteractionAsync/Interaction-async.tsx +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/InteractionAsync/Interaction-async.tsx @@ -65,6 +65,7 @@ * } * */ +import { handleScrollAndHighlight } from "@/parser/IsCurrent"; import { cn } from "@/utils"; import { Comment } from "../Comment/Comment"; @@ -72,13 +73,15 @@ import { SelfInvocationAsync } from "./SelfInvocationAsync/SelfInvocationAsync"; import { Message } from "../Message"; import CommentClass from "@/components/Comment/Comment"; import { useAtomValue } from "jotai"; -import { cursorAtom, onElementClickAtom } from "@/store/Store"; +import { onElementClickAtom } from "@/store/Store"; import { CodeRange } from "@/parser/CodeRange"; import { useArrow } from "../useArrow"; - -function isNullOrUndefined(value: any) { - return value === null || value === undefined; -} +import { + cursorAtom, + enableCurrentElementHighlightAtom, + enableCurrentElementScrollIntoViewAtom, +} from "@/store/Store"; +import { useEffect, useRef } from "react"; export const InteractionAsync = (props: { context: any; @@ -88,7 +91,6 @@ export const InteractionAsync = (props: { number?: string; className?: string; }) => { - const cursor = useAtomValue(cursorAtom); const onElementClick = useAtomValue(onElementClickAtom); const asyncMessage = props.context?.asyncMessage(); const signature = asyncMessage?.content()?.getFormattedText(); @@ -96,6 +98,42 @@ export const InteractionAsync = (props: { const source = providedSource || props.origin; const target = asyncMessage?.to()?.getFormattedText(); const isSelf = source === target; + const msgRef = useRef(null); + const cursor = useAtomValue(cursorAtom); + const enableCurrentElementHighlight = useAtomValue( + enableCurrentElementHighlightAtom, + ); + const enableCurrentElementScrollIntoView = useAtomValue( + enableCurrentElementScrollIntoViewAtom, + ); + const isCurrent = () => { + const start = asyncMessage.start.start; + const stop = asyncMessage.stop.stop + 1; + if ( + isNullOrUndefined(cursor) || + isNullOrUndefined(start) || + isNullOrUndefined(stop) + ) + return false; + return cursor! >= start && cursor! <= stop; + }; + + function isNullOrUndefined(value: any) { + return value === null || value === undefined; + } + + useEffect(() => { + return handleScrollAndHighlight({ + ref: msgRef, + isCurrent: isCurrent(), + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + }); + }, [ + isCurrent, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + ]); const { translateX, interactionWidth, rightToLeft } = useArrow({ context: props.context, @@ -107,19 +145,9 @@ export const InteractionAsync = (props: { console.log(props.commentObj); const messageClassNames = props.commentObj?.messageClassNames; const messageTextStyle = props.commentObj?.messageStyle; - const getIsCurrent = () => { - const start = asyncMessage.start.start; - const stop = asyncMessage.stop.stop + 1; - if ( - isNullOrUndefined(cursor) || - isNullOrUndefined(start) || - isNullOrUndefined(stop) - ) - return false; - return cursor! >= start && cursor! <= stop; - }; return (
{ const mode = useAtomValue(modeAtom); const [code, setCode] = useAtom(codeAtom); + const cursor = useAtomValue(cursorAtom); const onContentChange = useAtomValue(onContentChangeAtom); + const enableCurrentElementHighlight = useAtomValue( + enableCurrentElementHighlightAtom, + ); + const enableCurrentElementScrollIntoView = useAtomValue( + enableCurrentElementScrollIntoViewAtom, + ); const formattedLabelText = formatText(props.labelText); + const labelRef = useRef(null); + + const isCurrent = + cursor != null && + cursor >= props.labelPosition[0] && + cursor <= props.labelPosition[1] + 1; + + useEffect(() => { + if (props.labelText) { + return handleScrollAndHighlight({ + ref: labelRef, + isCurrent, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + }); + } + }, [ + isCurrent, + props.labelText, + enableCurrentElementScrollIntoView, + enableCurrentElementHighlight, + ]); const replaceLabelText = (e: FocusEvent | KeyboardEvent | MouseEvent) => { e.preventDefault(); @@ -61,6 +99,7 @@ export const MessageLabel = (props: { return (