Preliminary support for user input

This commit is contained in:
Ladd Hoffman 2023-06-28 08:40:19 -05:00
parent 36acc56fa2
commit a8544dfd39
25 changed files with 269 additions and 30 deletions

View File

@ -1,9 +0,0 @@
## Client/UI
Voting consists of staking operations performed by software operated by owners of EOA.
This software may be referred to as "The UI". It may also be considered "a client".
It will need to be a network-connected application. It will need a certain minimum of RAM,
and for some features disk storage,
and for some features uptime .

View File

@ -1,5 +1,18 @@
# Client Operations ## Client
Client must communicate with one or more servers. Clients play a key role in an MVPR DAO.
Client must build a local view Clients must be operated by reputation holders.
Clients are the agents that submit posts to the forum, initiate validation pools, and vote in validation pools.
We sometimes refer to the client as "the UI".
It will need to be a network-connected application. It will need a certain minimum of RAM,
and for some features disk storage,
and for some features uptime .
The behavior of the client constitutes what we refer to as the DAO's "soft protocols".
Malicious actors may freely modify their own client's behavior.
Therefore honest clients must engage in policing to preserve the integrity of the network.

View File

@ -0,0 +1,6 @@
export class Client {
constructor(dao, expert) {
this.dao = dao;
this.expert = expert;
}
}

View File

