From 5d04102bbd4cf1ba7f06d99607fc0e667ad4cc3e Mon Sep 17 00:00:00 2001 From: dontry Date: Sat, 6 Apr 2024 23:07:07 +1100 Subject: [PATCH 01/12] Add participant positions to Participants.spec.ts and ToCollector.js --- .../SeqDiagram/LifeLineLayer/Participant.vue | 15 +++- .../LifeLineLayer/ParticipantLabel.vue | 78 +++++++++++++++++++ src/parser/Participants.spec.ts | 3 + src/parser/Participants.ts | 35 ++++++++- src/parser/ToCollector.js | 10 ++- 5 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue diff --git a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue index 140f09843..192863e5f 100644 --- a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue +++ b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue @@ -31,7 +31,11 @@ - + + @@ -46,11 +50,15 @@ import { useStore } from "vuex"; import { getElementDistanceToTop } from "@/utils/dom"; import { PARTICIPANT_HEIGHT } from "@/positioning/Constants"; import { RenderMode } from "@/store/Store"; +import ParticipantLabel from "./ParticipantLabel.vue"; const INTERSECTION_ERROR_MARGIN = 10; // a threshold for judging whether the participant is intersecting with the viewport export default { name: "Participant", + components: { + ParticipantLabel, + }, setup(props) { const store = useStore(); const participant = ref(null); @@ -58,6 +66,9 @@ export default { return { translate: 0, participant }; } + const participantPositions = computed(() => + store.getters.participants.Positions().get(props.entity.name), + ); const intersectionTop = useIntersectionTop(); const [scrollTop] = useDocumentScroll(); const translate = computed(() => { @@ -78,7 +89,7 @@ export default { participantOffsetTop ); }); - return { translate, participant }; + return { translate, participant, participantPositions }; }, props: { entity: { diff --git a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue new file mode 100644 index 000000000..2e32b273c --- /dev/null +++ b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue @@ -0,0 +1,78 @@ + + diff --git a/src/parser/Participants.spec.ts b/src/parser/Participants.spec.ts index ff77e7c99..d56538a73 100644 --- a/src/parser/Participants.spec.ts +++ b/src/parser/Participants.spec.ts @@ -48,6 +48,8 @@ describe("Participants", () => { participants.Add( "A", false, + 1, + 2, undefined, undefined, undefined, @@ -61,5 +63,6 @@ describe("Participants", () => { width: undefined, explicit: true, }); + expect(participants.Positions("A")?.has("[1,2]")); }); }); diff --git a/src/parser/Participants.ts b/src/parser/Participants.ts index 586005454..dd619fe45 100644 --- a/src/parser/Participants.ts +++ b/src/parser/Participants.ts @@ -20,7 +20,7 @@ export class Participant { private stereotype: string | undefined; private width: number | undefined; private groupId: number | string | undefined; - private explicit: boolean | undefined; + explicit: boolean | undefined; isStarter: boolean | undefined; private label: string | undefined; private type: string | undefined; @@ -85,14 +85,22 @@ export class Participant { } } +export type PositionStr< + A extends number = number, + B extends number = number, +> = `[${A},${B}]`; + export class Participants { - private participants = new Map(); + private participants = new Map(); + private participantPositions = new Map>(); public Add(name: string): void; public Add(name: string, isStarter: boolean): void; public Add( name: string, isStarter?: boolean, + start?: number, + end?: number, stereotype?: string, width?: number, groupId?: number | string, @@ -102,6 +110,8 @@ export class Participants { public Add( name: string, isStarter?: boolean, + start?: number, + end?: number, stereotype?: string, width?: number, groupId?: number | string, @@ -127,12 +137,17 @@ export class Participants { name, mergeWith({}, this.Get(name), participant, (a, b) => a || b), ); + if (start !== undefined && end !== undefined) { + this.addPosition(name, start, end); + } } // Returns an array of participants that are deduced from messages // It does not include the Starter. ImplicitArray() { - return this.Array().filter((p) => !p.explicit && !p.isStarter); + return this.Array().filter( + (p) => !this.Get(p.name)?.explicit && !p.isStarter, + ); } // Items in entries are in the order of entry insertion: @@ -162,4 +177,18 @@ export class Participants { // const type = first.name === 'User' || first.name === 'Actor' ? 'actor' : undefined; return first.isStarter ? first : undefined; } + + Positions() { + return this.participantPositions; + } + + private addPosition(name: string, start: number, end: number) { + let positions = this.participantPositions.get(name); + if (!positions) { + positions = new Set(); + this.participantPositions.set(name, positions); + } + + positions.add(`[${start},${end}]`); + } } diff --git a/src/parser/ToCollector.js b/src/parser/ToCollector.js index 543bdabd0..52f18f323 100644 --- a/src/parser/ToCollector.js +++ b/src/parser/ToCollector.js @@ -28,9 +28,12 @@ let onParticipant = function (ctx) { const explicit = true; const color = ctx.COLOR()?.getText(); const comment = ctx.getComment(); + const nameCtx = ctx.name(); participants.Add( participant, false, + nameCtx.start.start, + nameCtx.stop.stop + 1, stereotype, width, groupId, @@ -46,7 +49,7 @@ ToCollector.enterParticipant = onParticipant; let onTo = function (ctx) { if (isBlind) return; let participant = ctx.getFormattedText(); - participants.Add(participant); + participants.Add(participant, false, ctx.start.start, ctx.stop.stop + 1); }; ToCollector.enterFrom = onTo; @@ -54,13 +57,14 @@ ToCollector.enterTo = onTo; ToCollector.enterStarter = function (ctx) { let participant = ctx.getFormattedText(); - participants.Add(participant, true); + participants.Add(participant, true, ctx.start.start, ctx.stop.stop + 1); }; ToCollector.enterCreation = function (ctx) { if (isBlind) return; const participant = ctx.Owner(); - participants.Add(participant); + const ctor = ctx.creationBody().construct(); + participants.Add(participant, false, ctor.start.start, ctor.stop.stop + 1); }; ToCollector.enterParameters = function () { From 4cd4a09cc8353a441f887fe7be232b6d3d299e84 Mon Sep 17 00:00:00 2001 From: dontry Date: Mon, 29 Apr 2024 23:24:21 +1000 Subject: [PATCH 02/12] Enhance Participant label --- .../SeqDiagram/LifeLineLayer/ParticipantLabel.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue index 2e32b273c..2b858833f 100644 --- a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue +++ b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue @@ -3,8 +3,7 @@ title="Double click to edit" class="name leading-4 cursor-text right hover:text-skin-message-hover hover:bg-skin-message-hover" :class="{ - 'absolute right-1/2 translate-x-1/2 bottom-0 py-1 px-2 ml-1 cursor-text': - editing, + 'py-1 px-2 cursor-text': editing, }" :contenteditable="editing" @dblclick="handleDblClick" @@ -51,10 +50,15 @@ function replaceLabelText(e: Event) { return; } + if (newText.includes(" ")) { + newText = newText.replace(/\s+/g, " "); // remove extra spaces + } + // If text has special characters or space, we wrap it with double quotes - if (specialCharRegex.test(newText) || newText.includes(" ")) { - newText = newText.replace(/[\s"]/g, ""); // remove existing double quotes and empty spaces + if (specialCharRegex.test(newText)) { + newText = newText.replace(/"/g, ""); // remove existing double quotes newText = `"${newText}"`; + specialCharRegex.lastIndex = 0; } if (!labelPositions?.value) return; From eb31301ff9959edf1935d6ab5458b58d963a83c9 Mon Sep 17 00:00:00 2001 From: dontry Date: Thu, 2 May 2024 21:26:42 +1000 Subject: [PATCH 03/12] remove comment --- .../DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue index 192863e5f..089dc2c52 100644 --- a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue +++ b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.vue @@ -35,7 +35,6 @@ :labelText="entity.label || entity.name" :labelPositions="participantPositions" /> - From d9621da9d50e7fa8476c9d3b3bee10ccad781a7c Mon Sep 17 00:00:00 2001 From: dontry Date: Thu, 2 May 2024 21:43:58 +1000 Subject: [PATCH 04/12] Add unit tests --- src/parser/Participants.spec.ts | 2 +- src/parser/Participants.ts | 4 + src/parser/ToCollector.js | 4 +- test/unit/parser/to-collector.spec.js | 234 +++++++++++++++----------- 4 files changed, 140 insertions(+), 104 deletions(-) diff --git a/src/parser/Participants.spec.ts b/src/parser/Participants.spec.ts index d56538a73..e46b5d382 100644 --- a/src/parser/Participants.spec.ts +++ b/src/parser/Participants.spec.ts @@ -63,6 +63,6 @@ describe("Participants", () => { width: undefined, explicit: true, }); - expect(participants.Positions("A")?.has("[1,2]")); + expect(participants.GetPositions("A")?.has("[1,2]")); }); }); diff --git a/src/parser/Participants.ts b/src/parser/Participants.ts index dd619fe45..66921bf91 100644 --- a/src/parser/Participants.ts +++ b/src/parser/Participants.ts @@ -182,6 +182,10 @@ export class Participants { return this.participantPositions; } + GetPositions(name: string) { + return this.participantPositions.get(name); + } + private addPosition(name: string, start: number, end: number) { let positions = this.participantPositions.get(name); if (!positions) { diff --git a/src/parser/ToCollector.js b/src/parser/ToCollector.js index 52f18f323..c6e4b4077 100644 --- a/src/parser/ToCollector.js +++ b/src/parser/ToCollector.js @@ -32,8 +32,8 @@ let onParticipant = function (ctx) { participants.Add( participant, false, - nameCtx.start.start, - nameCtx.stop.stop + 1, + nameCtx?.start.start, + (nameCtx?.stop.stop ?? 0) + 1, stereotype, width, groupId, diff --git a/test/unit/parser/to-collector.spec.js b/test/unit/parser/to-collector.spec.js index 48b9e14d8..4199ce839 100644 --- a/test/unit/parser/to-collector.spec.js +++ b/test/unit/parser/to-collector.spec.js @@ -1,9 +1,9 @@ -import { Fixture } from './fixture/Fixture'; +import { Fixture } from "./fixture/Fixture"; -import { RootContext } from '../../../src/parser/index'; +import { RootContext } from "../../../src/parser/index"; -import ToCollector from '../../../src/parser/ToCollector'; -test('smoke test2', () => { +import ToCollector from "../../../src/parser/ToCollector"; +test("smoke test2", () => { const code = ` C // comment @@ -14,111 +14,111 @@ test('smoke test2', () => { new F `; let participants = getParticipants(code); - expect(participants.Get('B 1')).toEqual({ - name: 'B 1', - comment: ' comment\n', + expect(participants.Get("B 1")).toEqual({ + name: "B 1", + comment: " comment\n", color: undefined, label: undefined, isStarter: true, explicit: true, groupId: undefined, - stereotype: 'A', + stereotype: "A", width: 1024, type: undefined, }); }); -describe('Plain participants', () => { - test.each(['A', 'A\n', 'A\n\r'])( - 'get participant with width and stereotype undefined', +describe("Plain participants", () => { + test.each(["A", "A\n", "A\n\r"])( + "get participant with width and stereotype undefined", (code) => { // `A` will be parsed as a participant which matches `participant EOF` let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.Get('A').width).toBeUndefined(); - expect(participants.Get('A').stereotype).toBeUndefined(); - } + expect(participants.Get("A").width).toBeUndefined(); + expect(participants.Get("A").stereotype).toBeUndefined(); + }, ); }); -describe('with width', () => { +describe("with width", () => { test.each([ - ['A 1024', 1024], - ['A 1024 A 1025', 1024], - ['A 1024\nA 1025', 1024], - ])('code:%s => width:%s', (code, width) => { + ["A 1024", 1024], + ["A 1024 A 1025", 1024], + ["A 1024\nA 1025", 1024], + ])("code:%s => width:%s", (code, width) => { // `A` will be parsed as a participant which matches `participant EOF` let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.First().name).toBe('A'); - expect(participants.Get('A').name).toBe('A'); - expect(participants.Get('A').width).toBe(width); + expect(participants.First().name).toBe("A"); + expect(participants.Get("A").name).toBe("A"); + expect(participants.Get("A").width).toBe(width); }); }); -describe('with interface', () => { +describe("with interface", () => { test.each([ - ['<> X 1024', 'A'], - ['<> X <> X', 'A'], // Ignore redefining - ['<> X\n<> X', 'A'], - ])('code:%s => width:%s', (code, stereotype) => { + ["<> X 1024", "A"], + ["<> X <> X", "A"], // Ignore redefining + ["<> X\n<> X", "A"], + ])("code:%s => width:%s", (code, stereotype) => { // `A` will be parsed as a participant which matches `participant EOF` let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.Get('X').name).toBe('X'); - expect(participants.Get('X').stereotype).toBe(stereotype); + expect(participants.Get("X").name).toBe("X"); + expect(participants.Get("X").stereotype).toBe(stereotype); }); }); -describe('with group', () => { +describe("with group", () => { test.each([ - ['group { A }', 'A', undefined], - ['group group1 { A }', 'A', 'group1'], - ['group "group 2" { A }', 'A', 'group 2'], - ['group "group 2" { A } group "group 3" { A }', 'A', 'group 2'], - ])('code:%s => participant:%s', (code, participant, groupId) => { + ["group { A }", "A", undefined], + ["group group1 { A }", "A", "group1"], + ['group "group 2" { A }', "A", "group 2"], + ['group "group 2" { A } group "group 3" { A }', "A", "group 2"], + ])("code:%s => participant:%s", (code, participant, groupId) => { // `A` will be parsed as a participant which matches `participant EOF` let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.Get('A').name).toBe(participant); - expect(participants.Get('A').groupId).toBe(groupId); + expect(participants.Get("A").name).toBe(participant); + expect(participants.Get("A").groupId).toBe(groupId); }); }); -describe('without starter', () => { +describe("without starter", () => { test.each([ - ['A.method', 'A', 2], - ['@Starter(A)', 'A', 1], - ])('code:%s => participant:%s', (code, participant, numberOfParticipants) => { + ["A.method", "A", 2], + ["@Starter(A)", "A", 1], + ])("code:%s => participant:%s", (code, participant, numberOfParticipants) => { // `A` will be parsed as a participant which matches `participant EOF` let participants = getParticipants(code, true); expect(participants.Size()).toBe(numberOfParticipants); - expect(participants.Get('A').name).toBe(participant); + expect(participants.Get("A").name).toBe(participant); }); }); -describe('with label', () => { +describe("with label", () => { test.each([ - ['A as AA', 'AA'], - ['A as "AA"', 'AA'], - ])('code:%s => label:%s', (code, label) => { + ["A as AA", "AA"], + ['A as "AA"', "AA"], + ])("code:%s => label:%s", (code, label) => { let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.Get('A').name).toBe('A'); - expect(participants.Get('A').label).toBe(label); + expect(participants.Get("A").name).toBe("A"); + expect(participants.Get("A").label).toBe(label); }); }); -describe('with participantType', () => { +describe("with participantType", () => { test.each([ - ['@actor A', 'actor'], - ['@actor A\nA', 'actor'], - ['@Actor A', 'Actor'], - ['@database A', 'database'], - ])('code:%s => participantType:%s', (code, participantType) => { + ["@actor A", "actor"], + ["@actor A\nA", "actor"], + ["@Actor A", "Actor"], + ["@database A", "database"], + ])("code:%s => participantType:%s", (code, participantType) => { let participants = getParticipants(code, true); expect(participants.Size()).toBe(1); - expect(participants.Get('A').name).toBe('A'); - expect(participants.Get('A').type).toBe(participantType); + expect(participants.Get("A").name).toBe("A"); + expect(participants.Get("A").type).toBe(participantType); }); }); @@ -127,76 +127,96 @@ function getParticipants(code, withStarter) { return ToCollector.getParticipants(rootContext, withStarter); } -describe('Add Starter to participants', () => { - test('Empty context', () => { - let rootContext = RootContext(''); +describe("Add Starter to participants", () => { + test("Empty context", () => { + let rootContext = RootContext(""); const participants = ToCollector.getParticipants(rootContext, true); expect(participants.Size()).toBe(1); - expect(participants.Get('_STARTER_').name).toBe('_STARTER_'); - expect(participants.Get('_STARTER_').isStarter).toBeTruthy(); + expect(participants.Get("_STARTER_").name).toBe("_STARTER_"); + expect(participants.Get("_STARTER_").isStarter).toBeTruthy(); }); - test('A B->A.m', () => { - let rootContext = RootContext('A B B->A.m'); + test("A B->A.m", () => { + let rootContext = RootContext("A B B->A.m"); const participants = ToCollector.getParticipants(rootContext, true); expect(participants.Size()).toBe(2); - expect(participants.Get('_STARTER_')).toBeUndefined(); - expect(participants.Get('B').isStarter).toBeTruthy(); - expect(participants.Names()).toStrictEqual(['B', 'A']); + expect(participants.Get("_STARTER_")).toBeUndefined(); + expect(participants.Get("B").isStarter).toBeTruthy(); + expect(participants.Names()).toStrictEqual(["B", "A"]); }); }); -describe('implicit', () => { - describe('from new', () => { - test('from new', () => { - let participants = getParticipants('new A()', true); - expect(participants.Get('A')).toEqual({ +describe("implicit", () => { + describe("from new", () => { + test("from new", () => { + let participants = getParticipants("new A()", true); + expect(participants.Get("A")).toEqual({ + color: undefined, + comment: undefined, explicit: undefined, groupId: undefined, - name: 'A', + isStarter: false, + label: undefined, + name: "A", stereotype: undefined, + type: undefined, width: undefined, }); }); - test('seqDsl should treat creation as a participant - assignment', () => { - let participants = getParticipants('a = new A()', true); + test("seqDsl should treat creation as a participant - assignment", () => { + let participants = getParticipants("a = new A()", true); expect(participants.Size()).toBe(2); - expect(participants.Get('a:A').width).toBeUndefined(); + expect(participants.Get("a:A").width).toBeUndefined(); }); - test('seqDsl should treat creation as a participant - assignment with type', () => { + test("seqDsl should treat creation as a participant - assignment with type", () => { // We need @Starter, otherwise IA becomes a participant declaration - let participants = getParticipants('@Starter(X) IA a = new A()', true); + let participants = getParticipants("@Starter(X) IA a = new A()", true); expect(participants.Size()).toBe(2); - expect(participants.Get('X').width).toBeUndefined(); - expect(participants.Get('a:A').width).toBeUndefined(); + expect(participants.Get("X").width).toBeUndefined(); + expect(participants.Get("a:A").width).toBeUndefined(); + }); + test("seqDsl should treat method call as a participant - creation & assignment", () => { + let participants = getParticipants("a = new A()", true); + expect(participants.Size()).toBe(2); + expect(participants.Get("a:A").width).toBeUndefined(); }); }); - describe('from method call', () => { - test('get participants', () => { - const participants = getParticipants('A.method', true); - expect(participants.Get('A')).toEqual({ name: 'A', stereotype: undefined, width: undefined }); + describe("from method call", () => { + test("get participants", () => { + const participants = getParticipants("A.method", true); + expect(participants.Get("A")).toEqual({ + color: undefined, + comment: undefined, + explicit: undefined, + groupId: undefined, + isStarter: false, + label: undefined, + name: "A", + stereotype: undefined, + type: undefined, + width: undefined, + }); }); - test('seqDsl should get all participants but ignore parameters - method call', () => { + test("seqDsl should get all participants but ignore parameters - method call", () => { let participants = getParticipants('"b:B".method(x.m)', true); expect(participants.Size()).toBe(2); - expect(participants.Get('b:B').width).toBeUndefined(); + expect(participants.Get("b:B").width).toBeUndefined(); }); - test('seqDsl should get all participants but ignore parameters - creation', () => { + test("seqDsl should get all participants but ignore parameters - creation", () => { let participants = getParticipants('"b:B".method(new X())', true); expect(participants.Size()).toBe(2); - expect(participants.Get('b:B').width).toBeUndefined(); + expect(participants.Get("b:B").width).toBeUndefined(); }); - - test('seqDsl should get all participants including from', () => { - let participants = getParticipants('A->B.m', true); + test("seqDsl should get all participants including from", () => { + let participants = getParticipants("A->B.m", true); expect(participants.Size()).toBe(2); }); }); - describe('partial context', () => { + describe("partial context", () => { // TODO: this was to reproduce an issue. It can be simplified. - test('seqDsl should get all participants from a node of the root context', () => { + test("seqDsl should get all participants from a node of the root context", () => { const firstGrandChild = Fixture.firstGrandChild(`A->B.m { C.m { D.m { @@ -207,12 +227,18 @@ describe('implicit', () => { } }`); - const ifBlock = firstGrandChild.children[0].braceBlock().block().stat()[0]; + const ifBlock = firstGrandChild.children[0] + .braceBlock() + .block() + .stat()[0]; const participants = ToCollector.getParticipants(ifBlock, false); - expect(participants.ImplicitArray().map((p) => p.name)).toStrictEqual(['B', 'A']); + expect(participants.ImplicitArray().map((p) => p.name)).toStrictEqual([ + "B", + "A", + ]); }); - test('seqDsl should get all participants from a node of the root context', () => { + test("seqDsl should get all participants from a node of the root context", () => { const firstGrandChild = Fixture.firstGrandChild(`A->B.m { C.m { D.m { @@ -223,16 +249,22 @@ describe('implicit', () => { } }`); - const ifBlock = firstGrandChild.children[0].braceBlock().block().stat()[0]; + const ifBlock = firstGrandChild.children[0] + .braceBlock() + .block() + .stat()[0]; const participants = ToCollector.getParticipants(ifBlock, false); - expect(participants.ImplicitArray().map((p) => p.name)).toStrictEqual(['D', 'C']); + expect(participants.ImplicitArray().map((p) => p.name)).toStrictEqual([ + "D", + "C", + ]); }); }); }); -describe('Invalid input', () => { - test('<<', () => { - let participants = getParticipants('<<', false); - expect(participants.First().name).toBe('Missing `Participant`'); +describe("Invalid input", () => { + test("<<", () => { + let participants = getParticipants("<<", false); + expect(participants.First().name).toBe("Missing `Participant`"); }); }); From 8ad97f20ed0bcb387c440920942a33b2dbe5f4be Mon Sep 17 00:00:00 2001 From: dontry Date: Fri, 3 May 2024 22:11:17 +1000 Subject: [PATCH 05/12] Fix issue with participant label position update --- .../Block/Statement/Message/Message.vue | 2 +- src/parser/ToCollector.js | 30 ++++++++++++++++--- test/unit/parser/to-collector.spec.js | 17 +++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue index f6b4de638..863b79283 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue @@ -82,7 +82,7 @@ const labelPosition: ComputedRef<[number, number]> = computed(() => { switch (type?.value) { case "sync": { - const signature = context?.value?.messageBody().func().signature()[0]; + const signature = context?.value?.messageBody().func()?.signature()[0]; [start, stop] = [signature?.start.start, signature?.stop.stop]; } break; diff --git a/src/parser/ToCollector.js b/src/parser/ToCollector.js index c6e4b4077..d6ae24a39 100644 --- a/src/parser/ToCollector.js +++ b/src/parser/ToCollector.js @@ -24,16 +24,31 @@ let onParticipant = function (ctx) { const width = (ctx.width && ctx.width() && Number.parseInt(ctx.width().getText())) || undefined; - const label = ctx.label && ctx.label()?.name()?.getFormattedText(); + const labelCtx = ctx.label && ctx.label(); + const label = labelCtx?.name()?.getFormattedText(); const explicit = true; const color = ctx.COLOR()?.getText(); const comment = ctx.getComment(); const nameCtx = ctx.name(); + let start, stop; + + // When label is present, it means we edit label in diagram and update its code regardless of the occurrence of the participant name + if (labelCtx) { + const labelNameCtx = labelCtx.name(); + if (labelNameCtx) { + start = labelNameCtx.start.start; + stop = labelNameCtx.stop.stop + 1; + } + } else if (nameCtx) { + start = nameCtx.start.start; + stop = nameCtx.stop.stop + 1; + } + participants.Add( participant, false, - nameCtx?.start.start, - (nameCtx?.stop.stop ?? 0) + 1, + start, + stop, stereotype, width, groupId, @@ -49,7 +64,14 @@ ToCollector.enterParticipant = onParticipant; let onTo = function (ctx) { if (isBlind) return; let participant = ctx.getFormattedText(); - participants.Add(participant, false, ctx.start.start, ctx.stop.stop + 1); + const participantInstance = participants.Get(participant); + + // Skip adding participant position if label is present + if (participantInstance?.label) { + participants.Add(participant, false); + } else { + participants.Add(participant, false, ctx.start.start, ctx.stop.stop + 1); + } }; ToCollector.enterFrom = onTo; diff --git a/test/unit/parser/to-collector.spec.js b/test/unit/parser/to-collector.spec.js index 4199ce839..5282ff577 100644 --- a/test/unit/parser/to-collector.spec.js +++ b/test/unit/parser/to-collector.spec.js @@ -3,6 +3,7 @@ import { Fixture } from "./fixture/Fixture"; import { RootContext } from "../../../src/parser/index"; import ToCollector from "../../../src/parser/ToCollector"; +import { expect } from "vitest"; test("smoke test2", () => { const code = ` C @@ -106,6 +107,10 @@ describe("with label", () => { expect(participants.Get("A").name).toBe("A"); expect(participants.Get("A").label).toBe(label); }); + test("participant position with label", () => { + let participants = getParticipants("A as AA A.method", true); + expect(participants.GetPositions("A")).toEqual(new Set(["[5,7]"])); + }); }); describe("with participantType", () => { @@ -134,6 +139,7 @@ describe("Add Starter to participants", () => { expect(participants.Size()).toBe(1); expect(participants.Get("_STARTER_").name).toBe("_STARTER_"); expect(participants.Get("_STARTER_").isStarter).toBeTruthy(); + expect(participants.GetPositions("_STARTER_")).toBeUndefined(); }); test("A B->A.m", () => { @@ -167,6 +173,7 @@ describe("implicit", () => { let participants = getParticipants("a = new A()", true); expect(participants.Size()).toBe(2); expect(participants.Get("a:A").width).toBeUndefined(); + expect(participants.GetPositions("a:A")).toEqual(new Set(["[8,9]"])); }); test("seqDsl should treat creation as a participant - assignment with type", () => { // We need @Starter, otherwise IA becomes a participant declaration @@ -174,11 +181,15 @@ describe("implicit", () => { expect(participants.Size()).toBe(2); expect(participants.Get("X").width).toBeUndefined(); expect(participants.Get("a:A").width).toBeUndefined(); + expect(participants.GetPositions("X").size).toBe(1); + expect(participants.GetPositions("X")).toEqual(new Set(["[9,10]"])); + expect(participants.GetPositions("a:A")).toEqual(new Set(["[23,24]"])); }); test("seqDsl should treat method call as a participant - creation & assignment", () => { let participants = getParticipants("a = new A()", true); expect(participants.Size()).toBe(2); expect(participants.Get("a:A").width).toBeUndefined(); + expect(participants.GetPositions("a:A")).toEqual(new Set(["[8,9]"])); }); }); @@ -202,15 +213,19 @@ describe("implicit", () => { let participants = getParticipants('"b:B".method(x.m)', true); expect(participants.Size()).toBe(2); expect(participants.Get("b:B").width).toBeUndefined(); + expect(participants.GetPositions("b:B")).toEqual(new Set(["[0,5]"])); }); test("seqDsl should get all participants but ignore parameters - creation", () => { let participants = getParticipants('"b:B".method(new X())', true); expect(participants.Size()).toBe(2); expect(participants.Get("b:B").width).toBeUndefined(); + expect(participants.GetPositions("b:B")).toEqual(new Set(["[0,5]"])); }); test("seqDsl should get all participants including from", () => { let participants = getParticipants("A->B.m", true); expect(participants.Size()).toBe(2); + expect(participants.GetPositions("A")).toEqual(new Set(["[0,1]"])); + expect(participants.GetPositions("B")).toEqual(new Set(["[3,4]"])); }); }); @@ -236,6 +251,7 @@ describe("implicit", () => { "B", "A", ]); + expect(participants.Positions().size).toEqual(2); }); test("seqDsl should get all participants from a node of the root context", () => { @@ -258,6 +274,7 @@ describe("implicit", () => { "D", "C", ]); + expect(participants.Positions().size).toEqual(0); }); }); }); From 1ee95cc70a2e2686dd6b022d5609151ea28bffde Mon Sep 17 00:00:00 2001 From: dontry Date: Fri, 3 May 2024 22:29:10 +1000 Subject: [PATCH 06/12] Disable editing if render mode is static --- .../SeqDiagram/LifeLineLayer/ParticipantLabel.vue | 4 +++- .../MessageLayer/Block/Statement/Fragment/ConditionLabel.vue | 4 +++- .../MessageLayer/Block/Statement/Message/Message.vue | 4 ++++ .../DiagramFrame/SeqDiagram/MessageLayer/MessageLabel.vue | 4 +++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue index 2b858833f..6eb7faffd 100644 --- a/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue +++ b/src/components/DiagramFrame/SeqDiagram/LifeLineLayer/ParticipantLabel.vue @@ -5,7 +5,7 @@ :class="{ 'py-1 px-2 cursor-text': editing, }" - :contenteditable="editing" + :contenteditable="editing && mode === RenderMode.Dynamic" @dblclick="handleDblClick" @blur="handleBlur" @keyup="handleKeyup" @@ -18,6 +18,7 @@ import { computed, toRefs } from "vue"; import { useStore } from "vuex"; import { useEditLabel, specialCharRegex } from "@/functions/useEditLabel"; +import { RenderMode } from "@/store/Store"; const props = defineProps<{ labelText: string; @@ -26,6 +27,7 @@ const props = defineProps<{ const { labelText, labelPositions } = toRefs(props); const store = useStore(); +const mode = computed(() => store.state.mode); const code = computed(() => store.getters.code); const onContentChange = computed( () => store.getters.onContentChange || (() => {}), diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Fragment/ConditionLabel.vue b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Fragment/ConditionLabel.vue index 693fbd099..f925013b3 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Fragment/ConditionLabel.vue +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Fragment/ConditionLabel.vue @@ -5,7 +5,7 @@ :class="{ 'py-1 px-2 ml-1 cursor-text': editing, }" - :contenteditable="editing" + :contenteditable="editing && mode === RenderMode.Dynamic" @dblclick="handleDblClick" @blur="handleBlur" @keyup="handleKeyup" @@ -18,6 +18,7 @@ import { computed, toRefs } from "vue"; import { useStore } from "vuex"; import { useEditLabel, specialCharRegex } from "@/functions/useEditLabel"; +import { RenderMode } from "@/store/Store"; const equalityRegex = /\b(\w+)\s*==\s*(\w+)\b/g; @@ -27,6 +28,7 @@ const props = defineProps<{ const { condition } = toRefs(props); const store = useStore(); +const mode = computed(() => store.state.mode); const code = computed(() => store.getters.code); const onContentChange = computed( () => store.getters.onContentChange || (() => {}), diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue index 863b79283..d887c7961 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/Block/Statement/Message/Message.vue @@ -46,6 +46,7 @@ diff --git a/src/components/DiagramFrame/SeqDiagram/MessageLayer/MessageLabel.vue b/src/components/DiagramFrame/SeqDiagram/MessageLayer/MessageLabel.vue index 5a8ea4cb5..04331901d 100644 --- a/src/components/DiagramFrame/SeqDiagram/MessageLayer/MessageLabel.vue +++ b/src/components/DiagramFrame/SeqDiagram/MessageLayer/MessageLabel.vue @@ -6,7 +6,7 @@ 'py-1 px-2 ml-1 cursor-text': editing, 'absolute right-1/2 translate-x-1/2 bottom-0': editing && !isSelfAsync, }" - :contenteditable="editing" + :contenteditable="editing && mode === RenderMode.Dynamic" @dblclick="handleDblClick" @blur="handleBlur" @keyup="handleKeyup" @@ -18,6 +18,7 @@