Misc. fixes/improvements
Standardize vertex as label + list of properties, for rendering and editing
This commit is contained in:
parent
56132b2fec
commit
07370be4fa
|
@ -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}
|
||||
<table><tr>
|
||||
<td>reputation</td>
|
||||
<td>${displayNumber(this.getReputation())}</td>
|
||||
</tr></table>`
|
||||
.replaceAll(/\n\s*/g, '');
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.reputationKey = await CryptoUtil.generateAsymmetricKey();
|
||||
// this.reputationPublicKey = await CryptoUtil.exportKey(this.reputationKey.publicKey);
|
||||
|
|
|
@ -22,22 +22,15 @@ class Post extends Actor {
|
|||
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) {
|
||||
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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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) {
|
||||
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 });
|
||||
}));
|
||||
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);
|
||||
|
@ -77,7 +76,6 @@ export class SubForm extends FormElement {
|
|||
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, { ...opts, tagName: 'form' });
|
||||
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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,8 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -72,20 +73,19 @@ export class Edge {
|
|||
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 addEdgeForm = (edge) => {
|
||||
const { subForm } = form.subForm({ name: 'subform', subFormArray }).lastItem;
|
||||
doc.remark('<br/>', { parentEl: subForm.el });
|
||||
subForm.textField({ id: 'type', name: 'type', defaultValue: edge.type });
|
||||
subForm.textField({ id: 'weight', name: 'weight', defaultValue: edge.weight });
|
||||
subForm.button({
|
||||
subForm.textField({ id: 'type', name: 'type', defaultValue: edge.type })
|
||||
.textField({ id: 'weight', name: 'weight', defaultValue: edge.weight })
|
||||
.button({
|
||||
id: 'remove',
|
||||
name: 'Remove Edge Type',
|
||||
cb: (_, { initializing }) => {
|
||||
if (initializing) return;
|
||||
subFormArray.remove(subForm);
|
||||
},
|
||||
cb: () => subFormArray.remove(subForm),
|
||||
});
|
||||
doc.remark('<br>', { parentEl: subForm.el });
|
||||
};
|
||||
|
||||
for (const edge of graph.getEdges(null, from, to)) {
|
||||
|
@ -95,23 +95,19 @@ 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)));
|
||||
},
|
||||
cb: () => addEdgeForm(new Edge(graph, null, graph.getVertex(from), graph.getVertex(to))),
|
||||
})
|
||||
.button({
|
||||
id: 'save',
|
||||
name: 'Save',
|
||||
cb: ({ form: { value } }, { initializing }) => {
|
||||
if (initializing) return;
|
||||
cb: ({ form: { value: { edges } } }) => {
|
||||
// Handle additions and updates
|
||||
for (const { type, weight } of value.edges) {
|
||||
for (const { type, weight } of 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)) {
|
||||
if (!edges.find(({ type }) => type === edge.type)) {
|
||||
graph.deleteEdge(edge.type, from, to);
|
||||
}
|
||||
}
|
||||
|
@ -121,10 +117,7 @@ export class Edge {
|
|||
.button({
|
||||
id: 'cancel',
|
||||
name: 'Cancel',
|
||||
cb: (_, { initializing }) => {
|
||||
if (initializing) return;
|
||||
doc.clear();
|
||||
},
|
||||
cb: () => doc.clear(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import { displayNumber } from '../../util/helpers.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 = new Map();
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
@ -22,18 +26,28 @@ export class Vertex {
|
|||
);
|
||||
}
|
||||
|
||||
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 = `${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>';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -44,24 +58,50 @@ export class Vertex {
|
|||
if (!vertex) {
|
||||
throw new Error(`Could not find WDG Vertex ${vertexId}`);
|
||||
}
|
||||
doc.remark('<h3>Edit Vertex</h3>');
|
||||
const form = doc.form().lastElement;
|
||||
doc.remark('<h3>Edit Vertex</h3>', { parentEl: form.el });
|
||||
form
|
||||
.textField({
|
||||
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: 'label', name: 'label', defaultValue: vertex.label });
|
||||
|
||||
doc.remark('<h4>Properties</h4>', { 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('<br>', { parentEl: subForm.el });
|
||||
};
|
||||
|
||||
for (const [key, value] of vertex.properties.entries()) {
|
||||
addPropertyForm(key, value);
|
||||
}
|
||||
|
||||
form.button({
|
||||
id: 'add',
|
||||
name: 'Add Property',
|
||||
cb: () => addPropertyForm('', ''),
|
||||
})
|
||||
.button({
|
||||
id: 'save',
|
||||
name: 'Save',
|
||||
cb: ({ form: { value } }, { initializing }) => {
|
||||
if (initializing) return;
|
||||
type: 'submit',
|
||||
cb: ({ form: { value: formValue } }) => {
|
||||
let fullRedraw = false;
|
||||
if (value.id && value.id !== vertex.id) {
|
||||
if (formValue.id !== vertex.id) {
|
||||
fullRedraw = true;
|
||||
}
|
||||
Object.assign(vertex, value);
|
||||
// TODO: preserve data types of properties
|
||||
formValue.properties = new Map(formValue.properties.map(({ key, value }) => [key, value]));
|
||||
Object.assign(vertex, formValue);
|
||||
vertex.displayVertex();
|
||||
if (fullRedraw) {
|
||||
graph.redraw();
|
||||
|
@ -71,10 +111,7 @@ export class Vertex {
|
|||
.button({
|
||||
id: 'cancel',
|
||||
name: 'Cancel',
|
||||
cb: (_, { initializing }) => {
|
||||
if (initializing) return;
|
||||
doc.clear();
|
||||
},
|
||||
cb: () => doc.clear(),
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
|
|
@ -103,9 +103,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -83,3 +83,6 @@ label > div {
|
|||
display: inline-block;
|
||||
min-width: 50px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,13 @@ 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', () => {
|
||||
|
|
Loading…
Reference in New Issue