Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 4 additions & 3 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">
<div class="outer" @scroll="(ev) => (endpointYOffset = (ev.target as Element).scrollTop)">
<svg class="sequence-diagram" :width="`max(100%, ${isNaN(maxWidth) ? 0 : maxWidth}px)`" :height="maxHeight + 20">
<Endpoints />
<Timeline />
<Handlers />
<Routes />
<Endpoints :yOffset="endpointYOffset" />
</svg>
</div>
</template>
Expand Down
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,7 +27,8 @@ const handlerItems = computed(() => {
const fill = (() => {
if (handler.id === "First") return "black";
if (handler.state === HandlerState.Fail) return "var(--error)";
if (handler.route?.name === highlightId.value) return "var(--highlight-background)";
if (handler.route && handler.route.name === selectedId.value) return "var(--highlight)";
if (handler.route && handler.route.name === highlightId.value) return "var(--highlight-background)";
return "var(--gray60)";
})();
const icon = (() => {
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,18 +85,32 @@ 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>
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 @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTippy } from "vue-tippy";
import EndpointTooltip from "./EndpointTooltip.vue";
import HandlerTooltip from "./HandlerTooltip.vue";
import RouteTooltip from "./RouteTooltip.vue";
import { HandlerState } from "@/resources/SequenceDiagram/Handler";

export default function useTooltips() {
const store = useSequenceDiagramStore();
Expand All @@ -30,7 +31,7 @@ export default function useTooltips() {
() => handlers.value.map((handler) => handler.uiRef),
() =>
handlers.value
.filter((handler) => handler.uiRef)
.filter((handler) => handler.uiRef && handler.state !== HandlerState.Unknown)
.forEach((handler) =>
useTippy(handler.uiRef, {
interactive: true,
Expand Down
3 changes: 2 additions & 1 deletion src/Frontend/src/resources/SequenceDiagram/Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Handler {
export enum HandlerState {
Fail,
Success,
Unknown,
}

export const ConversationStartHandlerName = "First";
Expand Down Expand Up @@ -85,7 +86,7 @@ class HandlerItem implements Handler {
name?: string;
partOfSaga?: string;
inMessage?: RoutedMessage;
state: HandlerState = HandlerState.Fail;
state: HandlerState = HandlerState.Unknown;
processedAt?: Date;
processingTime?: number;
route?: MessageProcessingRoute;
Expand Down
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