From 62383f2aa9a0fc7a35376bb524b7796cf4b1745e Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Fri, 21 Jun 2019 18:15:44 +0000 Subject: [PATCH] Add Link, stop auto-remove when input empty, merge NodeList and Editor --- architype.css | 21 +++--- architype.js | 196 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 156 insertions(+), 61 deletions(-) diff --git a/architype.css b/architype.css index b62dcdf..894b9b8 100644 --- a/architype.css +++ b/architype.css @@ -10,7 +10,7 @@ body { font-family: 'Courier', monospace; } -#definition { +.editor { list-style: none; margin: 0; @@ -22,7 +22,11 @@ body { overflow-y: scroll; } -#definition li { +.editor:focus { + outline: none; +} + +.editor li { display: flex; flex-direction: row; align-items: center; @@ -38,32 +42,31 @@ body { cursor: default; } -#definition li.node { +.editor li.node { background-color: #daf0db; } -#definition li.group { +.editor li.group { background-color: #d8e6f4; } -#definition li:focus { +.editor li:focus { border-color: red; outline: none; } -#definition li input { +.editor li input { background-color: rgba(255,255,255,0.8); border: 3px solid black; padding: 4px; font-family: 'Courier', monospace; } -#definition li input:focus { +.editor li input:focus { border-color: red; outline: none; } -#definition .nodelist { - width: 100%; +.editor .editor { margin: 5px; } diff --git a/architype.js b/architype.js index ddf76cd..20ff922 100644 --- a/architype.js +++ b/architype.js @@ -22,12 +22,17 @@ class List { constructor(container) { this.container_ = container; this.minEntries_ = 0; + this.maxEntries_ = Number.MAX_SAFE_INTEGER; } setMinEntries(min) { this.minEntries_ = min; } + setMaxEntries(max) { + this.maxEntries_ = max; + } + getEntries() { let ret = []; for (let elem of this.container_.children) { @@ -36,6 +41,10 @@ class List { return ret; } + mayAdd() { + return this.container_.children.length < this.maxEntries_; + } + getSelected() { let iter = document.activeElement; while (iter) { @@ -184,62 +193,63 @@ class List { } } -class NodeList extends List { - constructor(container) { +class Editor extends List { + static NODE = 1; + static GROUP = 2; + static LINK = 3; + + constructor(container, allowedTypes) { super(container); + + this.allowedTypes_ = new Set(allowedTypes || + [Editor.NODE, Editor.GROUP, Editor.LINK]); + + this.container_.classList.add('editor'); // Needs to accept focus to receive keydown, but shouldn't be in the normal // tab flow. this.container_.tabIndex = 99999; this.container_.addEventListener('keydown', e => { this.onKeyDown(e); }); + this.container_.focus(); + } + + isAllowed(type) { + return this.mayAdd() && this.allowedTypes_.has(type); } addNodeAfter() { - Node.addAfter(this.container_, this.getSelected()); - } - - addNodeBefore() { - Node.addBefore(this.container_, this.getSelected()); - } - - onKeyDown(e) { - switch (e.key) { - case 'n': - this.addNodeAfter(); - e.stopPropagation(); - e.preventDefault(); - return; - - case 'N': - this.addNodeBefore(); - e.stopPropagation(); - e.preventDefault(); - return; + if (this.isAllowed(Editor.NODE)) { + Node.addAfter(this.container_, this.getSelected()); } - - super.onKeyDown(e); - } -} - -class Editor extends List { - constructor(container) { - super(container); - document.addEventListener('keydown', e => { this.onKeyDown(e); }); - } - - addNodeAfter() { - Node.addAfter(this.container_, this.getSelected()); } addNodeBefore() { - Node.addBefore(this.container_, this.getSelected()); + if (this.isAllowed(Editor.NODE)) { + Node.addBefore(this.container_, this.getSelected()); + } + } + + addLinkAfter() { + if (this.isAllowed(Editor.LINK)) { + Link.addAfter(this.container_, this.getSelected()); + } + } + + addLinkBefore() { + if (this.isAllowed(Editor.LINK)) { + Link.addBefore(this.container_, this.getSelected()); + } } addGroupAfter() { - Group.addAfter(this.container_, this.getSelected()); + if (this.isAllowed(Editor.GROUP)) { + Group.addAfter(this.container_, this.getSelected()); + } } addGroupBefore() { - Group.addBefore(this.container_, this.getSelected()); + if (this.isAllowed(Editor.GROUP)) { + Group.addBefore(this.container_, this.getSelected()); + } } onKeyDown(e) { @@ -256,6 +266,18 @@ class Editor extends List { e.preventDefault(); return; + case 'l': + this.addLinkAfter(); + e.stopPropagation(); + e.preventDefault(); + return; + + case 'L': + this.addLinkBefore(); + e.stopPropagation(); + e.preventDefault(); + return; + case 'n': this.addNodeAfter(); e.stopPropagation(); @@ -336,7 +358,6 @@ class Node extends EditorEntryBase { this.input_.type = 'text'; this.input_.placeholder = 'node name'; this.listen(this.input_, 'keydown', (e) => this.onInputKeyDown(e)); - this.listen(this.input_, 'blur', () => this.onInputBlur()); this.elem_.appendChild(this.input_); } @@ -401,13 +422,6 @@ class Node extends EditorEntryBase { this.elem_.focus(); } - onInputBlur() { - if (this.input_.value.length == 0 && (this.elem_.previousElementSibling || - this.elem_.nextElementSibling)) { - this.remove(); - } - } - getValue() { return this.input_.value; } @@ -424,12 +438,10 @@ class Group extends EditorEntryBase { this.input_.type = 'text'; this.input_.placeholder = 'group name'; this.listen(this.input_, 'keydown', (e) => this.onInputKeyDown(e)); - this.listen(this.input_, 'blur', () => this.onInputBlur()); this.elem_.appendChild(this.input_); let nodeList = document.createElement('div'); - nodeList.classList.add('nodelist'); - this.nodes_ = new NodeList(nodeList); + this.nodes_ = new Editor(nodeList, [Editor.NODE]); this.nodes_.setMinEntries(1); this.nodes_.addNodeAfter(); this.elem_.appendChild(nodeList); @@ -495,12 +507,92 @@ class Group extends EditorEntryBase { stopEdit() { this.elem_.focus(); } +} - onInputBlur() { - if (this.input_.value.length == 0) { - this.remove(); +class Link extends EditorEntryBase { + constructor() { + super(); + + this.elem_.innerText = 'Link: '; + this.elem_.classList.add('link'); + + this.input_ = document.createElement('input'); + this.input_.type = 'text'; + this.input_.placeholder = 'label'; + this.listen(this.input_, 'keydown', (e) => this.onInputKeyDown(e)); + this.elem_.appendChild(this.input_); + + let nodeList = document.createElement('div'); + this.nodes_ = new Editor(nodeList, [Editor.NODE]); + this.nodes_.setMinEntries(2); + this.nodes_.setMaxEntries(2); + this.nodes_.addNodeAfter(); + this.nodes_.addNodeAfter(); + this.elem_.appendChild(nodeList); + } + + afterDomAdd() { + this.input_.focus(); + } + + onInputKeyDown(e) { + switch (e.key) { + case 'Enter': + e.stopPropagation(); + e.preventDefault(); + this.stopEdit(); + { + let nodes = this.nodes_.getEntries(); + if (nodes[0].getValue() == '') { + nodes[0].startEdit(); + } else if (nodes[1].getValue() == '') { + nodes[1].startEdit(); + } + } + break; + + case 'Escape': + e.stopPropagation(); + e.preventDefault(); + this.stopEdit(); + break; + + case 'ArrowUp': + case 'ArrowDown': + case 'PageUp': + case 'PageDown': + this.stopEdit(); + break; + + default: + e.stopPropagation(); + break; } } + + onKeyDown(e) { + super.onKeyDown(e); + + switch (e.key) { + case 'Enter': + this.startEdit(); + e.stopPropagation(); + e.preventDefault(); + break; + + case 'ArrowRight': + this.nodes_.selectNext(); + break; + } + } + + startEdit() { + this.input_.focus(); + } + + stopEdit() { + this.elem_.focus(); + } } new Editor(document.getElementById('definition'));