import { displayNumber } from '../../util/helpers.js'; import { Edge } from './edge.js'; export class Vertex { constructor(graph, type, id, data, options = {}) { this.graph = graph; this.type = type; this.id = id; this.data = data; this.label = options.label ?? this.id; this.options = options; this.edges = { from: [], to: [], }; this.installedClickCallback = false; this.properties = options.properties ?? new Map(); } toJSON() { return { id: this.id, type: this.type, label: this.label, properties: Array.from(this.properties.entries()).reduce((props, [key, value]) => { props[key] = value; return props; }, {}), }; } reset() { this.installedClickCallback = false; } getEdges(type, away) { return this.edges[away ? 'from' : 'to'].filter( (edge) => edge.type === type, ); } setProperty(key, value) { this.properties.set(key, value); return this; } displayVertex() { if (this.options.hide) { return; } let html = ''; if (this.type) { html += `<span class='small'>${this.type}</span>`; } html += `${this.label || this.id}`; html += '<table>'; console.log('displayVertex', { properties: this.properties }); for (const [key, value] of this.properties.entries()) { const displayValue = typeof value === 'number' ? displayNumber(value) : value; html += `<tr><td>${key}</td><td>${displayValue}</td></tr>`; } html += '</table>'; if (this.label && this.id !== this.label) { html += `<span class=small>${this.id}</span><br>`; } html = html.replaceAll(/\n\s*/g, ''); this.graph.flowchart?.log(`${this.id}["${html}"]`); if (this.graph.editable && !this.installedClickCallback) { this.graph.flowchart?.log(`click ${this.id} WDGHandler${this.graph.index} "Edit Vertex ${this.id}"`); this.installedClickCallback = true; } } static prepareEditorDocument(graph, doc, vertexId) { const vertex = vertexId ? graph.getVertex(vertexId) : undefined; const form = doc.form().lastElement; if (vertex) { form.button({ name: 'New Vertex', cb: () => { graph.resetEditor(); }, }); } doc.remark(`<h3>${vertex ? 'Edit' : 'Add'} Vertex</h3>`, { parentEl: form.el }); form .textField({ name: 'id', defaultValue: vertex?.id, }) .textField({ name: 'type', defaultValue: vertex?.type }) .textField({ name: 'label', defaultValue: vertex?.label }); doc.remark('<h4>Properties</h4>', { parentEl: form.el }); const subFormArray = form.subFormArray({ name: 'properties' }).lastItem; const addPropertyForm = (key, value) => { const { subForm } = form.subForm({ subFormArray }).lastItem; subForm.textField({ name: 'key', defaultValue: key }) .textField({ name: 'value', defaultValue: value }) .button({ name: 'Remove Property', cb: () => subFormArray.remove(subForm), }) .remark('<br>'); }; if (vertex) { for (const [key, value] of vertex.properties.entries()) { addPropertyForm(key, value); } } form.button({ name: 'Add Property', cb: () => addPropertyForm('', ''), }); form.submit({ name: 'Save', cb: ({ form: { value: formValue } }) => { let fullRedraw = false; if (vertex && formValue.id !== vertex.id) { fullRedraw = true; } // TODO: preserve data types of properties formValue.properties = new Map(formValue.properties.map(({ key, value }) => [key, value])); if (vertex) { Object.assign(vertex, formValue); vertex.displayVertex(); } else { const { type, id, label, properties, } = formValue; const newVertex = graph.addVertex(type, id, null, label, { properties }); Object.assign(newVertex, formValue); doc.clear(); Vertex.prepareEditorDocument(graph, doc, newVertex.id); } if (fullRedraw) { graph.redraw(); } }, }); if (vertex) { form.button({ name: 'Delete Vertex', cb: () => { graph.deleteVertex(vertex.id); graph.redraw(); graph.resetEditor(); }, }); doc.remark('<h3>New Edge</h3>', { parentEl: form.el }); const newEdgeForm = doc.form({ name: 'newEdge' }).lastElement; newEdgeForm.textField({ name: 'to' }); newEdgeForm.textField({ name: 'type' }); newEdgeForm.textField({ name: 'weight' }); newEdgeForm.submit({ name: 'Save', cb: ({ form: { value: { to, type, weight } } }) => { graph.setEdgeWeight(type, vertex, to, weight, null); doc.clear(); Edge.prepareEditorDocument(graph, doc, vertex.id, to); }, }); } form.button({ name: 'Cancel', cb: () => graph.resetEditor(), parentEl: doc.el, }); return doc; } }