Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
10 changes: 6 additions & 4 deletions src/Frontend/src/components/messages2/SequenceDiagram.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ import Routes from "./SequenceDiagram/RoutesComponent.vue";
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";
import useTooltips from "./SequenceDiagram/tooltipOverlay.ts";
import { onMounted } from "vue";
import { onMounted, ref } from "vue";

const store = useSequenceDiagramStore();
const { maxWidth, maxHeight } = storeToRefs(store);
const endpointYOffset = ref(0);

useTooltips();

onMounted(() => store.refreshConversation());
</script>

<template>
<div class="outer">
<svg class="sequence-diagram" :width="`max(100%, ${isNaN(maxWidth) ? 0 : maxWidth}px)`" :height="maxHeight + 20">
<Endpoints />
<div class="outer" @scroll="(ev) => (endpointYOffset = (ev.target as Element).scrollTop)">
<svg class="sequence-diagram" :style="`--sequence-diagram-max-width: ${isNaN(maxWidth) ? 0 : maxWidth}px`" :height="maxHeight + 20">
<Timeline />
<Handlers />
<Routes />
<Endpoints :yOffset="endpointYOffset" />
</svg>
</div>
</template>
Expand All @@ -46,5 +47,6 @@ onMounted(() => store.refreshConversation());
--highlight: #0b6eef;
--highlight-background: #c5dee9;
background: white;
width: max(100%, var(--sequence-diagram-max-width));
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ interface EndpointSurround {
const Endpoint_Gap = 30;
const Endpoint_Image_Width = 20;

defineProps<{
yOffset: number;
}>();

const store = useSequenceDiagramStore();
const { startX, endpoints } = storeToRefs(store);

Expand Down Expand Up @@ -79,7 +83,7 @@ function setEndpointTextRef(el: SVGTextElement, index: number) {
</script>

<template>
<g v-for="(endpoint, i) in endpointItems" :key="endpoint.name" transform="translate(0,15)" :ref="(el) => (endpoint.uiRef = el as SVGElement)">
<g v-for="(endpoint, i) in endpointItems" :key="endpoint.name" :transform="`translate(0,${yOffset + 15})`" :ref="(el) => (endpoint.uiRef = el as SVGElement)">
<rect
v-if="endpoint.surround"
:x="endpoint.surround.x"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Handler_Gap = 20;
const Handler_Width = 14;

const store = useSequenceDiagramStore();
const { handlers, endpointCentrePoints, highlightId } = storeToRefs(store);
const { handlers, endpointCentrePoints, highlightId, selectedId } = storeToRefs(store);

const messageTypeRefs = ref<SVGTextElement[]>([]);

Expand All @@ -27,6 +27,7 @@ const handlerItems = computed(() => {
const fill = (() => {
if (handler.id === "First") return "black";
if (handler.state === HandlerState.Fail) return "var(--error)";
if (handler.route?.name === selectedId.value) return "var(--highlight)";
if (handler.route?.name === highlightId.value) return "var(--highlight-background)";
return "var(--gray60)";
})();
Expand All @@ -51,6 +52,8 @@ const handlerItems = computed(() => {

return {
id: handler.id,
messageId: { id: handler.selectedMessage?.message_id, uniqueId: handler.selectedMessage?.id },
isError: handler.state === HandlerState.Fail,
endpointName: handler.endpoint.name,
incomingId: handler.route?.name,
left,
Expand All @@ -63,6 +66,7 @@ const handlerItems = computed(() => {
messageType: handler.name,
messageTypeOffset,
messageTypeHighlight: handler.route?.name === highlightId.value,
messageTypeSelected: handler.route?.name === selectedId.value,
setUIRef: (el: SVGElement) => (handler.uiRef = el),
};
});
Expand All @@ -81,21 +85,35 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
<template>
<g v-for="(handler, i) in handlerItems" :key="`${handler.id}###${handler.endpointName}`" :transform="`translate(${handler.left}, ${handler.y})`">
<!--Handler Activation Box-->
<g :ref="(el) => handler.setUIRef(el as SVGElement)">
<rect :width="Handler_Width" :height="handler.height" :class="handler.incomingId && 'clickable'" :fill="handler.fill" @mouseover="() => store.setHighlightId(handler.incomingId)" @mouseleave="() => store.setHighlightId()" />
<g :ref="(el) => handler.setUIRef(el as SVGElement)" class="activation-box">
<rect
:width="Handler_Width"
:height="handler.height"
:class="{
clickable: handler.incomingId && !handler.messageTypeSelected,
}"
:fill="handler.fill"
@mouseover="() => store.setHighlightId(handler.incomingId)"
@mouseleave="() => store.setHighlightId()"
@click="handler.incomingId && !handler.messageTypeSelected && store.navigateTo(handler.messageId.uniqueId, handler.messageId.id, handler.isError)"
/>
<path v-if="handler.icon" :d="handler.icon" fill="white" :transform="`translate(${Handler_Width / 2 - handler.iconSize / 2}, ${handler.height / 2 - handler.iconSize / 2})`" />
</g>
<!--Message Type and Icon-->
<g
v-if="handler.messageType"
:transform="`translate(${handler.messageTypeOffset}, 4)`"
class="clickable"
:fill="handler.messageTypeHighlight ? 'var(--highlight)' : 'var(--gray40)'"
:class="{
clickable: !handler.messageTypeSelected,
'message-type': true,
highlight: handler.messageTypeHighlight || handler.messageTypeSelected,
}"
@mouseover="() => store.setHighlightId(handler.incomingId)"
@mouseleave="() => store.setHighlightId()"
@click="handler.incomingId && !handler.messageTypeSelected && store.navigateTo(handler.messageId.uniqueId, handler.messageId.id, handler.isError)"
>
<path d="M9,3L9,3 9,0 0,0 0,3 4,3 4,6 0,6 0,9 4,9 4,12 0,12 0,15 9,15 9,12 5,12 5,9 9,9 9,6 5,6 5,3z" />
<text x="14" y="10" alignment-baseline="middle" :ref="(el) => setMessageTypeRef(el as SVGTextElement, i)">{{ handler.messageType }}</text>
<text x="14" y="0" dominant-baseline="text-before-edge" :ref="(el) => setMessageTypeRef(el as SVGTextElement, i)">{{ handler.messageType }}</text>
</g>
</g>
</template>
Expand All @@ -104,4 +122,16 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
.clickable {
cursor: pointer;
}

.activation-box:focus {
outline: none;
}

.message-type {
fill: var(--gray40);
}

.message-type.highlight {
fill: var(--highlight);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { Direction, RoutedMessageType } from "@/resources/SequenceDiagram/Routed
import { computed, ref } from "vue";
import { useSequenceDiagramStore } from "@/stores/SequenceDiagramStore";
import { storeToRefs } from "pinia";
import { HandlerState } from "@/resources/SequenceDiagram/Handler";

const Arrow_Head_Width = 10;
const Message_Type_Margin = 4;

const store = useSequenceDiagramStore();
const { routes, handlerLocations, highlightId } = storeToRefs(store);
const { selectedId, routes, handlerLocations, highlightId } = storeToRefs(store);

const messageTypeRefs = ref<SVGTextElement[]>([]);

Expand Down Expand Up @@ -41,6 +42,9 @@ const arrows = computed(() =>

return {
id: route.name,
selected: route.name === selectedId.value,
messageId: { uniqueId: route.fromRoutedMessage.selectedMessage.id, id: route.fromRoutedMessage.selectedMessage.message_id },
isHandlerError: route.processingHandler?.state === HandlerState.Fail,
fromX,
y,
direction,
Expand Down Expand Up @@ -73,25 +77,30 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
<path v-if="arrow.direction === Direction.Left" :d="`M${arrow.toHandlerCentre + 1} ${arrow.y} l10,-7.5 0,15z`" fill="black" />
</g>
<!--Highlight Arrow-->
<g v-if="arrow.highlight" :transform="`translate(${arrow.toHandlerCentre},${arrow.y})`" stroke="var(--highlight)" fill="var(--highlight)">
<g v-if="arrow.highlight || arrow.selected" :transform="`translate(${arrow.toHandlerCentre},${arrow.y})`" stroke="var(--highlight)" fill="var(--highlight)">
<path :d="`M0 0 v${arrow.height - 6}`" stroke-width="2" />
<path :d="`M0 ${arrow.height} l-3,-6 6,0z`" />
</g>
<!--Message Type and Icon-->
<g
class="clickable message-type"
:class="{
clickable: !arrow.selected,
'message-type': true,
highlight: arrow.highlight,
selected: arrow.selected,
}"
:transform="`translate(${arrow.messageTypeOffset}, ${arrow.y - 7.5 - Message_Type_Margin})`"
:fill="arrow.highlight ? 'var(--highlight)' : 'black'"
@mouseover="() => store.setHighlightId(arrow.id)"
@mouseleave="() => store.setHighlightId()"
@click="!arrow.selected && store.navigateTo(arrow.messageId.uniqueId, arrow.messageId.id, arrow.isHandlerError)"
:ref="(el) => arrow.setUIRef(el as SVGElement)"
>
<!--19 is width of MessageType icon, plus a gap-->
<rect
v-if="arrow.highlight && arrow.messageTypeOffset"
:width="arrow.highlightTextWidth + 19 + Message_Type_Margin + Message_Type_Margin"
:height="arrow.highlightTextHeight + Message_Type_Margin + Message_Type_Margin"
fill="var(--highlight-background)"
v-if="(arrow.highlight || arrow.selected) && arrow.messageTypeOffset"
:width="(arrow.highlightTextWidth ?? 0) + 19 + Message_Type_Margin + Message_Type_Margin"
:height="(arrow.highlightTextHeight ?? 0) + Message_Type_Margin + Message_Type_Margin"
class="border"
/>
<svg :x="Message_Type_Margin" :y="Message_Type_Margin" width="15" height="15" viewBox="0 0 32 32">
<path
Expand All @@ -105,7 +114,7 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
<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" />
<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" />
</svg>
<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>
<text :x="15 + Message_Type_Margin + Message_Type_Margin" :y="Message_Type_Margin" dominant-baseline="text-before-edge" :ref="(el) => setMessageTypeRef(el as SVGTextElement, i)">{{ arrow.messageType }}</text>
</g>
</g>
</template>
Expand All @@ -116,7 +125,32 @@ function setMessageTypeRef(el: SVGTextElement, index: number) {
cursor: pointer;
}

.message-type {
fill: black;
outline: none;
}

.message-type.selected {
fill: white;
}

.message-type .border {
fill: var(--highlight-background);
}

.message-type.selected .border {
fill: var(--highlight);
}

.message-type:not(.selected).highlight {
fill: var(--highlight);
}

.message-type text::selection {
fill: white;
}

.message-type.selected text::selection {
background-color: black;
}
</style>
4 changes: 2 additions & 2 deletions src/Frontend/src/resources/SequenceDiagram/SequenceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ export class ModelCreator implements ConversationModel {
sendingHandler.addOutMessage(routedMessage);
}

const start = firstOrderHandlers.find((h) => h.id === ConversationStartHandlerName);
const start = firstOrderHandlers.filter((h) => h.id === ConversationStartHandlerName);
const orderByHandledAt = firstOrderHandlers.filter((h) => h.id !== ConversationStartHandlerName).sort((a, b) => (a.handledAt?.getTime() ?? 0) - (b.handledAt?.getTime() ?? 0));

this.#handlers = [start!, ...orderByHandledAt];
this.#handlers = [...start, ...orderByHandledAt];
}

get endpoints(): Endpoint[] {
Expand Down
21 changes: 18 additions & 3 deletions src/Frontend/src/stores/SequenceDiagramStore.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { acceptHMRUpdate, defineStore } from "pinia";
import { ref, watch } from "vue";
import { ModelCreator } from "@/resources/SequenceDiagram/SequenceModel";
import { acceptHMRUpdate, defineStore, storeToRefs } from "pinia";
import { computed, ref, watch } from "vue";
import { friendlyTypeName, ModelCreator } from "@/resources/SequenceDiagram/SequenceModel";
import { Endpoint } from "@/resources/SequenceDiagram/Endpoint";
import { Handler } from "@/resources/SequenceDiagram/Handler";
import { MessageProcessingRoute } from "@/resources/SequenceDiagram/RoutedMessage";
import { useMessageStore } from "./MessageStore";
import { useRouter } from "vue-router";
import routeLinks from "@/router/routeLinks";

export interface EndpointCentrePoint {
name: string;
Expand All @@ -25,6 +27,8 @@ export const Endpoint_Width = 260;

export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () => {
const messageStore = useMessageStore();
const { state } = storeToRefs(messageStore);
const router = useRouter();

const startX = ref(Endpoint_Width / 2);
const endpoints = ref<Endpoint[]>([]);
Expand All @@ -36,6 +40,8 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
const handlerLocations = ref<HandlerLocation[]>([]);
const highlightId = ref<string>();

const selectedId = computed(() => `${friendlyTypeName(state.value.data.message_type ?? "")}(${state.value.data.id})`);

watch(
() => messageStore.conversationData.data,
(conversationData) => {
Expand Down Expand Up @@ -80,6 +86,13 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
if (messageStore.state.data.conversation_id) messageStore.loadConversation(messageStore.state.data.conversation_id);
}

function navigateTo(messageUniqueId: string | undefined, messageId: string | undefined, isError: boolean) {
if (messageUniqueId == null) return;
if (!isError && messageId == null) return;

router.push({ path: isError ? routeLinks.messages.failedMessage.link(messageUniqueId) : routeLinks.messages.successMessage.link(messageId!, messageUniqueId) });
}

return {
startX,
endpoints,
Expand All @@ -90,13 +103,15 @@ export const useSequenceDiagramStore = defineStore("SequenceDiagramStore", () =>
maxHeight,
handlerLocations,
highlightId,
selectedId,
setStartX,
setMaxWidth,
setMaxHeight,
setEndpointCentrePoints,
setHandlerLocations,
setHighlightId,
refreshConversation,
navigateTo,
};
});

Expand Down