Skip to content

Feat/auto scroll highlight #270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions src/assets/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,9 +28,15 @@ export const Creation = (props: {
number?: string;
className?: string;
}) => {
const messageContainerRef = useRef<HTMLDivElement>(null);
const cursor = useAtomValue(cursorAtom);
const msgRef = useRef<HTMLDivElement>(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();
Expand Down Expand Up @@ -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 (
<div
ref={msgRef}
data-origin={props.origin}
className={cn(
"interaction creation sync",
{
"right-to-left": rightToLeft,
highlight: isCurrent,
},
props.className,
)}
Expand All @@ -95,7 +120,6 @@ export const Creation = (props: {
>
{props.comment && <Comment commentObj={props.commentObj} />}
<div
ref={messageContainerRef}
data-type="creation"
className={cn(
"message-container pointer-events-none flex items-center h-10 relative",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,42 @@ import { cn } from "@/utils";
import { SelfInvocation } from "./SelfInvocation/SelfInvocation";
import { Message } from "../Message";
import { Occurrence } from "./Occurrence/Occurrence";
import { useAtomValue } from "jotai";
import { cursorAtom } from "@/store/Store";
import { _STARTER_ } from "@/parser/OrderedParticipants";
import { Comment } from "../Comment/Comment";
import { useArrow } from "../useArrow";

import {
cursorAtom,
enableCurrentElementHighlightAtom,
enableCurrentElementScrollIntoViewAtom,
} from "@/store/Store";
import { useEffect, useRef } from "react";
import { useAtomValue } from "jotai";
// import { handleScrollAndHighlight } from "@/parser/IsCurrent";
export const Interaction = (props: {
context: any;
origin: string;
commentObj?: CommentClass;
number?: string;
className?: string;
}) => {
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<HTMLDivElement>(null);
const cursor = useAtomValue(cursorAtom);
const enableCurrentElementHighlight = useAtomValue(
enableCurrentElementHighlightAtom,
);
const enableCurrentElementScrollIntoView = useAtomValue(
enableCurrentElementScrollIntoViewAtom,
);
const isCurrent = message?.isCurrent(cursor);

const {
translateX,
Expand All @@ -42,17 +54,30 @@ export const Interaction = (props: {
target,
});

useEffect(() => {
// return handleScrollAndHighlight({
// ref: msgRef,
// isCurrent,
// enableCurrentElementScrollIntoView,
// enableCurrentElementHighlight,
// });
}, [
isCurrent,
enableCurrentElementScrollIntoView,
enableCurrentElementHighlight,
]);

return (
<div
className={cn(
"interaction sync inline-block",
{
highlight: isCurrent,
self: isSelf,
"right-to-left": rightToLeft,
},
props.className,
)}
ref={msgRef}
onClick={(e) => e.stopPropagation()}
data-to={target}
data-origin={origin}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,23 @@
* }
*
*/
import { handleScrollAndHighlight } from "@/parser/IsCurrent";

import { cn } from "@/utils";
import { Comment } from "../Comment/Comment";
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;
Expand All @@ -88,14 +91,49 @@ 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();
const providedSource = asyncMessage?.ProvidedFrom();
const source = providedSource || props.origin;
const target = asyncMessage?.to()?.getFormattedText();
const isSelf = source === target;
const msgRef = useRef<HTMLDivElement>(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,
Expand All @@ -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 (
<div
ref={msgRef}
data-origin={origin}
data-to={target}
data-source={source}
Expand All @@ -129,7 +157,6 @@ export const InteractionAsync = (props: {
{
"left-to-right": !rightToLeft,
"right-to-left": rightToLeft,
highlight: getIsCurrent(),
"self-invocation": isSelf,
},
props.className,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { codeAtom, modeAtom, onContentChangeAtom } from "@/store/Store";
import {
codeAtom,
modeAtom,
onContentChangeAtom,
cursorAtom,
enableCurrentElementHighlightAtom,
enableCurrentElementScrollIntoViewAtom,
} from "@/store/Store";
import { handleScrollAndHighlight } from "@/parser/IsCurrent";
import { cn } from "@/utils";
import { useAtom, useAtomValue } from "jotai";
import { formatText } from "@/utils/StringUtil";
import { useEditLabel, specialCharRegex } from "@/functions/useEditLabel";
import { RenderMode } from "@/store/Store";
import type { FocusEvent, KeyboardEvent, MouseEvent } from "react";
import { useEffect, useRef } from "react";

export const MessageLabel = (props: {
labelText: string;
Expand All @@ -16,8 +25,37 @@ export const MessageLabel = (props: {
}) => {
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<HTMLLabelElement>(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();
Expand Down Expand Up @@ -61,6 +99,7 @@ export const MessageLabel = (props: {

return (
<label
ref={labelRef}
title="Double click to edit"
className={cn(
"px-1 cursor-text right hover:text-skin-message-hover hover:bg-skin-message-hover",
Expand Down
Loading