Merge branch 'dev' into 'main'
Improved graph editing See merge request dao-governance-framework/science-publishing-dao!10
This commit is contained in:
commit
4d53f5c70e
|
@ -2,7 +2,6 @@ import { Action } from '../display/action.js';
|
||||||
import { CryptoUtil } from '../supporting/crypto.js';
|
import { CryptoUtil } from '../supporting/crypto.js';
|
||||||
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
import { ReputationHolder } from '../reputation/reputation-holder.js';
|
||||||
import { EdgeTypes } from '../../util/constants.js';
|
import { EdgeTypes } from '../../util/constants.js';
|
||||||
import { displayNumber } from '../../util/helpers.js';
|
|
||||||
|
|
||||||
export class Expert extends ReputationHolder {
|
export class Expert extends ReputationHolder {
|
||||||
constructor(dao, name, scene, options) {
|
constructor(dao, name, scene, options) {
|
||||||
|
@ -30,15 +29,6 @@ export class Expert extends ReputationHolder {
|
||||||
return tokenValues.reduce((value, total) => total += value, 0);
|
return tokenValues.reduce((value, total) => total += value, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabel() {
|
|
||||||
return `${this.name}
|
|
||||||
<table><tr>
|
|
||||||
<td>reputation</td>
|
|
||||||
<td>${displayNumber(this.getReputation())}</td>
|
|
||||||
</tr></table>`
|
|
||||||
.replaceAll(/\n\s*/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.reputationKey = await CryptoUtil.generateAsymmetricKey();
|
this.reputationKey = await CryptoUtil.generateAsymmetricKey();
|
||||||
// this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
|
// this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
|
||||||
|
|
|
@ -22,22 +22,15 @@ class Post extends Actor {
|
||||||
this.title = postContent.title;
|
this.title = postContent.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabel() {
|
|
||||||
return `${this.name}
|
|
||||||
<table><tr>
|
|
||||||
<td>initial</td>
|
|
||||||
<td>${displayNumber(this.initialValue)}</td>
|
|
||||||
</tr><tr>
|
|
||||||
<td>value</td>
|
|
||||||
<td>${displayNumber(this.value)}</td>
|
|
||||||
</tr></table>`
|
|
||||||
.replaceAll(/\n\s*/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async setValue(value) {
|
async setValue(value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
await this.setDisplayValue('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) {
|
async addPost(senderId, postContent) {
|
||||||
console.log('addPost', { senderId, postContent });
|
console.log('addPost', { senderId, postContent });
|
||||||
const post = new Post(this, 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) {
|
for (const { postId: citedPostId, weight } of post.citations) {
|
||||||
// Special case: Incinerator
|
// Special case: Incinerator
|
||||||
if (citedPostId === INCINERATOR_ADDRESS && !this.graph.getVertex(INCINERATOR_ADDRESS)) {
|
if (citedPostId === INCINERATOR_ADDRESS && !this.graph.getVertex(INCINERATOR_ADDRESS)) {
|
||||||
|
@ -98,14 +91,13 @@ export class Forum extends ReputationHolder {
|
||||||
const post = postVertex.data;
|
const post = postVertex.data;
|
||||||
post.setStatus('Validated');
|
post.setStatus('Validated');
|
||||||
post.initialValue = initialValue;
|
post.initialValue = initialValue;
|
||||||
postVertex.setDisplayLabel(post.getLabel());
|
|
||||||
|
|
||||||
const addAuthorToGraph = (publicKey, weight, authorTokenId) => {
|
const addAuthorToGraph = (publicKey, weight, authorTokenId) => {
|
||||||
// For graph display purposes, we want to use the existing Expert actors from the current scene.
|
// For graph display purposes, we want to use the existing Expert actors from the current scene.
|
||||||
const author = this.scene.findActor(({ reputationPublicKey }) => reputationPublicKey === publicKey);
|
const author = this.scene.findActor(({ reputationPublicKey }) => reputationPublicKey === publicKey);
|
||||||
author.setDisplayValue('reputation', () => author.getReputation());
|
author.setDisplayValue('reputation', () => author.getReputation());
|
||||||
const authorVertex = this.graph.getVertex(publicKey)
|
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,
|
hide: author.options.hide,
|
||||||
});
|
});
|
||||||
this.graph.addEdge(
|
this.graph.addEdge(
|
||||||
|
@ -161,15 +153,15 @@ export class Forum extends ReputationHolder {
|
||||||
} else {
|
} else {
|
||||||
this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount);
|
this.dao.reputation.transferValueFrom(tokenId, authorTokenId, amount);
|
||||||
}
|
}
|
||||||
await author.computeDisplayValues();
|
await author.computeDisplayValues((label, value) => authorVertex.setProperty(label, value));
|
||||||
authorVertex.setDisplayLabel(author.getLabel());
|
authorVertex.displayVertex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderVertex = this.graph.getVertex(post.senderId);
|
const senderVertex = this.graph.getVertex(post.senderId);
|
||||||
const { data: sender } = senderVertex;
|
const { data: sender } = senderVertex;
|
||||||
await sender.computeDisplayValues();
|
await sender.computeDisplayValues((label, value) => senderVertex.setProperty(label, value));
|
||||||
senderVertex.setDisplayLabel(sender.getLabel());
|
senderVertex.displayVertex();
|
||||||
|
|
||||||
// Transfer ownership of the minted tokens to the authors
|
// Transfer ownership of the minted tokens to the authors
|
||||||
for (const authorEdge of postVertex.getEdges(EdgeTypes.AUTHOR, true)) {
|
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})`);
|
this.actions.propagate.log(post, author, `(${authorIncrement})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the value of the post
|
|
||||||
await post.setValue(newValue);
|
|
||||||
|
|
||||||
console.log('propagateValue end', {
|
console.log('propagateValue end', {
|
||||||
depth,
|
depth,
|
||||||
increment,
|
increment,
|
||||||
|
@ -325,6 +314,9 @@ export class Forum extends ReputationHolder {
|
||||||
refundToInbound,
|
refundToInbound,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Increment the value of the post
|
||||||
|
await post.setValue(newValue);
|
||||||
|
|
||||||
return refundToInbound;
|
return refundToInbound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,10 +83,16 @@ export class Actor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async computeDisplayValues() {
|
/**
|
||||||
|
* @param {(label: string, value) => {}} cb
|
||||||
|
*/
|
||||||
|
async computeDisplayValues(cb) {
|
||||||
for (const [label, fn] of this.valueFunctions.entries()) {
|
for (const [label, fn] of this.valueFunctions.entries()) {
|
||||||
const value = fn();
|
const value = fn();
|
||||||
await this.setDisplayValue(label, value);
|
await this.setDisplayValue(label, value);
|
||||||
|
if (cb) {
|
||||||
|
cb(label, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { randomID } from '../../util/helpers.js';
|
||||||
export class Box {
|
export class Box {
|
||||||
constructor(name, parentEl, options = {}) {
|
constructor(name, parentEl, options = {}) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.el = document.createElement('div');
|
const { tagName = 'div' } = options;
|
||||||
|
this.el = document.createElement(tagName);
|
||||||
this.el.box = this;
|
this.el.box = this;
|
||||||
const id = options.id ?? randomID();
|
const id = options.id ?? randomID();
|
||||||
this.el.id = `${parentEl.id}_box_${id}`;
|
this.el.id = `${parentEl.id}_box_${id}`;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { MermaidDiagram } from './mermaid.js';
|
import { MermaidDiagram } from './mermaid.js';
|
||||||
|
|
||||||
export class Flowchart extends MermaidDiagram {
|
export class Flowchart extends MermaidDiagram {
|
||||||
constructor(box, logBox, direction = 'BT') {
|
constructor(box, logBox, { direction = 'BT' } = {}) {
|
||||||
super(box, logBox);
|
super(box, logBox);
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.init();
|
this.init();
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
import { randomID } from '../../util/helpers.js';
|
import { randomID } from '../../util/helpers.js';
|
||||||
import { Box } from './box.js';
|
import { Box } from './box.js';
|
||||||
|
|
||||||
const updateValuesOnEventTypes = ['keyup', 'mouseup'];
|
|
||||||
|
|
||||||
export class FormElement extends Box {
|
export class FormElement extends Box {
|
||||||
constructor(name, form, opts) {
|
constructor(name, form, opts = {}) {
|
||||||
const parentEl = opts.parentEl ?? form.el;
|
const parentEl = opts.parentEl ?? form.el;
|
||||||
super(name, parentEl, opts);
|
super(name, parentEl, opts);
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.id = opts.id ?? name;
|
this.id = opts.id ?? name;
|
||||||
this.includeInOutput = opts.includeInOutput ?? true;
|
const { cb, cbEventTypes = ['change'], cbOnInit = false } = opts;
|
||||||
const { cb } = opts;
|
|
||||||
if (cb) {
|
if (cb) {
|
||||||
updateValuesOnEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
|
cbEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
|
||||||
cb(this, { initializing: false });
|
cb(this, { initializing: false });
|
||||||
}));
|
}));
|
||||||
cb(this, { initializing: true });
|
if (cbOnInit) {
|
||||||
|
cb(this, { initializing: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Button extends FormElement {
|
export class Button extends FormElement {
|
||||||
constructor(name, form, opts) {
|
constructor(name, form, opts) {
|
||||||
super(name, form, opts);
|
super(name, form, { ...opts, cbEventTypes: ['click'] });
|
||||||
this.button = document.createElement('button');
|
this.button = document.createElement('button');
|
||||||
this.button.setAttribute('type', 'button');
|
this.button.setAttribute('type', opts.type ?? 'button');
|
||||||
this.button.innerHTML = name;
|
this.button.innerHTML = name;
|
||||||
this.button.disabled = !!opts.disabled;
|
this.button.disabled = !!opts.disabled;
|
||||||
this.el.appendChild(this.button);
|
this.el.appendChild(this.button);
|
||||||
|
@ -72,12 +71,11 @@ export class SubFormArray extends FormElement {
|
||||||
export class SubForm extends FormElement {
|
export class SubForm extends FormElement {
|
||||||
constructor(name, form, opts) {
|
constructor(name, form, opts) {
|
||||||
const parentEl = opts.subFormArray ? opts.subFormArray.el : form.el;
|
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 });
|
super(name, form, { ...opts, parentEl });
|
||||||
this.subForm = subForm;
|
this.subForm = subForm;
|
||||||
if (opts.subFormArray) {
|
if (opts.subFormArray) {
|
||||||
opts.subFormArray.subForms.push(this);
|
opts.subFormArray.subForms.push(this);
|
||||||
this.includeInOutput = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,10 +86,12 @@ export class SubForm extends FormElement {
|
||||||
|
|
||||||
export class Form extends Box {
|
export class Form extends Box {
|
||||||
constructor(document, opts = {}) {
|
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.document = document;
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.id = opts.id ?? `form_${randomID()}`;
|
this.id = opts.id ?? `form_${randomID()}`;
|
||||||
|
// Action should be handled by a submit button
|
||||||
|
this.el.onsubmit = () => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
button(opts) {
|
button(opts) {
|
||||||
|
@ -124,11 +124,11 @@ export class Form extends Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.items.reduce((result, { id, value, includeInOutput }) => {
|
return this.items.reduce((obj, { id, value }) => {
|
||||||
if (includeInOutput && value !== undefined) {
|
if (value !== undefined) {
|
||||||
result[id] = value;
|
obj[id] = value;
|
||||||
}
|
}
|
||||||
return result;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ export class MermaidDiagram {
|
||||||
this.renderBox = this.box.addBox('Render');
|
this.renderBox = this.box.addBox('Render');
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
this.box.addBox('Spacer').setInnerHTML(' ');
|
||||||
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
|
this.logBoxPre = logBox.el.appendChild(document.createElement('pre'));
|
||||||
this.inSection = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static initializeAPI() {
|
static initializeAPI() {
|
||||||
|
@ -20,10 +19,8 @@ export class MermaidDiagram {
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
primaryColor: '#2a5b6c',
|
primaryColor: '#2a5b6c',
|
||||||
primaryTextColor: '#b6b6b6',
|
primaryTextColor: '#b6b6b6',
|
||||||
// lineColor: '#349cbd',
|
|
||||||
lineColor: '#57747d',
|
lineColor: '#57747d',
|
||||||
signalColor: '#57747d',
|
signalColor: '#57747d',
|
||||||
// signalColor: '#349cbd',
|
|
||||||
noteBkgColor: '#516f77',
|
noteBkgColor: '#516f77',
|
||||||
noteTextColor: '#cecece',
|
noteTextColor: '#cecece',
|
||||||
activationBkgColor: '#1d3f49',
|
activationBkgColor: '#1d3f49',
|
||||||
|
@ -31,6 +28,7 @@ export class MermaidDiagram {
|
||||||
},
|
},
|
||||||
securityLevel: 'loose', // 'loose' so that we can use click events
|
securityLevel: 'loose', // 'loose' so that we can use click events
|
||||||
// logLevel: 'debug',
|
// logLevel: 'debug',
|
||||||
|
useMaxWidth: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Controls {
|
export class SceneControls {
|
||||||
constructor(parentBox) {
|
constructor(parentBox) {
|
||||||
this.disableAutoplayButton = new Button('Disable Auto-play', () => {
|
this.disableAutoplayButton = new Button('Disable Auto-play', () => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
|
@ -4,7 +4,7 @@ import { MermaidDiagram } from './mermaid.js';
|
||||||
import { SequenceDiagram } from './sequence.js';
|
import { SequenceDiagram } from './sequence.js';
|
||||||
import { Table } from './table.js';
|
import { Table } from './table.js';
|
||||||
import { Flowchart } from './flowchart.js';
|
import { Flowchart } from './flowchart.js';
|
||||||
import { Controls } from './controls.js';
|
import { SceneControls } from './scene-controls.js';
|
||||||
import { Box } from './box.js';
|
import { Box } from './box.js';
|
||||||
import { Document } from './document.js';
|
import { Document } from './document.js';
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ export class Scene {
|
||||||
|
|
||||||
if (!window.disableSceneControls) {
|
if (!window.disableSceneControls) {
|
||||||
this.topRail = new Box('Top rail', document.body, { prepend: true }).addClass('top-rail');
|
this.topRail = new Box('Top rail', document.body, { prepend: true }).addClass('top-rail');
|
||||||
this.controlsBox = this.topRail.addBox('Controls').addClass('controls');
|
const controlsBox = this.topRail.addBox('SceneControls').addClass('scene-controls');
|
||||||
this.controls = new Controls(this.controlsBox);
|
this.controls = new SceneControls(controlsBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,26 +47,27 @@ export class Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
withFlowchart({ direction = 'BT' } = {}) {
|
withFlowchart({ direction = 'BT' } = {}) {
|
||||||
const box = this.topSection.addBox('Flowchart').addClass('padded');
|
this.withSectionFlowchart({ direction, section: this.topSection });
|
||||||
this.box.addBox('Spacer').setInnerHTML(' ');
|
this.flowchart = this.lastFlowchart;
|
||||||
const logBox = this.box.addBox('Flowchart text').addClass('dim');
|
|
||||||
this.flowchart = new Flowchart(box, logBox, direction);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
|
withSectionFlowchart({
|
||||||
|
id, name, direction = 'BT', section,
|
||||||
|
} = {}) {
|
||||||
|
section = section ?? this.middleSection;
|
||||||
const index = this.flowcharts.size;
|
const index = this.flowcharts.size;
|
||||||
name = name ?? `Flowchart ${index}`;
|
name = name ?? `Flowchart ${index}`;
|
||||||
id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`;
|
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 box = container.addBox('Flowchart').addClass('padded');
|
||||||
const logBox = container.addBox('Flowchart text').addClass('dim');
|
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);
|
this.flowcharts.set(id, flowchart);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFlowchart() {
|
get lastFlowchart() {
|
||||||
if (!this.flowcharts.size) {
|
if (!this.flowcharts.size) {
|
||||||
if (this.flowchart) {
|
if (this.flowchart) {
|
||||||
return this.flowchart;
|
return this.flowchart;
|
||||||
|
|
|
@ -29,16 +29,19 @@ export class Edge {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
getHtml() {
|
||||||
let html = '<table>';
|
const edges = this.getComorphicEdges();
|
||||||
for (const { type, weight } of this.getComorphicEdges()) {
|
let html = '';
|
||||||
|
html += '<table>';
|
||||||
|
for (const { type, weight } of edges) {
|
||||||
html += `<tr><td>${type}</td><td>${weight}</td></tr>`;
|
html += `<tr><td>${type}</td><td>${weight}</td></tr>`;
|
||||||
}
|
}
|
||||||
html += '</table>';
|
html += '</table>';
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFlowchartNode() {
|
getFlowchartNode() {
|
||||||
return `${Edge.getCombinedKey(this)}(${this.getHtml()})`;
|
return `${Edge.getCombinedKey(this)}("${this.getHtml()}")`;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayEdgeNode() {
|
displayEdgeNode() {
|
||||||
|
@ -55,13 +58,13 @@ export class Edge {
|
||||||
this.graph.flowchart?.log(`${this.from.id} --- ${this.getFlowchartNode()} --> ${this.to.id}`);
|
this.graph.flowchart?.log(`${this.from.id} --- ${this.getFlowchartNode()} --> ${this.to.id}`);
|
||||||
this.graph.flowchart?.log(`class ${Edge.getCombinedKey(this)} edge`);
|
this.graph.flowchart?.log(`class ${Edge.getCombinedKey(this)} edge`);
|
||||||
if (this.graph.editable && !this.installedClickCallback) {
|
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;
|
this.installedClickCallback = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static prepareEditorDocument(graph, doc, from, to) {
|
static prepareEditorDocument(graph, doc, from, to) {
|
||||||
doc.clear();
|
|
||||||
const form = doc.form({ name: 'editorForm' }).lastElement;
|
const form = doc.form({ name: 'editorForm' }).lastElement;
|
||||||
doc.remark('<h3>Edit Edge</h3>', { parentEl: form.el });
|
doc.remark('<h3>Edit Edge</h3>', { parentEl: form.el });
|
||||||
form
|
form
|
||||||
|
@ -72,20 +75,23 @@ export class Edge {
|
||||||
id: 'to', name: 'to', defaultValue: to, disabled: true,
|
id: 'to', name: 'to', defaultValue: to, disabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
doc.remark('<h4>Edge Types</h4>', { parentEl: form.el });
|
||||||
const subFormArray = form.subFormArray({ id: 'edges', name: 'edges' }).lastItem;
|
const subFormArray = form.subFormArray({ id: 'edges', name: 'edges' }).lastItem;
|
||||||
|
|
||||||
const addEdgeForm = (edge) => {
|
const addEdgeForm = (edge) => {
|
||||||
const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
|
const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
|
||||||
doc.remark('<br/>', { parentEl: subForm.el });
|
subForm.textField({
|
||||||
subForm.textField({ id: 'type', name: 'type', defaultValue: edge.type });
|
id: 'type', name: 'type', defaultValue: edge.type, required: true,
|
||||||
subForm.textField({ id: 'weight', name: 'weight', defaultValue: edge.weight });
|
})
|
||||||
subForm.button({
|
.textField({
|
||||||
id: 'remove',
|
id: 'weight', name: 'weight', defaultValue: edge.weight, required: true,
|
||||||
name: 'Remove Edge Type',
|
})
|
||||||
cb: (_, { initializing }) => {
|
.button({
|
||||||
if (initializing) return;
|
id: 'remove',
|
||||||
subFormArray.remove(subForm);
|
name: 'Remove Edge Type',
|
||||||
},
|
cb: () => subFormArray.remove(subForm),
|
||||||
});
|
});
|
||||||
|
doc.remark('<br>', { parentEl: subForm.el });
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const edge of graph.getEdges(null, from, to)) {
|
for (const edge of graph.getEdges(null, from, to)) {
|
||||||
|
@ -95,29 +101,38 @@ export class Edge {
|
||||||
form.button({
|
form.button({
|
||||||
id: 'add',
|
id: 'add',
|
||||||
name: 'Add Edge Type',
|
name: 'Add Edge Type',
|
||||||
cb: (_, { initializing }) => {
|
cb: () => addEdgeForm(new Edge(graph, null, graph.getVertex(from), graph.getVertex(to))),
|
||||||
if (initializing) return;
|
})
|
||||||
addEdgeForm(new Edge(graph, null, graph.getVertex(from), graph.getVertex(to)));
|
.button({
|
||||||
},
|
id: 'save',
|
||||||
});
|
name: 'Save',
|
||||||
|
type: 'submit',
|
||||||
form.button({
|
cb: ({ form: { value: { edges } } }) => {
|
||||||
id: 'save',
|
// Do validation
|
||||||
name: 'Save',
|
for (const { type, weight } of edges) {
|
||||||
cb: ({ form: { value } }, { initializing }) => {
|
if (type === null || weight === null) {
|
||||||
if (initializing) return;
|
graph.errorDoc.remark('<pre>type and weight are required</pre>');
|
||||||
// Handle additions and updates
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
// Handle additions and updates
|
||||||
graph.redraw();
|
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(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +1,159 @@
|
||||||
|
import { displayNumber } from '../../util/helpers.js';
|
||||||
|
|
||||||
|
import { Edge } from './edge.js';
|
||||||
|
|
||||||
export class Vertex {
|
export class Vertex {
|
||||||
constructor(graph, type, id, data, options = {}) {
|
constructor(graph, type, id, data, options = {}) {
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this._id = id;
|
this.id = id;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.label = options.label ?? this.id;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.edges = {
|
this.edges = {
|
||||||
from: [],
|
from: [],
|
||||||
to: [],
|
to: [],
|
||||||
};
|
};
|
||||||
this.installedClickCallback = false;
|
this.installedClickCallback = false;
|
||||||
|
this.properties = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.installedClickCallback = false;
|
this.installedClickCallback = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(newId) {
|
|
||||||
this._id = newId;
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
|
||||||
return this._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEdges(type, away) {
|
getEdges(type, away) {
|
||||||
return this.edges[away ? 'from' : 'to'].filter(
|
return this.edges[away ? 'from' : 'to'].filter(
|
||||||
(edge) => edge.type === type,
|
(edge) => edge.type === type,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisplayLabel(label) {
|
setProperty(key, value) {
|
||||||
this.label = label;
|
this.properties.set(key, value);
|
||||||
this.displayVertex();
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayVertex() {
|
displayVertex() {
|
||||||
if (this.options.hide) {
|
if (this.options.hide) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.graph.flowchart?.log(`${this.id}[${this.label}]`);
|
|
||||||
|
let html = '';
|
||||||
|
html += `${this.label}`;
|
||||||
|
html += '<table>';
|
||||||
|
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.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) {
|
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;
|
this.installedClickCallback = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static prepareEditorDocument(graph, doc, vertexId) {
|
static prepareEditorDocument(graph, doc, vertexId) {
|
||||||
doc.clear();
|
doc.clear();
|
||||||
const vertex = graph.getVertex(vertexId);
|
const vertex = vertexId ? graph.getVertex(vertexId) : undefined;
|
||||||
if (!vertex) {
|
|
||||||
throw new Error(`Could not find WDG Vertex ${vertexId}`);
|
|
||||||
}
|
|
||||||
doc.remark('<h3>Edit Vertex</h3>');
|
|
||||||
const form = doc.form().lastElement;
|
const form = doc.form().lastElement;
|
||||||
|
doc.remark(`<h3>${vertex ? 'Edit' : 'Add'} Vertex</h3>`, { parentEl: form.el });
|
||||||
form
|
form
|
||||||
.textField({
|
.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: 'type', name: 'type', defaultValue: vertex?.type })
|
||||||
.textField({ id: 'label', name: 'label', defaultValue: vertex.label })
|
.textField({ id: 'label', name: 'label', defaultValue: vertex?.label });
|
||||||
|
|
||||||
.button({
|
doc.remark('<h4>Properties</h4>', { parentEl: form.el });
|
||||||
id: 'save',
|
const subFormArray = form.subFormArray({ id: 'properties', name: 'properties' }).lastItem;
|
||||||
name: 'Save',
|
const addPropertyForm = (key, value) => {
|
||||||
cb: ({ form: { value } }, { initializing }) => {
|
const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
|
||||||
if (initializing) return;
|
subForm.textField({ id: 'key', name: 'key', defaultValue: key })
|
||||||
if (value.id && value.id !== vertex.id) {
|
.textField({ id: 'value', name: 'value', defaultValue: value })
|
||||||
// TODO: When an ID changes we really need to wipe out and redraw!
|
.button({
|
||||||
// But we don't yet have a systematic approach for doing that.
|
id: 'remove',
|
||||||
// Everything is getting rendered as needed. Lacking abstraction.
|
name: 'Remove Property',
|
||||||
// HMM we're not actually that far! Just wipe everything out and draw each vertex and edge :)
|
cb: () => subFormArray.remove(subForm),
|
||||||
// for (const vertex of )
|
});
|
||||||
// for (const edge of [...vertex.edges.to, ...vertex.edges.from]) {
|
doc.remark('<br>', { parentEl: subForm.el });
|
||||||
// edge.displayEdge();
|
};
|
||||||
// }
|
|
||||||
}
|
if (vertex) {
|
||||||
Object.assign(vertex, value);
|
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();
|
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('<h3>New Edge</h3>', { 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;
|
return doc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Vertex } from './vertex.js';
|
import { Vertex } from './vertex.js';
|
||||||
import { Edge } from './edge.js';
|
import { Edge } from './edge.js';
|
||||||
|
import { Document } from '../display/document.js';
|
||||||
|
|
||||||
const graphs = [];
|
const allGraphs = [];
|
||||||
|
|
||||||
const makeWDGHandler = (graphIndex) => (vertexId) => {
|
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
|
// We want a document for editing this node, which may be a vertex or an edge
|
||||||
const editorDoc = graph.scene.getDocument('editorDocument')
|
const { editorDoc } = graph;
|
||||||
?? graph.scene.withDocument('editorDocument').lastDocument;
|
editorDoc.clear();
|
||||||
if (vertexId.startsWith('edge:')) {
|
if (vertexId.startsWith('edge:')) {
|
||||||
const [, from, to] = vertexId.split(':');
|
const [, from, to] = vertexId.split(':');
|
||||||
Edge.prepareEditorDocument(graph, editorDoc, from, to);
|
Edge.prepareEditorDocument(graph, editorDoc, from, to);
|
||||||
|
@ -24,12 +25,15 @@ export class WeightedDirectedGraph {
|
||||||
this.nextVertexId = 0;
|
this.nextVertexId = 0;
|
||||||
this.flowchart = scene?.flowchart;
|
this.flowchart = scene?.flowchart;
|
||||||
this.editable = options.editable;
|
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.
|
// 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
|
// In order to provide the appropriate graph context for each callback, we create a separate callback
|
||||||
// function for each graph.
|
// function for each graph.
|
||||||
|
this.index = allGraphs.length;
|
||||||
|
allGraphs.push(this);
|
||||||
window[`WDGHandler${this.index}`] = makeWDGHandler(this.index);
|
window[`WDGHandler${this.index}`] = makeWDGHandler(this.index);
|
||||||
|
|
||||||
|
// TODO: Populate history
|
||||||
this.history = {};
|
this.history = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,16 +87,26 @@ export class WeightedDirectedGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rerender
|
// Ensure rerender
|
||||||
this.flowchart?.render();
|
this.flowchart?.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
withFlowchart() {
|
withFlowchart() {
|
||||||
this.scene?.withAdditionalFlowchart();
|
this.scene?.withSectionFlowchart();
|
||||||
this.flowchart = this.scene?.lastFlowchart();
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetEditorDocument() {
|
||||||
|
this.editorDoc.clear();
|
||||||
|
Vertex.prepareEditorDocument(this, this.editorDoc);
|
||||||
|
}
|
||||||
|
|
||||||
addVertex(type, id, data, label, options) {
|
addVertex(type, id, data, label, options) {
|
||||||
// Supports simple case of auto-incremented numeric ids
|
// Supports simple case of auto-incremented numeric ids
|
||||||
if (typeof id === 'object') {
|
if (typeof id === 'object') {
|
||||||
|
@ -103,9 +117,9 @@ export class WeightedDirectedGraph {
|
||||||
if (this.vertices.has(id)) {
|
if (this.vertices.has(id)) {
|
||||||
throw new Error(`Vertex already exists with id: ${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);
|
this.vertices.set(id, vertex);
|
||||||
vertex.setDisplayLabel(label ?? id);
|
vertex.displayVertex();
|
||||||
return vertex;
|
return vertex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,22 +147,6 @@ export class WeightedDirectedGraph {
|
||||||
return edges?.get(edgeKey);
|
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) {
|
getEdgeWeight(type, from, to) {
|
||||||
return this.getEdge(type, from, to)?.weight;
|
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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ a:visited {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
.controls {
|
.scene-controls {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 150px;
|
left: 150px;
|
||||||
}
|
}
|
||||||
|
@ -83,3 +83,12 @@ label > div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
min-width: 20em;
|
||||||
|
}
|
||||||
|
span.small {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { DAO } from '../../classes/dao/dao.js';
|
import { DAO } from '../../classes/dao/dao.js';
|
||||||
import { Public } from '../../classes/actors/public.js';
|
import { Public } from '../../classes/actors/public.js';
|
||||||
import { PostContent } from '../../classes/supporting/post-content.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';
|
import { mochaRun } from '../../util/helpers.js';
|
||||||
|
|
||||||
const DELAY_INTERVAL = 100;
|
const DELAY_INTERVAL = 100;
|
||||||
|
@ -18,10 +18,6 @@ const newExpert = async () => {
|
||||||
const index = experts.length;
|
const index = experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(dao, name, scene).initialize();
|
const expert = await new Expert(dao, name, scene).initialize();
|
||||||
expert.setDisplayValue(
|
|
||||||
'rep',
|
|
||||||
() => dao.reputation.valueOwnedBy(expert.reputationPublicKey),
|
|
||||||
);
|
|
||||||
experts.push(expert);
|
experts.push(expert);
|
||||||
return expert;
|
return expert;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Scene } from '../../classes/display/scene.js';
|
||||||
import { Expert } from '../../classes/actors/expert.js';
|
import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { PostContent } from '../../classes/supporting/post-content.js';
|
import { PostContent } from '../../classes/supporting/post-content.js';
|
||||||
import { DAO } from '../../classes/dao/dao.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 {
|
export class ForumTest {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
|
@ -27,11 +27,17 @@ describe('Document > Form > TextField', () => {
|
||||||
dv.set(value);
|
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...!');
|
doc.remark('Hmm...!');
|
||||||
});
|
});
|
||||||
// it('can exist within a graph', () => {
|
// it('can exist within a graph', () => {
|
||||||
// scene.withAdditionalFlowchart({ id: 'flowchart', name: 'Graph' });
|
// scene.withSectionFlowchart({ id: 'flowchart', name: 'Graph' });
|
||||||
// const graph = scene.lastFlowchart();
|
// const graph = scene.lastFlowchart();
|
||||||
// });
|
// });
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Scene } from '../../classes/display/scene.js';
|
||||||
import { Expert } from '../../classes/actors/expert.js';
|
import { Expert } from '../../classes/actors/expert.js';
|
||||||
import { PostContent } from '../../classes/supporting/post-content.js';
|
import { PostContent } from '../../classes/supporting/post-content.js';
|
||||||
import { DAO } from '../../classes/dao/dao.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';
|
import { mochaRun } from '../../util/helpers.js';
|
||||||
|
|
||||||
const POOL_DURATION_MS = 100;
|
const POOL_DURATION_MS = 100;
|
||||||
|
@ -17,7 +17,6 @@ async function newExpert() {
|
||||||
const index = experts.length;
|
const index = experts.length;
|
||||||
const name = `Expert${index + 1}`;
|
const name = `Expert${index + 1}`;
|
||||||
const expert = await new Expert(dao, name, scene).initialize();
|
const expert = await new Expert(dao, name, scene).initialize();
|
||||||
await expert.addComputedValue('rep', () => dao.reputation.valueOwnedBy(expert.reputationPublicKey));
|
|
||||||
experts.push(expert);
|
experts.push(expert);
|
||||||
return expert;
|
return expert;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const EPSILON = 2.23e-16;
|
export const EPSILON = 2.23e-16;
|
||||||
|
|
||||||
export const INCINERATOR_ADDRESS = 0;
|
export const INCINERATOR_ADDRESS = '0';
|
||||||
|
|
||||||
export const EdgeTypes = {
|
export const EdgeTypes = {
|
||||||
CITATION: 'citation',
|
CITATION: 'citation',
|
||||||
|
|
Loading…
Reference in New Issue