added mermaid diagram

This commit is contained in:
sara-pervana
2024-01-14 14:39:01 +01:00
committed by Sara Pervana
parent 47700b7bd0
commit 7b2b675c2c
8 changed files with 367 additions and 13 deletions

View File

@@ -0,0 +1,133 @@
export const generateMermaidString = (data: any) => {
if (!data || data.length === 0)
return '';
// Extract unique participants
const participants = Array.from(new Set(data.map((item: any) => item.src_did).concat(data.map((item: any) => item.des_did))));
// Begin the sequence diagram definition
let mermaidString = "sequenceDiagram\n";
// Add participants to the diagram
participants.forEach((participant, index) => {
mermaidString += ` participant ${String.fromCharCode(65 + index)} as ${participant}\n`;
});
// Add messages to the diagram
data.forEach((item: any, index: number) => {
const srcParticipant = String.fromCharCode(65 + participants.indexOf(item.src_did));
const desParticipant = String.fromCharCode(65 + participants.indexOf(item.des_did));
const timestamp = new Date(item.timestamp * 1000).toLocaleString(); // Convert Unix timestamp to readable date
const message = item.msg.text || `Event message ${index + 1}`;
// If group_name or group_id exists, start an 'alt' block
if (item.group_id != null) {
mermaidString += ` alt ${item.group_name || item.group_id}\n`;
mermaidString += ` rect rgb(191, 223, 255)\n`;
}
// Add the message interaction
mermaidString += ` ${srcParticipant}->>${desParticipant}: [${timestamp}] ${message}\n`;
// If there was an 'alt' block, close it
if (item.group_id != null) {
mermaidString += ` end\n`;
mermaidString += ` end\n`;
}
});
return mermaidString;
};
// Dummy Data
export const dataFromBE = [
{
"id": 12,
"timestamp": 1704892813,
"group": 0,
"group_id": 12,
// "group_name": "Data",
"msg_type": 4,
"src_did": "did:sov:test:121",
// "src_name": "Entity A",
"des_did": "did:sov:test:120",
// "des_name": "Entity B",
"msg": {
text: 'Hello World'
}
},
{
"id": 60,
"timestamp": 1704892823,
"group": 1,
"group_id": 19,
"msg_type": 4,
"src_did": "did:sov:test:122",
"des_did": "did:sov:test:121",
"msg": {}
},
{
"id": 30162,
"timestamp": 1704892817,
"group": 1,
"group_id": 53,
"msg_type": 2,
"src_did": "did:sov:test:121",
"des_did": "did:sov:test:122",
"msg": {}
},
{
"id": 63043,
"timestamp": 1704892809,
"group": 0,
"group_id": 12,
"msg_type": 3,
"src_did": "did:sov:test:121",
"des_did": "did:sov:test:120",
"msg": {}
},
{
"id": 66251,
"timestamp": 1704892805,
"group": 0,
"group_id": 51,
"msg_type": 1,
"src_did": "did:sov:test:120",
"des_did": "did:sov:test:121",
"msg": {}
},
{
"id": 85434,
"timestamp": 1704892807,
"group": 0,
"group_id": 51,
"msg_type": 2,
"src_did": "did:sov:test:120",
"des_did": "did:sov:test:121",
"msg": {}
},
{
"id": 124842,
"timestamp": 1704892819,
"group": 1,
"group_id": 19,
"msg_type": 3,
"src_did": "did:sov:test:122",
"des_did": "did:sov:test:121",
"msg": {}
},
{
"id": 246326,
"timestamp": 1704892815,
"group": 1,
"group_id": 53,
"msg_type": 1,
"src_did": "did:sov:test:121",
"des_did": "did:sov:test:122",
"msg": {}
}
]

View File