@ -5,7 +5,9 @@ export class Box {
constructor(name, parentEl, options = {}) { constructor(name, parentEl, options = {}) {
this.name = name; this.name = name;
this.el = document.createElement('div'); this.el = document.createElement('div');
this.el.id = `box_${randomID()}`; this.el.box = this;
const id = options.id ?? randomID();
this.el.id = `${parentEl.id}_box_${id}`;
this.el.classList.add('box'); this.el.classList.add('box');
if (name) { if (name) {
this.el.setAttribute('box-name', name); this.el.setAttribute('box-name', name);

View File

@ -0,0 +1,30 @@
import { Box } from './box.js';
import { Form } from './form.js';
/**
* @example
* ```typescript
* const doc = new Document();
* const form1 = doc.form();
* ```
*/
export class Document extends Box {
form() {
return this.addElement(new Form(this));
}
remarks(text, opts) {
return this.addElement(new Box('Remark', this.el, opts).setInnerHTML(text));
}
addElement(element) {
this.elements = this.elements ?? [];
this.elements.push(element);
return this;
}
get lastElement() {
if (!this.elements?.length) return null;
return this.elements[this.elements.length - 1];
}
}

View File

@ -0,0 +1,57 @@
import { randomID } from '../../util/helpers.js';
import { Box } from './box.js';
const updateValuesOnEventTypes = ['keyup', 'mouseup'];
export class FormElement extends Box {
constructor(name, parentEl, opts) {
super(name, parentEl, opts);
this.id = opts.id ?? name;
const { cb } = opts;
if (cb) {
updateValuesOnEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
cb(this);
}));
cb(this);
}
}
}
export class Button extends FormElement { }
export class TextField extends FormElement {
constructor(name, parentEl, opts) {
super(name, parentEl, opts);
this.input = document.createElement('input');
this.el.appendChild(this.input);
}
get value() {
return this.input?.value;
}
}
export class TextArea extends FormElement { }
export class Form {
constructor(document, opts = {}) {
this.document = document;
this.items = [];
this.id = opts.id ?? `form_${randomID()}`;
}
button(opts) {
this.items.push(new Button(opts.name, this.document.el, opts));
return this;
}
textField(opts) {
this.items.push(new TextField(opts.name, this.document.el, opts));
return this;
}
textArea(opts) {
this.items.push(new TextArea(opts.name, this.document.el, opts));
return this;
}
}

View File

@ -6,6 +6,7 @@ import { Table } from './table.js';
import { Flowchart } from './flowchart.js'; import { Flowchart } from './flowchart.js';
import { Controls } from './controls.js'; import { Controls } from './controls.js';
import { Box } from './box.js'; import { Box } from './box.js';
import { Document } from './document.js';
export class Scene { export class Scene {
constructor(name, rootBox) { constructor(name, rootBox) {
@ -86,6 +87,24 @@ export class Scene {
return this; return this;
} }
/**
*
* @param {string} name
* @param {(Document): Document} cb
* @returns {Scene}
*/
withDocument(name, cb) {
this.documents = this.documents ?? [];
const doc = new Document(name, this.middleSection.el);
this.documents.push(cb ? cb(doc) : doc);
return this;
}
get lastDocument() {
if (!this.documents?.length) return null;
return this.documents[this.documents.length - 1];
}
registerActor(actor) { registerActor(actor) {
this.actors.add(actor); this.actors.add(actor);
if (actor.options.announce) { if (actor.options.announce) {

View File

@ -28,6 +28,12 @@
<li><a href="./tests/forum11.test.html">Multiple posts with overlapping authors</a></li> <li><a href="./tests/forum11.test.html">Multiple posts with overlapping authors</a></li>
</ol> </ol>
</ul> </ul>
<ul>
<h3>Client</h3>
<ol>
<li><a href="./tests/client1.test.html">Expert can run a client</a></li>
</ol>
</ul>
<ul> <ul>
<li><a href="./tests/vm.test.html">VM</a></li> <li><a href="./tests/vm.test.html">VM</a></li>
</ul> </ul>
@ -38,6 +44,7 @@
<li><a href="./tests/debounce.test.html">Debounce</a></li> <li><a href="./tests/debounce.test.html">Debounce</a></li>
<li><a href="./tests/flowchart.test.html">Flowchart</a></li> <li><a href="./tests/flowchart.test.html">Flowchart</a></li>
<li><a href="./tests/mocha.test.html">Mocha</a></li> <li><a href="./tests/mocha.test.html">Mocha</a></li>
<li><a href="./tests/input.test.html">Input</a></li>
</ul> </ul>
<ul> <ul>
<h4><a href="./tests/all.test.html">All</a></h4> <h4><a href="./tests/all.test.html">All</a></h4>

View File

@ -37,6 +37,7 @@
<script type="module" src="./scripts/forum/forum9.test.js"></script> <script type="module" src="./scripts/forum/forum9.test.js"></script>
<script type="module" src="./scripts/forum/forum10.test.js"></script> <script type="module" src="./scripts/forum/forum10.test.js"></script>
<script type="module" src="./scripts/forum/forum11.test.js"></script> <script type="module" src="./scripts/forum/forum11.test.js"></script>
<script type="module" src="./scripts/input.test.js"></script>
<script defer class="mocha-init"> <script defer class="mocha-init">
mocha.setup({ mocha.setup({
ui: 'bdd', ui: 'bdd',

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<head>
<title>Client test 1</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
</head>
<body>
<h2><a href="../">DGF Tests</a></h2>
<div id="mocha"></div>
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/client/client1.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',
});
chai.should();
</script>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<head>
<title>Input</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
</head>
<body>
<h2><a href="../">DGF Tests</a></h2>
<div id="mocha"></div>
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/input.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',
});
window.should = chai.should();
</script>

View File

@ -0,0 +1,26 @@
import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from '../forum.test-util.js';
import { Client } from '../../../classes/dao/client.js';
describe('Forum', function tests() {
this.timeout(0);
const forumTest = new ForumTest({ displayAuthors: false });
let client;
before(async () => {
await forumTest.setup();
await forumTest.newExpert();
await forumTest.newExpert();
client = new Client(forumTest.dao, forumTest.experts[0]);
});
it('Expert can run a client', async () => {
client.should.not.be.undefined;
client.dao.should.equal(forumTest.dao);
client.expert.should.equal(forumTest.experts[0]);
});
});
mochaRun();

View File

@ -1,9 +1,9 @@
import { Box } from '../../../classes/display/box.js'; import { Box } from '../../classes/display/box.js';
import { Scene } from '../../../classes/display/scene.js'; 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/controls.js';
export class ForumTest { export class ForumTest {
constructor(options) { constructor(options) {

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { EPSILON } from '../../../util/constants.js'; import { EPSILON } from '../../../util/constants.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; import { INCINERATOR_ADDRESS } from '../../../util/constants.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js'; import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.js'; import { INCINERATOR_ADDRESS } from '../../../util/constants.js';
import { ForumTest } from './forum.test-util.js'; import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() { describe('Forum', function tests() {
this.timeout(0); this.timeout(0);

View File

@ -0,0 +1,35 @@
import { Box } from '../../classes/display/box.js';
// import { Document } from '../../classes/display/document.js';
import { Scene } from '../../classes/display/scene.js';
import { mochaRun } from '../../util/helpers.js';
const rootElement = document.getElementById('scene');
const rootBox = new Box('rootBox', rootElement).flex();
const scene = window.scene = new Scene('Input test', rootBox);
scene.withDocument();
describe('Document', () => {
it('Exists', () => {
scene.withDocument('Document', (doc) => doc.remarks('Hello'));
});
describe('Input', () => {
it('Accepts input', () => {
scene.withDocument('Document', (doc) => doc.form());
const doc = scene.lastDocument;
const form1 = doc.lastElement;
const dvMap = new Map();
const updateFieldValueDisplay = ({ name, value }) => {
const dv = dvMap.get(name) ?? scene.addDisplayValue(name);
dvMap.set(name, dv);
dv.set(value);
};
form1.textField({ id: 'input1', name: 'Input 1', cb: updateFieldValueDisplay });
doc.remarks('Hmm...!');
});
});
});
mochaRun();