Skip to content

Commit 1440e61

Browse files
authored
Sequence Diagram reverse and local message flow and events (#2310)
1 parent abb1d95 commit 1440e61

File tree

5 files changed

+61
-27
lines changed

5 files changed

+61
-27
lines changed

src/Frontend/src/components/messages/SequenceDiagram/EndpointsComponent.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script setup lang="ts">
22
import { Endpoint } from "@/resources/SequenceDiagram/Endpoint";
3-
import { EndpointCentrePoint, useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
3+
import { Endpoint_Width, EndpointCentrePoint, useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
44
import { storeToRefs } from "pinia";
55
import { computed, ref, watch } from "vue";
66
77
interface EndpointWithLocation extends Endpoint {
88
width: number;
99
textWidth: number;
1010
x?: number;
11-
surround: EndpointSurround;
11+
surround?: EndpointSurround;
1212
}
1313
1414
interface EndpointSurround {
@@ -22,12 +22,11 @@ interface EndpointSurround {
2222
stroke: string;
2323
}
2424
25-
const Endpoint_Width = 260;
2625
const Endpoint_Gap = 30;
2726
const Endpoint_Image_Width = 20;
2827
2928
const store = useSequenceDiagramStore();
30-
const { endpoints } = storeToRefs(store);
29+
const { startX, endpoints } = storeToRefs(store);
3130
3231
const epRefs = ref<SVGTextElement[]>([]);
3332
const endpointItems = computed(() =>
@@ -39,7 +38,7 @@ const endpointItems = computed(() =>
3938
const previousEndpoint = index > 0 ? endpointItems.value[index - 1] : undefined;
4039
endpoint.width = Math.max(Endpoint_Width, bounds.width);
4140
endpoint.textWidth = bounds.width;
42-
endpoint.x = (previousEndpoint?.x ?? Endpoint_Width / 2) + (previousEndpoint?.width ?? 0) + Endpoint_Gap;
41+
endpoint.x = (previousEndpoint?.x ?? startX.value) + (previousEndpoint?.width ?? 0) + Endpoint_Gap;
4342
4443
if (!endpoint.surround && el.isConnected) {
4544
const style = getComputedStyle(el);
@@ -69,6 +68,11 @@ watch(endpointItems, () => {
6968
store.setMaxWidth((lastEndpoint.x ?? 0) + lastEndpoint.width);
7069
});
7170
71+
watch(startX, () => {
72+
epRefs.value = [];
73+
endpoints.value.forEach((endpoint) => ((endpoint as EndpointWithLocation).surround = undefined));
74+
});
75+
7276
function setEndpointRef(el: SVGTextElement, index: number) {
7377
if (el) epRefs.value[index] = el;
7478
}

src/Frontend/src/components/messages/SequenceDiagram/HandlersComponent.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ const handlerItems = computed(() => {
4141
return 0;
4242
})();
4343
44+
//determine which side of the handler to render the messageType on. If it's the left side (for a right arrow) then we apply a negative offset
45+
const messageTypeOffset = handler.direction === Direction.Right ? ((messageTypeElement?.getBBox().width ?? 0) + 24) * -1 : 20;
46+
if (messageTypeOffset < 0) {
47+
store.setStartX(-1 * messageTypeOffset);
48+
}
49+
4450
return {
4551
id: handler.id,
52+
endpointName: handler.endpoint.name,
4653
incomingId: handler.route?.name,
4754
left: (endpoint?.centre ?? 0) - Handler_Width / 2,
4855
right: (endpoint?.centre ?? 0) + Handler_Width / 2,
@@ -52,13 +59,13 @@ const handlerItems = computed(() => {
5259
icon,
5360
iconSize,
5461
messageType: handler.name,
55-
messageTypeOffset: handler.direction === Direction.Right ? ((messageTypeElement?.getBBox().width ?? 0) + 24) * -1 : 20,
62+
messageTypeOffset,
5663
messageTypeHighlight: handler.route?.name === highlightId.value,
5764
};
5865
});
5966
6067
store.setMaxHeight(nextY);
61-
store.setHandlerLocations(result.map((handler) => ({ id: handler.id, left: handler.left, right: handler.right, y: handler.y, height: handler.height })));
68+
store.setHandlerLocations(result.map((handler) => ({ id: handler.id, endpointName: handler.endpointName, left: handler.left, right: handler.right, y: handler.y, height: handler.height })));
6269
6370
return result;
6471
});

src/Frontend/src/components/messages/SequenceDiagram/RoutesComponent.vue

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { computed, ref } from "vue";
44
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
55
import { storeToRefs } from "pinia";
66
7-
const Arrow_Head_Width = 4;
7+
const Arrow_Head_Width = 10;
88
const Message_Type_Margin = 4;
99
1010
const store = useSequenceDiagramStore();
@@ -17,37 +17,39 @@ const arrows = computed(() =>
1717
if (!route.name) return;
1818
const fromHandler = route.fromRoutedMessage?.fromHandler;
1919
if (!fromHandler) return;
20-
const fromHandlerLocation = handlerLocations.value.find((hl) => hl.id === fromHandler.id);
20+
const fromHandlerLocation = handlerLocations.value.find((hl) => hl.id === fromHandler.id && hl.endpointName === fromHandler.endpoint.name);
2121
if (!fromHandlerLocation) return;
22-
const toHandlerLocation = handlerLocations.value.find((hl) => hl.id === route.fromRoutedMessage?.toHandler?.id);
22+
const toHandlerLocation = handlerLocations.value.find((hl) => hl.id === route.fromRoutedMessage?.toHandler?.id && hl.endpointName === route.fromRoutedMessage?.receiving.name);
2323
if (!toHandlerLocation) return;
2424
25-
//TODO: is messageId enough to uniquely identify?
26-
const arrowIndex = fromHandler.outMessages.findIndex((out) => route.fromRoutedMessage?.messageId === out.messageId) + 1;
25+
const messageTypeElement = messageTypeRefs.value[index];
26+
const messageTypeElementBounds = messageTypeElement?.getBBox();
27+
const arrowIndex = fromHandler.outMessages.findIndex((out) => route.fromRoutedMessage?.messageId === out.messageId && route.fromRoutedMessage?.receiving.name === out.receiving.name) + 1;
2728
const y = fromHandlerLocation.y + (fromHandlerLocation.height / (fromHandler.outMessages.length + 1)) * arrowIndex; //TODO work out the reason - 15 is applied in WPF;
2829
29-
const [direction, width, fromX] = (() => {
30-
if (fromHandlerLocation.id === toHandlerLocation.id) return [Direction.Right, 15 + Arrow_Head_Width, fromHandlerLocation.right];
31-
if (handlerLocations.value.indexOf(fromHandlerLocation) < handlerLocations.value.indexOf(toHandlerLocation)) return [Direction.Right, toHandlerLocation.left - fromHandlerLocation.right - Arrow_Head_Width, fromHandlerLocation.right];
32-
return [Direction.Left, toHandlerLocation.left - fromHandlerLocation.right - Arrow_Head_Width, toHandlerLocation.left];
30+
const toHandlerCentre = toHandlerLocation.left + (toHandlerLocation.right - toHandlerLocation.left) / 2;
31+
const [direction, width, fromX, messageTypeOffset] = (() => {
32+
if (fromHandlerLocation.left === toHandlerLocation.left) return [Direction.Right, 15 + Arrow_Head_Width, fromHandlerLocation.right, toHandlerCentre + 45];
33+
if (fromHandlerLocation.left < toHandlerLocation.left) return [Direction.Right, toHandlerCentre - fromHandlerLocation.right - Arrow_Head_Width - 1, fromHandlerLocation.right, toHandlerCentre + 3];
34+
return [Direction.Left, toHandlerCentre - fromHandlerLocation.left + Arrow_Head_Width + 1, fromHandlerLocation.left, toHandlerCentre - ((messageTypeElementBounds?.width ?? 0) + 15 + Message_Type_Margin * 3 + 3)];
3335
})();
3436
route.fromRoutedMessage.direction = direction;
3537
36-
const toX = toHandlerLocation.left + (toHandlerLocation.right - toHandlerLocation.left) / 2;
37-
const messageTypeElement = messageTypeRefs.value[index];
38-
const messageTypeElementBounds = messageTypeElement?.getBBox();
38+
if (messageTypeOffset < 0) {
39+
store.setStartX(-1 * messageTypeOffset);
40+
}
3941
4042
return {
4143
id: route.name,
4244
fromX,
4345
y,
4446
direction,
4547
width,
46-
toX,
48+
toHandlerCentre,
4749
height: Math.abs(y - toHandlerLocation.y),
4850
type: route.fromRoutedMessage.type,
4951
messageType: route.fromRoutedMessage.name,
50-
messageTypeOffset: toX + 3, //TODO: apply using messageTypeRef if arrow is left
52+
messageTypeOffset,
5153
highlight: highlightId.value === route.name,
5254
highlightTextWidth: messageTypeElementBounds?.width,
5355
highlightTextHeight: messageTypeElementBounds?.height,
@@ -65,12 +67,12 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
6567
<g v-if="arrow != null">
6668
<!--Main Arrow-->
6769
<g>
68-
<path :d="`M${arrow.fromX} ${arrow.y} h${arrow.width}`" stroke-width="4" stroke="black" />
70+
<path :d="`M${arrow.fromX} ${arrow.y} h${arrow.width}`" stroke-width="3.5" stroke="black" :stroke-dasharray="arrow.type === RoutedMessageType.Event ? '12 8' : undefined" />
6971
<path v-if="arrow.direction === Direction.Right" :d="`M${arrow.fromX + arrow.width} ${arrow.y - 7.5} l10 7.5 -10,7.5z`" fill="black" />
70-
<path v-if="arrow.direction === Direction.Left" :d="`M${arrow.fromX - Arrow_Head_Width} ${arrow.y} l10,-7.5 0,15z`" fill="black" />
72+
<path v-if="arrow.direction === Direction.Left" :d="`M${arrow.toHandlerCentre + 1} ${arrow.y} l10,-7.5 0,15z`" fill="black" />
7173
</g>
7274
<!--Highlight Arrow-->
73-
<g v-if="arrow.highlight" :transform="`translate(${arrow.toX},${arrow.y})`" stroke="var(--highlight)" fill="var(--highlight)">
75+
<g v-if="arrow.highlight" :transform="`translate(${arrow.toHandlerCentre},${arrow.y})`" stroke="var(--highlight)" fill="var(--highlight)">
7476
<path :d="`M0 0 v${arrow.height - 6}`" stroke-width="2" />
7577
<path :d="`M0 ${arrow.height} l-3,-6 6,0z`" />
7678
</g>
@@ -96,10 +98,10 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
9698
/>
9799
<path
98100
v-else-if="arrow.type === RoutedMessageType.Event"
99-
d="M 0,0 M 32,32 M 0,16 A 6,6 0 1 1 12,16 A 6,6 0 1 1 0,16 M 14,13 v6 h10 v2 L32,16 L24,11 v2 M13.78,19.54 L9.54,23.78 L16.61,30.85 L15.19,32.26 L24.38,34.38 L22.26,25.19 L20.85,26.61 M9.54,8.22 L13.78,12.46 L20.85,5.39 L22.26,6.81 L24.38,-2.38 L15.19,-0.26 L16.61,1.15"
101+
d="M 0 2 M 27.8 29.8 M 0 15.9 A 5.2 5.2 90 1 1 10.4 15.9 A 5.2 5.2 90 1 1 0 15.9 M 12.1 13.3 v 5.2 h 8.7 v 1.8 L 27.8 15.9 L 20.8 11.6 v 1.8 M 11.9 19 L 8.3 22.6 L 14.3 28.8 L 13.1 30 L 21.2 31.9 L 19.3 23.9 L 18.1 25.1 M 8.3 9.1 L 11.9 12.9 L 18.1 6.7 L 19.3 7.9 L 21.2 0 L 13.1 1.9 L 14.3 3.1"
100102
/>
101103
<path v-else-if="arrow.type === RoutedMessageType.Command" d="M 0,0 M 32,32 M 0,16 A 6,6 0 1 1 12,16 A 6,6 0 1 1 0,16 M 14,13 v6 h10 v2 L32,16 L24,11 v2 z" />
102-
<path v-else-if="arrow.type === RoutedMessageType.Local" d="M17-1h-7v2h5v7H9V5.8L3,9l6,3.2V10h8V-1z M9,0.1C9,1.7,7.7,3,6,3S3,1.7,3,0.1S4.3-3,6-3S9-1.6,9,0.1z" />
104+
<path v-else-if="arrow.type === RoutedMessageType.Local" d="M 32 6 h -14 v 4 h 10 v 14 H 16 V 19.6 L 4 26 l 12 6.4 V 28 h 16 V 6 z M 16 8.2 C 16 11.4 13.4 14 10 14 S 4 11.4 4 8.2 S 6.6 2 10 2 S 16 4.8 16 8 z" />
103105
</svg>
104106
<text :x="15 + Message_Type_Margin + Message_Type_Margin" :y="Message_Type_Margin" alignment-baseline="before-edge" :ref="(el) => setMessageTypeRef(el as SVGTextElement, i)">{{ arrow.messageType }}</text>
105107
</g>

src/Frontend/src/resources/SequenceDiagram/RoutedMessage.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Handler } from "./Handler";
55
import { friendlyTypeName } from "./SequenceModel";
66

77
export interface RoutedMessage {
8+
id: string;
89
name: string;
910
readonly selectedMessage: Message;
1011
fromHandler?: Handler;
@@ -66,7 +67,7 @@ class MessageProcessingRouteItem implements MessageProcessingRoute {
6667
this.processingHandler = processingHandler;
6768

6869
if (routedMessage && this.processingHandler) {
69-
this.name = `${processingHandler?.name}(${routedMessage.messageId})`;
70+
this.name = `${processingHandler?.name}(${routedMessage.id})`;
7071
}
7172

7273
if (routedMessage) routedMessage.route = this;
@@ -92,6 +93,10 @@ class RoutedMessageItem implements RoutedMessage {
9293
this.name = friendlyTypeName(message.message_type) ?? "";
9394
}
9495

96+
get id() {
97+
return this.selectedMessage.id;
98+
}
99+
95100
get receiving() {
96101
return this.selectedMessage.receiving_endpoint;
97102
}

src/Frontend/src/stores/SequenceDiagramStore.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ export interface EndpointCentrePoint {
1515

1616
export interface HandlerLocation {
1717
id: string;
18+
endpointName: string;
1819
left: number;
1920
right: number;
2021
y: number;
2122
height: number;
2223
}
2324

25+
export const Endpoint_Width = 260;
26+
2427
export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () => {
2528
const conversationId = ref<string>();
2629

30+
const startX = ref(Endpoint_Width / 2);
2731
const endpoints = ref<Endpoint[]>([]);
2832
const handlers = ref<Handler[]>([]);
2933
const routes = ref<MessageProcessingRoute[]>([]);
@@ -47,9 +51,19 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
4751
});
4852

4953
function setConversationId(id: string) {
54+
endpoints.value = [];
55+
handlers.value = [];
56+
routes.value = [];
57+
startX.value = Endpoint_Width / 2;
5058
conversationId.value = id;
5159
}
5260

61+
function setStartX(offset: number) {
62+
const newValue = Math.max(offset + Endpoint_Width / 2, startX.value);
63+
if (newValue === startX.value) return;
64+
startX.value = newValue;
65+
}
66+
5367
function setMaxWidth(width: number) {
5468
maxWidth.value = width;
5569
}
@@ -72,6 +86,7 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
7286

7387
return {
7488
setConversationId,
89+
startX,
7590
endpoints,
7691
handlers,
7792
routes,
@@ -80,6 +95,7 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
8095
maxHeight,
8196
handlerLocations,
8297
highlightId,
98+
setStartX,
8399
setMaxWidth,
84100
setMaxHeight,
85101
setEndpointCentrePoints,

0 commit comments

Comments
 (0)