2019-07-03 01:42:17 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
2019-07-11 18:14:06 +00:00
|
|
|
addEventListener('error', (e) => {
|
|
|
|
|
console.log(e);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-03 01:42:17 +00:00
|
|
|
class Architype {
|
|
|
|
|
constructor(container) {
|
|
|
|
|
this.container_ = container;
|
|
|
|
|
|
|
|
|
|
this.container_.classList.add('architype');
|
2019-07-12 03:37:48 +00:00
|
|
|
|
|
|
|
|
this.themes_ = ['light', 'dark'];
|
|
|
|
|
this.setTheme(localStorage.getItem('theme') || 'dark');
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-11 21:44:50 +00:00
|
|
|
document.addEventListener('keydown', (e) => { this.onKeyDown(e); });
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-10 20:21:46 +00:00
|
|
|
this.editorElem_ = document.createElement('ul');
|
|
|
|
|
this.container_.appendChild(this.editorElem_);
|
|
|
|
|
this.editor_ = new Editor(this.editorElem_);
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-10 22:43:29 +00:00
|
|
|
this.gridElem_ = document.createElement('div');
|
|
|
|
|
this.container_.appendChild(this.gridElem_);
|
|
|
|
|
this.grid_ = new Grid(this.gridElem_);
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-03 22:10:36 +00:00
|
|
|
this.generation_ = 0;
|
2019-07-03 22:23:41 +00:00
|
|
|
this.renderGeneration_ = -1;
|
2019-07-03 22:10:36 +00:00
|
|
|
this.drawGeneration_ = -1;
|
|
|
|
|
|
2019-07-03 22:23:41 +00:00
|
|
|
this.render_ = [];
|
2019-07-11 18:14:06 +00:00
|
|
|
for (let i = 0; i < (navigator.hardwareConcurrency || 2); ++i) {
|
2019-07-03 22:23:41 +00:00
|
|
|
let render = new Worker('render.js');
|
|
|
|
|
render.addEventListener('message', (e) => { this.onRender(e); });
|
|
|
|
|
this.render_.push(render);
|
|
|
|
|
}
|
2019-07-03 22:10:36 +00:00
|
|
|
|
2019-07-11 22:30:53 +00:00
|
|
|
addEventListener('hashchange', (e) => { this.onHashChange(e); });
|
2019-07-12 00:45:03 +00:00
|
|
|
addEventListener('popstate', (e) => { this.onPopState(e); });
|
|
|
|
|
this.first_ = true;
|
2019-07-11 22:30:53 +00:00
|
|
|
|
|
|
|
|
if (location.hash.length > 1) {
|
|
|
|
|
this.unserialize(JSON.parse(atob(location.hash.substring(1))));
|
|
|
|
|
} else {
|
|
|
|
|
this.unserialize(JSON.parse(localStorage.getItem('currentState')));
|
|
|
|
|
}
|
2019-07-10 21:32:52 +00:00
|
|
|
if (this.editor_.getEntries().length == 0) {
|
2019-07-13 01:54:08 +00:00
|
|
|
this.addDefaultEntries();
|
2019-07-10 21:32:52 +00:00
|
|
|
}
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-03 01:51:13 +00:00
|
|
|
this.observer_ = new MutationObserver(e => { this.onChange(e); });
|
2019-07-11 22:30:53 +00:00
|
|
|
this.observer2_ = new MutationObserver(e => { this.snapshot(e); });
|
|
|
|
|
this.observe();
|
|
|
|
|
|
|
|
|
|
this.saveAndRender();
|
|
|
|
|
|
2019-07-12 00:45:03 +00:00
|
|
|
history.replaceState('first', null, '#' + btoa(this.serializedStr_));
|
2019-07-11 22:30:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
observe() {
|
2019-07-10 20:21:46 +00:00
|
|
|
this.observer_.observe(this.editorElem_, {
|
2019-07-03 01:42:17 +00:00
|
|
|
attributes: true,
|
2019-07-11 04:44:33 +00:00
|
|
|
attributeFilter: ['data-arch-refresh'],
|
2019-07-03 01:51:13 +00:00
|
|
|
childList: true,
|
2019-07-03 01:42:17 +00:00
|
|
|
subtree: true,
|
|
|
|
|
});
|
2019-07-11 22:30:53 +00:00
|
|
|
this.observer2_.observe(this.editorElem_, {
|
|
|
|
|
attributes: true,
|
|
|
|
|
attributeFilter: ['data-arch-snapshot'],
|
|
|
|
|
childList: true,
|
|
|
|
|
subtree: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-07-03 01:42:17 +00:00
|
|
|
|
2019-07-11 22:30:53 +00:00
|
|
|
unobserve() {
|
|
|
|
|
this.observer_.disconnect();
|
|
|
|
|
this.observer2_.disconnect();
|
2019-07-03 01:42:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serialize() {
|
2019-07-12 01:05:04 +00:00
|
|
|
let selected = null;
|
|
|
|
|
let iter = document.activeElement;
|
|
|
|
|
while (iter) {
|
|
|
|
|
if (iter.xArchObj && iter.id) {
|
|
|
|
|
selected = iter.id;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
iter = iter.parentElement;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-03 01:42:17 +00:00
|
|
|
return {
|
|
|
|
|
version: 1,
|
2019-07-11 22:30:53 +00:00
|
|
|
generation: this.generation_,
|
|
|
|
|
nextId: idSource.peekId(),
|
2019-07-03 01:42:17 +00:00
|
|
|
editor: this.editor_.serialize(),
|
2019-07-12 01:05:04 +00:00
|
|
|
selected: selected,
|
2019-07-03 01:42:17 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unserialize(ser) {
|
|
|
|
|
if (!ser) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 22:30:53 +00:00
|
|
|
this.renderGeneration_ = -1;
|
|
|
|
|
this.drawGeneration_ = -1;
|
|
|
|
|
|
2019-07-03 01:42:17 +00:00
|
|
|
switch (ser.version) {
|
|
|
|
|
case 1:
|
2019-07-03 22:10:36 +00:00
|
|
|
this.generation_ = ser.generation;
|
2019-07-11 16:12:02 +00:00
|
|
|
idSource.setId(ser.nextId);
|
2019-07-03 01:42:17 +00:00
|
|
|
this.editor_.unserialize(ser.editor);
|
2019-07-12 01:05:04 +00:00
|
|
|
if (ser.selected) {
|
|
|
|
|
let elem = document.getElementById(ser.selected);
|
|
|
|
|
if (elem) {
|
|
|
|
|
elem.focus();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.editor_.selectNext();
|
|
|
|
|
}
|
2019-07-03 01:42:17 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
console.log('unrecognized localStorage.currentState version', ser);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 22:30:53 +00:00
|
|
|
overwrite(ser) {
|
|
|
|
|
this.unobserve();
|
|
|
|
|
this.editor_.clear();
|
|
|
|
|
this.unserialize(ser);
|
|
|
|
|
this.observe();
|
|
|
|
|
this.saveAndRender();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onHashChange() {
|
|
|
|
|
if (location.hash.length > 1) {
|
|
|
|
|
this.overwrite(JSON.parse(atob(location.hash.substring(1))));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-12 00:45:03 +00:00
|
|
|
onPopState(e) {
|
|
|
|
|
this.first_ = (e.state == 'first');
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 22:30:53 +00:00
|
|
|
onChange() {
|
|
|
|
|
++this.generation_;
|
|
|
|
|
this.saveAndRender();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snapshot() {
|
|
|
|
|
history.pushState(null, null, '#' + btoa(this.serializedStr_));
|
2019-07-12 00:45:03 +00:00
|
|
|
this.first_ = false;
|
2019-07-11 22:30:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveAndRender() {
|
2019-07-03 22:10:36 +00:00
|
|
|
this.serialized_ = this.serialize();
|
2019-07-03 22:23:41 +00:00
|
|
|
this.startRender();
|
2019-07-11 22:30:53 +00:00
|
|
|
this.serializedStr_ = JSON.stringify(this.serialized_);
|
|
|
|
|
localStorage.setItem('currentState', this.serializedStr_);
|
2019-07-03 22:10:36 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-13 01:54:08 +00:00
|
|
|
addDefaultEntries() {
|
|
|
|
|
this.editor_.addHelpAfter();
|
|
|
|
|
|
|
|
|
|
let node1 = this.editor_.addNodeAfter();
|
|
|
|
|
node1.setLabel('node1');
|
|
|
|
|
|
|
|
|
|
let node2 = this.editor_.addNodeAfter();
|
|
|
|
|
node2.setLabel('node2');
|
|
|
|
|
|
|
|
|
|
node1.setHighlight(true);
|
|
|
|
|
node2.setHighlight(true);
|
|
|
|
|
let link = this.editor_.addLinkAfter();
|
|
|
|
|
link.setLabel('link1');
|
|
|
|
|
node1.setHighlight(false);
|
|
|
|
|
node2.setHighlight(false);
|
|
|
|
|
|
|
|
|
|
let node3 = this.editor_.addNodeAfter();
|
|
|
|
|
node3.setLabel('node3');
|
|
|
|
|
|
|
|
|
|
node2.setHighlight(true);
|
|
|
|
|
node3.setHighlight(true);
|
|
|
|
|
let group = this.editor_.addGroupAfter();
|
|
|
|
|
group.setLabel('group1');
|
|
|
|
|
|
|
|
|
|
let label = this.editor_.addLabelAfter();
|
|
|
|
|
label.setLabel('Example');
|
2019-07-13 03:25:00 +00:00
|
|
|
|
|
|
|
|
node1.remove();
|
|
|
|
|
node2.remove();
|
|
|
|
|
node3.remove();
|
2019-07-13 01:54:08 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-12 03:37:48 +00:00
|
|
|
setTheme(theme) {
|
|
|
|
|
this.container_.classList.remove('theme-' + this.getTheme());
|
|
|
|
|
this.container_.classList.add('theme-' + theme);
|
|
|
|
|
localStorage.setItem('theme', theme);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTheme() {
|
|
|
|
|
for (let cls of this.container_.classList) {
|
|
|
|
|
if (cls.startsWith('theme-')) {
|
|
|
|
|
return cls.substring(6);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextTheme() {
|
|
|
|
|
let cur = this.themes_.indexOf(this.getTheme());
|
|
|
|
|
this.setTheme(this.themes_[(cur + 1) % this.themes_.length]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prevTheme() {
|
|
|
|
|
let cur = this.themes_.indexOf(this.getTheme());
|
|
|
|
|
let num = this.themes_.length;
|
|
|
|
|
let next = (((cur - 1) % num) + num) % num;
|
|
|
|
|
this.setTheme(this.themes_[next]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-10 20:21:46 +00:00
|
|
|
onKeyDown(e) {
|
2019-07-12 00:45:03 +00:00
|
|
|
switch (e.key) {
|
2019-07-12 03:37:48 +00:00
|
|
|
case 'm':
|
|
|
|
|
this.nextTheme();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case 'M':
|
|
|
|
|
this.prevTheme();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-12 00:45:03 +00:00
|
|
|
case 'u':
|
|
|
|
|
// Stop us from backing up out of the page
|
|
|
|
|
if (!this.first_) {
|
|
|
|
|
history.back();
|
|
|
|
|
}
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case 'U':
|
|
|
|
|
history.forward();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-10 20:21:46 +00:00
|
|
|
let elem = document.activeElement;
|
|
|
|
|
while (elem) {
|
|
|
|
|
if (elem == this.editorElem_) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
elem = elem.parentElement;
|
|
|
|
|
}
|
|
|
|
|
this.editor_.onKeyDown(e);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-03 22:10:36 +00:00
|
|
|
onRender(e) {
|
2019-07-03 22:23:41 +00:00
|
|
|
this.render_.push(e.target);
|
2019-07-03 22:10:36 +00:00
|
|
|
|
|
|
|
|
if (e.data.generation > this.drawGeneration_) {
|
2019-07-03 22:23:41 +00:00
|
|
|
// Received newer than we've drawn; redraw
|
|
|
|
|
this.drawGeneration_ = e.data.generation;
|
2019-07-10 22:43:29 +00:00
|
|
|
this.grid_.draw(e.data.steps);
|
2019-07-03 22:10:36 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-03 22:23:41 +00:00
|
|
|
this.startRender();
|
2019-07-03 22:10:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startRender() {
|
2019-07-03 22:23:41 +00:00
|
|
|
if (this.generation_ == this.renderGeneration_) {
|
|
|
|
|
// Already sent this generation for rendering
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let render = this.render_.pop();
|
|
|
|
|
if (!render) {
|
|
|
|
|
// Ran out of workers
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.renderGeneration_ = this.serialized_.generation;
|
|
|
|
|
render.postMessage(this.serialized_);
|
2019-07-03 01:42:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<!--# include file="Editor.js" -->
|
2019-07-10 22:18:00 +00:00
|
|
|
<!--# include file="Grid.js" -->
|
2019-07-11 05:12:08 +00:00
|
|
|
<!--# include file="IdSource.js" -->
|
2019-07-03 01:42:17 +00:00
|
|
|
|
|
|
|
|
<!--# include file="utils.js" -->
|
|
|
|
|
|
|
|
|
|
new Architype(document.getElementById('architype'));
|