@@ -0,0 +1,178 @@
'use client';
import { useRef, useEffect, useState } from 'react';
import mermaid from 'mermaid';
import { IconButton } from '@mui/material';
import RefreshIcon from '@mui/icons-material/Refresh';
import ZoomInIcon from '@mui/icons-material/ZoomIn';
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import DownloadIcon from '@mui/icons-material/Download';
import ResetIcon from '@mui/icons-material/Autorenew';
import Tooltip from '@mui/material/Tooltip';
import { NoDataOverlay } from '../noDataOverlay';
import { useGetAllEventmessages } from '@/api/eventmessages/eventmessages';
import { mutate } from 'swr';
import { LoadingOverlay } from '../join/loadingOverlay';
import { generateMermaidString, dataFromBE } from './helpers';
const SequenceDiagram = () => {
const {
data: eventMessagesData,
isLoading: loadingEventMessages,
swrKey: eventMessagesKeyFunc,
} = useGetAllEventmessages();
const mermaidRef: any = useRef(null);
const [scale, setScale] = useState(1);
const hasData = eventMessagesData?.data && eventMessagesData?.data.length > 0;
const mermaidString = generateMermaidString(eventMessagesData?.data);
useEffect(() => {
if (!loadingEventMessages && hasData)
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
sequence: {
mirrorActors: false,
},
});
if (mermaidRef.current) {
mermaidRef.current.innerHTML = mermaidString;
mermaid.init(undefined, mermaidRef.current);
}
}, [loadingEventMessages, hasData, mermaidString]);
useEffect(() => {
if (mermaidRef.current) {
const svg = mermaidRef.current.querySelector('svg');
if (svg) {
svg.style.transform = `scale(${scale})`;
svg.style.transformOrigin = 'top left'; // Set transform origin to top left
mermaidRef.current.style.width = `${svg.getBoundingClientRect().width * scale}px`;
mermaidRef.current.style.height = `${svg.getBoundingClientRect().height * scale}px`;
}
}
}, [scale]);
const onRefresh = () => {
const eventMessagesKey =
typeof eventMessagesKeyFunc === "function"
? eventMessagesKeyFunc()
: eventMessagesKeyFunc;
if (eventMessagesKey) {
mutate(eventMessagesKey);
}
};
const zoomIn = () => {
setScale((scale) => scale * 1.1);
};
const zoomOut = () => {
setScale((scale) => scale / 1.1);
};
const resetZoom = () => {
setScale(1);
};
const viewInFullScreen = () => {
if (mermaidRef.current) {
const svg = mermaidRef.current.querySelector('svg');
const serializer = new XMLSerializer();
const svgBlob = new Blob([serializer.serializeToString(svg)], {
type: 'image/svg+xml',
});
const url = URL.createObjectURL(svgBlob);
window.open(url, '_blank');
}
};
const downloadAsPng = () => {
if (mermaidRef.current) {
const svg = mermaidRef.current.querySelector('svg');
const svgData = new XMLSerializer().serializeToString(svg);
// Create a canvas element to convert SVG to PNG
const canvas = document.createElement('canvas');
const svgSize = svg.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;
const ctx = canvas.getContext('2d');
const img = document.createElement('img');
img.onload = () => {
ctx?.drawImage(img, 0, 0);
const pngData = canvas.toDataURL('image/png');
// Trigger download
const link = document.createElement('a');
link.download = 'sequence-diagram.png';
link.href = pngData;
link.click();
};
img.src =
'data:image/svg+xml;base64,' +
btoa(unescape(encodeURIComponent(svgData)));
}
};
if (loadingEventMessages)
return <LoadingOverlay title="Loading Diagram" subtitle="Please wait..." />
return (
<div className="flex flex-col items-end w-full">
{
hasData ? <>
<div className='flex justify-end gap-2.5 mb-5 w-full'>
<Tooltip placement='top' title='Refresh Diagram'>
<IconButton color="default" onClick={onRefresh}>
<RefreshIcon />
</IconButton>
</Tooltip>
<Tooltip title="Zoom In" placement='top'>
<IconButton color="primary" onClick={zoomIn}>
<ZoomInIcon />
</IconButton>
</Tooltip>
<Tooltip title="Zoom Out" placement='top'>
<IconButton color="primary" onClick={zoomOut}>
<ZoomOutIcon />
</IconButton>
</Tooltip>
<Tooltip title="Reset" placement='top'>
<IconButton color="primary" onClick={resetZoom}>
<ResetIcon />
</IconButton>
</Tooltip>
<Tooltip title="View in Fullscreen" placement='top'>
<IconButton color="primary" onClick={viewInFullScreen}>
<FullscreenIcon />
</IconButton>
</Tooltip>
<Tooltip title="Download as PNG" placement='top'>
<IconButton color="primary" onClick={downloadAsPng}>
<DownloadIcon />
</IconButton>
</Tooltip>
</div>
<div className="w-full h-500 overflow-auto p-2.5 box-border h-full">
<div className='mermaid' ref={mermaidRef}></div>
</div>
</> : <div className="flex justify-center items-center w-full h-500">
<NoDataOverlay label="No Activity yet" />
</div>
}
</div>
)
}
export default SequenceDiagram;

View File

@@ -0,0 +1,21 @@
.container {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.buttons-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-bottom: 20px;
}
.diagram-container {
width: 100%;
height: 500px;
overflow: auto;
border: 1px solid #ddd;
padding: 10px;
box-sizing: border-box;
}