diff --git a/forum-network/src/classes/actors/expert.js b/forum-network/src/classes/actors/expert.js
index 112f867..eb297e2 100644
--- a/forum-network/src/classes/actors/expert.js
+++ b/forum-network/src/classes/actors/expert.js
@@ -2,7 +2,6 @@ import { Action } from '../display/action.js';
import { CryptoUtil } from '../supporting/crypto.js';
import { ReputationHolder } from '../reputation/reputation-holder.js';
import { EdgeTypes } from '../../util/constants.js';
-import { displayNumber } from '../../util/helpers.js';
export class Expert extends ReputationHolder {
constructor(dao, name, scene, options) {
@@ -30,15 +29,6 @@ export class Expert extends ReputationHolder {
return tokenValues.reduce((value, total) => total += value, 0);
}
- getLabel() {
- return `${this.name}
-
- reputation |
- ${displayNumber(this.getReputation())} |
-
`
- .replaceAll(/\n\s*/g, '');
- }
-
async initialize() {
this.reputationKey = await CryptoUtil.generateAsymmetricKey();
// this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
diff --git a/forum-network/src/classes/dao/forum.js b/forum-network/src/classes/dao/forum.js
index e65dee2..44b9121 100644
--- a/forum-network/src/classes/dao/forum.js
+++ b/forum-network/src/classes/dao/forum.js
@@ -22,22 +22,15 @@ class Post extends Actor {
this.title = postContent.title;
}
- getLabel() {
- return `${this.name}
-
- initial |
- ${displayNumber(this.initialValue)} |
-
- value |
- ${displayNumber(this.value)} |
-
`
- .replaceAll(/\n\s*/g, '');
- }
-
async setValue(value) {
this.value = value;
await this.setDisplayValue('value', value);
- this.forum.graph.getVertex(this.id).setDisplayLabel(this.getLabel());
+ this.forum.graph.getVertex(this.id).setProperty('value', value).displayVertex();
+ }
+
+ setInitialValue(value) {
+ this.initialValue = value;
+ this.forum.graph.getVertex(this.id).setProperty('initialValue', value).displayVertex();
}
}
@@ -62,7 +55,7 @@ export class Forum extends ReputationHolder {
async addPost(senderId, postContent) {
console.log('addPost', { senderId, postContent });
const post = new Post(this, senderId, postContent);
- this.graph.addVertex(VertexTypes.POST, post.id, post, post.getLabel());
+ this.graph.addVertex(VertexTypes.POST, post.id, post, post.name);
for (const { postId: citedPostId, weight } of post.citations) {
// Special case: Incinerator
if (citedPostId === INCINERATOR_ADDRESS && !this.graph.getVertex(INCINERATOR_ADDRESS)) {
@@ -98,14 +91,13 @@ export class Forum extends ReputationHolder {
const post = postVertex.data;
post.setStatus('Validated');
post.initialValue = initialValue;
- postVertex.setDisplayLabel(post.getLabel());
const addAuthorToGraph = (publicKey, weight, authorTokenId) => {
// For graph display purposes, we want to use the existing Expert actors from the current scene.
const author = this.scene.findActor(({ reputationPublicKey }) => reputationPublicKey === publicKey);
author.setDisplayValue('reputation', () => author.getReputation());
const authorVertex = this.graph.getVertex(publicKey)
- ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.getLabel(), {
+ ?? this.graph.addVertex(VertexTypes.AUTHOR, publicKey, author, author.name, {
hide: author.options.hide,
});
this.graph.addEdge(
@@ -161,15 +153,15 @@ export class Forum extends ReputationHolder {
} else {
this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount);
}
- await author.computeDisplayValues();
- authorVertex.setDisplayLabel(author.getLabel());
+ await author.computeDisplayValues((label, value) => authorVertex.setProperty(label, value));
+ authorVertex.displayVertex();
}
}
const senderVertex = this.graph.getVertex(post.senderId);
const { data: sender } = senderVertex;
- await sender.computeDisplayValues();
- senderVertex.setDisplayLabel(sender.getLabel());
+ await sender.computeDisplayValues((label, value) => senderVertex.setProperty(label, value));
+ senderVertex.displayVertex();
// Transfer ownership of the minted tokens to the authors
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) {
@@ -313,9 +305,6 @@ export class Forum extends ReputationHolder {
this.actions.propagate.log(post, author, `(${authorIncrement})`);
}
- // Increment the value of the post
- await post.setValue(newValue);
-
console.log('propagateValue end', {
depth,
increment,
@@ -325,6 +314,9 @@ export class Forum extends ReputationHolder {
refundToInbound,
});
+ // Increment the value of the post
+ await post.setValue(newValue);
+
return refundToInbound;
}
}
diff --git a/forum-network/src/classes/display/actor.js b/forum-network/src/classes/display/actor.js
index 0f1b011..08ef1c6 100644
--- a/forum-network/src/classes/display/actor.js
+++ b/forum-network/src/classes/display/actor.js
@@ -83,10 +83,16 @@ export class Actor {
return this;
}
- async computeDisplayValues() {
+ /**
+ * @param {(label: string, value) => {}} cb
+ */
+ async computeDisplayValues(cb) {
for (const [label, fn] of this.valueFunctions.entries()) {
const value = fn();
await this.setDisplayValue(label, value);
+ if (cb) {
+ cb(label, value);
+ }
}
}
diff --git a/forum-network/src/classes/display/box.js b/forum-network/src/classes/display/box.js
index 1ed666a..082bff5 100644
--- a/forum-network/src/classes/display/box.js
+++ b/forum-network/src/classes/display/box.js
@@ -4,7 +4,8 @@ import { randomID } from '../../util/helpers.js';
export class Box {
constructor(name, parentEl, options = {}) {
this.name = name;
- this.el = document.createElement('div');
+ const { tagName = 'div' } = options;
+ this.el = document.createElement(tagName);
this.el.box = this;
const id = options.id ?? randomID();
this.el.id = `${parentEl.id}_box_${id}`;
diff --git a/forum-network/src/classes/display/flowchart.js b/forum-network/src/classes/display/flowchart.js
index 058e928..33d4d97 100644
--- a/forum-network/src/classes/display/flowchart.js
+++ b/forum-network/src/classes/display/flowchart.js
@@ -1,7 +1,7 @@
import { MermaidDiagram } from './mermaid.js';
export class Flowchart extends MermaidDiagram {
- constructor(box, logBox, direction = 'BT') {
+ constructor(box, logBox, { direction = 'BT' } = {}) {
super(box, logBox);
this.direction = direction;
this.init();
diff --git a/forum-network/src/classes/display/form.js b/forum-network/src/classes/display/form.js
index 9ad297b..3dec6bc 100644
--- a/forum-network/src/classes/display/form.js
+++ b/forum-network/src/classes/display/form.js
@@ -1,30 +1,29 @@
import { randomID } from '../../util/helpers.js';
import { Box } from './box.js';
-const updateValuesOnEventTypes = ['keyup', 'mouseup'];
-
export class FormElement extends Box {
- constructor(name, form, opts) {
+ constructor(name, form, opts = {}) {
const parentEl = opts.parentEl ?? form.el;
super(name, parentEl, opts);
this.form = form;
this.id = opts.id ?? name;
- this.includeInOutput = opts.includeInOutput ?? true;
- const { cb } = opts;
+ const { cb, cbEventTypes = ['change'], cbOnInit = false } = opts;
if (cb) {
- updateValuesOnEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
+ cbEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
cb(this, { initializing: false });
}));
- cb(this, { initializing: true });
+ if (cbOnInit) {
+ cb(this, { initializing: true });
+ }
}
}
}
export class Button extends FormElement {
constructor(name, form, opts) {
- super(name, form, opts);
+ super(name, form, { ...opts, cbEventTypes: ['click'] });
this.button = document.createElement('button');
- this.button.setAttribute('type', 'button');
+ this.button.setAttribute('type', opts.type ?? 'button');
this.button.innerHTML = name;
this.button.disabled = !!opts.disabled;
this.el.appendChild(this.button);
@@ -72,12 +71,11 @@ export class SubFormArray extends FormElement {
export class SubForm extends FormElement {
constructor(name, form, opts) {
const parentEl = opts.subFormArray ? opts.subFormArray.el : form.el;
- const subForm = form.document.form({ name, parentEl }).lastElement;
+ const subForm = form.document.form({ name, parentEl, tagName: 'div' }).lastElement;
super(name, form, { ...opts, parentEl });
this.subForm = subForm;
if (opts.subFormArray) {
opts.subFormArray.subForms.push(this);
- this.includeInOutput = false;
}
}
@@ -88,10 +86,12 @@ export class SubForm extends FormElement {
export class Form extends Box {
constructor(document, opts = {}) {
- super(opts.name, opts.parentEl || document.el, opts);
+ super(opts.name, opts.parentEl || document.el, { tagName: 'form', ...opts });
this.document = document;
this.items = [];
this.id = opts.id ?? `form_${randomID()}`;
+ // Action should be handled by a submit button
+ this.el.onsubmit = () => false;
}
button(opts) {
@@ -124,11 +124,11 @@ export class Form extends Box {
}
get value() {
- return this.items.reduce((result, { id, value, includeInOutput }) => {
- if (includeInOutput && value !== undefined) {
- result[id] = value;
+ return this.items.reduce((obj, { id, value }) => {
+ if (value !== undefined) {
+ obj[id] = value;
}
- return result;
+ return obj;
}, {});
}
}
diff --git a/forum-network/src/classes/display/mermaid.js b/forum-network/src/classes/display/mermaid.js
index a11a588..9e1b99a 100644
--- a/forum-network/src/classes/display/mermaid.js
+++ b/forum-network/src/classes/display/mermaid.js
@@ -9,7 +9,6 @@ export class MermaidDiagram {
this.renderBox = this.box.addBox('Render');
this.box.addBox('Spacer').setInnerHTML(' ');
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
- this.inSection = 0;
}
static initializeAPI() {
@@ -20,10 +19,8 @@ export class MermaidDiagram {
darkMode: true,
primaryColor: '#2a5b6c',
primaryTextColor: '#b6b6b6',
- // lineColor: '#349cbd',
lineColor: '#57747d',
signalColor: '#57747d',
- // signalColor: '#349cbd',
noteBkgColor: '#516f77',
noteTextColor: '#cecece',
activationBkgColor: '#1d3f49',
@@ -31,6 +28,7 @@ export class MermaidDiagram {
},
securityLevel: 'loose', // 'loose' so that we can use click events
// logLevel: 'debug',
+ useMaxWidth: true,
});
}
diff --git a/forum-network/src/classes/display/controls.js b/forum-network/src/classes/display/scene-controls.js
similarity index 98%
rename from forum-network/src/classes/display/controls.js
rename to forum-network/src/classes/display/scene-controls.js
index 4911456..02340e3 100644
--- a/forum-network/src/classes/display/controls.js
+++ b/forum-network/src/classes/display/scene-controls.js
@@ -6,7 +6,7 @@ class Button {
}
}
-export class Controls {
+export class SceneControls {
constructor(parentBox) {
this.disableAutoplayButton = new Button('Disable Auto-play', () => {
const url = new URL(window.location.href);
diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js
index cc40e96..43103e5 100644
--- a/forum-network/src/classes/display/scene.js
+++ b/forum-network/src/classes/display/scene.js
@@ -4,7 +4,7 @@ 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 { SceneControls } from './scene-controls.js';
import { Box } from './box.js';
import { Document } from './document.js';
@@ -33,8 +33,8 @@ export class Scene {
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);
+ const controlsBox = this.topRail.addBox('SceneControls').addClass('scene-controls');
+ this.controls = new SceneControls(controlsBox);
}
}
@@ -47,26 +47,27 @@ export class Scene {
}
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);
+ this.withSectionFlowchart({ direction, section: this.topSection });
+ this.flowchart = this.lastFlowchart;
return this;
}
- withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
+ withSectionFlowchart({
+ id, name, direction = 'BT', section,
+ } = {}) {
+ section = section ?? this.middleSection;
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 container = section.addBox(name).flex();
const box = container.addBox('Flowchart').addClass('padded');
const logBox = container.addBox('Flowchart text').addClass('dim');
- const flowchart = new Flowchart(box, logBox, direction);
+ const flowchart = new Flowchart(box, logBox, { direction });
this.flowcharts.set(id, flowchart);
return this;
}
- lastFlowchart() {
+ get lastFlowchart() {
if (!this.flowcharts.size) {
if (this.flowchart) {
return this.flowchart;
diff --git a/forum-network/src/classes/supporting/edge.js b/forum-network/src/classes/supporting/edge.js
index bc15b81..dd22fe1 100644
--- a/forum-network/src/classes/supporting/edge.js
+++ b/forum-network/src/classes/supporting/edge.js
@@ -29,16 +29,19 @@ export class Edge {
}
getHtml() {
- let html = '';
- for (const { type, weight } of this.getComorphicEdges()) {
+ const edges = this.getComorphicEdges();
+ let html = '';
+ html += '';
+ for (const { type, weight } of edges) {
html += `${type} | ${weight} |
`;
}
html += '
';
+
return html;
}
getFlowchartNode() {
- return `${Edge.getCombinedKey(this)}(${this.getHtml()})`;
+ return `${Edge.getCombinedKey(this)}("${this.getHtml()}")`;
}
displayEdgeNode() {
@@ -55,13 +58,13 @@ export class Edge {
this.graph.flowchart?.log(`${this.from.id} --- ${this.getFlowchartNode()} --> ${this.to.id}`);
this.graph.flowchart?.log(`class ${Edge.getCombinedKey(this)} edge`);
if (this.graph.editable && !this.installedClickCallback) {
- this.graph.flowchart?.log(`click ${Edge.getCombinedKey(this)} WDGHandler${this.graph.index} "Edit Edge"`);
+ this.graph.flowchart?.log(`click ${Edge.getCombinedKey(this)} WDGHandler${this.graph.index} \
+"Edit Edge ${this.from.id} -> ${this.to.id}"`);
this.installedClickCallback = true;
}
}
static prepareEditorDocument(graph, doc, from, to) {
- doc.clear();
const form = doc.form({ name: 'editorForm' }).lastElement;
doc.remark('Edit Edge
', { parentEl: form.el });
form
@@ -72,20 +75,23 @@ export class Edge {
id: 'to', name: 'to', defaultValue: to, disabled: true,
});
+ doc.remark('Edge Types
', { parentEl: form.el });
const subFormArray = form.subFormArray({ id: 'edges', name: 'edges' }).lastItem;
+
const addEdgeForm = (edge) => {
const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
- doc.remark('
', { parentEl: subForm.el });
- subForm.textField({ id: 'type', name: 'type', defaultValue: edge.type });
- subForm.textField({ id: 'weight', name: 'weight', defaultValue: edge.weight });
- subForm.button({
- id: 'remove',
- name: 'Remove Edge Type',
- cb: (_, { initializing }) => {
- if (initializing) return;
- subFormArray.remove(subForm);
- },
- });
+ subForm.textField({
+ id: 'type', name: 'type', defaultValue: edge.type, required: true,
+ })
+ .textField({
+ id: 'weight', name: 'weight', defaultValue: edge.weight, required: true,
+ })
+ .button({
+ id: 'remove',
+ name: 'Remove Edge Type',
+ cb: () => subFormArray.remove(subForm),
+ });
+ doc.remark('
', { parentEl: subForm.el });
};
for (const edge of graph.getEdges(null, from, to)) {
@@ -95,29 +101,38 @@ export class Edge {
form.button({
id: 'add',
name: 'Add Edge Type',
- cb: (_, { initializing }) => {
- if (initializing) return;
- addEdgeForm(new Edge(graph, null, graph.getVertex(from), graph.getVertex(to)));
- },
- });
-
- form.button({
- id: 'save',
- name: 'Save',
- cb: ({ form: { value } }, { initializing }) => {
- if (initializing) return;
- // Handle additions and updates
- for (const { type, weight } of value.edges) {
- graph.setEdgeWeight(type, from, to, weight);
- }
- // Handle removals
- for (const edge of graph.getEdges(null, from, to)) {
- if (!value.edges.find(({ type }) => type === edge.type)) {
- graph.deleteEdge(edge.type, from, to);
+ cb: () => addEdgeForm(new Edge(graph, null, graph.getVertex(from), graph.getVertex(to))),
+ })
+ .button({
+ id: 'save',
+ name: 'Save',
+ type: 'submit',
+ cb: ({ form: { value: { edges } } }) => {
+ // Do validation
+ for (const { type, weight } of edges) {
+ if (type === null || weight === null) {
+ graph.errorDoc.remark('type and weight are required
');
+ return;
+ }
}
- }
- graph.redraw();
- },
- });
+ // Handle additions and updates
+ for (const { type, weight } of edges) {
+ graph.setEdgeWeight(type, from, to, weight);
+ }
+ // Handle removals
+ for (const edge of graph.getEdges(null, from, to)) {
+ if (!edges.find(({ type }) => type === edge.type)) {
+ graph.deleteEdge(edge.type, from, to);
+ }
+ }
+ graph.redraw();
+ graph.errorDoc.clear();
+ },
+ })
+ .button({
+ id: 'cancel',
+ name: 'Cancel',
+ cb: () => graph.resetEditorDocument(),
+ });
}
}
diff --git a/forum-network/src/classes/supporting/vertex.js b/forum-network/src/classes/supporting/vertex.js
index 160126c..fb27be6 100644
--- a/forum-network/src/classes/supporting/vertex.js
+++ b/forum-network/src/classes/supporting/vertex.js
@@ -1,86 +1,159 @@
+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.id = id;
this.data = data;
+ this.label = options.label ?? this.id;
this.options = options;
this.edges = {
from: [],
to: [],
};
this.installedClickCallback = false;
+ this.properties = new Map();
}
reset() {
this.installedClickCallback = false;
}
- set id(newId) {
- this._id = newId;
- }
-
- get id() {
- return this._id;
- }
-
getEdges(type, away) {
return this.edges[away ? 'from' : 'to'].filter(
(edge) => edge.type === type,
);
}
- setDisplayLabel(label) {
- this.label = label;
- this.displayVertex();
+ setProperty(key, value) {
+ this.properties.set(key, value);
+ return this;
}
displayVertex() {
if (this.options.hide) {
return;
}
- this.graph.flowchart?.log(`${this.id}[${this.label}]`);
+
+ let html = '';
+ html += `${this.label}`;
+ html += '';
+ for (const [key, value] of this.properties.entries()) {
+ const displayValue = typeof value === 'number' ? displayNumber(value) : value;
+ html += `${key} | ${displayValue} |
`;
+ }
+ html += '
';
+ if (this.id !== this.label) {
+ html += `${this.id}
`;
+ }
+ 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.graph.flowchart?.log(`click ${this.id} WDGHandler${this.graph.index} "Edit Vertex ${this.id}"`);
this.installedClickCallback = true;
}
}
static prepareEditorDocument(graph, doc, vertexId) {
doc.clear();
- const vertex = graph.getVertex(vertexId);
- if (!vertex) {
- throw new Error(`Could not find WDG Vertex ${vertexId}`);
- }
- doc.remark('Edit Vertex
');
+ const vertex = vertexId ? graph.getVertex(vertexId) : undefined;
const form = doc.form().lastElement;
+ doc.remark(`${vertex ? 'Edit' : 'Add'} Vertex
`, { parentEl: form.el });
form
.textField({
- id: 'id', name: 'id', defaultValue: vertex.id, disabled: true,
+ id: 'id', name: 'id', defaultValue: vertex?.id,
})
- .textField({ id: 'type', name: 'type', defaultValue: vertex.type })
- .textField({ id: 'label', name: 'label', defaultValue: vertex.label })
+ .textField({ id: 'type', name: 'type', defaultValue: vertex?.type })
+ .textField({ id: 'label', name: 'label', defaultValue: vertex?.label });
- .button({
- id: 'save',
- name: 'Save',
- cb: ({ form: { value } }, { initializing }) => {
- if (initializing) return;
- if (value.id && value.id !== vertex.id) {
- // TODO: When an ID changes we really need to wipe out and redraw!
- // But we don't yet have a systematic approach for doing that.
- // Everything is getting rendered as needed. Lacking abstraction.
- // HMM we're not actually that far! Just wipe everything out and draw each vertex and edge :)
- // for (const vertex of )
- // for (const edge of [...vertex.edges.to, ...vertex.edges.from]) {
- // edge.displayEdge();
- // }
- }
- Object.assign(vertex, value);
+ doc.remark('Properties
', { parentEl: form.el });
+ const subFormArray = form.subFormArray({ id: 'properties', name: 'properties' }).lastItem;
+ const addPropertyForm = (key, value) => {
+ const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
+ subForm.textField({ id: 'key', name: 'key', defaultValue: key })
+ .textField({ id: 'value', name: 'value', defaultValue: value })
+ .button({
+ id: 'remove',
+ name: 'Remove Property',
+ cb: () => subFormArray.remove(subForm),
+ });
+ doc.remark('
', { parentEl: subForm.el });
+ };
+
+ if (vertex) {
+ for (const [key, value] of vertex.properties.entries()) {
+ addPropertyForm(key, value);
+ }
+ }
+
+ form.button({
+ id: 'add',
+ name: 'Add Property',
+ cb: () => addPropertyForm('', ''),
+ });
+
+ form.button({
+ id: 'save',
+ name: 'Save',
+ type: 'submit',
+ 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 newVertex = graph.addVertex(formValue.type, formValue.id, null, formValue.label);
+ Object.assign(newVertex, formValue);
+ doc.clear();
+ Vertex.prepareEditorDocument(graph, doc, newVertex.id);
+ }
+ if (fullRedraw) {
+ graph.redraw();
+ }
+ },
+ });
+
+ if (vertex) {
+ form.button({
+ id: 'delete',
+ name: 'Delete Vertex',
+ cb: () => {
+ graph.deleteVertex(vertex.id);
+ graph.redraw();
+ graph.resetEditorDocument();
},
});
+ doc.remark('New Edge
', { parentEl: form.el });
+ const { subForm } = form.subForm({ name: 'newEdge' }).lastItem;
+ subForm.textField({ name: 'to' });
+ subForm.textField({ name: 'type' });
+ subForm.textField({ name: 'weight' });
+ subForm.button({
+ name: 'Save',
+ cb: ({ form: { value: { to, type, weight } } }) => {
+ graph.addEdge(type, vertex, to, weight, null);
+ doc.clear();
+ Edge.prepareEditorDocument(graph, doc, vertex.id, to);
+ },
+ });
+ }
+
+ form.button({
+ id: 'cancel',
+ name: 'Cancel',
+ cb: () => graph.resetEditorDocument(),
+ });
+
return doc;
}
}
diff --git a/forum-network/src/classes/supporting/wdg.js b/forum-network/src/classes/supporting/wdg.js
index 41f2ad6..ab083e7 100644
--- a/forum-network/src/classes/supporting/wdg.js
+++ b/forum-network/src/classes/supporting/wdg.js
@@ -1,13 +1,14 @@
import { Vertex } from './vertex.js';
import { Edge } from './edge.js';
+import { Document } from '../display/document.js';
-const graphs = [];
+const allGraphs = [];
const makeWDGHandler = (graphIndex) => (vertexId) => {
- const graph = graphs[graphIndex];
+ const graph = allGraphs[graphIndex];
// We want a document for editing this node, which may be a vertex or an edge
- const editorDoc = graph.scene.getDocument('editorDocument')
- ?? graph.scene.withDocument('editorDocument').lastDocument;
+ const { editorDoc } = graph;
+ editorDoc.clear();
if (vertexId.startsWith('edge:')) {
const [, from, to] = vertexId.split(':');
Edge.prepareEditorDocument(graph, editorDoc, from, to);
@@ -24,12 +25,15 @@ export class WeightedDirectedGraph {
this.nextVertexId = 0;
this.flowchart = scene?.flowchart;
this.editable = options.editable;
- this.index = graphs.length;
- graphs.push(this);
+
// Mermaid supports a click callback, but we can't customize arguments; we just get the vertex ID.
// In order to provide the appropriate graph context for each callback, we create a separate callback
// function for each graph.
+ this.index = allGraphs.length;
+ allGraphs.push(this);
window[`WDGHandler${this.index}`] = makeWDGHandler(this.index);
+
+ // TODO: Populate history
this.history = {};
}
@@ -83,16 +87,26 @@ export class WeightedDirectedGraph {
}
}
- // Rerender
+ // Ensure rerender
this.flowchart?.render();
}
withFlowchart() {
- this.scene?.withAdditionalFlowchart();
- this.flowchart = this.scene?.lastFlowchart();
+ this.scene?.withSectionFlowchart();
+ this.flowchart = this.scene?.lastFlowchart;
+ if (this.editable) {
+ this.editorDoc = new Document('WDGControls', this.flowchart.box.el);
+ this.resetEditorDocument();
+ }
+ this.errorDoc = new Document('WDGErrors', this.flowchart.box.el);
return this;
}
+ resetEditorDocument() {
+ this.editorDoc.clear();
+ Vertex.prepareEditorDocument(this, this.editorDoc);
+ }
+
addVertex(type, id, data, label, options) {
// Supports simple case of auto-incremented numeric ids
if (typeof id === 'object') {
@@ -103,9 +117,9 @@ export class WeightedDirectedGraph {
if (this.vertices.has(id)) {
throw new Error(`Vertex already exists with id: ${id}`);
}
- const vertex = new Vertex(this, type, id, data, options);
+ const vertex = new Vertex(this, type, id, data, { ...options, label });
this.vertices.set(id, vertex);
- vertex.setDisplayLabel(label ?? id);
+ vertex.displayVertex();
return vertex;
}
@@ -133,22 +147,6 @@ export class WeightedDirectedGraph {
return edges?.get(edgeKey);
}
- deleteEdge(type, from, to) {
- from = from instanceof Vertex ? from : this.getVertex(from);
- to = to instanceof Vertex ? to : this.getVertex(to);
- const edges = this.edgeTypes.get(type);
- const edgeKey = Edge.getKey({ type, from, to });
- if (!edges) return;
- const edge = edges.get(edgeKey);
- if (!edge) return;
- to.edges.from.forEach((x, i) => (x === edge) && to.edges.from.splice(i, 1));
- from.edges.to.forEach((x, i) => (x === edge) && from.edges.to.splice(i, 1));
- edges.delete(edgeKey);
- if (edges.size === 0) {
- this.edgeTypes.delete(type);
- }
- }
-
getEdgeWeight(type, from, to) {
return this.getEdge(type, from, to)?.weight;
}
@@ -206,4 +204,28 @@ export class WeightedDirectedGraph {
}
return Array.from(this.vertices.values()).filter((vertex) => vertex.type === type).length;
}
+
+ deleteEdge(type, from, to) {
+ from = from instanceof Vertex ? from : this.getVertex(from);
+ to = to instanceof Vertex ? to : this.getVertex(to);
+ const edges = this.edgeTypes.get(type);
+ const edgeKey = Edge.getKey({ type, from, to });
+ if (!edges) return;
+ const edge = edges.get(edgeKey);
+ if (!edge) return;
+ to.edges.from.forEach((x, i) => (x === edge) && to.edges.from.splice(i, 1));
+ from.edges.to.forEach((x, i) => (x === edge) && from.edges.to.splice(i, 1));
+ edges.delete(edgeKey);
+ if (edges.size === 0) {
+ this.edgeTypes.delete(type);
+ }
+ }
+
+ deleteVertex(id) {
+ const vertex = this.getVertex(id);
+ for (const { type, from, to } of [...vertex.edges.to, ...vertex.edges.from]) {
+ this.deleteEdge(type, from, to);
+ }
+ this.vertices.delete(id);
+ }
}
diff --git a/forum-network/src/index.css b/forum-network/src/index.css
index 2db2c1b..a42bab2 100644
--- a/forum-network/src/index.css
+++ b/forum-network/src/index.css
@@ -42,7 +42,7 @@ a:visited {
width: 100%;
height: 0;
}
-.controls {
+.scene-controls {
position: relative;
left: 150px;
}
@@ -83,3 +83,12 @@ label > div {
display: inline-block;
min-width: 50px;
}
+table {
+ width: 100%;
+}
+form {
+ min-width: 20em;
+}
+span.small {
+ font-size: smaller;
+}
\ No newline at end of file
diff --git a/forum-network/src/tests/scripts/availability.test.js b/forum-network/src/tests/scripts/availability.test.js
index 64c45a7..5ac1619 100644
--- a/forum-network/src/tests/scripts/availability.test.js
+++ b/forum-network/src/tests/scripts/availability.test.js
@@ -4,7 +4,7 @@ import { Expert } from '../../classes/actors/expert.js';
import { DAO } from '../../classes/dao/dao.js';
import { Public } from '../../classes/actors/public.js';
import { PostContent } from '../../classes/supporting/post-content.js';
-import { delayOrWait } from '../../classes/display/controls.js';
+import { delayOrWait } from '../../classes/display/scene-controls.js';
import { mochaRun } from '../../util/helpers.js';
const DELAY_INTERVAL = 100;
@@ -18,10 +18,6 @@ const newExpert = async () => {
const index = experts.length;
const name = `Expert${index + 1}`;
const expert = await new Expert(dao, name, scene).initialize();
- expert.setDisplayValue(
- 'rep',
- () => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
- );
experts.push(expert);
return expert;
};
diff --git a/forum-network/src/tests/scripts/forum.test-util.js b/forum-network/src/tests/scripts/forum.test-util.js
index 02e85d5..36cb47a 100644
--- a/forum-network/src/tests/scripts/forum.test-util.js
+++ b/forum-network/src/tests/scripts/forum.test-util.js
@@ -3,7 +3,7 @@ import { Scene } from '../../classes/display/scene.js';
import { Expert } from '../../classes/actors/expert.js';
import { PostContent } from '../../classes/supporting/post-content.js';
import { DAO } from '../../classes/dao/dao.js';
-import { delayOrWait } from '../../classes/display/controls.js';
+import { delayOrWait } from '../../classes/display/scene-controls.js';
export class ForumTest {
constructor(options) {
diff --git a/forum-network/src/tests/scripts/input.test.js b/forum-network/src/tests/scripts/input.test.js
index b3bf2ee..8908dc4 100644
--- a/forum-network/src/tests/scripts/input.test.js
+++ b/forum-network/src/tests/scripts/input.test.js
@@ -27,11 +27,17 @@ describe('Document > Form > TextField', () => {
dv.set(value);
};
- form.textField({ id: 'input1', name: 'Input 1', cb: updateFieldValueDisplay });
+ form.textField({
+ id: 'input1',
+ name: 'Input 1',
+ cb: updateFieldValueDisplay,
+ cbEventTypes: ['keydown', 'keyup'],
+ cbOnInit: true,
+ });
doc.remark('Hmm...!');
});
// it('can exist within a graph', () => {
- // scene.withAdditionalFlowchart({ id: 'flowchart', name: 'Graph' });
+ // scene.withSectionFlowchart({ id: 'flowchart', name: 'Graph' });
// const graph = scene.lastFlowchart();
// });
});
diff --git a/forum-network/src/tests/scripts/validation-pool.test.js b/forum-network/src/tests/scripts/validation-pool.test.js
index b7a5b05..15ceef4 100644
--- a/forum-network/src/tests/scripts/validation-pool.test.js
+++ b/forum-network/src/tests/scripts/validation-pool.test.js
@@ -3,7 +3,7 @@ import { Scene } from '../../classes/display/scene.js';
import { Expert } from '../../classes/actors/expert.js';
import { PostContent } from '../../classes/supporting/post-content.js';
import { DAO } from '../../classes/dao/dao.js';
-import { delayOrWait } from '../../classes/display/controls.js';
+import { delayOrWait } from '../../classes/display/scene-controls.js';
import { mochaRun } from '../../util/helpers.js';
const POOL_DURATION_MS = 100;
@@ -17,7 +17,6 @@ async function newExpert() {
const index = experts.length;
const name = `Expert${index + 1}`;
const expert = await new Expert(dao, name, scene).initialize();
- await expert.addComputedValue('rep', () => dao.reputation.valueOwnedBy(expert.reputationPublicKey));
experts.push(expert);
return expert;
}
diff --git a/forum-network/src/util/constants.js b/forum-network/src/util/constants.js
index 65b28c9..fbfdb79 100644
--- a/forum-network/src/util/constants.js
+++ b/forum-network/src/util/constants.js
@@ -1,6 +1,6 @@
export const EPSILON = 2.23e-16;
-export const INCINERATOR_ADDRESS = 0;
+export const INCINERATOR_ADDRESS = '0';
export const EdgeTypes = {
CITATION: 'citation',