Files
architype/architype.js

336 lines
7.0 KiB
JavaScript
Raw Normal View History

2019-05-25 04:16:20 +00:00
'use strict';
class Editor {
constructor(container) {
this.container_ = container;
}
getSelected() {
let iter = document.activeElement;
while (iter) {
if (iter.parentElement == this.container_) {
return iter;
}
iter = iter.parentElement;
}
return null;
2019-05-25 04:16:20 +00:00
}
deleteSelected() {
let sel = this.getSelected();
if (sel) {
let newSel = sel.nextElementSibling || sel.previousElementSibling;
sel.remove();
this.select(newSel);
}
2019-05-25 04:16:20 +00:00
}
deleteSelectedAndAfter() {
let sel = this.getSelected();
if (sel) {
while (sel.nextElementSibling) {
sel.nextElementSibling.remove();
}
let newSel = null;
if (sel.previousElementSibling) {
newSel = sel.previousElementSibling;
}
sel.remove();
this.select(newSel);
2019-05-25 04:16:20 +00:00
}
}
selectNext() {
let sel = this.getSelected() || this.container_.lastElementChild;
if (sel) {
this.select(sel.nextElementSibling ||
this.container_.firstElementChild);
}
2019-05-25 04:16:20 +00:00
}
selectPrev() {
let sel = this.getSelected() || this.container_.firstElementChild;
if (sel) {
this.select(sel.previousElementSibling ||
this.container_.lastElementChild);
}
2019-05-25 04:16:20 +00:00
}
selectPrevPage() {
let targetTop = this.container_.scrollTop - this.container_.clientHeight;
let sel = this.getSelected() || this.container_.lastElementSibling;
if (sel) {
while (sel.previousElementSibling &&
this.container_.scrollTop > targetTop) {
sel = sel.previousElementSibling;
this.select(sel);
}
2019-05-25 04:16:20 +00:00
}
}
selectNextPage() {
let targetTop = this.container_.scrollTop + this.container_.clientHeight;
let sel = this.getSelected() || this.container_.firstElementSibling;
if (sel) {
while (sel.nextElementSibling && this.container_.scrollTop < targetTop) {
sel = sel.nextElementSibling;
this.select(sel);
}
2019-05-25 04:16:20 +00:00
}
}
selectFirst() {
this.select(this.container_.firstElementChild);
}
selectLast() {
this.select(this.container_.lastElementChild);
}
select(elem) {
if (!elem) {
return;
}
elem.focus();
2019-05-25 04:16:20 +00:00
elem.scrollIntoView({
2019-05-25 00:03:19 +00:00
block: 'center',
});
}
2019-05-25 04:16:20 +00:00
2019-05-25 04:33:31 +00:00
startEdit() {
let sel = this.getSelected();
if (sel) {
sel.xArchObj.startEdit();
}
2019-05-25 04:33:31 +00:00
}
stopEdit() {
let sel = this.getSelected();
if (sel) {
sel.xArchObj.stopEdit();
}
2019-05-25 04:33:31 +00:00
}
isEditing() {
let sel = this.getSelected();
return sel && sel.xArchObj.isEditing();
}
2019-05-25 04:16:20 +00:00
addNodeAfter() {
let node = Node.addAfter(this.container_, this.getSelected());
this.select(node);
this.startEdit();
2019-05-25 04:16:20 +00:00
}
addNodeBefore() {
let node = Node.addBefore(this.container_, this.getSelected());
this.select(node);
this.startEdit();
2019-05-25 04:16:20 +00:00
}
2019-05-26 02:53:55 +00:00
addGroupAfter() {
let group = Group.addAfter(this.container_, this.getSelected());
this.select(group);
this.startEdit();
}
addGroupBefore() {
let group = Group.addBefore(this.container_, this.getSelected());
this.select(group);
this.startEdit();
2019-05-26 02:53:55 +00:00
}
2019-05-25 04:16:20 +00:00
onKeyDown(e) {
2019-05-25 04:33:31 +00:00
if (this.isEditing()) {
switch (e.key) {
case 'Enter':
case 'Escape':
this.stopEdit();
// Do not allow other actions below to run
return;
case 'ArrowUp':
case 'ArrowDown':
case 'PageUp':
case 'PageDown':
this.stopEdit();
// Allow other actions below to run
break;
default:
// Most keystrokes just go into the input field
return;
}
}
2019-05-25 04:16:20 +00:00
switch (e.key) {
case 'd':
this.deleteSelected();
2019-05-26 02:53:55 +00:00
break;
case 'D':
this.deleteSelectedAndAfter();
2019-05-26 02:53:55 +00:00
break;
case'g':
this.addGroupAfter();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case'g':
this.addGroupBefore();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'j':
case 'ArrowDown':
this.selectNext();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'k':
case 'ArrowUp':
this.selectPrev();
e.preventDefault();
break;
case 'n':
this.addNodeAfter();
e.preventDefault();
break;
case 'N':
this.addNodeBefore();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'PageUp':
this.selectPrevPage();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'PageDown':
this.selectNextPage();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'Home':
this.selectFirst();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
case 'End':
this.selectLast();
e.preventDefault();
2019-05-25 04:16:20 +00:00
break;
2019-05-25 04:33:31 +00:00
case 'Enter':
this.startEdit();
e.preventDefault();
2019-05-25 04:33:31 +00:00
break;
2019-05-25 04:16:20 +00:00
}
}
}
class Node {
constructor() {
this.elem_ = document.createElement('li');
this.elem_.innerText = 'Node: ';
this.elem_.tabIndex = 0;
2019-05-25 04:16:20 +00:00
2019-05-25 04:33:31 +00:00
this.input_ = document.createElement('input');
this.input_.type = 'text';
this.input_.placeholder = 'node name';
this.input_.addEventListener('blur', () => { this.onInputBlur(); });
2019-05-25 04:33:31 +00:00
this.elem_.appendChild(this.input_);
2019-05-25 04:16:20 +00:00
this.elem_.classList.add('node');
// TODO: fix reference loop
this.elem_.xArchObj = this;
}
2019-05-25 04:33:31 +00:00
startEdit() {
this.input_.focus();
}
stopEdit() {
this.elem_.focus();
2019-05-25 04:33:31 +00:00
}
isEditing() {
return document.activeElement == this.input_;
}
onInputBlur() {
if (this.input_.value.length == 0) {
// this.elem_.remove();
}
}
2019-05-25 04:16:20 +00:00
static addBefore(container, elem) {
let node = new Node();
container.insertBefore(node.elem_, elem);
return node.elem_;
}
static addAfter(container, elem) {
let node = new Node();
container.insertBefore(node.elem_, elem ? elem.nextSibling : null);
return node.elem_;
}
}
2019-05-26 02:53:55 +00:00
class Group {
constructor() {
this.elem_ = document.createElement('li');
this.elem_.innerText = 'Group: ';
this.elem_.tabIndex = 0;
2019-05-26 02:53:55 +00:00
this.input_ = document.createElement('input');
this.input_.type = 'text';
this.input_.placeholder = 'group name';
this.input_.addEventListener('blur', () => { this.onInputBlur(); });
2019-05-26 02:53:55 +00:00
this.elem_.appendChild(this.input_);
this.elem_.classList.add('group');
// TODO: fix reference loop
this.elem_.xArchObj = this;
2019-05-26 02:53:55 +00:00
}
startEdit() {
this.input_.focus();
}
stopEdit() {
this.elem_.focus();
2019-05-26 02:53:55 +00:00
}
isEditing() {
return document.activeElement == this.input_;
}
onInputBlur() {
if (this.input_.value.length == 0) {
this.elem_.remove();
}
}
2019-05-26 02:53:55 +00:00
static addBefore(container, elem) {
let group = new Group();
container.insertBefore(group.elem_, elem);
return group.elem_;
}
static addAfter(container, elem) {
let group = new Group();
container.insertBefore(group.elem_, elem ? elem.nextSibling : null);
return group.elem_;
2019-05-26 02:53:55 +00:00
}
}
2019-05-25 04:16:20 +00:00
let editor = new Editor(document.getElementById('definition'));
document.addEventListener('keydown', e => { editor.onKeyDown(e); });