240 lines
6.8 KiB
JavaScript
240 lines
6.8 KiB
JavaScript
import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
|
|
import { Actor } from './actor.js';
|
|
import { Action } from './action.js';
|
|
import { debounce, hexToRGB } from '../util.js';
|
|
import { CryptoUtil } from './crypto.js';
|
|
|
|
class MermaidDiagram {
|
|
constructor(box, logBox) {
|
|
this.box = box;
|
|
this.container = this.box.addBox('Container');
|
|
this.element = this.box.addBox('Element');
|
|
this.renderBox = this.box.addBox('Render');
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
|
|
this.inSection = 0;
|
|
}
|
|
|
|
async log(msg, render = true) {
|
|
this.logBoxPre.textContent = `${this.logBoxPre.textContent}\n${msg}`;
|
|
if (render) {
|
|
await this.render();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
async render() {
|
|
const render = async () => {
|
|
let text = this.logBoxPre.textContent;
|
|
for (let i = 0; i < this.inSection; i++) {
|
|
text += '\nend';
|
|
}
|
|
const graph = await mermaid.mermaidAPI.render(
|
|
this.element.getId(),
|
|
text,
|
|
);
|
|
this.renderBox.setInnerHTML(graph);
|
|
};
|
|
await debounce(render, 100);
|
|
}
|
|
}
|
|
|
|
class Table {
|
|
constructor(box) {
|
|
this.box = box;
|
|
this.columns = [];
|
|
this.rows = [];
|
|
this.table = box.el.appendChild(document.createElement('table'));
|
|
this.headings = this.table.appendChild(document.createElement('tr'));
|
|
}
|
|
|
|
setColumns(columns) {
|
|
if (JSON.stringify(columns) === JSON.stringify(this.columns)) {
|
|
return;
|
|
}
|
|
if (this.columns.length) {
|
|
this.table.innerHTML = '';
|
|
this.headings = this.table.appendChild(document.createElement('tr'));
|
|
this.columns = [];
|
|
}
|
|
this.columns = columns;
|
|
for (const { title } of columns) {
|
|
const heading = document.createElement('th');
|
|
this.headings.appendChild(heading);
|
|
heading.innerHTML = title ?? '';
|
|
}
|
|
if (this.rows.length) {
|
|
const { rows } = this;
|
|
this.rows = [];
|
|
for (const row of rows) {
|
|
this.addRow(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
addRow(rowMap) {
|
|
this.rows.push(rowMap);
|
|
const row = this.table.appendChild(document.createElement('tr'));
|
|
for (const { key } of this.columns) {
|
|
const value = rowMap.get(key);
|
|
const cell = row.appendChild(document.createElement('td'));
|
|
cell.innerHTML = value ?? '';
|
|
}
|
|
}
|
|
}
|
|
|
|
export class Scene {
|
|
constructor(name, rootBox) {
|
|
this.name = name;
|
|
this.box = rootBox.addBox(name);
|
|
this.titleBox = this.box.addBox('Title').setInnerHTML(name);
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
this.topSection = this.box.addBox('Top section').flex();
|
|
this.displayValuesBox = this.topSection.addBox('Values');
|
|
this.middleSection = this.box.addBox('Middle section');
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
this.actors = new Set();
|
|
this.dateStart = new Date();
|
|
this.flowcharts = new Map();
|
|
|
|
mermaid.mermaidAPI.initialize({
|
|
startOnLoad: false,
|
|
theme: 'base',
|
|
themeVariables: {
|
|
darkMode: true,
|
|
primaryColor: '#2a5b6c',
|
|
primaryTextColor: '#b6b6b6',
|
|
// lineColor: '#349cbd',
|
|
lineColor: '#57747d',
|
|
signalColor: '#57747d',
|
|
// signalColor: '#349cbd',
|
|
noteBkgColor: '#516f77',
|
|
noteTextColor: '#cecece',
|
|
activationBkgColor: '#1d3f49',
|
|
activationBorderColor: '#569595',
|
|
},
|
|
});
|
|
|
|
this.options = {
|
|
edgeNodeColor: '#4d585c',
|
|
};
|
|
}
|
|
|
|
withSequenceDiagram() {
|
|
const box = this.box.addBox('Sequence diagram');
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
const logBox = this.box.addBox('Sequence diagram text').addClass('dim');
|
|
this.sequence = new MermaidDiagram(box, logBox);
|
|
this.sequence.log('sequenceDiagram', false);
|
|
return this;
|
|
}
|
|
|
|
withFlowchart({ direction = 'BT' } = {}) {
|
|
const box = this.topSection.addBox('Flowchart').addClass('padded');
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
|
this.flowchart = new MermaidDiagram(box, logBox);
|
|
this.flowchart.log(`graph ${direction}`, false);
|
|
return this;
|
|
}
|
|
|
|
withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
|
|
const index = this.flowcharts.size;
|
|
name = name ?? `Flowchart ${index}`;
|
|
id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`;
|
|
const container = this.middleSection.addBox(name).flex();
|
|
const box = container.addBox('Flowchart').addClass('padded');
|
|
const logBox = container.addBox('Flowchart text').addClass('dim');
|
|
const flowchart = new MermaidDiagram(box, logBox);
|
|
flowchart.log(`graph ${direction}`, false);
|
|
this.flowcharts.set(id, flowchart);
|
|
return this;
|
|
}
|
|
|
|
lastFlowchart() {
|
|
if (!this.flowcharts.size) {
|
|
if (this.flowchart) {
|
|
return this.flowchart;
|
|
}
|
|
throw new Error('lastFlowchart: No additional flowcharts have been added.');
|
|
}
|
|
const flowcharts = Array.from(this.flowcharts.values());
|
|
return flowcharts[flowcharts.length - 1];
|
|
}
|
|
|
|
withTable() {
|
|
if (this.table) {
|
|
return this;
|
|
}
|
|
const box = this.middleSection.addBox('Table').addClass('padded');
|
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
|
this.table = new Table(box);
|
|
return this;
|
|
}
|
|
|
|
async addActor(name) {
|
|
const actor = new Actor(name);
|
|
if (this.sequence) {
|
|
await this.sequence.log(`participant ${name}`);
|
|
}
|
|
return actor;
|
|
}
|
|
|
|
registerActor(actor) {
|
|
this.actors.add(actor);
|
|
}
|
|
|
|
findActor(fn) {
|
|
return Array.from(this.actors.values()).find(fn);
|
|
}
|
|
|
|
addAction(name) {
|
|
const action = new Action(name, this);
|
|
return action;
|
|
}
|
|
|
|
addDisplayValue(name) {
|
|
const dv = this.displayValuesBox.addDisplayValue(name);
|
|
return dv;
|
|
}
|
|
|
|
async deactivateAll() {
|
|
for (const actor of this.actors.values()) {
|
|
while (actor.active) {
|
|
await actor.deactivate();
|
|
}
|
|
}
|
|
}
|
|
|
|
async startSection(color = '#08252c') {
|
|
const { r, g, b } = hexToRGB(color);
|
|
this.sequence.inSection++;
|
|
this.sequence.log(`rect rgb(${r}, ${g}, ${b})`, false);
|
|
}
|
|
|
|
async endSection() {
|
|
this.sequence.inSection--;
|
|
this.sequence.log('end');
|
|
}
|
|
|
|
stateToTable(label) {
|
|
const row = new Map();
|
|
const columns = [];
|
|
columns.push({ key: 'seqNum', title: '#' });
|
|
columns.push({ key: 'elapsedMs', title: 'Time (ms)' });
|
|
row.set('seqNum', this.table.rows.length + 1);
|
|
row.set('elapsedMs', new Date() - this.dateStart);
|
|
row.set('label', label);
|
|
for (const actor of this.actors) {
|
|
for (const [aKey, { name, value }] of actor.getValuesMap()) {
|
|
const key = `${actor.name}:${aKey}`;
|
|
columns.push({ key, title: name });
|
|
row.set(key, value);
|
|
}
|
|
}
|
|
columns.push({ key: 'label', title: '' });
|
|
this.table.setColumns(columns);
|
|
this.table.addRow(row);
|
|
}
|
|
}
|