import { Action } from './action.js'; import { CryptoUtil } from '../supporting/crypto.js'; import { MermaidDiagram } from './mermaid.js'; import { SequenceDiagram } from './sequence.js'; import { Table } from './table.js'; import { Flowchart } from './flowchart.js'; import { Controls } from './controls.js'; import { Box } from './box.js'; 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(); MermaidDiagram.initializeAPI(); this.options = { edgeNodeColor: '#4d585c', }; window.autoPlay = new URL(window.location.href).searchParams.get('auto') !== 'false'; if (!window.disableSceneControls) { this.topRail = new Box('Top rail', document.body, { prepend: true }).addClass('top-rail'); this.controlsBox = this.topRail.addBox('Controls').addClass('controls'); this.controls = new Controls(this.controlsBox); } } 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 SequenceDiagram(box, logBox); 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 Flowchart(box, logBox, direction); 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; } registerActor(actor) { this.actors.add(actor); if (actor.options.announce) { this.sequence?.log(`participant ${actor.name}`); } } 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; } 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) { if (!actor.options.hide) { 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); } }