From bc110e31b7baef8b2234cd9871211f5521818c02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sat, 16 Oct 2021 20:05:15 +0200
Subject: [PATCH 01/42] reorganization + build pipeline
---
.gitignore | 2 +
Makefile | 83 +-
my-mind.js | 10184 ++++++++----------
package.json | 6 +
src/action.js | 2 +-
src/{ => backend}/backend.file.js | 0
src/{ => backend}/backend.firebase.js | 0
src/{ => backend}/backend.gdrive.js | 0
src/{ => backend}/backend.image.js | 0
src/{ => backend}/backend.js | 0
src/{ => backend}/backend.local.js | 0
src/{ => backend}/backend.webdav.js | 0
src/{ => command}/command.edit.js | 0
src/{ => command}/command.js | 0
src/{ => command}/command.select.js | 0
src/{ => format}/format.freemind.js | 0
src/{ => format}/format.js | 0
src/{ => format}/format.json.js | 0
src/{ => format}/format.mma.js | 0
src/{ => format}/format.mup.js | 0
src/{ => format}/format.plaintext.js | 0
src/{ => layout}/layout.graph.js | 0
src/{ => layout}/layout.js | 0
src/{ => layout}/layout.map.js | 0
src/{ => layout}/layout.tree.js | 0
src/mm.js | 2 +-
src/{app.js => my-mind.js} | 85 +-
src/{ => shape}/shape.box.js | 0
src/{ => shape}/shape.ellipse.js | 0
src/{ => shape}/shape.js | 0
src/{ => shape}/shape.underline.js | 0
src/{ => ui/backend}/ui.backend.file.js | 0
src/{ => ui/backend}/ui.backend.firebase.js | 0
src/{ => ui/backend}/ui.backend.gdrive.js | 0
src/{ => ui/backend}/ui.backend.image.js | 0
src/{ => ui/backend}/ui.backend.js | 0
src/{ => ui/backend}/ui.backend.local.js | 0
src/{ => ui/backend}/ui.backend.webdav.js | 0
src/{ => ui}/ui.color.js | 0
src/{ => ui}/ui.help.js | 0
src/{ => ui}/ui.icon.js | 0
src/{ => ui}/ui.io.js | 0
src/{ => ui}/ui.js | 0
src/{ => ui}/ui.layout.js | 0
src/{ => ui}/ui.notes.js | 0
src/{ => ui}/ui.shape.js | 0
src/{ => ui}/ui.status.js | 0
src/{ => ui}/ui.value.js | 0
tsconfig.json | 10 +
49 files changed, 4892 insertions(+), 5482 deletions(-)
create mode 100644 .gitignore
create mode 100644 package.json
rename src/{ => backend}/backend.file.js (100%)
rename src/{ => backend}/backend.firebase.js (100%)
rename src/{ => backend}/backend.gdrive.js (100%)
rename src/{ => backend}/backend.image.js (100%)
rename src/{ => backend}/backend.js (100%)
rename src/{ => backend}/backend.local.js (100%)
rename src/{ => backend}/backend.webdav.js (100%)
rename src/{ => command}/command.edit.js (100%)
rename src/{ => command}/command.js (100%)
rename src/{ => command}/command.select.js (100%)
rename src/{ => format}/format.freemind.js (100%)
rename src/{ => format}/format.js (100%)
rename src/{ => format}/format.json.js (100%)
rename src/{ => format}/format.mma.js (100%)
rename src/{ => format}/format.mup.js (100%)
rename src/{ => format}/format.plaintext.js (100%)
rename src/{ => layout}/layout.graph.js (100%)
rename src/{ => layout}/layout.js (100%)
rename src/{ => layout}/layout.map.js (100%)
rename src/{ => layout}/layout.tree.js (100%)
rename src/{app.js => my-mind.js} (71%)
rename src/{ => shape}/shape.box.js (100%)
rename src/{ => shape}/shape.ellipse.js (100%)
rename src/{ => shape}/shape.js (100%)
rename src/{ => shape}/shape.underline.js (100%)
rename src/{ => ui/backend}/ui.backend.file.js (100%)
rename src/{ => ui/backend}/ui.backend.firebase.js (100%)
rename src/{ => ui/backend}/ui.backend.gdrive.js (100%)
rename src/{ => ui/backend}/ui.backend.image.js (100%)
rename src/{ => ui/backend}/ui.backend.js (100%)
rename src/{ => ui/backend}/ui.backend.local.js (100%)
rename src/{ => ui/backend}/ui.backend.webdav.js (100%)
rename src/{ => ui}/ui.color.js (100%)
rename src/{ => ui}/ui.help.js (100%)
rename src/{ => ui}/ui.icon.js (100%)
rename src/{ => ui}/ui.io.js (100%)
rename src/{ => ui}/ui.js (100%)
rename src/{ => ui}/ui.layout.js (100%)
rename src/{ => ui}/ui.notes.js (100%)
rename src/{ => ui}/ui.shape.js (100%)
rename src/{ => ui}/ui.status.js (100%)
rename src/{ => ui}/ui.value.js (100%)
create mode 100644 tsconfig.json
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..d9ea5e3c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.js
diff --git a/Makefile b/Makefile
index 4e5e27ef..df5a8bc1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,70 +1,25 @@
-SOURCES = src/mm.js \
- src/promise.js \
- src/promise-addons.js \
- src/repo.js \
- src/item.js \
- src/map.js \
- src/keyboard.js \
- src/tip.js \
- src/action.js \
- src/clipboard.js \
- src/menu.js \
- src/command.js \
- src/command.edit.js \
- src/command.select.js \
- src/layout.js \
- src/layout.graph.js \
- src/layout.tree.js \
- src/layout.map.js \
- src/shape.js \
- src/shape.underline.js \
- src/shape.box.js \
- src/shape.ellipse.js \
- src/format.js \
- src/format.json.js \
- src/format.freemind.js \
- src/format.mma.js \
- src/format.mup.js \
- src/format.plaintext.js \
- src/backend.js \
- src/backend.local.js \
- src/backend.webdav.js \
- src/backend.image.js \
- src/backend.file.js \
- src/backend.firebase.js \
- src/backend.gdrive.js \
- src/ui.js \
- src/ui.layout.js \
- src/ui.shape.js \
- src/ui.value.js \
- src/ui.status.js \
- src/ui.color.js \
- src/ui.icon.js \
- src/ui.help.js \
- src/ui.notes.js \
- src/ui.io.js \
- src/ui.backend.js \
- src/ui.backend.file.js \
- src/ui.backend.webdav.js \
- src/ui.backend.image.js \
- src/ui.backend.local.js \
- src/ui.backend.firebase.js \
- src/ui.backend.gdrive.js \
- src/mouse.js \
- src/app.js
+MAKEOPTS = "-r"
-.PHONY: all push clean
+BIN := $(shell npm bin)
+TSC := $(BIN)/tsc
+LESSC := $(BIN)/lessc
+ESBUILD := $(BIN)/esbuild
+GCC := $(BIN)/google-closure-compiler
-all: my-mind.js
+JS := .js
+FLAG := $(JS)/.tsflag
+APP := my-mind.js
-my-mind.js: $(SOURCES)
- @echo "/* My Mind web app: all source files combined. */" > $@
- @cat $^ >> $@
+all: $(APP)
-push:
- @hg bookmark -f master
- @hg push ; true
- @hg push github ; true
+$(APP): $(FLAG)
+ $(ESBUILD) --bundle $(JS)/$(APP) > $@
+
+$(FLAG): $(shell find src -type f)
+ $(TSC)
+ touch $@
clean:
- @rm my-mind.js
+ rm -rf $(JS) $(APP)
+
+.PHONY: all clean
diff --git a/my-mind.js b/my-mind.js
index fbfe917c..60bc4e7f 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -1,5412 +1,4794 @@
-/* My Mind web app: all source files combined. */
-if (!Function.prototype.bind) {
- Function.prototype.bind = function(thisObj) {
- var fn = this;
- var args = Array.prototype.slice.call(arguments, 1);
- return function() {
- return fn.apply(thisObj, args.concat(Array.prototype.slice.call(arguments)));
- }
- }
-};
-
-var MM = {
- _subscribers: {},
-
- publish: function(message, publisher, data) {
- var subscribers = this._subscribers[message] || [];
- subscribers.forEach(function(subscriber) {
- subscriber.handleMessage(message, publisher, data);
- });
- },
-
- subscribe: function(message, subscriber) {
- if (!(message in this._subscribers)) {
- this._subscribers[message] = [];
- }
- var index = this._subscribers[message].indexOf(subscriber);
- if (index == -1) { this._subscribers[message].push(subscriber); }
- },
-
- unsubscribe: function(message, subscriber) {
- var index = this._subscribers[message].indexOf(subscriber);
- if (index > -1) { this._subscribers[message].splice(index, 1); }
- },
-
- generateId: function() {
- var str = "";
- for (var i=0;i<8;i++) {
- var code = Math.floor(Math.random()*26);
- str += String.fromCharCode("a".charCodeAt(0) + code);
- }
- return str;
- },
-
- isMac: function() {
- return !!navigator.platform.match(/mac/i);
- }
-};
-/*
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-*/
-
-/**
- * @class A promise - value to be resolved in the future.
- * Implements the "Promises/A+" specification.
- */
-var Promise = function(executor) {
- this._state = 0; /* 0 = pending, 1 = fulfilled, 2 = rejected */
- this._value = null; /* fulfillment / rejection value */
-
- this._cb = {
- fulfilled: [],
- rejected: []
- }
-
- this._thenPromises = []; /* promises returned by then() */
-
- executor && executor(this.fulfill.bind(this), this.reject.bind(this));
-}
-
-Promise.resolve = function(value) {
- return new Promise().fulfill(value);
-}
-
-Promise.reject = function(value) {
- return new Promise().reject(value);
-}
-
-/**
- * @param {function} onFulfilled To be called once this promise gets fulfilled
- * @param {function} onRejected To be called once this promise gets rejected
- * @returns {Promise}
- */
-Promise.prototype.then = function(onFulfilled, onRejected) {
- this._cb.fulfilled.push(onFulfilled);
- this._cb.rejected.push(onRejected);
-
- var thenPromise = new Promise();
-
- this._thenPromises.push(thenPromise);
-
- if (this._state > 0) {
- setTimeout(this._processQueue.bind(this), 0);
- }
-
- /* 3.2.6. then must return a promise. */
- return thenPromise;
-}
-
-/**
- * Fulfill this promise with a given value
- * @param {any} value
- */
-Promise.prototype.fulfill = function(value) {
- if (this._state != 0) { return this; }
-
- this._state = 1;
- this._value = value;
-
- this._processQueue();
-
- return this;
-}
-
-/**
- * Reject this promise with a given value
- * @param {any} value
- */
-Promise.prototype.reject = function(value) {
- if (this._state != 0) { return this; }
-
- this._state = 2;
- this._value = value;
-
- this._processQueue();
-
- return this;
-}
-
-/**
- * Pass this promise's resolved value to another promise
- * @param {Promise} promise
- */
-Promise.prototype.chain = function(promise) {
- return this.then(promise.fulfill.bind(promise), promise.reject.bind(promise));
-}
-
-/**
- * @param {function} onRejected To be called once this promise gets rejected
- * @returns {Promise}
- */
-Promise.prototype["catch"] = function(onRejected) {
- return this.then(null, onRejected);
-}
-
-Promise.prototype._processQueue = function() {
- while (this._thenPromises.length) {
- var onFulfilled = this._cb.fulfilled.shift();
- var onRejected = this._cb.rejected.shift();
- this._executeCallback(this._state == 1 ? onFulfilled : onRejected);
- }
-}
-
-Promise.prototype._executeCallback = function(cb) {
- var thenPromise = this._thenPromises.shift();
-
- if (typeof(cb) != "function") {
- if (this._state == 1) {
- /* 3.2.6.4. If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value. */
- thenPromise.fulfill(this._value);
- } else {
- /* 3.2.6.5. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason. */
- thenPromise.reject(this._value);
- }
- return;
- }
-
- try {
- var returned = cb(this._value);
-
- if (returned && typeof(returned.then) == "function") {
- /* 3.2.6.3. If either onFulfilled or onRejected returns a promise (call it returnedPromise), promise2 must assume the state of returnedPromise */
- var fulfillThenPromise = function(value) { thenPromise.fulfill(value); }
- var rejectThenPromise = function(value) { thenPromise.reject(value); }
- returned.then(fulfillThenPromise, rejectThenPromise);
- } else {
- /* 3.2.6.1. If either onFulfilled or onRejected returns a value that is not a promise, promise2 must be fulfilled with that value. */
- thenPromise.fulfill(returned);
- }
-
- } catch (e) {
-
- /* 3.2.6.2. If either onFulfilled or onRejected throws an exception, promise2 must be rejected with the thrown exception as the reason. */
- thenPromise.reject(e);
-
- }
-}
-/**
- * Wait for all these promises to complete. One failed => this fails too.
- */
-Promise.all = Promise.when = function(all) {
- var promise = new this();
- var counter = 0;
- var results = [];
-
- for (var i=0;i\(\)\[\]'"])?($|\b)/i;
-
-MM.Item.fromJSON = function(data) {
- return new this().fromJSON(data);
-}
-
-MM.Item.prototype.toJSON = function() {
- var data = {
- id: this._id,
- text: this.getText(),
- notes: this.getNotes()
- }
-
-
- if (this._side) { data.side = this._side; }
- if (this._color) { data.color = this._color; }
- if (this._icon) { data.icon = this._icon; }
- if (this._value) { data.value = this._value; }
- if (this._status) { data.status = this._status; }
- if (this._layout) { data.layout = this._layout.id; }
- if (!this._autoShape) { data.shape = this._shape.id; }
- if (this._collapsed) { data.collapsed = 1; }
- if (this._children.length) {
- data.children = this._children.map(function(child) { return child.toJSON(); });
- }
-
- return data;
-}
-
-/**
- * Only when creating a new item. To merge existing items, use .mergeWith().
- */
-MM.Item.prototype.fromJSON = function(data) {
- this.setText(data.text);
- if (data.notes) {
- this.setNotes(data.notes);
- }
- if (data.id) { this._id = data.id; }
- if (data.side) { this._side = data.side; }
- if (data.color) { this._color = data.color; }
- if (data.icon) { this._icon = data.icon; }
- if (data.value) { this._value = data.value; }
- if (data.status) {
- this._status = data.status;
- if (this._status == "maybe") { this._status = "computed"; }
- }
- if (data.collapsed) { this.collapse(); }
- if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
- if (data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
-
- (data.children || []).forEach(function(child) {
- this.insertChild(MM.Item.fromJSON(child));
- }, this);
-
- return this;
-}
-
-MM.Item.prototype.mergeWith = function(data) {
- var dirty = 0;
-
- if (this.getText() != data.text && !this._dom.text.contentEditable) { this.setText(data.text); }
-
- if (this._side != data.side) {
- this._side = data.side;
- dirty = 1;
- }
-
- if (this._color != data.color) {
- this._color = data.color;
- dirty = 2;
- }
-
- if (this._icon != data.icon) {
- this._icon = data.icon;
- dirty = 1;
- }
-
- if (this._value != data.value) {
- this._value = data.value;
- dirty = 1;
- }
-
- if (this._status != data.status) {
- this._status = data.status;
- dirty = 1;
- }
-
- if (this._collapsed != !!data.collapsed) { this[this._collapsed ? "expand" : "collapse"](); }
-
- if (this.getOwnLayout() != data.layout) {
- this._layout = MM.Layout.getById(data.layout);
- dirty = 2;
- }
-
- var s = (this._autoShape ? null : this._shape.id);
- if (s != data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
-
- (data.children || []).forEach(function(child, index) {
- if (index >= this._children.length) { /* new child */
- this.insertChild(MM.Item.fromJSON(child));
- } else { /* existing child */
- var myChild = this._children[index];
- if (myChild.getId() == child.id) { /* recursive merge */
- myChild.mergeWith(child);
- } else { /* changed; replace */
- this.removeChild(this._children[index]);
- this.insertChild(MM.Item.fromJSON(child), index);
- }
- }
- }, this);
-
- /* remove dead children */
- var newLength = (data.children || []).length;
- while (this._children.length > newLength) { this.removeChild(this._children[this._children.length-1]); }
-
- if (dirty == 1) { this.update(); }
- if (dirty == 2) { this.updateSubtree(); }
-}
-
-MM.Item.prototype.clone = function() {
- var data = this.toJSON();
-
- var removeId = function(obj) {
- delete obj.id;
- obj.children && obj.children.forEach(removeId);
- }
- removeId(data);
-
- return this.constructor.fromJSON(data);
-}
-
-MM.Item.prototype.select = function() {
- this._dom.node.classList.add("current");
- if (window.editor) {
- if (this._notes) {
- window.editor.setContent(this._notes);
+(() => {
+ // .js/mm.js
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function(thisObj) {
+ var fn = this;
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ return fn.apply(thisObj, args.concat(Array.prototype.slice.call(arguments)));
+ };
+ };
+ }
+ window.MM = {
+ _subscribers: {},
+ publish: function(message, publisher, data) {
+ var subscribers = this._subscribers[message] || [];
+ subscribers.forEach(function(subscriber) {
+ subscriber.handleMessage(message, publisher, data);
+ });
+ },
+ subscribe: function(message, subscriber) {
+ if (!(message in this._subscribers)) {
+ this._subscribers[message] = [];
+ }
+ var index = this._subscribers[message].indexOf(subscriber);
+ if (index == -1) {
+ this._subscribers[message].push(subscriber);
+ }
+ },
+ unsubscribe: function(message, subscriber) {
+ var index = this._subscribers[message].indexOf(subscriber);
+ if (index > -1) {
+ this._subscribers[message].splice(index, 1);
+ }
+ },
+ generateId: function() {
+ var str = "";
+ for (var i = 0; i < 8; i++) {
+ var code = Math.floor(Math.random() * 26);
+ str += String.fromCharCode("a".charCodeAt(0) + code);
+ }
+ return str;
+ },
+ isMac: function() {
+ return !!navigator.platform.match(/mac/i);
+ }
+ };
+
+ // .js/promise.js
+ var Promise2 = function(executor) {
+ this._state = 0;
+ this._value = null;
+ this._cb = {
+ fulfilled: [],
+ rejected: []
+ };
+ this._thenPromises = [];
+ executor && executor(this.fulfill.bind(this), this.reject.bind(this));
+ };
+ Promise2.resolve = function(value) {
+ return new Promise2().fulfill(value);
+ };
+ Promise2.reject = function(value) {
+ return new Promise2().reject(value);
+ };
+ Promise2.prototype.then = function(onFulfilled, onRejected) {
+ this._cb.fulfilled.push(onFulfilled);
+ this._cb.rejected.push(onRejected);
+ var thenPromise = new Promise2();
+ this._thenPromises.push(thenPromise);
+ if (this._state > 0) {
+ setTimeout(this._processQueue.bind(this), 0);
+ }
+ return thenPromise;
+ };
+ Promise2.prototype.fulfill = function(value) {
+ if (this._state != 0) {
+ return this;
+ }
+ this._state = 1;
+ this._value = value;
+ this._processQueue();
+ return this;
+ };
+ Promise2.prototype.reject = function(value) {
+ if (this._state != 0) {
+ return this;
+ }
+ this._state = 2;
+ this._value = value;
+ this._processQueue();
+ return this;
+ };
+ Promise2.prototype.chain = function(promise) {
+ return this.then(promise.fulfill.bind(promise), promise.reject.bind(promise));
+ };
+ Promise2.prototype["catch"] = function(onRejected) {
+ return this.then(null, onRejected);
+ };
+ Promise2.prototype._processQueue = function() {
+ while (this._thenPromises.length) {
+ var onFulfilled = this._cb.fulfilled.shift();
+ var onRejected = this._cb.rejected.shift();
+ this._executeCallback(this._state == 1 ? onFulfilled : onRejected);
+ }
+ };
+ Promise2.prototype._executeCallback = function(cb) {
+ var thenPromise = this._thenPromises.shift();
+ if (typeof cb != "function") {
+ if (this._state == 1) {
+ thenPromise.fulfill(this._value);
+ } else {
+ thenPromise.reject(this._value);
+ }
+ return;
+ }
+ try {
+ var returned = cb(this._value);
+ if (returned && typeof returned.then == "function") {
+ var fulfillThenPromise = function(value) {
+ thenPromise.fulfill(value);
+ };
+ var rejectThenPromise = function(value) {
+ thenPromise.reject(value);
+ };
+ returned.then(fulfillThenPromise, rejectThenPromise);
+ } else {
+ thenPromise.fulfill(returned);
+ }
+ } catch (e) {
+ thenPromise.reject(e);
+ }
+ };
+
+ // .js/promise-addons.js
+ Promise.all = Promise.when = function(all) {
+ var promise = new this();
+ var counter = 0;
+ var results = [];
+ for (var i = 0; i < all.length; i++) {
+ counter++;
+ all[i].then(function(index, result) {
+ results[index] = result;
+ counter--;
+ if (!counter) {
+ promise.fulfill(results);
+ }
+ }.bind(null, i), function(reason) {
+ counter = 1 / 0;
+ promise.reject(reason);
+ });
+ }
+ return promise;
+ };
+ Promise.setTimeout = function(ms) {
+ var promise = new this();
+ setTimeout(function() {
+ promise.fulfill();
+ }, ms);
+ return promise;
+ };
+ Promise.event = function(element, event, capture) {
+ var promise = new this();
+ var cb = function(e) {
+ element.removeEventListener(event, cb, capture);
+ promise.fulfill(e);
+ };
+ element.addEventListener(event, cb, capture);
+ return promise;
+ };
+ Promise.transition = function(element) {
+ if ("transition" in element.style) {
+ return this.event(element, "transitionend", false);
+ } else if ("webkitTransition" in element.style) {
+ return this.event(element, "webkitTransitionEnd", false);
+ } else {
+ return new this().fulfill();
+ }
+ };
+ Promise.send = function(xhr, data) {
+ var promise = new this();
+ xhr.addEventListener("readystatechange", function(e) {
+ if (e.target.readyState != 4) {
+ return;
+ }
+ if (e.target.status.toString().charAt(0) == "2") {
+ promise.fulfill(e.target);
+ } else {
+ promise.reject(e.target);
+ }
+ });
+ xhr.send(data);
+ return promise;
+ };
+ Promise.worker = function(url, message) {
+ var promise = new this();
+ var worker = new Worker(url);
+ Promise.event(worker, "message").then(function(e) {
+ promise.fulfill(e.data);
+ });
+ Promise.event(worker, "error").then(function(e) {
+ promise.reject(e.message);
+ });
+ worker.postMessage(message);
+ return promise;
+ };
+
+ // .js/repo.js
+ MM.Repo = {
+ id: "",
+ label: "",
+ getAll: function() {
+ var all = [];
+ for (var p in this) {
+ var val = this[p];
+ if (this.isPrototypeOf(val)) {
+ all.push(val);
+ }
+ }
+ return all;
+ },
+ getByProperty: function(property, value) {
+ return this.getAll().filter(function(item) {
+ return item[property] == value;
+ })[0] || null;
+ },
+ getById: function(id) {
+ return this.getByProperty("id", id);
+ },
+ buildOption: function() {
+ var o = document.createElement("option");
+ o.value = this.id;
+ o.innerHTML = this.label;
+ return o;
+ }
+ };
+
+ // .js/item.js
+ MM.Item = function() {
+ this._parent = null;
+ this._children = [];
+ this._collapsed = false;
+ this._layout = null;
+ this._shape = null;
+ this._autoShape = true;
+ this._color = null;
+ this._value = null;
+ this._status = null;
+ this._side = null;
+ this._icon = null;
+ this._notes = null;
+ this._id = MM.generateId();
+ this._oldText = "";
+ this._computed = {
+ value: 0,
+ status: null
+ };
+ this._dom = {
+ node: document.createElement("li"),
+ content: document.createElement("div"),
+ notes: document.createElement("div"),
+ status: document.createElement("span"),
+ icon: document.createElement("span"),
+ value: document.createElement("span"),
+ text: document.createElement("div"),
+ children: document.createElement("ul"),
+ toggle: document.createElement("div"),
+ canvas: document.createElement("canvas")
+ };
+ this._dom.node.classList.add("item");
+ this._dom.content.classList.add("content");
+ this._dom.notes.classList.add("notes-indicator");
+ this._dom.status.classList.add("status");
+ this._dom.icon.classList.add("icon");
+ this._dom.value.classList.add("value");
+ this._dom.text.classList.add("text");
+ this._dom.toggle.classList.add("toggle");
+ this._dom.children.classList.add("children");
+ this._dom.content.appendChild(this._dom.text);
+ this._dom.node.appendChild(this._dom.canvas);
+ this._dom.node.appendChild(this._dom.content);
+ this._dom.content.appendChild(this._dom.notes);
+ this._dom.toggle.addEventListener("click", this);
+ };
+ MM.Item.COLOR = "#999";
+ MM.Item.RE = /\b(([a-z][\w-]+:\/\/\w)|(([\w-]+\.){2,}[a-z][\w-]+)|([\w-]+\.[a-z][\w-]+\/))[^\s]*([^\s,.;:?!<>\(\)\[\]'"])?($|\b)/i;
+ MM.Item.fromJSON = function(data) {
+ return new this().fromJSON(data);
+ };
+ MM.Item.prototype.toJSON = function() {
+ var data = {
+ id: this._id,
+ text: this.getText(),
+ notes: this.getNotes()
+ };
+ if (this._side) {
+ data.side = this._side;
+ }
+ if (this._color) {
+ data.color = this._color;
+ }
+ if (this._icon) {
+ data.icon = this._icon;
+ }
+ if (this._value) {
+ data.value = this._value;
+ }
+ if (this._status) {
+ data.status = this._status;
+ }
+ if (this._layout) {
+ data.layout = this._layout.id;
+ }
+ if (!this._autoShape) {
+ data.shape = this._shape.id;
+ }
+ if (this._collapsed) {
+ data.collapsed = 1;
+ }
+ if (this._children.length) {
+ data.children = this._children.map(function(child) {
+ return child.toJSON();
+ });
+ }
+ return data;
+ };
+ MM.Item.prototype.fromJSON = function(data) {
+ this.setText(data.text);
+ if (data.notes) {
+ this.setNotes(data.notes);
+ }
+ if (data.id) {
+ this._id = data.id;
+ }
+ if (data.side) {
+ this._side = data.side;
+ }
+ if (data.color) {
+ this._color = data.color;
+ }
+ if (data.icon) {
+ this._icon = data.icon;
+ }
+ if (data.value) {
+ this._value = data.value;
+ }
+ if (data.status) {
+ this._status = data.status;
+ if (this._status == "maybe") {
+ this._status = "computed";
+ }
+ }
+ if (data.collapsed) {
+ this.collapse();
+ }
+ if (data.layout) {
+ this._layout = MM.Layout.getById(data.layout);
+ }
+ if (data.shape) {
+ this.setShape(MM.Shape.getById(data.shape));
+ }
+ (data.children || []).forEach(function(child) {
+ this.insertChild(MM.Item.fromJSON(child));
+ }, this);
+ return this;
+ };
+ MM.Item.prototype.mergeWith = function(data) {
+ var dirty = 0;
+ if (this.getText() != data.text && !this._dom.text.contentEditable) {
+ this.setText(data.text);
+ }
+ if (this._side != data.side) {
+ this._side = data.side;
+ dirty = 1;
+ }
+ if (this._color != data.color) {
+ this._color = data.color;
+ dirty = 2;
+ }
+ if (this._icon != data.icon) {
+ this._icon = data.icon;
+ dirty = 1;
+ }
+ if (this._value != data.value) {
+ this._value = data.value;
+ dirty = 1;
+ }
+ if (this._status != data.status) {
+ this._status = data.status;
+ dirty = 1;
+ }
+ if (this._collapsed != !!data.collapsed) {
+ this[this._collapsed ? "expand" : "collapse"]();
+ }
+ if (this.getOwnLayout() != data.layout) {
+ this._layout = MM.Layout.getById(data.layout);
+ dirty = 2;
+ }
+ var s = this._autoShape ? null : this._shape.id;
+ if (s != data.shape) {
+ this.setShape(MM.Shape.getById(data.shape));
+ }
+ (data.children || []).forEach(function(child, index) {
+ if (index >= this._children.length) {
+ this.insertChild(MM.Item.fromJSON(child));
+ } else {
+ var myChild = this._children[index];
+ if (myChild.getId() == child.id) {
+ myChild.mergeWith(child);
} else {
- window.editor.setContent('');
+ this.removeChild(this._children[index]);
+ this.insertChild(MM.Item.fromJSON(child), index);
}
- }
- this.getMap().ensureItemVisibility(this);
- MM.Clipboard.focus(); /* going to mode 2c */
- MM.publish("item-select", this);
-}
-
-MM.Item.prototype.deselect = function() {
- /* we were in 2b; finish that via 3b */
- if (MM.App.editing) { MM.Command.Finish.execute(); }
- this._dom.node.classList.remove("current");
-}
-
-MM.Item.prototype.update = function(doNotRecurse) {
- var map = this.getMap();
- if (!map || !map.isVisible()) { return this; }
-
- MM.publish("item-change", this);
-
- if (this._autoShape) { /* check for changed auto-shape */
- var autoShape = this._getAutoShape();
- if (autoShape != this._shape) {
- if (this._shape) { this._shape.unset(this); }
- this._shape = autoShape;
- this._shape.set(this);
- }
- }
-
- this._updateStatus();
- this._updateIcon();
+ }
+ }, this);
+ var newLength = (data.children || []).length;
+ while (this._children.length > newLength) {
+ this.removeChild(this._children[this._children.length - 1]);
+ }
+ if (dirty == 1) {
+ this.update();
+ }
+ if (dirty == 2) {
+ this.updateSubtree();
+ }
+ };
+ MM.Item.prototype.clone = function() {
+ var data = this.toJSON();
+ var removeId = function(obj) {
+ delete obj.id;
+ obj.children && obj.children.forEach(removeId);
+ };
+ removeId(data);
+ return this.constructor.fromJSON(data);
+ };
+ MM.Item.prototype.select = function() {
+ this._dom.node.classList.add("current");
+ if (window.editor) {
+ if (this._notes) {
+ window.editor.setContent(this._notes);
+ } else {
+ window.editor.setContent("");
+ }
+ }
+ this.getMap().ensureItemVisibility(this);
+ MM.Clipboard.focus();
+ MM.publish("item-select", this);
+ };
+ MM.Item.prototype.deselect = function() {
+ if (MM.App.editing) {
+ MM.Command.Finish.execute();
+ }
+ this._dom.node.classList.remove("current");
+ };
+ MM.Item.prototype.update = function(doNotRecurse) {
+ var map = this.getMap();
+ if (!map || !map.isVisible()) {
+ return this;
+ }
+ MM.publish("item-change", this);
+ if (this._autoShape) {
+ var autoShape = this._getAutoShape();
+ if (autoShape != this._shape) {
+ if (this._shape) {
+ this._shape.unset(this);
+ }
+ this._shape = autoShape;
+ this._shape.set(this);
+ }
+ }
+ this._updateStatus();
+ this._updateIcon();
this._updateNotesIndicator();
- this._updateValue();
-
- this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
-
- this.getLayout().update(this);
- this.getShape().update(this);
- if (!this.isRoot() && !doNotRecurse) { this._parent.update(); }
-
- return this;
-}
-
-MM.Item.prototype.updateSubtree = function(isSubChild) {
- this._children.forEach(function(child) {
- child.updateSubtree(true);
- });
- return this.update(isSubChild);
-}
-
-MM.Item.prototype.setText = function(text) {
- this._dom.text.innerHTML = text;
- this._findLinks(this._dom.text);
- return this.update();
-}
-
-MM.Item.prototype.setNotes = function(notes) {
- this._notes = notes;
- return this.update();
-}
-
-MM.Item.prototype.getId = function() {
- return this._id;
-}
-
-MM.Item.prototype.getText = function() {
- return this._dom.text.innerHTML;
-}
-
-MM.Item.prototype.getNotes = function() {
- return this._notes;
-}
-
-MM.Item.prototype.collapse = function() {
- if (this._collapsed) { return; }
- this._collapsed = true;
- return this.update();
-}
-
-MM.Item.prototype.expand = function() {
- if (!this._collapsed) { return; }
- this._collapsed = false;
- this.update();
- return this.updateSubtree();
-}
-
-MM.Item.prototype.isCollapsed = function() {
- return this._collapsed;
-}
-
-MM.Item.prototype.setValue = function(value) {
- this._value = value;
- return this.update();
-}
-
-MM.Item.prototype.getValue = function() {
- return this._value;
-}
-
-MM.Item.prototype.getComputedValue = function() {
- return this._computed.value;
-}
-
-MM.Item.prototype.setStatus = function(status) {
- this._status = status;
- return this.update();
-}
-
-MM.Item.prototype.getStatus = function() {
- return this._status;
-}
-
-MM.Item.prototype.setIcon = function(icon) {
- this._icon = icon;
- return this.update();
-}
-
-MM.Item.prototype.getIcon = function() {
- return this._icon;
-}
-
-MM.Item.prototype.getComputedStatus = function() {
- return this._computed.status;
-}
-
-MM.Item.prototype.setSide = function(side) {
- this._side = side;
- return this;
-}
-
-MM.Item.prototype.getSide = function() {
- return this._side;
-}
-
-MM.Item.prototype.getChildren = function() {
- return this._children;
-}
-
-MM.Item.prototype.setColor = function(color) {
- this._color = color;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.getColor = function() {
- return this._color || (this.isRoot() ? MM.Item.COLOR : this._parent.getColor());
-}
-
-MM.Item.prototype.getOwnColor = function() {
- return this._color;
-}
-
-MM.Item.prototype.getLayout = function() {
- return this._layout || this._parent.getLayout();
-}
-
-MM.Item.prototype.getOwnLayout = function() {
- return this._layout;
-}
-
-MM.Item.prototype.setLayout = function(layout) {
- this._layout = layout;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.getShape = function() {
- return this._shape;
-}
-
-MM.Item.prototype.getOwnShape = function() {
- return (this._autoShape ? null : this._shape);
-}
-
-MM.Item.prototype.setShape = function(shape) {
- if (this._shape) { this._shape.unset(this); }
-
- if (shape) {
- this._autoShape = false;
- this._shape = shape;
- } else {
- this._autoShape = true;
- this._shape = this._getAutoShape();
- }
-
- this._shape.set(this);
- return this.update();
-}
-
-MM.Item.prototype.getDOM = function() {
- return this._dom;
-}
-
-MM.Item.prototype.getMap = function() {
- var item = this._parent;
- while (item) {
- if (item instanceof MM.Map) { return item; }
- item = item.getParent();
- }
- return null;
-}
-
-MM.Item.prototype.getParent = function() {
- return this._parent;
-}
-
-MM.Item.prototype.isRoot = function() {
- return (this._parent instanceof MM.Map);
-}
-
-MM.Item.prototype.setParent = function(parent) {
- this._parent = parent;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.insertChild = function(child, index) {
- /* Create or remove child as necessary. This must be done before computing the index (inserting own child) */
- var newChild = false;
- if (!child) {
- child = new MM.Item();
- newChild = true;
- } else if (child.getParent() && child.getParent().removeChild) { /* only when the child has non-map parent */
- child.getParent().removeChild(child);
- }
-
- if (!this._children.length) {
- this._dom.node.appendChild(this._dom.toggle);
- this._dom.node.appendChild(this._dom.children);
- }
-
- if (arguments.length < 2) { index = this._children.length; }
-
- var next = null;
- if (index < this._children.length) { next = this._children[index].getDOM().node; }
- this._dom.children.insertBefore(child.getDOM().node, next);
- this._children.splice(index, 0, child);
-
- return child.setParent(this);
-}
-
-MM.Item.prototype.removeChild = function(child) {
- var index = this._children.indexOf(child);
- this._children.splice(index, 1);
- var node = child.getDOM().node;
- node.parentNode.removeChild(node);
-
- child.setParent(null);
-
- if (!this._children.length) {
- this._dom.toggle.parentNode.removeChild(this._dom.toggle);
- this._dom.children.parentNode.removeChild(this._dom.children);
- }
-
- return this.update();
-}
-
-MM.Item.prototype.startEditing = function() {
- this._oldText = this.getText();
- this._dom.text.contentEditable = true;
- this._dom.text.focus(); /* switch to 2b */
- document.execCommand("styleWithCSS", null, false);
-
- this._dom.text.addEventListener("input", this);
- this._dom.text.addEventListener("keydown", this);
- this._dom.text.addEventListener("blur", this);
- return this;
-}
-
-MM.Item.prototype.stopEditing = function() {
- this._dom.text.removeEventListener("input", this);
- this._dom.text.removeEventListener("keydown", this);
- this._dom.text.removeEventListener("blur", this);
-
- this._dom.text.blur();
- this._dom.text.contentEditable = false;
- var result = this._dom.text.innerHTML;
- this._dom.text.innerHTML = this._oldText;
- this._oldText = "";
-
- this.update(); /* text changed */
-
- MM.Clipboard.focus();
-
- return result;
-}
-
-MM.Item.prototype.handleEvent = function(e) {
- switch (e.type) {
- case "input":
- this.update();
- this.getMap().ensureItemVisibility(this);
- break;
-
- case "keydown":
- if (e.keyCode == 9) { e.preventDefault(); } /* TAB has a special meaning in this app, do not use it to change focus */
- break;
-
- case "blur": /* 3d */
- MM.Command.Finish.execute();
- break;
-
- case "click":
- if (this._collapsed) { this.expand(); } else { this.collapse(); }
- MM.App.select(this);
- break;
- }
-}
-
-MM.Item.prototype._getAutoShape = function() {
- var depth = 0;
- var node = this;
- while (!node.isRoot()) {
- depth++;
- node = node.getParent();
- }
- switch (depth) {
- case 0: return MM.Shape.Ellipse;
- case 1: return MM.Shape.Box;
- default: return MM.Shape.Underline;
- }
-}
-
-MM.Item.prototype._updateStatus = function() {
- this._dom.status.className = "status";
- this._dom.status.style.display = "";
-
- var status = this._status;
- if (this._status == "computed") {
- var childrenStatus = this._children.every(function(child) {
- return (child.getComputedStatus() !== false);
- });
- status = (childrenStatus ? "yes" : "no");
- }
-
- switch (status) {
- case "yes":
- this._dom.status.classList.add("yes");
- this._computed.status = true;
- break;
-
- case "no":
- this._dom.status.classList.add("no");
- this._computed.status = false;
- break;
-
- default:
- this._computed.status = null;
- this._dom.status.style.display = "none";
- break;
- }
-}
-MM.Item.prototype._updateIcon = function() {
+ this._updateValue();
+ this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
+ this.getLayout().update(this);
+ this.getShape().update(this);
+ if (!this.isRoot() && !doNotRecurse) {
+ this._parent.update();
+ }
+ return this;
+ };
+ MM.Item.prototype.updateSubtree = function(isSubChild) {
+ this._children.forEach(function(child) {
+ child.updateSubtree(true);
+ });
+ return this.update(isSubChild);
+ };
+ MM.Item.prototype.setText = function(text) {
+ this._dom.text.innerHTML = text;
+ this._findLinks(this._dom.text);
+ return this.update();
+ };
+ MM.Item.prototype.setNotes = function(notes) {
+ this._notes = notes;
+ return this.update();
+ };
+ MM.Item.prototype.getId = function() {
+ return this._id;
+ };
+ MM.Item.prototype.getText = function() {
+ return this._dom.text.innerHTML;
+ };
+ MM.Item.prototype.getNotes = function() {
+ return this._notes;
+ };
+ MM.Item.prototype.collapse = function() {
+ if (this._collapsed) {
+ return;
+ }
+ this._collapsed = true;
+ return this.update();
+ };
+ MM.Item.prototype.expand = function() {
+ if (!this._collapsed) {
+ return;
+ }
+ this._collapsed = false;
+ this.update();
+ return this.updateSubtree();
+ };
+ MM.Item.prototype.isCollapsed = function() {
+ return this._collapsed;
+ };
+ MM.Item.prototype.setValue = function(value) {
+ this._value = value;
+ return this.update();
+ };
+ MM.Item.prototype.getValue = function() {
+ return this._value;
+ };
+ MM.Item.prototype.getComputedValue = function() {
+ return this._computed.value;
+ };
+ MM.Item.prototype.setStatus = function(status) {
+ this._status = status;
+ return this.update();
+ };
+ MM.Item.prototype.getStatus = function() {
+ return this._status;
+ };
+ MM.Item.prototype.setIcon = function(icon) {
+ this._icon = icon;
+ return this.update();
+ };
+ MM.Item.prototype.getIcon = function() {
+ return this._icon;
+ };
+ MM.Item.prototype.getComputedStatus = function() {
+ return this._computed.status;
+ };
+ MM.Item.prototype.setSide = function(side) {
+ this._side = side;
+ return this;
+ };
+ MM.Item.prototype.getSide = function() {
+ return this._side;
+ };
+ MM.Item.prototype.getChildren = function() {
+ return this._children;
+ };
+ MM.Item.prototype.setColor = function(color) {
+ this._color = color;
+ return this.updateSubtree();
+ };
+ MM.Item.prototype.getColor = function() {
+ return this._color || (this.isRoot() ? MM.Item.COLOR : this._parent.getColor());
+ };
+ MM.Item.prototype.getOwnColor = function() {
+ return this._color;
+ };
+ MM.Item.prototype.getLayout = function() {
+ return this._layout || this._parent.getLayout();
+ };
+ MM.Item.prototype.getOwnLayout = function() {
+ return this._layout;
+ };
+ MM.Item.prototype.setLayout = function(layout) {
+ this._layout = layout;
+ return this.updateSubtree();
+ };
+ MM.Item.prototype.getShape = function() {
+ return this._shape;
+ };
+ MM.Item.prototype.getOwnShape = function() {
+ return this._autoShape ? null : this._shape;
+ };
+ MM.Item.prototype.setShape = function(shape) {
+ if (this._shape) {
+ this._shape.unset(this);
+ }
+ if (shape) {
+ this._autoShape = false;
+ this._shape = shape;
+ } else {
+ this._autoShape = true;
+ this._shape = this._getAutoShape();
+ }
+ this._shape.set(this);
+ return this.update();
+ };
+ MM.Item.prototype.getDOM = function() {
+ return this._dom;
+ };
+ MM.Item.prototype.getMap = function() {
+ var item = this._parent;
+ while (item) {
+ if (item instanceof MM.Map) {
+ return item;
+ }
+ item = item.getParent();
+ }
+ return null;
+ };
+ MM.Item.prototype.getParent = function() {
+ return this._parent;
+ };
+ MM.Item.prototype.isRoot = function() {
+ return this._parent instanceof MM.Map;
+ };
+ MM.Item.prototype.setParent = function(parent) {
+ this._parent = parent;
+ return this.updateSubtree();
+ };
+ MM.Item.prototype.insertChild = function(child, index) {
+ var newChild = false;
+ if (!child) {
+ child = new MM.Item();
+ newChild = true;
+ } else if (child.getParent() && child.getParent().removeChild) {
+ child.getParent().removeChild(child);
+ }
+ if (!this._children.length) {
+ this._dom.node.appendChild(this._dom.toggle);
+ this._dom.node.appendChild(this._dom.children);
+ }
+ if (arguments.length < 2) {
+ index = this._children.length;
+ }
+ var next = null;
+ if (index < this._children.length) {
+ next = this._children[index].getDOM().node;
+ }
+ this._dom.children.insertBefore(child.getDOM().node, next);
+ this._children.splice(index, 0, child);
+ return child.setParent(this);
+ };
+ MM.Item.prototype.removeChild = function(child) {
+ var index = this._children.indexOf(child);
+ this._children.splice(index, 1);
+ var node = child.getDOM().node;
+ node.parentNode.removeChild(node);
+ child.setParent(null);
+ if (!this._children.length) {
+ this._dom.toggle.parentNode.removeChild(this._dom.toggle);
+ this._dom.children.parentNode.removeChild(this._dom.children);
+ }
+ return this.update();
+ };
+ MM.Item.prototype.startEditing = function() {
+ this._oldText = this.getText();
+ this._dom.text.contentEditable = true;
+ this._dom.text.focus();
+ document.execCommand("styleWithCSS", null, false);
+ this._dom.text.addEventListener("input", this);
+ this._dom.text.addEventListener("keydown", this);
+ this._dom.text.addEventListener("blur", this);
+ return this;
+ };
+ MM.Item.prototype.stopEditing = function() {
+ this._dom.text.removeEventListener("input", this);
+ this._dom.text.removeEventListener("keydown", this);
+ this._dom.text.removeEventListener("blur", this);
+ this._dom.text.blur();
+ this._dom.text.contentEditable = false;
+ var result = this._dom.text.innerHTML;
+ this._dom.text.innerHTML = this._oldText;
+ this._oldText = "";
+ this.update();
+ MM.Clipboard.focus();
+ return result;
+ };
+ MM.Item.prototype.handleEvent = function(e) {
+ switch (e.type) {
+ case "input":
+ this.update();
+ this.getMap().ensureItemVisibility(this);
+ break;
+ case "keydown":
+ if (e.keyCode == 9) {
+ e.preventDefault();
+ }
+ break;
+ case "blur":
+ MM.Command.Finish.execute();
+ break;
+ case "click":
+ if (this._collapsed) {
+ this.expand();
+ } else {
+ this.collapse();
+ }
+ MM.App.select(this);
+ break;
+ }
+ };
+ MM.Item.prototype._getAutoShape = function() {
+ var depth = 0;
+ var node = this;
+ while (!node.isRoot()) {
+ depth++;
+ node = node.getParent();
+ }
+ switch (depth) {
+ case 0:
+ return MM.Shape.Ellipse;
+ case 1:
+ return MM.Shape.Box;
+ default:
+ return MM.Shape.Underline;
+ }
+ };
+ MM.Item.prototype._updateStatus = function() {
+ this._dom.status.className = "status";
+ this._dom.status.style.display = "";
+ var status = this._status;
+ if (this._status == "computed") {
+ var childrenStatus = this._children.every(function(child) {
+ return child.getComputedStatus() !== false;
+ });
+ status = childrenStatus ? "yes" : "no";
+ }
+ switch (status) {
+ case "yes":
+ this._dom.status.classList.add("yes");
+ this._computed.status = true;
+ break;
+ case "no":
+ this._dom.status.classList.add("no");
+ this._computed.status = false;
+ break;
+ default:
+ this._computed.status = null;
+ this._dom.status.style.display = "none";
+ break;
+ }
+ };
+ MM.Item.prototype._updateIcon = function() {
this._dom.icon.className = "icon";
this._dom.icon.style.display = "";
-
var icon = this._icon;
- if (icon)
- {
- this._dom.icon.classList.add('fa');
- this._dom.icon.classList.add(icon);
- this._computed.icon = true;
- } else {
- this._computed.icon = null;
- this._dom.icon.style.display = "none";
- }
-}
-
-MM.Item.prototype._updateNotesIndicator = function() {
- if (this._notes)
- {
- this._dom.notes.classList.add("notes-indicator-visible");
+ if (icon) {
+ this._dom.icon.classList.add("fa");
+ this._dom.icon.classList.add(icon);
+ this._computed.icon = true;
} else {
- this._dom.notes.classList.remove("notes-indicator-visible");
+ this._computed.icon = null;
+ this._dom.icon.style.display = "none";
}
-}
-
-MM.Item.prototype._updateValue = function() {
- this._dom.value.style.display = "";
-
- if (typeof(this._value) == "number") {
- this._computed.value = this._value;
- this._dom.value.innerHTML = this._value;
- return;
- }
-
- var childValues = this._children.map(function(child) {
- return child.getComputedValue();
- });
-
- var result = 0;
- switch (this._value) {
- case "sum":
- result = childValues.reduce(function(prev, cur) {
- return prev+cur;
- }, 0);
- break;
-
- case "avg":
- var sum = childValues.reduce(function(prev, cur) {
- return prev+cur;
- }, 0);
- result = (childValues.length ? sum/childValues.length : 0);
- break;
-
- case "max":
- result = Math.max.apply(Math, childValues);
- break;
-
- case "min":
- result = Math.min.apply(Math, childValues);
- break;
-
- default:
- this._computed.value = 0;
- this._dom.value.innerHTML = "";
- this._dom.value.style.display = "none";
- return;
- break;
- }
-
- this._computed.value = result;
- this._dom.value.innerHTML = (Math.round(result) == result ? result : result.toFixed(3));
-}
-
-MM.Item.prototype._findLinks = function(node) {
-
- var children = [].slice.call(node.childNodes);
- for (var i=0;i 0) { delta[0] = dx; }
- var dx = parentRect.right-itemRect.right-padding;
- if (dx < 0) { delta[0] = dx; }
-
- var dy = parentRect.top-itemRect.top+padding;
- if (dy > 0) { delta[1] = dy; }
- var dy = parentRect.bottom-itemRect.bottom-padding;
- if (dy < 0) { delta[1] = dy; }
-
- if (delta[0] || delta[1]) {
- this.moveBy(delta[0], delta[1]);
- }
-}
-
-MM.Map.prototype.getParent = function() {
- return null;
-}
-
-MM.Map.prototype.getRoot = function() {
- return this._root;
-}
-
-MM.Map.prototype.getName = function() {
- var name = this._root.getText();
- return MM.Format.br2nl(name).replace(/\n/g, " ").replace(/<.*?>/g, "").trim();
-}
-
-MM.Map.prototype.getId = function() {
- return this._root.getId();
-}
-
-MM.Map.prototype.pick = function(item, direction) {
- var candidates = [];
- var currentRect = item.getDOM().content.getBoundingClientRect();
-
- this._getPickCandidates(currentRect, this._root, direction, candidates);
- if (!candidates.length) { return item; }
-
- candidates.sort(function(a, b) {
- return a.dist - b.dist;
- });
-
- return candidates[0].item;
-}
-
-MM.Map.prototype._getPickCandidates = function(currentRect, item, direction, candidates) {
- if (!item.isCollapsed()) {
- item.getChildren().forEach(function(child) {
- this._getPickCandidates(currentRect, child, direction, candidates);
- }, this);
- }
-
- var node = item.getDOM().content;
- var rect = node.getBoundingClientRect();
-
- if (direction == "left" || direction == "right") {
- var x1 = currentRect.left + currentRect.width/2;
- var x2 = rect.left + rect.width/2;
- if (direction == "left" && x2 > x1) { return; }
- if (direction == "right" && x2 < x1) { return; }
-
- var diff1 = currentRect.top - rect.bottom;
- var diff2 = rect.top - currentRect.bottom;
- var dist = Math.abs(x2-x1);
- } else {
- var y1 = currentRect.top + currentRect.height/2;
- var y2 = rect.top + rect.height/2;
- if (direction == "top" && y2 > y1) { return; }
- if (direction == "bottom" && y2 < y1) { return; }
-
- var diff1 = currentRect.left - rect.right;
- var diff2 = rect.left - currentRect.right;
- var dist = Math.abs(y2-y1);
- }
-
- var diff = Math.max(diff1, diff2);
- if (diff > 0) { return; }
- if (!dist || dist < diff) { return; }
-
- candidates.push({item:item, dist:dist});
-}
-
-MM.Map.prototype._moveTo = function(left, top) {
- this._position = [left, top];
-
- var node = this._root.getDOM().node;
- node.style.left = left + "px";
- node.style.top = top + "px";
-}
-
-MM.Map.prototype._setRoot = function(item) {
- this._root = item;
- this._root.setParent(this);
-}
-MM.Keyboard = {};
-MM.Keyboard.init = function() {
- window.addEventListener("keydown", this);
- window.addEventListener("keypress", this);
-}
-
-MM.Keyboard.handleEvent = function(e) {
- /* mode 2a: ignore keyboard when the activeElement resides somewhere inside of the UI pane */
- var node = document.activeElement;
- while (node && node != document) {
- if (node.classList.contains("ui")) { return; }
- node = node.parentNode;
- }
-
- var commands = MM.Command.getAll();
- for (var i=0;i forbidden */
- item = item.getParent();
- }
-
- var action = new MM.Action.MoveItem(sourceItem, targetItem);
- MM.App.action(action);
-
- this._endCut();
- break;
-
- case "copy":
- var action = new MM.Action.AppendItem(targetItem, sourceItem.clone());
- MM.App.action(action);
- break;
- }
-}
-
-MM.Clipboard._pastePlaintext = function(plaintext, targetItem) {
- if (this._mode == "cut") { this._endCut(); } /* external paste => abort cutting */
-
- var json = MM.Format.Plaintext.from(plaintext);
- var map = MM.Map.fromJSON(json);
- var root = map.getRoot();
-
- if (root.getText()) {
- var action = new MM.Action.AppendItem(targetItem, root);
- MM.App.action(action);
- } else {
- var actions = root.getChildren().map(function(item) {
- return new MM.Action.AppendItem(targetItem, item);
- });
- var action = new MM.Action.Multi(actions);
- MM.App.action(action);
- }
-}
-
-MM.Clipboard.cut = function(sourceItem) {
- this._endCut();
-
- this._item = sourceItem;
- this._item.getDOM().node.classList.add("cut");
- this._mode = "cut";
-
- this._expose();
-}
-
-/**
- * Expose plaintext data to the textarea to be copied to system clipboard. Clear afterwards.
- */
-MM.Clipboard._expose = function() {
- var json = this._item.toJSON();
- var plaintext = MM.Format.Plaintext.to(json);
- this._node.value = plaintext;
- this._node.selectionStart = 0;
- this._node.selectionEnd = this._node.value.length;
- setTimeout(this._empty.bind(this), this._delay);
-}
-
-MM.Clipboard._empty = function() {
- /* safari needs a non-empty selection in order to actually perfrom a real copy on cmd+c */
- this._node.value = "\n";
- this._node.selectionStart = 0;
- this._node.selectionEnd = this._node.value.length;
-}
-
-MM.Clipboard._endCut = function() {
- if (this._mode != "cut") { return; }
-
- this._item.getDOM().node.classList.remove("cut");
- this._item = null;
- this._mode = "";
-}
-MM.Menu = {
- _dom: {},
- _port: null,
-
- open: function(x, y) {
- this._dom.node.style.display = "";
- var w = this._dom.node.offsetWidth;
- var h = this._dom.node.offsetHeight;
-
- var left = x;
- var top = y;
-
- if (left > this._port.offsetWidth / 2) { left -= w; }
- if (top > this._port.offsetHeight / 2) { top -= h; }
-
- this._dom.node.style.left = left+"px";
- this._dom.node.style.top = top+"px";
- },
-
- close: function() {
- this._dom.node.style.display = "none";
- },
-
- handleEvent: function(e) {
- if (e.currentTarget != this._dom.node) {
- this.close();
- return;
- }
-
- e.stopPropagation(); /* no dragdrop, no blur of activeElement */
- e.preventDefault(); /* we do not want to focus the button */
-
- var command = e.target.getAttribute("data-command");
- if (!command) { return; }
-
- command = MM.Command[command];
- if (!command.isValid()) { return; }
-
- command.execute();
- this.close();
- },
-
- init: function(port) {
- this._port = port;
- this._dom.node = document.querySelector("#menu");
- var buttons = this._dom.node.querySelectorAll("[data-command]");
- [].slice.call(buttons).forEach(function(button) {
- button.innerHTML = MM.Command[button.getAttribute("data-command")].label;
- });
-
- this._port.addEventListener("mousedown", this);
- this._dom.node.addEventListener("mousedown", this);
-
- this.close();
- }
-}
-
-MM.Command = Object.create(MM.Repo, {
- keys: {value: []},
- editMode: {value: false},
- prevent: {value: true}, /* prevent default keyboard action? */
- label: {value: ""}
-});
-
-MM.Command.isValid = function() {
- return (this.editMode === null || this.editMode == MM.App.editing);
-}
-MM.Command.execute = function() {}
-
-MM.Command.Notes = Object.create(MM.Command, {
- label: {value: "Notes"},
- keys: {value: [{keyCode: "M".charCodeAt(0), ctrlKey: true}]}
-});
-
-MM.Command.Notes.isValid = function() {
- return MM.Command.isValid.call(this);
-}
-
-MM.Command.Notes.execute = function() {
- MM.App.notes.toggle();
-}
-
-MM.Command.Undo = Object.create(MM.Command, {
- label: {value: "Undo"},
- keys: {value: [{keyCode: "Z".charCodeAt(0), ctrlKey: true}]}
-});
-MM.Command.Undo.isValid = function() {
- return MM.Command.isValid.call(this) && !!MM.App.historyIndex;
-}
-MM.Command.Undo.execute = function() {
- MM.App.history[MM.App.historyIndex-1].undo();
- MM.App.historyIndex--;
-}
-
-MM.Command.Redo = Object.create(MM.Command, {
- label: {value: "Redo"},
- keys: {value: [{keyCode: "Y".charCodeAt(0), ctrlKey: true}]},
-});
-MM.Command.Redo.isValid = function() {
- return (MM.Command.isValid.call(this) && MM.App.historyIndex != MM.App.history.length);
-}
-MM.Command.Redo.execute = function() {
- MM.App.history[MM.App.historyIndex].perform();
- MM.App.historyIndex++;
-}
-
-MM.Command.InsertSibling = Object.create(MM.Command, {
- label: {value: "Insert a sibling"},
- keys: {value: [{keyCode: 13}]}
-});
-MM.Command.InsertSibling.execute = function() {
- var item = MM.App.current;
- if (item.isRoot()) {
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
- } else {
- var parent = item.getParent();
- var index = parent.getChildren().indexOf(item);
- var action = new MM.Action.InsertNewItem(parent, index+1);
- }
- MM.App.action(action);
-
- MM.Command.Edit.execute();
-
- MM.publish("command-sibling");
-}
-
-MM.Command.InsertChild = Object.create(MM.Command, {
- label: {value: "Insert a child"},
- keys: {value: [
- {keyCode: 9, ctrlKey:false},
- {keyCode: 45}
- ]}
-});
-MM.Command.InsertChild.execute = function() {
- var item = MM.App.current;
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
- MM.App.action(action);
-
- MM.Command.Edit.execute();
-
- MM.publish("command-child");
-}
-
-MM.Command.Delete = Object.create(MM.Command, {
- label: {value: "Delete an item"},
- keys: {value: [{keyCode: MM.isMac() ? 8 : 46}]} // Mac keyboards' "delete" button generates 8 (backspace)
-});
-MM.Command.Delete.isValid = function() {
- return MM.Command.isValid.call(this) && !MM.App.current.isRoot();
-}
-MM.Command.Delete.execute = function() {
- var action = new MM.Action.RemoveItem(MM.App.current);
- MM.App.action(action);
-}
-
-MM.Command.Swap = Object.create(MM.Command, {
- label: {value: "Swap sibling"},
- keys: {value: [
- {keyCode: 38, ctrlKey:true},
- {keyCode: 40, ctrlKey:true},
- ]}
-});
-MM.Command.Swap.execute = function(e) {
- var current = MM.App.current;
- if (current.isRoot() || current.getParent().getChildren().length < 2) { return; }
-
- var diff = (e.keyCode == 38 ? -1 : 1);
- var action = new MM.Action.Swap(MM.App.current, diff);
- MM.App.action(action);
-}
-
-MM.Command.Side = Object.create(MM.Command, {
- label: {value: "Change side"},
- keys: {value: [
- {keyCode: 37, ctrlKey:true},
- {keyCode: 39, ctrlKey:true},
- ]}
-});
-MM.Command.Side.execute = function(e) {
- var current = MM.App.current;
- if (current.isRoot() || !current.getParent().isRoot()) { return; }
-
- var side = (e.keyCode == 37 ? "left" : "right");
- var action = new MM.Action.SetSide(MM.App.current, side);
- MM.App.action(action);
-}
-
-MM.Command.Save = Object.create(MM.Command, {
- label: {value: "Save map"},
- keys: {value: [{keyCode: "S".charCodeAt(0), ctrlKey:true, shiftKey:false}]}
-});
-MM.Command.Save.execute = function() {
- MM.App.io.quickSave();
-}
-
-MM.Command.SaveAs = Object.create(MM.Command, {
- label: {value: "Save as…"},
- keys: {value: [{keyCode: "S".charCodeAt(0), ctrlKey:true, shiftKey:true}]}
-});
-MM.Command.SaveAs.execute = function() {
- MM.App.io.show("save");
-}
-
-MM.Command.Load = Object.create(MM.Command, {
- label: {value: "Load map"},
- keys: {value: [{keyCode: "O".charCodeAt(0), ctrlKey:true}]}
-});
-MM.Command.Load.execute = function() {
- MM.App.io.show("load");
-}
-
-MM.Command.Center = Object.create(MM.Command, {
- label: {value: "Center map"},
- keys: {value: [{keyCode: 35}]}
-});
-MM.Command.Center.execute = function() {
- MM.App.map.center();
-}
-
-MM.Command.New = Object.create(MM.Command, {
- label: {value: "New map"},
- keys: {value: [{keyCode: "N".charCodeAt(0), ctrlKey:true}]}
-});
-MM.Command.New.execute = function() {
- if (!confirm("Throw away your current map and start a new one?")) { return; }
- var map = new MM.Map();
- MM.App.setMap(map);
- MM.publish("map-new", this);
-}
-
-MM.Command.ZoomIn = Object.create(MM.Command, {
- label: {value: "Zoom in"},
- keys: {value: [{charCode:"+".charCodeAt(0)}]}
-});
-MM.Command.ZoomIn.execute = function() {
- MM.App.adjustFontSize(1);
-}
-
-MM.Command.ZoomOut = Object.create(MM.Command, {
- label: {value: "Zoom out"},
- keys: {value: [{charCode:"-".charCodeAt(0)}]}
-});
-MM.Command.ZoomOut.execute = function() {
- MM.App.adjustFontSize(-1);
-}
-
-MM.Command.Help = Object.create(MM.Command, {
- label: {value: "Show/hide help"},
- keys: {value: [{charCode: "?".charCodeAt(0)}]}
-});
-MM.Command.Help.execute = function() {
- MM.App.help.toggle();
-}
-
-MM.Command.UI = Object.create(MM.Command, {
- label: {value: "Show/hide UI"},
- keys: {value: [{charCode: "*".charCodeAt(0)}]}
-});
-MM.Command.UI.execute = function() {
- MM.App.ui.toggle();
-}
-
-MM.Command.Pan = Object.create(MM.Command, {
- label: {value: "Pan the map"},
- keys: {value: [
- {keyCode: "W".charCodeAt(0), ctrlKey:false, altKey:false, metaKey:false},
- {keyCode: "A".charCodeAt(0), ctrlKey:false, altKey:false, metaKey:false},
- {keyCode: "S".charCodeAt(0), ctrlKey:false, altKey:false, metaKey:false},
- {keyCode: "D".charCodeAt(0), ctrlKey:false, altKey:false, metaKey:false}
- ]},
- chars: {value: []}
-});
-MM.Command.Pan.execute = function(e) {
- var ch = String.fromCharCode(e.keyCode);
- var index = this.chars.indexOf(ch);
- if (index > -1) { return; }
-
- if (!this.chars.length) {
- window.addEventListener("keyup", this);
- this.interval = setInterval(this._step.bind(this), 50);
- }
-
- this.chars.push(ch);
- this._step();
-}
-
-MM.Command.Pan._step = function() {
- var dirs = {
- "W": [0, 1],
- "A": [1, 0],
- "S": [0, -1],
- "D": [-1, 0]
- }
- var offset = [0, 0];
-
- this.chars.forEach(function(ch) {
- offset[0] += dirs[ch][0];
- offset[1] += dirs[ch][1];
- });
-
- MM.App.map.moveBy(15*offset[0], 15*offset[1]);
-}
-
-MM.Command.Pan.handleEvent = function(e) {
- var ch = String.fromCharCode(e.keyCode);
- var index = this.chars.indexOf(ch);
- if (index > -1) {
- this.chars.splice(index, 1);
- if (!this.chars.length) {
- window.removeEventListener("keyup", this);
- clearInterval(this.interval);
- }
- }
-}
-
-MM.Command.Copy = Object.create(MM.Command, {
- label: {value: "Copy"},
- prevent: {value: false},
- keys: {value: [
- {keyCode: "C".charCodeAt(0), ctrlKey:true},
- {keyCode: "C".charCodeAt(0), metaKey:true}
- ]}
-});
-MM.Command.Copy.execute = function() {
- MM.Clipboard.copy(MM.App.current);
-}
-
-MM.Command.Cut = Object.create(MM.Command, {
- label: {value: "Cut"},
- prevent: {value: false},
- keys: {value: [
- {keyCode: "X".charCodeAt(0), ctrlKey:true},
- {keyCode: "X".charCodeAt(0), metaKey:true}
- ]}
-});
-MM.Command.Cut.execute = function() {
- MM.Clipboard.cut(MM.App.current);
-}
-
-MM.Command.Paste = Object.create(MM.Command, {
- label: {value: "Paste"},
- prevent: {value: false},
- keys: {value: [
- {keyCode: "V".charCodeAt(0), ctrlKey:true},
- {keyCode: "V".charCodeAt(0), metaKey:true}
- ]}
-});
-MM.Command.Paste.execute = function() {
- MM.Clipboard.paste(MM.App.current);
-}
-
-MM.Command.Fold = Object.create(MM.Command, {
- label: {value: "Fold/Unfold"},
- keys: {value: [{charCode: "f".charCodeAt(0), ctrlKey:false}]}
-});
-MM.Command.Fold.execute = function() {
- var item = MM.App.current;
- if (item.isCollapsed()) { item.expand(); } else { item.collapse(); }
- MM.App.map.ensureItemVisibility(item);
-}
-MM.Command.Edit = Object.create(MM.Command, {
- label: {value: "Edit item"},
- keys: {value: [
- {keyCode: 32},
- {keyCode: 113}
- ]}
-});
-MM.Command.Edit.execute = function() {
- MM.App.current.startEditing();
- MM.App.editing = true;
-}
-
-MM.Command.Finish = Object.create(MM.Command, {
- keys: {value: [{keyCode: 13, altKey:false, ctrlKey:false, shiftKey:false}]},
- editMode: {value: true}
-});
-MM.Command.Finish.execute = function() {
- MM.App.editing = false;
- var text = MM.App.current.stopEditing();
- if (text) {
- var action = new MM.Action.SetText(MM.App.current, text);
- } else {
- var action = new MM.Action.RemoveItem(MM.App.current);
- }
- MM.App.action(action);
-}
-
-MM.Command.Newline = Object.create(MM.Command, {
- label: {value: "Line break"},
- keys: {value: [
- {keyCode: 13, shiftKey:true},
- {keyCode: 13, ctrlKey:true}
- ]},
- editMode: {value: true}
-});
-MM.Command.Newline.execute = function() {
- var range = getSelection().getRangeAt(0);
- var br = document.createElement("br");
- range.insertNode(br);
- range.setStartAfter(br);
- MM.App.current.updateSubtree();
-}
-
-MM.Command.Cancel = Object.create(MM.Command, {
- editMode: {value: true},
- keys: {value: [{keyCode: 27}]}
-});
-MM.Command.Cancel.execute = function() {
- MM.App.editing = false;
- MM.App.current.stopEditing();
- var oldText = MM.App.current.getText();
- if (!oldText) { /* newly added node */
- var action = new MM.Action.RemoveItem(MM.App.current);
- MM.App.action(action);
- }
-}
-
-MM.Command.Style = Object.create(MM.Command, {
- editMode: {value: null},
- command: {value: ""}
-});
-
-MM.Command.Style.execute = function() {
- if (MM.App.editing) {
- document.execCommand(this.command, null, null);
- } else {
- MM.Command.Edit.execute();
- var selection = getSelection();
- var range = selection.getRangeAt(0);
- range.selectNodeContents(MM.App.current.getDOM().text);
- selection.removeAllRanges();
- selection.addRange(range);
- this.execute();
- MM.Command.Finish.execute();
- }
-}
-
-MM.Command.Bold = Object.create(MM.Command.Style, {
- command: {value: "bold"},
- label: {value: "Bold"},
- keys: {value: [{keyCode: "B".charCodeAt(0), ctrlKey:true}]}
-});
-
-MM.Command.Underline = Object.create(MM.Command.Style, {
- command: {value: "underline"},
- label: {value: "Underline"},
- keys: {value: [{keyCode: "U".charCodeAt(0), ctrlKey:true}]}
-});
-
-MM.Command.Italic = Object.create(MM.Command.Style, {
- command: {value: "italic"},
- label: {value: "Italic"},
- keys: {value: [{keyCode: "I".charCodeAt(0), ctrlKey:true}]}
-});
-
-MM.Command.Strikethrough = Object.create(MM.Command.Style, {
- command: {value: "strikeThrough"},
- label: {value: "Strike-through"},
- keys: {value: [{keyCode: "S".charCodeAt(0), ctrlKey:true}]}
-});
-
-MM.Command.Value = Object.create(MM.Command, {
- label: {value: "Set value"},
- keys: {value: [{charCode: "v".charCodeAt(0), ctrlKey:false, metaKey:false}]}
-});
-MM.Command.Value.execute = function() {
- var item = MM.App.current;
- var oldValue = item.getValue();
- var newValue = prompt("Set item value", oldValue);
- if (newValue == null) { return; }
-
- if (!newValue.length) { newValue = null; }
-
- var numValue = parseFloat(newValue);
- var action = new MM.Action.SetValue(item, isNaN(numValue) ? newValue : numValue);
- MM.App.action(action);
-}
-
-MM.Command.Yes = Object.create(MM.Command, {
- label: {value: "Yes"},
- keys: {value: [{charCode: "y".charCodeAt(0), ctrlKey:false}]}
-});
-MM.Command.Yes.execute = function() {
- var item = MM.App.current;
- var status = (item.getStatus() == "yes" ? null : "yes");
- var action = new MM.Action.SetStatus(item, status);
- MM.App.action(action);
-}
-
-MM.Command.No = Object.create(MM.Command, {
- label: {value: "No"},
- keys: {value: [{charCode: "n".charCodeAt(0), ctrlKey:false}]}
-});
-MM.Command.No.execute = function() {
- var item = MM.App.current;
- var status = (item.getStatus() == "no" ? null : "no");
- var action = new MM.Action.SetStatus(item, status);
- MM.App.action(action);
-}
-
-MM.Command.Computed = Object.create(MM.Command, {
- label: {value: "Computed"},
- keys: {value: [{charCode: "c".charCodeAt(0), ctrlKey:false, metaKey:false}]}
-});
-MM.Command.Computed.execute = function() {
- var item = MM.App.current;
- var status = (item.getStatus() == "computed" ? null : "computed");
- var action = new MM.Action.SetStatus(item, status);
- MM.App.action(action);
-}
-MM.Command.Select = Object.create(MM.Command, {
- label: {value: "Move selection"},
- keys: {value: [
- {keyCode: 38, ctrlKey:false},
- {keyCode: 37, ctrlKey:false},
- {keyCode: 40, ctrlKey:false},
- {keyCode: 39, ctrlKey:false}
- ]}
-});
-MM.Command.Select.execute = function(e) {
- var dirs = {
- 37: "left",
- 38: "top",
- 39: "right",
- 40: "bottom"
- }
- var dir = dirs[e.keyCode];
-
- var layout = MM.App.current.getLayout();
- var item = /*MM.App.map*/layout.pick(MM.App.current, dir);
- MM.App.select(item);
-}
-
-MM.Command.SelectRoot = Object.create(MM.Command, {
- label: {value: "Select root"},
- keys: {value: [{keyCode: 36}]}
-});
-MM.Command.SelectRoot.execute = function() {
- var item = MM.App.current;
- while (!item.isRoot()) { item = item.getParent(); }
- MM.App.select(item);
-}
-
-// Macs use keyCode 8 to delete instead
-if (!MM.isMac()) {
- MM.Command.SelectParent = Object.create(MM.Command, {
- label: {value: "Select parent"},
- keys: {value: [{keyCode: 8}]}
- });
- MM.Command.SelectParent.execute = function() {
- if (MM.App.current.isRoot()) { return; }
- MM.App.select(MM.App.current.getParent());
- }
-}
-MM.Layout = Object.create(MM.Repo, {
- ALL: {value: []},
- SPACING_RANK: {value: 4},
- SPACING_CHILD: {value: 4},
-});
-
-MM.Layout.getAll = function() {
- return this.ALL;
-}
-
-/**
- * Re-draw an item and its children
- */
-MM.Layout.update = function(item) {
- return this;
-}
-
-/**
- * @param {MM.Item} child Child node (its parent uses this layout)
- */
-MM.Layout.getChildDirection = function(child) {
- return "";
-}
-
-MM.Layout.pick = function(item, dir) {
- var opposite = {
- left: "right",
- right: "left",
- top: "bottom",
- bottom: "top"
- }
-
- /* direction for a child */
- if (!item.isCollapsed()) {
- var children = item.getChildren();
- for (var i=0;i 1) { bbox[childIndex] += this.SPACING_CHILD * (children.length-1); } /* child separation */
-
- return bbox;
-}
-
-MM.Layout._alignItem = function(item, side) {
- var dom = item.getDOM();
-
- switch (side) {
- case "left":
- dom.content.insertBefore(dom.icon, dom.content.firstChild);
- dom.content.appendChild(dom.value);
- dom.content.appendChild(dom.status);
- break;
- case "right":
- dom.content.insertBefore(dom.icon, dom.content.firstChild);
- dom.content.insertBefore(dom.value, dom.content.firstChild);
- dom.content.insertBefore(dom.status, dom.content.firstChild);
- break;
- }
-}
-MM.Layout.Graph = Object.create(MM.Layout, {
- SPACING_RANK: {value: 16},
- childDirection: {value: ""}
-});
-
-MM.Layout.Graph.getChildDirection = function(child) {
- return this.childDirection;
-}
-
-MM.Layout.Graph.create = function(direction, id, label) {
- var layout = Object.create(this, {
- childDirection: {value:direction},
- id: {value:id},
- label: {value:label}
- });
- MM.Layout.ALL.push(layout);
- return layout;
-}
-
-MM.Layout.Graph.update = function(item) {
- var side = this.childDirection;
- if (!item.isRoot()) {
- side = item.getParent().getLayout().getChildDirection(item);
- }
- this._alignItem(item, side);
-
- this._layoutItem(item, this.childDirection);
-
- if (this.childDirection == "left" || this.childDirection == "right") {
- this._drawLinesHorizontal(item, this.childDirection);
- } else {
- this._drawLinesVertical(item, this.childDirection);
- }
-
- return this;
-}
-
-
-/**
- * Generic graph child layout routine. Updates item's orthogonal size according to the sum of its children.
- */
-MM.Layout.Graph._layoutItem = function(item, rankDirection) {
- var sizeProps = ["width", "height"];
- var posProps = ["left", "top"];
- var rankIndex = (rankDirection == "left" || rankDirection == "right" ? 0 : 1);
- var childIndex = (rankIndex+1) % 2;
-
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
- var rankSizeProp = sizeProps[rankIndex];
- var childSizeProp = sizeProps[childIndex];
-
- var dom = item.getDOM();
-
- /* content size */
- var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
-
- /* children size */
- var bbox = this._computeChildrenBBox(item.getChildren(), childIndex);
-
- /* node size */
- var rankSize = contentSize[rankIndex];
- if (bbox[rankIndex]) { rankSize += bbox[rankIndex] + this.SPACING_RANK; }
- var childSize = Math.max(bbox[childIndex], contentSize[childIndex]);
- dom.node.style[rankSizeProp] = rankSize + "px";
- dom.node.style[childSizeProp] = childSize + "px";
-
- var offset = [0, 0];
- if (rankDirection == "right") { offset[0] = contentSize[0] + this.SPACING_RANK; }
- if (rankDirection == "bottom") { offset[1] = contentSize[1] + this.SPACING_RANK; }
- offset[childIndex] = Math.round((childSize - bbox[childIndex])/2);
- this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
-
- /* label position */
- var labelPos = 0;
- if (rankDirection == "left") { labelPos = rankSize - contentSize[0]; }
- if (rankDirection == "top") { labelPos = rankSize - contentSize[1]; }
- dom.content.style[childPosProp] = Math.round((childSize - contentSize[childIndex])/2) + "px";
- dom.content.style[rankPosProp] = labelPos + "px";
-
- return this;
-}
-
-MM.Layout.Graph._layoutChildren = function(children, rankDirection, offset, bbox) {
- var posProps = ["left", "top"];
-
- var rankIndex = (rankDirection == "left" || rankDirection == "right" ? 0 : 1);
- var childIndex = (rankIndex+1) % 2;
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
-
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var childSize = [node.offsetWidth, node.offsetHeight];
-
- if (rankDirection == "left") { offset[0] = bbox[0] - childSize[0]; }
- if (rankDirection == "top") { offset[1] = bbox[1] - childSize[1]; }
-
- node.style[childPosProp] = offset[childIndex] + "px";
- node.style[rankPosProp] = offset[rankIndex] + "px";
-
- offset[childIndex] += childSize[childIndex] + this.SPACING_CHILD; /* offset for next child */
- }, this);
-
- return bbox;
-}
-
-MM.Layout.Graph._drawLinesHorizontal = function(item, side) {
- this._anchorCanvas(item);
- this._drawHorizontalConnectors(item, side, item.getChildren());
-}
-
-MM.Layout.Graph._drawLinesVertical = function(item, side) {
- this._anchorCanvas(item);
- this._drawVerticalConnectors(item, side, item.getChildren());
-}
-
-MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
- if (children.length == 0) { return; }
-
- var dom = item.getDOM();
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
- ctx.strokeStyle = item.getColor();
- var R = this.SPACING_RANK/2;
-
- /* first part */
- var y1 = item.getShape().getVerticalAnchor(item);
- if (side == "left") {
- var x1 = dom.content.offsetLeft - 0.5;
- } else {
- var x1 = dom.content.offsetWidth + dom.content.offsetLeft + 0.5;
- }
-
- this._anchorToggle(item, x1, y1, side);
- if (item.isCollapsed()) { return; }
-
- if (children.length == 1) {
- var child = children[0];
- var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
- var x2 = this._getChildAnchor(child, side);
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.bezierCurveTo((x1+x2)/2, y1, (x1+x2)/2, y2, x2, y2);
- ctx.stroke();
- return;
- }
-
- if (side == "left") {
- var x2 = x1 - R;
- } else {
- var x2 = x1 + R;
- }
-
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y1);
- ctx.stroke();
-
- /* rounded connectors */
- var c1 = children[0];
- var c2 = children[children.length-1];
- var x = x2;
- var xx = x + (side == "left" ? -R : R);
-
- var y1 = c1.getShape().getVerticalAnchor(c1) + c1.getDOM().node.offsetTop;
- var y2 = c2.getShape().getVerticalAnchor(c2) + c2.getDOM().node.offsetTop;
- var x1 = this._getChildAnchor(c1, side);
- var x2 = this._getChildAnchor(c2, side);
-
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(xx, y1)
- ctx.arcTo(x, y1, x, y1+R, R);
- ctx.lineTo(x, y2-R);
- ctx.arcTo(x, y2, xx, y2, R);
- ctx.lineTo(x2, y2);
-
- for (var i=1; i counts.left ? "left" : "right");
- children[i].setSide(side);
- }
- counts[side]++;
- }
-
- return child.getSide();
-}
-
-MM.Layout.Map.pickSibling = function(item, dir) {
- if (item.isRoot()) { return item; }
-
- var parent = item.getParent();
- var children = parent.getChildren();
- if (parent.isRoot()) {
- var side = this.getChildDirection(item);
- children = children.filter(function(child) {
- return (this.getChildDirection(child) == side);
- }, this);
- }
-
- var index = children.indexOf(item);
- index += dir;
- index = (index+children.length) % children.length;
- return children[index];
-}
-
-MM.Layout.Map._layoutRoot = function(item) {
- this._alignItem(item, "right");
-
- var dom = item.getDOM();
-
- var children = item.getChildren();
- var childrenLeft = [];
- var childrenRight = [];
-
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var side = this.getChildDirection(child);
-
- if (side == "left") {
- childrenLeft.push(child);
- } else {
- childrenRight.push(child);
- }
- }, this);
-
- var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
- var bboxRight = this._computeChildrenBBox(childrenRight, 1);
- var height = Math.max(bboxLeft[1], bboxRight[1], dom.content.offsetHeight);
-
- var left = 0;
- this._layoutChildren(childrenLeft, "left", [left, Math.round((height-bboxLeft[1])/2)], bboxLeft);
- left += bboxLeft[0];
-
- if (childrenLeft.length) { left += this.SPACING_RANK; }
- dom.content.style.left = left + "px";
- left += dom.content.offsetWidth;
-
- if (childrenRight.length) { left += this.SPACING_RANK; }
- this._layoutChildren(childrenRight, "right", [left, Math.round((height-bboxRight[1])/2)], bboxRight);
- left += bboxRight[0];
-
- dom.content.style.top = Math.round((height - dom.content.offsetHeight)/2) + "px";
- dom.node.style.height = height + "px";
- dom.node.style.width = left + "px";
-
- this._anchorCanvas(item);
- this._drawRootConnectors(item, "left", childrenLeft);
- this._drawRootConnectors(item, "right", childrenRight);
-}
-
-MM.Layout.Map._drawRootConnectors = function(item, side, children) {
- if (children.length == 0 || item.isCollapsed()) { return; }
-
- var dom = item.getDOM();
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
- var R = this.SPACING_RANK/2;
-
- var x1 = dom.content.offsetLeft + dom.content.offsetWidth/2;
- var y1 = item.getShape().getVerticalAnchor(item);
- var half = this.LINE_THICKNESS/2;
-
- for (var i=0;i");
-}
-
-MM.Format.br2nl = function(str) {
- return str.replace(/
/g, "\n");
-}
-MM.Format.JSON = Object.create(MM.Format, {
- id: {value: "json"},
- label: {value: "Native (JSON)"},
- extension: {value: "mymind"},
- mime: {value: "application/vnd.mymind+json"}
-});
-
-MM.Format.JSON.to = function(data) {
- return JSON.stringify(data, null, "\t") + "\n";
-}
-
-MM.Format.JSON.from = function(data) {
- return JSON.parse(data);
-}
-MM.Format.FreeMind = Object.create(MM.Format, {
- id: {value: "freemind"},
- label: {value: "FreeMind"},
- extension: {value: "mm"},
- mime: {value: "application/x-freemind"}
-});
-
-MM.Format.FreeMind.to = function(data) {
- var doc = document.implementation.createDocument(null, null, null);
- var map = doc.createElement("map");
-
- map.setAttribute("version", "1.0.1");
- map.appendChild(this._serializeItem(doc, data.root));
-
- doc.appendChild(map);
- var serializer = new XMLSerializer();
- return serializer.serializeToString(doc);
-}
-
-MM.Format.FreeMind.from = function(data) {
- var parser = new DOMParser();
- var doc = parser.parseFromString(data, "application/xml");
- if (doc.documentElement.nodeName.toLowerCase() == "parsererror") { throw new Error(doc.documentElement.textContent); }
-
- var root = doc.documentElement.getElementsByTagName("node")[0];
- if (!root) { throw new Error("No root node found"); }
-
- var json = {
- root: this._parseNode(root, {shape:"underline"})
- };
- json.root.layout = "map";
- json.root.shape = "ellipse";
-
- return json;
-}
-
-MM.Format.FreeMind._serializeItem = function(doc, json) {
- var elm = this._serializeAttributes(doc, json);
-
- (json.children || []).forEach(function(child) {
- elm.appendChild(this._serializeItem(doc, child));
- }, this);
-
- return elm;
-}
-
-MM.Format.FreeMind._serializeAttributes = function(doc, json) {
- var elm = doc.createElement("node");
- elm.setAttribute("TEXT", MM.Format.br2nl(json.text));
- elm.setAttribute("ID", json.id);
-
- if (json.side) { elm.setAttribute("POSITION", json.side); }
- if (json.shape == "box") { elm.setAttribute("STYLE", "bubble"); }
- if (json.collapsed) { elm.setAttribute("FOLDED", "true"); }
-
- if (json.notes) {
- var notesElm = doc.createElement("richcontent");
- notesElm.setAttribute("TYPE", "NOTE");
- // note: the freemind file format isn't very good.
- notesElm.appendChild(doc.createCDATASection('' + json.notes + ''));
- elm.appendChild(notesElm);
- }
-
- return elm;
-}
-
-MM.Format.FreeMind._parseNode = function(node, parent) {
- var json = this._parseAttributes(node, parent);
-
- for (var i=0;i *");
- if (body) {
- var serializer = new XMLSerializer();
- json.notes = serializer.serializeToString(body).trim();
- }
- }
- break;
-
- case "font":
- if (child.getAttribute("ITALIC") == "true") { json.text = "" + json.text + ""; }
- if (child.getAttribute("BOLD") == "true") { json.text = "" + json.text + ""; }
- break;
- }
- }
-
- return json;
-}
-MM.Format.MMA = Object.create(MM.Format.FreeMind, {
- id: {value: "mma"},
- label: {value: "Mind Map Architect"},
- extension: {value: "mma"}
-});
-
-MM.Format.MMA._parseAttributes = function(node, parent) {
- var json = {
- children: [],
- text: MM.Format.nl2br(node.getAttribute("title") || ""),
- shape: "box"
- };
-
- if (node.getAttribute("expand") == "false") { json.collapsed = 1; }
-
- var direction = node.getAttribute("direction");
- if (direction == "0") { json.side = "left"; }
- if (direction == "1") { json.side = "right"; }
-
- var color = node.getAttribute("color");
- if (color) {
- var re = color.match(/^#(....)(....)(....)$/);
- if (re) {
- var r = parseInt(re[1], 16) >> 8;
- var g = parseInt(re[2], 16) >> 8;
- var b = parseInt(re[3], 16) >> 8;
- r = Math.round(r/17).toString(16);
- g = Math.round(g/17).toString(16);
- b = Math.round(b/17).toString(16);
- }
- json.color = "#" + [r,g,b].join("");
- }
-
- json.icon = node.getAttribute("icon");
-
- return json;
-}
-
-MM.Format.MMA._serializeAttributes = function(doc, json) {
- var elm = doc.createElement("node");
- elm.setAttribute("title", MM.Format.br2nl(json.text));
- elm.setAttribute("expand", json.collapsed ? "false" : "true");
-
- if (json.side) { elm.setAttribute("direction", json.side == "left" ? "0" : "1"); }
- if (json.color) {
- var parts = json.color.match(/^#(.)(.)(.)$/);
- var r = new Array(5).join(parts[1]);
- var g = new Array(5).join(parts[2]);
- var b = new Array(5).join(parts[3]);
- elm.setAttribute("color", "#" + [r,g,b].join(""));
- }
- if (json.icon) {
- elm.setAttribute("icon", json.icon);
- }
-
- return elm;
-}
-MM.Format.Mup = Object.create(MM.Format, {
- id: {value: "mup"},
- label: {value: "MindMup"},
- extension: {value: "mup"}
-});
-
-MM.Format.Mup.to = function(data) {
- var root = this._MMtoMup(data.root);
- return JSON.stringify(root, null, 2);
-}
-
-MM.Format.Mup.from = function(data) {
- var source = JSON.parse(data);
- var root = this._MupToMM(source);
- root.layout = "map";
-
- var map = {
- root: root
- }
-
- return map;
-}
-
-MM.Format.Mup._MupToMM = function(item) {
- var json = {
- text: MM.Format.nl2br(item.title),
- id: item.id,
- shape: "box",
- icon: item.icon
- }
-
- if (item.attr && item.attr.style && item.attr.style.background) {
- json.color = item.attr.style.background;
- }
-
- if (item.attr && item.attr.collapsed) {
- json.collapsed = 1;
- }
-
- if (item.ideas) {
- var data = [];
- for (var key in item.ideas) {
- var child = this._MupToMM(item.ideas[key]);
- var num = parseFloat(key);
- child.side = (num < 0 ? "left" : "right");
- data.push({
- child: child,
- num: num
- });
- }
-
- data.sort(function(a, b) {
- return a.num-b.num;
- });
-
- json.children = data.map(function(item) { return item.child; });
- }
-
- return json;
-}
-
-MM.Format.Mup._MMtoMup = function(item, side) {
- var result = {
- id: item.id,
- title: MM.Format.br2nl(item.text),
- icon: item.icon,
- attr: {}
- }
- if (item.color) {
- result.attr.style = {background:item.color};
- }
- if (item.collapsed) {
- result.attr.collapsed = true;
- }
-
- if (item.children) {
- result.ideas = {};
-
- for (var i=0;i 1) { return; }
- e.clientX = e.touches[0].clientX;
- e.clientY = e.touches[0].clientY;
- case "mousedown":
- var item = MM.App.map.getItemFor(e.target);
- if (MM.App.editing) {
- if (item == MM.App.current) { return; } /* ignore dnd on edited node */
- MM.Command.Finish.execute(); /* clicked elsewhere => finalize edit */
- }
-
- if (e.type == "mousedown") { e.preventDefault(); } /* to prevent blurring the clipboard node */
-
- if (e.type == "touchstart") { /* context menu here, after we have the item */
- this._touchTimeout = setTimeout(function() {
- item && MM.App.select(item);
- MM.Menu.open(e.clientX, e.clientY);
- }, this.TOUCH_DELAY);
- }
-
- this._startDrag(e, item);
- break;
-
- case "touchmove":
- if (e.touches.length > 1) { return; }
- e.clientX = e.touches[0].clientX;
- e.clientY = e.touches[0].clientY;
- clearTimeout(this._touchTimeout);
- case "mousemove":
- this._processDrag(e);
- break;
-
- case "touchend":
- clearTimeout(this._touchTimeout);
- case "mouseup":
- this._endDrag();
- break;
-
- case "wheel":
- case "mousewheel":
- var dir = 0;
- if (e.wheelDelta) {
- if (e.wheelDelta < 0) {
- dir = -1;
- } else if (e.wheelDelta > 0) {
- dir = 1;
- }
- }
- if (e.deltaY) {
- if (e.deltaY > 0) {
- dir = -1;
- } else if (e.deltaY < 0) {
- dir = 1;
- }
- }
- if (dir) {
- MM.App.adjustFontSize(dir);
- }
- break;
- }
-}
-
-MM.Mouse._startDrag = function(e, item) {
-
- if (e.type == "mousedown") {
- e.preventDefault(); /* no selections allowed. only for mouse; preventing touchstart would prevent Safari from emulating clicks */
- this._port.addEventListener("mousemove", this);
- this._port.addEventListener("mouseup", this);
- } else {
- this._port.addEventListener("touchmove", this);
- this._port.addEventListener("touchend", this);
- }
-
- this._cursor[0] = e.clientX;
- this._cursor[1] = e.clientY;
-
- if (item && !item.isRoot()) {
- this._mode = "drag";
- this._item = item;
- } else {
- this._mode = "pan";
- this._port.style.cursor = "move";
- }
-}
-
-MM.Mouse._processDrag = function(e) {
- e.preventDefault();
- var dx = e.clientX - this._cursor[0];
- var dy = e.clientY - this._cursor[1];
- this._cursor[0] = e.clientX;
- this._cursor[1] = e.clientY;
-
- switch (this._mode) {
- case "drag":
- if (!this._ghost) {
- this._port.style.cursor = "move";
- this._buildGhost(dx, dy);
- }
- this._moveGhost(dx, dy);
- var state = this._computeDragState();
- this._visualizeDragState(state);
- break;
-
- case "pan":
- MM.App.map.moveBy(dx, dy);
- break;
- }
-}
-
-MM.Mouse._endDrag = function() {
- this._port.style.cursor = "";
- this._port.removeEventListener("mousemove", this);
- this._port.removeEventListener("mouseup", this);
-
- if (this._mode == "pan") { return; } /* no cleanup after panning */
-
- if (this._ghost) {
- var state = this._computeDragState();
- this._finishDragDrop(state);
-
- this._ghost.parentNode.removeChild(this._ghost);
- this._ghost = null;
- }
-
- this._item = null;
-}
-
-MM.Mouse._buildGhost = function() {
- var content = this._item.getDOM().content;
- this._ghost = content.cloneNode(true);
- this._ghost.classList.add("ghost");
- this._pos[0] = content.offsetLeft;
- this._pos[1] = content.offsetTop;
- content.parentNode.appendChild(this._ghost);
-}
-
-MM.Mouse._moveGhost = function(dx, dy) {
- this._pos[0] += dx;
- this._pos[1] += dy;
- this._ghost.style.left = this._pos[0] + "px";
- this._ghost.style.top = this._pos[1] + "px";
-
- var state = this._computeDragState();
-}
-
-MM.Mouse._finishDragDrop = function(state) {
- this._visualizeDragState(null);
-
- var target = state.item;
- switch (state.result) {
- case "append":
- var action = new MM.Action.MoveItem(this._item, target);
- break;
-
- case "sibling":
- var index = target.getParent().getChildren().indexOf(target);
- var targetIndex = index + (state.direction == "right" || state.direction == "bottom" ? 1 : 0);
- var action = new MM.Action.MoveItem(this._item, target.getParent(), targetIndex, target.getSide());
- break;
-
- default:
- return;
- break;
- }
-
- MM.App.action(action);
-}
-
-/**
- * Compute a state object for a drag: current result (""/"append"/"sibling"), parent/sibling, direction
- */
-MM.Mouse._computeDragState = function() {
- var rect = this._ghost.getBoundingClientRect();
- var closest = MM.App.map.getClosestItem(rect.left + rect.width/2, rect.top + rect.height/2);
- var target = closest.item;
-
- var state = {
- result: "",
- item: target,
- direction: ""
- }
-
- var tmp = target;
- while (!tmp.isRoot()) {
- if (tmp == this._item) { return state; } /* drop on a child or self */
- tmp = tmp.getParent();
- }
-
- var w1 = this._item.getDOM().content.offsetWidth;
- var w2 = target.getDOM().content.offsetWidth;
- var w = Math.max(w1, w2);
- var h1 = this._item.getDOM().content.offsetHeight;
- var h2 = target.getDOM().content.offsetHeight;
- var h = Math.max(h1, h2);
-
- if (target.isRoot()) { /* append here */
- state.result = "append";
- } else if (Math.abs(closest.dx) < w && Math.abs(closest.dy) < h) { /* append here */
- state.result = "append";
- } else {
- state.result = "sibling";
- var childDirection = target.getParent().getLayout().getChildDirection(target);
- var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
-
- if (childDirection == "left" || childDirection == "right") {
- state.direction = (closest.dy < 0 ? "bottom" : "top");
- } else {
- state.direction = (closest.dx < 0 ? "right" : "left");
- }
- }
-
- return state;
-}
-
-MM.Mouse._visualizeDragState = function(state) {
- if (this._oldState && state && this._oldState.item == state.item && this._oldState.result == state.result) { return; } /* nothing changed */
-
- if (this._oldDragState) { /* remove old vis */
- var item = this._oldDragState.item;
- var node = item.getDOM().content;
- node.style.boxShadow = "";
- }
-
- this._oldDragState = state;
-
- if (state) { /* show new vis */
- var item = state.item;
- var node = item.getDOM().content;
-
- var x = 0;
- var y = 0;
- var offset = 5;
- if (state.result == "sibling") {
- if (state.direction == "left") { x = -1; }
- if (state.direction == "right") { x = +1; }
- if (state.direction == "top") { y = -1; }
- if (state.direction == "bottom") { y = +1; }
- }
- var spread = (x || y ? -2 : 2);
- node.style.boxShadow = (x*offset) + "px " + (y*offset) + "px 2px " + spread + "px #000";
- }
-}
-/*
-setInterval(function() {
- console.log(document.activeElement);
-}, 1000);
-*/
-
-/*
- * Notes regarding app state/modes, activeElements, focusing etc.
- * ==============================================================
- *
- * 1) There is always exactly one item selected. All executed commands
- * operate on this item.
- *
- * 2) The app distinguishes three modes with respect to focus:
- * 2a) One of the UI panes has focus (inputs, buttons, selects).
- * Keyboard shortcuts are disabled.
- * 2b) Current item is being edited. It is contentEditable and focused.
- * Blurring ends the edit mode.
- * 2c) ELSE the Clipboard is focused (its invisible textarea)
- *
- * In 2a, we try to lose focus as soon as possible
- * (after clicking, after changing select's value), switching to 2c.
- *
- * 3) Editing mode (2b) can be ended by multiple ways:
- * 3a) By calling current.stopEditing();
- * this shall be followed by some resolution.
- * 3b) By executing MM.Command.{Finish,Cancel};
- * these call 3a internally.
- * 3c) By blurring the item itself (by selecting another);
- * this calls MM.Command.Finish (3b).
- * 3b) By blurring the currentElement;
- * this calls MM.Command.Finish (3b).
- *
- */
-MM.App = {
- keyboard: null,
- current: null,
- editing: false,
- history: [],
- historyIndex: 0,
- portSize: [0, 0],
- map: null,
- ui: null,
- io: null,
- help: null,
- _port: null,
- _throbber: null,
- _drag: {
- pos: [0, 0],
- item: null,
- ghost: null
- },
- _fontSize: 100,
-
- action: function(action) {
- if (this.historyIndex < this.history.length) { /* remove undoed actions */
- this.history.splice(this.historyIndex, this.history.length-this.historyIndex);
- }
-
- this.history.push(action);
- this.historyIndex++;
-
- action.perform();
- return this;
- },
-
- setMap: function(map) {
- if (this.map) { this.map.hide(); }
-
- this.history = [];
- this.historyIndex = 0;
-
- this.map = map;
- this.map.show(this._port);
- },
-
- select: function(item) {
- if (this.current && this.current != item) { this.current.deselect(); }
- this.current = item;
- this.current.select();
- },
-
- adjustFontSize: function(diff) {
- this._fontSize = Math.max(30, this._fontSize + 10*diff);
- this._port.style.fontSize = this._fontSize + "%";
- this.map.update();
- this.map.ensureItemVisibility(this.current);
- },
-
- handleMessage: function(message, publisher) {
- switch (message) {
- case "ui-change":
- this._syncPort();
- break;
-
- case "item-change":
- if (publisher.isRoot() && publisher.getMap() == this.map) {
- document.title = this.map.getName() + " :: My Mind";
- }
- break;
- }
- },
-
- handleEvent: function(e) {
- switch (e.type) {
- case "resize":
- this._syncPort();
- break;
-
- case "keyup":
- if (e.key === "Escape") {
- MM.App.notes.close();
- MM.App.help.close();
- }
- break;
-
- case "message":
- if (e.data && e.data.action) {
- switch (e.data.action) {
- case "setContent":
- MM.App.notes.update(e.data.value);
- break;
-
- case "closeEditor":
- MM.App.notes.close();
- break;
- }
- }
-
- break;
-
- case "beforeunload":
- e.preventDefault();
- return "";
- break;
- }
- },
-
- setThrobber: function(visible) {
- this._throbber.classList[visible ? "add" : "remove"]("visible");
- },
-
- init: function() {
- this._port = document.querySelector("#port");
- this._throbber = document.querySelector("#throbber");
- this.ui = new MM.UI();
- this.io = new MM.UI.IO();
- this.help = new MM.UI.Help();
- this.notes = new MM.UI.Notes();
+ };
+ MM.Item.prototype._updateNotesIndicator = function() {
+ if (this._notes) {
+ this._dom.notes.classList.add("notes-indicator-visible");
+ } else {
+ this._dom.notes.classList.remove("notes-indicator-visible");
+ }
+ };
+ MM.Item.prototype._updateValue = function() {
+ this._dom.value.style.display = "";
+ if (typeof this._value == "number") {
+ this._computed.value = this._value;
+ this._dom.value.innerHTML = this._value;
+ return;
+ }
+ var childValues = this._children.map(function(child) {
+ return child.getComputedValue();
+ });
+ var result = 0;
+ switch (this._value) {
+ case "sum":
+ result = childValues.reduce(function(prev, cur) {
+ return prev + cur;
+ }, 0);
+ break;
+ case "avg":
+ var sum = childValues.reduce(function(prev, cur) {
+ return prev + cur;
+ }, 0);
+ result = childValues.length ? sum / childValues.length : 0;
+ break;
+ case "max":
+ result = Math.max.apply(Math, childValues);
+ break;
+ case "min":
+ result = Math.min.apply(Math, childValues);
+ break;
+ default:
+ this._computed.value = 0;
+ this._dom.value.innerHTML = "";
+ this._dom.value.style.display = "none";
+ return;
+ break;
+ }
+ this._computed.value = result;
+ this._dom.value.innerHTML = Math.round(result) == result ? result : result.toFixed(3);
+ };
+ MM.Item.prototype._findLinks = function(node) {
+ var children = [].slice.call(node.childNodes);
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ switch (child.nodeType) {
+ case 1:
+ if (child.nodeName.toLowerCase() == "a") {
+ continue;
+ }
+ this._findLinks(child);
+ break;
+ case 3:
+ var result = child.nodeValue.match(this.constructor.RE);
+ if (result) {
+ var before = child.nodeValue.substring(0, result.index);
+ var after = child.nodeValue.substring(result.index + result[0].length);
+ var link = document.createElement("a");
+ link.innerHTML = link.href = result[0];
+ if (before) {
+ node.insertBefore(document.createTextNode(before), child);
+ }
+ node.insertBefore(link, child);
+ if (after) {
+ child.nodeValue = after;
+ i--;
+ } else {
+ node.removeChild(child);
+ }
+ }
+ break;
+ }
+ }
+ };
+
+ // .js/map.js
+ MM.Map = function(options) {
+ var o = {
+ root: "My Mind Map",
+ layout: MM.Layout.Map
+ };
+ for (var p in options) {
+ o[p] = options[p];
+ }
+ this._root = null;
+ this._visible = false;
+ this._position = [0, 0];
+ this._setRoot(new MM.Item().setText(o.root).setLayout(o.layout));
+ };
+ MM.Map.fromJSON = function(data) {
+ return new this().fromJSON(data);
+ };
+ MM.Map.prototype.toJSON = function() {
+ var data = {
+ root: this._root.toJSON()
+ };
+ return data;
+ };
+ MM.Map.prototype.fromJSON = function(data) {
+ this._setRoot(MM.Item.fromJSON(data.root));
+ return this;
+ };
+ MM.Map.prototype.mergeWith = function(data) {
+ var ids = [];
+ var current = MM.App.current;
+ var node = current;
+ while (node != this) {
+ ids.push(node.getId());
+ node = node.getParent();
+ }
+ this._root.mergeWith(data.root);
+ if (current.getMap()) {
+ var node = current.getParent();
+ var hidden = false;
+ while (node != this) {
+ if (node.isCollapsed()) {
+ hidden = true;
+ }
+ node = node.getParent();
+ }
+ if (!hidden) {
+ return;
+ }
+ }
+ if (MM.App.editing) {
+ current.stopEditing();
+ }
+ var idMap = {};
+ var scan = function(item) {
+ idMap[item.getId()] = item;
+ item.getChildren().forEach(scan);
+ };
+ scan(this._root);
+ while (ids.length) {
+ var id = ids.shift();
+ if (id in idMap) {
+ MM.App.select(idMap[id]);
+ return;
+ }
+ }
+ };
+ MM.Map.prototype.isVisible = function() {
+ return this._visible;
+ };
+ MM.Map.prototype.update = function() {
+ this._root.updateSubtree();
+ return this;
+ };
+ MM.Map.prototype.show = function(where) {
+ var node = this._root.getDOM().node;
+ where.appendChild(node);
+ this._visible = true;
+ this._root.updateSubtree();
+ this.center();
+ MM.App.select(this._root);
+ return this;
+ };
+ MM.Map.prototype.hide = function() {
+ var node = this._root.getDOM().node;
+ node.parentNode.removeChild(node);
+ this._visible = false;
+ return this;
+ };
+ MM.Map.prototype.center = function() {
+ var node = this._root.getDOM().node;
+ var port = MM.App.portSize;
+ var left = (port[0] - node.offsetWidth) / 2;
+ var top = (port[1] - node.offsetHeight) / 2;
+ this._moveTo(Math.round(left), Math.round(top));
+ return this;
+ };
+ MM.Map.prototype.moveBy = function(dx, dy) {
+ return this._moveTo(this._position[0] + dx, this._position[1] + dy);
+ };
+ MM.Map.prototype.getClosestItem = function(x, y) {
+ var all = [];
+ var scan = function(item) {
+ var rect = item.getDOM().content.getBoundingClientRect();
+ var dx = rect.left + rect.width / 2 - x;
+ var dy = rect.top + rect.height / 2 - y;
+ all.push({
+ item,
+ dx,
+ dy
+ });
+ if (!item.isCollapsed()) {
+ item.getChildren().forEach(scan);
+ }
+ };
+ scan(this._root);
+ all.sort(function(a, b) {
+ var da = a.dx * a.dx + a.dy * a.dy;
+ var db = b.dx * b.dx + b.dy * b.dy;
+ return da - db;
+ });
+ return all[0];
+ };
+ MM.Map.prototype.getItemFor = function(node) {
+ var port = this._root.getDOM().node.parentNode;
+ while (node != port && !node.classList.contains("content")) {
+ node = node.parentNode;
+ }
+ if (node == port) {
+ return null;
+ }
+ var scan = function(item, node2) {
+ if (item.getDOM().content == node2) {
+ return item;
+ }
+ var children = item.getChildren();
+ for (var i = 0; i < children.length; i++) {
+ var result = scan(children[i], node2);
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+ };
+ return scan(this._root, node);
+ };
+ MM.Map.prototype.ensureItemVisibility = function(item) {
+ var padding = 10;
+ var node = item.getDOM().content;
+ var itemRect = node.getBoundingClientRect();
+ var root = this._root.getDOM().node;
+ var parentRect = root.parentNode.getBoundingClientRect();
+ var delta = [0, 0];
+ var dx = parentRect.left - itemRect.left + padding;
+ if (dx > 0) {
+ delta[0] = dx;
+ }
+ var dx = parentRect.right - itemRect.right - padding;
+ if (dx < 0) {
+ delta[0] = dx;
+ }
+ var dy = parentRect.top - itemRect.top + padding;
+ if (dy > 0) {
+ delta[1] = dy;
+ }
+ var dy = parentRect.bottom - itemRect.bottom - padding;
+ if (dy < 0) {
+ delta[1] = dy;
+ }
+ if (delta[0] || delta[1]) {
+ this.moveBy(delta[0], delta[1]);
+ }
+ };
+ MM.Map.prototype.getParent = function() {
+ return null;
+ };
+ MM.Map.prototype.getRoot = function() {
+ return this._root;
+ };
+ MM.Map.prototype.getName = function() {
+ var name = this._root.getText();
+ return MM.Format.br2nl(name).replace(/\n/g, " ").replace(/<.*?>/g, "").trim();
+ };
+ MM.Map.prototype.getId = function() {
+ return this._root.getId();
+ };
+ MM.Map.prototype.pick = function(item, direction) {
+ var candidates = [];
+ var currentRect = item.getDOM().content.getBoundingClientRect();
+ this._getPickCandidates(currentRect, this._root, direction, candidates);
+ if (!candidates.length) {
+ return item;
+ }
+ candidates.sort(function(a, b) {
+ return a.dist - b.dist;
+ });
+ return candidates[0].item;
+ };
+ MM.Map.prototype._getPickCandidates = function(currentRect, item, direction, candidates) {
+ if (!item.isCollapsed()) {
+ item.getChildren().forEach(function(child) {
+ this._getPickCandidates(currentRect, child, direction, candidates);
+ }, this);
+ }
+ var node = item.getDOM().content;
+ var rect = node.getBoundingClientRect();
+ if (direction == "left" || direction == "right") {
+ var x1 = currentRect.left + currentRect.width / 2;
+ var x2 = rect.left + rect.width / 2;
+ if (direction == "left" && x2 > x1) {
+ return;
+ }
+ if (direction == "right" && x2 < x1) {
+ return;
+ }
+ var diff1 = currentRect.top - rect.bottom;
+ var diff2 = rect.top - currentRect.bottom;
+ var dist = Math.abs(x2 - x1);
+ } else {
+ var y1 = currentRect.top + currentRect.height / 2;
+ var y2 = rect.top + rect.height / 2;
+ if (direction == "top" && y2 > y1) {
+ return;
+ }
+ if (direction == "bottom" && y2 < y1) {
+ return;
+ }
+ var diff1 = currentRect.left - rect.right;
+ var diff2 = rect.left - currentRect.right;
+ var dist = Math.abs(y2 - y1);
+ }
+ var diff = Math.max(diff1, diff2);
+ if (diff > 0) {
+ return;
+ }
+ if (!dist || dist < diff) {
+ return;
+ }
+ candidates.push({ item, dist });
+ };
+ MM.Map.prototype._moveTo = function(left, top) {
+ this._position = [left, top];
+ var node = this._root.getDOM().node;
+ node.style.left = left + "px";
+ node.style.top = top + "px";
+ };
+ MM.Map.prototype._setRoot = function(item) {
+ this._root = item;
+ this._root.setParent(this);
+ };
+
+ // .js/keyboard.js
+ MM.Keyboard = {};
+ MM.Keyboard.init = function() {
+ window.addEventListener("keydown", this);
+ window.addEventListener("keypress", this);
+ };
+ MM.Keyboard.handleEvent = function(e) {
+ var node = document.activeElement;
+ while (node && node != document) {
+ if (node.classList.contains("ui")) {
+ return;
+ }
+ node = node.parentNode;
+ }
+ var commands = MM.Command.getAll();
+ for (var i = 0; i < commands.length; i++) {
+ var command = commands[i];
+ if (!command.isValid()) {
+ continue;
+ }
+ var keys = command.keys;
+ for (var j = 0; j < keys.length; j++) {
+ if (this._keyOK(keys[j], e)) {
+ command.prevent && e.preventDefault();
+ command.execute(e);
+ return;
+ }
+ }
+ }
+ };
+ MM.Keyboard._keyOK = function(key, e) {
+ if ("keyCode" in key && e.type != "keydown") {
+ return false;
+ }
+ if ("charCode" in key && e.type != "keypress") {
+ return false;
+ }
+ for (var p in key) {
+ if (key[p] != e[p]) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // .js/tip.js
+ MM.Tip = {
+ _node: null,
+ handleEvent: function() {
+ this._hide();
+ },
+ handleMessage: function() {
+ this._hide();
+ },
+ init: function() {
+ this._node = document.querySelector("#tip");
+ this._node.addEventListener("click", this);
+ MM.subscribe("command-child", this);
+ MM.subscribe("command-sibling", this);
+ },
+ _hide: function() {
+ MM.unsubscribe("command-child", this);
+ MM.unsubscribe("command-sibling", this);
+ this._node.removeEventListener("click", this);
+ this._node.classList.add("hidden");
+ this._node = null;
+ }
+ };
+
+ // .js/action.js
+ MM.Action = function() {
+ };
+ MM.Action.prototype.perform = function() {
+ };
+ MM.Action.prototype.undo = function() {
+ };
+ MM.Action.Multi = function(actions) {
+ this._actions = actions;
+ };
+ MM.Action.Multi.prototype = Object.create(MM.Action.prototype);
+ MM.Action.Multi.prototype.perform = function() {
+ this._actions.forEach(function(action) {
+ action.perform();
+ });
+ };
+ MM.Action.Multi.prototype.undo = function() {
+ this._actions.slice().reverse().forEach(function(action) {
+ action.undo();
+ });
+ };
+ MM.Action.InsertNewItem = function(parent, index) {
+ this._parent = parent;
+ this._index = index;
+ this._item = new MM.Item();
+ };
+ MM.Action.InsertNewItem.prototype = Object.create(MM.Action.prototype);
+ MM.Action.InsertNewItem.prototype.perform = function() {
+ this._parent.expand();
+ this._item = this._parent.insertChild(this._item, this._index);
+ MM.App.select(this._item);
+ };
+ MM.Action.InsertNewItem.prototype.undo = function() {
+ this._parent.removeChild(this._item);
+ MM.App.select(this._parent);
+ };
+ MM.Action.AppendItem = function(parent, item) {
+ this._parent = parent;
+ this._item = item;
+ };
+ MM.Action.AppendItem.prototype = Object.create(MM.Action.prototype);
+ MM.Action.AppendItem.prototype.perform = function() {
+ this._parent.insertChild(this._item);
+ MM.App.select(this._item);
+ };
+ MM.Action.AppendItem.prototype.undo = function() {
+ this._parent.removeChild(this._item);
+ MM.App.select(this._parent);
+ };
+ MM.Action.RemoveItem = function(item) {
+ this._item = item;
+ this._parent = item.getParent();
+ this._index = this._parent.getChildren().indexOf(this._item);
+ };
+ MM.Action.RemoveItem.prototype = Object.create(MM.Action.prototype);
+ MM.Action.RemoveItem.prototype.perform = function() {
+ this._parent.removeChild(this._item);
+ MM.App.select(this._parent);
+ };
+ MM.Action.RemoveItem.prototype.undo = function() {
+ this._parent.insertChild(this._item, this._index);
+ MM.App.select(this._item);
+ };
+ MM.Action.MoveItem = function(item, newParent, newIndex, newSide) {
+ this._item = item;
+ this._newParent = newParent;
+ this._newIndex = arguments.length < 3 ? null : newIndex;
+ this._newSide = newSide || "";
+ this._oldParent = item.getParent();
+ this._oldIndex = this._oldParent.getChildren().indexOf(item);
+ this._oldSide = item.getSide();
+ };
+ MM.Action.MoveItem.prototype = Object.create(MM.Action.prototype);
+ MM.Action.MoveItem.prototype.perform = function() {
+ this._item.setSide(this._newSide);
+ if (this._newIndex === null) {
+ this._newParent.insertChild(this._item);
+ } else {
+ this._newParent.insertChild(this._item, this._newIndex);
+ }
+ MM.App.select(this._item);
+ };
+ MM.Action.MoveItem.prototype.undo = function() {
+ this._item.setSide(this._oldSide);
+ this._oldParent.insertChild(this._item, this._oldIndex);
+ MM.App.select(this._newParent);
+ };
+ MM.Action.Swap = function(item, diff) {
+ this._item = item;
+ this._parent = item.getParent();
+ var children = this._parent.getChildren();
+ var sibling = this._parent.getLayout().pickSibling(this._item, diff);
+ this._sourceIndex = children.indexOf(this._item);
+ this._targetIndex = children.indexOf(sibling);
+ };
+ MM.Action.Swap.prototype = Object.create(MM.Action.prototype);
+ MM.Action.Swap.prototype.perform = function() {
+ this._parent.insertChild(this._item, this._targetIndex);
+ };
+ MM.Action.Swap.prototype.undo = function() {
+ this._parent.insertChild(this._item, this._sourceIndex);
+ };
+ MM.Action.SetLayout = function(item, layout) {
+ this._item = item;
+ this._layout = layout;
+ this._oldLayout = item.getOwnLayout();
+ };
+ MM.Action.SetLayout.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetLayout.prototype.perform = function() {
+ this._item.setLayout(this._layout);
+ };
+ MM.Action.SetLayout.prototype.undo = function() {
+ this._item.setLayout(this._oldLayout);
+ };
+ MM.Action.SetShape = function(item, shape) {
+ this._item = item;
+ this._shape = shape;
+ this._oldShape = item.getOwnShape();
+ };
+ MM.Action.SetShape.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetShape.prototype.perform = function() {
+ this._item.setShape(this._shape);
+ };
+ MM.Action.SetShape.prototype.undo = function() {
+ this._item.setShape(this._oldShape);
+ };
+ MM.Action.SetColor = function(item, color) {
+ this._item = item;
+ this._color = color;
+ this._oldColor = item.getOwnColor();
+ };
+ MM.Action.SetColor.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetColor.prototype.perform = function() {
+ this._item.setColor(this._color);
+ };
+ MM.Action.SetColor.prototype.undo = function() {
+ this._item.setColor(this._oldColor);
+ };
+ MM.Action.SetText = function(item, text) {
+ this._item = item;
+ this._text = text;
+ this._oldText = item.getText();
+ this._oldValue = item.getValue();
+ };
+ MM.Action.SetText.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetText.prototype.perform = function() {
+ this._item.setText(this._text);
+ var numText = Number(this._text);
+ if (numText == this._text) {
+ this._item.setValue(numText);
+ }
+ };
+ MM.Action.SetText.prototype.undo = function() {
+ this._item.setText(this._oldText);
+ this._item.setValue(this._oldValue);
+ };
+ MM.Action.SetValue = function(item, value) {
+ this._item = item;
+ this._value = value;
+ this._oldValue = item.getValue();
+ };
+ MM.Action.SetValue.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetValue.prototype.perform = function() {
+ this._item.setValue(this._value);
+ };
+ MM.Action.SetValue.prototype.undo = function() {
+ this._item.setValue(this._oldValue);
+ };
+ MM.Action.SetStatus = function(item, status) {
+ this._item = item;
+ this._status = status;
+ this._oldStatus = item.getStatus();
+ };
+ MM.Action.SetStatus.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetStatus.prototype.perform = function() {
+ this._item.setStatus(this._status);
+ };
+ MM.Action.SetStatus.prototype.undo = function() {
+ this._item.setStatus(this._oldStatus);
+ };
+ MM.Action.SetIcon = function(item, icon) {
+ this._item = item;
+ this._icon = icon;
+ this._oldIcon = item.getIcon();
+ };
+ MM.Action.SetIcon.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetIcon.prototype.perform = function() {
+ this._item.setIcon(this._icon);
+ };
+ MM.Action.SetIcon.prototype.undo = function() {
+ this._item.setIcon(this._oldIcon);
+ };
+ MM.Action.SetSide = function(item, side) {
+ this._item = item;
+ this._side = side;
+ this._oldSide = item.getSide();
+ };
+ MM.Action.SetSide.prototype = Object.create(MM.Action.prototype);
+ MM.Action.SetSide.prototype.perform = function() {
+ this._item.setSide(this._side);
+ this._item.getMap().update();
+ };
+ MM.Action.SetSide.prototype.undo = function() {
+ this._item.setSide(this._oldSide);
+ this._item.getMap().update();
+ };
+
+ // .js/clipboard.js
+ MM.Clipboard = {
+ _item: null,
+ _mode: "",
+ _delay: 50,
+ _node: document.createElement("textarea")
+ };
+ MM.Clipboard.init = function() {
+ this._node.style.position = "absolute";
+ this._node.style.width = 0;
+ this._node.style.height = 0;
+ this._node.style.left = "-100px";
+ this._node.style.top = "-100px";
+ document.body.appendChild(this._node);
+ };
+ MM.Clipboard.focus = function() {
+ this._node.focus();
+ this._empty();
+ };
+ MM.Clipboard.copy = function(sourceItem) {
+ this._endCut();
+ this._item = sourceItem.clone();
+ this._mode = "copy";
+ this._expose();
+ };
+ MM.Clipboard.paste = function(targetItem) {
+ setTimeout(function() {
+ var pasted = this._node.value;
+ this._empty();
+ if (!pasted) {
+ return;
+ }
+ if (this._item && pasted == MM.Format.Plaintext.to(this._item.toJSON())) {
+ this._pasteItem(this._item, targetItem);
+ } else {
+ this._pastePlaintext(pasted, targetItem);
+ }
+ }.bind(this), this._delay);
+ };
+ MM.Clipboard._pasteItem = function(sourceItem, targetItem) {
+ switch (this._mode) {
+ case "cut":
+ if (sourceItem == targetItem || sourceItem.getParent() == targetItem) {
+ this._endCut();
+ return;
+ }
+ var item = targetItem;
+ while (!item.isRoot()) {
+ if (item == sourceItem) {
+ return;
+ }
+ item = item.getParent();
+ }
+ var action = new MM.Action.MoveItem(sourceItem, targetItem);
+ MM.App.action(action);
+ this._endCut();
+ break;
+ case "copy":
+ var action = new MM.Action.AppendItem(targetItem, sourceItem.clone());
+ MM.App.action(action);
+ break;
+ }
+ };
+ MM.Clipboard._pastePlaintext = function(plaintext, targetItem) {
+ if (this._mode == "cut") {
+ this._endCut();
+ }
+ var json = MM.Format.Plaintext.from(plaintext);
+ var map = MM.Map.fromJSON(json);
+ var root = map.getRoot();
+ if (root.getText()) {
+ var action = new MM.Action.AppendItem(targetItem, root);
+ MM.App.action(action);
+ } else {
+ var actions = root.getChildren().map(function(item) {
+ return new MM.Action.AppendItem(targetItem, item);
+ });
+ var action = new MM.Action.Multi(actions);
+ MM.App.action(action);
+ }
+ };
+ MM.Clipboard.cut = function(sourceItem) {
+ this._endCut();
+ this._item = sourceItem;
+ this._item.getDOM().node.classList.add("cut");
+ this._mode = "cut";
+ this._expose();
+ };
+ MM.Clipboard._expose = function() {
+ var json = this._item.toJSON();
+ var plaintext = MM.Format.Plaintext.to(json);
+ this._node.value = plaintext;
+ this._node.selectionStart = 0;
+ this._node.selectionEnd = this._node.value.length;
+ setTimeout(this._empty.bind(this), this._delay);
+ };
+ MM.Clipboard._empty = function() {
+ this._node.value = "\n";
+ this._node.selectionStart = 0;
+ this._node.selectionEnd = this._node.value.length;
+ };
+ MM.Clipboard._endCut = function() {
+ if (this._mode != "cut") {
+ return;
+ }
+ this._item.getDOM().node.classList.remove("cut");
+ this._item = null;
+ this._mode = "";
+ };
+
+ // .js/menu.js
+ MM.Menu = {
+ _dom: {},
+ _port: null,
+ open: function(x, y) {
+ this._dom.node.style.display = "";
+ var w = this._dom.node.offsetWidth;
+ var h = this._dom.node.offsetHeight;
+ var left = x;
+ var top = y;
+ if (left > this._port.offsetWidth / 2) {
+ left -= w;
+ }
+ if (top > this._port.offsetHeight / 2) {
+ top -= h;
+ }
+ this._dom.node.style.left = left + "px";
+ this._dom.node.style.top = top + "px";
+ },
+ close: function() {
+ this._dom.node.style.display = "none";
+ },
+ handleEvent: function(e) {
+ if (e.currentTarget != this._dom.node) {
+ this.close();
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ var command = e.target.getAttribute("data-command");
+ if (!command) {
+ return;
+ }
+ command = MM.Command[command];
+ if (!command.isValid()) {
+ return;
+ }
+ command.execute();
+ this.close();
+ },
+ init: function(port) {
+ this._port = port;
+ this._dom.node = document.querySelector("#menu");
+ var buttons = this._dom.node.querySelectorAll("[data-command]");
+ [].slice.call(buttons).forEach(function(button) {
+ button.innerHTML = MM.Command[button.getAttribute("data-command")].label;
+ });
+ this._port.addEventListener("mousedown", this);
+ this._dom.node.addEventListener("mousedown", this);
+ this.close();
+ }
+ };
+
+ // .js/command/command.js
+ MM.Command = Object.create(MM.Repo, {
+ keys: { value: [] },
+ editMode: { value: false },
+ prevent: { value: true },
+ label: { value: "" }
+ });
+ MM.Command.isValid = function() {
+ return this.editMode === null || this.editMode == MM.App.editing;
+ };
+ MM.Command.execute = function() {
+ };
+ MM.Command.Notes = Object.create(MM.Command, {
+ label: { value: "Notes" },
+ keys: { value: [{ keyCode: "M".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Notes.isValid = function() {
+ return MM.Command.isValid.call(this);
+ };
+ MM.Command.Notes.execute = function() {
+ MM.App.notes.toggle();
+ };
+ MM.Command.Undo = Object.create(MM.Command, {
+ label: { value: "Undo" },
+ keys: { value: [{ keyCode: "Z".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Undo.isValid = function() {
+ return MM.Command.isValid.call(this) && !!MM.App.historyIndex;
+ };
+ MM.Command.Undo.execute = function() {
+ MM.App.history[MM.App.historyIndex - 1].undo();
+ MM.App.historyIndex--;
+ };
+ MM.Command.Redo = Object.create(MM.Command, {
+ label: { value: "Redo" },
+ keys: { value: [{ keyCode: "Y".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Redo.isValid = function() {
+ return MM.Command.isValid.call(this) && MM.App.historyIndex != MM.App.history.length;
+ };
+ MM.Command.Redo.execute = function() {
+ MM.App.history[MM.App.historyIndex].perform();
+ MM.App.historyIndex++;
+ };
+ MM.Command.InsertSibling = Object.create(MM.Command, {
+ label: { value: "Insert a sibling" },
+ keys: { value: [{ keyCode: 13 }] }
+ });
+ MM.Command.InsertSibling.execute = function() {
+ var item = MM.App.current;
+ if (item.isRoot()) {
+ var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ } else {
+ var parent = item.getParent();
+ var index = parent.getChildren().indexOf(item);
+ var action = new MM.Action.InsertNewItem(parent, index + 1);
+ }
+ MM.App.action(action);
+ MM.Command.Edit.execute();
+ MM.publish("command-sibling");
+ };
+ MM.Command.InsertChild = Object.create(MM.Command, {
+ label: { value: "Insert a child" },
+ keys: { value: [
+ { keyCode: 9, ctrlKey: false },
+ { keyCode: 45 }
+ ] }
+ });
+ MM.Command.InsertChild.execute = function() {
+ var item = MM.App.current;
+ var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ MM.App.action(action);
+ MM.Command.Edit.execute();
+ MM.publish("command-child");
+ };
+ MM.Command.Delete = Object.create(MM.Command, {
+ label: { value: "Delete an item" },
+ keys: { value: [{ keyCode: MM.isMac() ? 8 : 46 }] }
+ });
+ MM.Command.Delete.isValid = function() {
+ return MM.Command.isValid.call(this) && !MM.App.current.isRoot();
+ };
+ MM.Command.Delete.execute = function() {
+ var action = new MM.Action.RemoveItem(MM.App.current);
+ MM.App.action(action);
+ };
+ MM.Command.Swap = Object.create(MM.Command, {
+ label: { value: "Swap sibling" },
+ keys: { value: [
+ { keyCode: 38, ctrlKey: true },
+ { keyCode: 40, ctrlKey: true }
+ ] }
+ });
+ MM.Command.Swap.execute = function(e) {
+ var current = MM.App.current;
+ if (current.isRoot() || current.getParent().getChildren().length < 2) {
+ return;
+ }
+ var diff = e.keyCode == 38 ? -1 : 1;
+ var action = new MM.Action.Swap(MM.App.current, diff);
+ MM.App.action(action);
+ };
+ MM.Command.Side = Object.create(MM.Command, {
+ label: { value: "Change side" },
+ keys: { value: [
+ { keyCode: 37, ctrlKey: true },
+ { keyCode: 39, ctrlKey: true }
+ ] }
+ });
+ MM.Command.Side.execute = function(e) {
+ var current = MM.App.current;
+ if (current.isRoot() || !current.getParent().isRoot()) {
+ return;
+ }
+ var side = e.keyCode == 37 ? "left" : "right";
+ var action = new MM.Action.SetSide(MM.App.current, side);
+ MM.App.action(action);
+ };
+ MM.Command.Save = Object.create(MM.Command, {
+ label: { value: "Save map" },
+ keys: { value: [{ keyCode: "S".charCodeAt(0), ctrlKey: true, shiftKey: false }] }
+ });
+ MM.Command.Save.execute = function() {
+ MM.App.io.quickSave();
+ };
+ MM.Command.SaveAs = Object.create(MM.Command, {
+ label: { value: "Save as…" },
+ keys: { value: [{ keyCode: "S".charCodeAt(0), ctrlKey: true, shiftKey: true }] }
+ });
+ MM.Command.SaveAs.execute = function() {
+ MM.App.io.show("save");
+ };
+ MM.Command.Load = Object.create(MM.Command, {
+ label: { value: "Load map" },
+ keys: { value: [{ keyCode: "O".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Load.execute = function() {
+ MM.App.io.show("load");
+ };
+ MM.Command.Center = Object.create(MM.Command, {
+ label: { value: "Center map" },
+ keys: { value: [{ keyCode: 35 }] }
+ });
+ MM.Command.Center.execute = function() {
+ MM.App.map.center();
+ };
+ MM.Command.New = Object.create(MM.Command, {
+ label: { value: "New map" },
+ keys: { value: [{ keyCode: "N".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.New.execute = function() {
+ if (!confirm("Throw away your current map and start a new one?")) {
+ return;
+ }
+ var map = new MM.Map();
+ MM.App.setMap(map);
+ MM.publish("map-new", this);
+ };
+ MM.Command.ZoomIn = Object.create(MM.Command, {
+ label: { value: "Zoom in" },
+ keys: { value: [{ charCode: "+".charCodeAt(0) }] }
+ });
+ MM.Command.ZoomIn.execute = function() {
+ MM.App.adjustFontSize(1);
+ };
+ MM.Command.ZoomOut = Object.create(MM.Command, {
+ label: { value: "Zoom out" },
+ keys: { value: [{ charCode: "-".charCodeAt(0) }] }
+ });
+ MM.Command.ZoomOut.execute = function() {
+ MM.App.adjustFontSize(-1);
+ };
+ MM.Command.Help = Object.create(MM.Command, {
+ label: { value: "Show/hide help" },
+ keys: { value: [{ charCode: "?".charCodeAt(0) }] }
+ });
+ MM.Command.Help.execute = function() {
+ MM.App.help.toggle();
+ };
+ MM.Command.UI = Object.create(MM.Command, {
+ label: { value: "Show/hide UI" },
+ keys: { value: [{ charCode: "*".charCodeAt(0) }] }
+ });
+ MM.Command.UI.execute = function() {
+ MM.App.ui.toggle();
+ };
+ MM.Command.Pan = Object.create(MM.Command, {
+ label: { value: "Pan the map" },
+ keys: { value: [
+ { keyCode: "W".charCodeAt(0), ctrlKey: false, altKey: false, metaKey: false },
+ { keyCode: "A".charCodeAt(0), ctrlKey: false, altKey: false, metaKey: false },
+ { keyCode: "S".charCodeAt(0), ctrlKey: false, altKey: false, metaKey: false },
+ { keyCode: "D".charCodeAt(0), ctrlKey: false, altKey: false, metaKey: false }
+ ] },
+ chars: { value: [] }
+ });
+ MM.Command.Pan.execute = function(e) {
+ var ch = String.fromCharCode(e.keyCode);
+ var index = this.chars.indexOf(ch);
+ if (index > -1) {
+ return;
+ }
+ if (!this.chars.length) {
+ window.addEventListener("keyup", this);
+ this.interval = setInterval(this._step.bind(this), 50);
+ }
+ this.chars.push(ch);
+ this._step();
+ };
+ MM.Command.Pan._step = function() {
+ var dirs = {
+ "W": [0, 1],
+ "A": [1, 0],
+ "S": [0, -1],
+ "D": [-1, 0]
+ };
+ var offset = [0, 0];
+ this.chars.forEach(function(ch) {
+ offset[0] += dirs[ch][0];
+ offset[1] += dirs[ch][1];
+ });
+ MM.App.map.moveBy(15 * offset[0], 15 * offset[1]);
+ };
+ MM.Command.Pan.handleEvent = function(e) {
+ var ch = String.fromCharCode(e.keyCode);
+ var index = this.chars.indexOf(ch);
+ if (index > -1) {
+ this.chars.splice(index, 1);
+ if (!this.chars.length) {
+ window.removeEventListener("keyup", this);
+ clearInterval(this.interval);
+ }
+ }
+ };
+ MM.Command.Copy = Object.create(MM.Command, {
+ label: { value: "Copy" },
+ prevent: { value: false },
+ keys: { value: [
+ { keyCode: "C".charCodeAt(0), ctrlKey: true },
+ { keyCode: "C".charCodeAt(0), metaKey: true }
+ ] }
+ });
+ MM.Command.Copy.execute = function() {
+ MM.Clipboard.copy(MM.App.current);
+ };
+ MM.Command.Cut = Object.create(MM.Command, {
+ label: { value: "Cut" },
+ prevent: { value: false },
+ keys: { value: [
+ { keyCode: "X".charCodeAt(0), ctrlKey: true },
+ { keyCode: "X".charCodeAt(0), metaKey: true }
+ ] }
+ });
+ MM.Command.Cut.execute = function() {
+ MM.Clipboard.cut(MM.App.current);
+ };
+ MM.Command.Paste = Object.create(MM.Command, {
+ label: { value: "Paste" },
+ prevent: { value: false },
+ keys: { value: [
+ { keyCode: "V".charCodeAt(0), ctrlKey: true },
+ { keyCode: "V".charCodeAt(0), metaKey: true }
+ ] }
+ });
+ MM.Command.Paste.execute = function() {
+ MM.Clipboard.paste(MM.App.current);
+ };
+ MM.Command.Fold = Object.create(MM.Command, {
+ label: { value: "Fold/Unfold" },
+ keys: { value: [{ charCode: "f".charCodeAt(0), ctrlKey: false }] }
+ });
+ MM.Command.Fold.execute = function() {
+ var item = MM.App.current;
+ if (item.isCollapsed()) {
+ item.expand();
+ } else {
+ item.collapse();
+ }
+ MM.App.map.ensureItemVisibility(item);
+ };
+
+ // .js/command/command.edit.js
+ MM.Command.Edit = Object.create(MM.Command, {
+ label: { value: "Edit item" },
+ keys: { value: [
+ { keyCode: 32 },
+ { keyCode: 113 }
+ ] }
+ });
+ MM.Command.Edit.execute = function() {
+ MM.App.current.startEditing();
+ MM.App.editing = true;
+ };
+ MM.Command.Finish = Object.create(MM.Command, {
+ keys: { value: [{ keyCode: 13, altKey: false, ctrlKey: false, shiftKey: false }] },
+ editMode: { value: true }
+ });
+ MM.Command.Finish.execute = function() {
+ MM.App.editing = false;
+ var text = MM.App.current.stopEditing();
+ if (text) {
+ var action = new MM.Action.SetText(MM.App.current, text);
+ } else {
+ var action = new MM.Action.RemoveItem(MM.App.current);
+ }
+ MM.App.action(action);
+ };
+ MM.Command.Newline = Object.create(MM.Command, {
+ label: { value: "Line break" },
+ keys: { value: [
+ { keyCode: 13, shiftKey: true },
+ { keyCode: 13, ctrlKey: true }
+ ] },
+ editMode: { value: true }
+ });
+ MM.Command.Newline.execute = function() {
+ var range = getSelection().getRangeAt(0);
+ var br = document.createElement("br");
+ range.insertNode(br);
+ range.setStartAfter(br);
+ MM.App.current.updateSubtree();
+ };
+ MM.Command.Cancel = Object.create(MM.Command, {
+ editMode: { value: true },
+ keys: { value: [{ keyCode: 27 }] }
+ });
+ MM.Command.Cancel.execute = function() {
+ MM.App.editing = false;
+ MM.App.current.stopEditing();
+ var oldText = MM.App.current.getText();
+ if (!oldText) {
+ var action = new MM.Action.RemoveItem(MM.App.current);
+ MM.App.action(action);
+ }
+ };
+ MM.Command.Style = Object.create(MM.Command, {
+ editMode: { value: null },
+ command: { value: "" }
+ });
+ MM.Command.Style.execute = function() {
+ if (MM.App.editing) {
+ document.execCommand(this.command, null, null);
+ } else {
+ MM.Command.Edit.execute();
+ var selection = getSelection();
+ var range = selection.getRangeAt(0);
+ range.selectNodeContents(MM.App.current.getDOM().text);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ this.execute();
+ MM.Command.Finish.execute();
+ }
+ };
+ MM.Command.Bold = Object.create(MM.Command.Style, {
+ command: { value: "bold" },
+ label: { value: "Bold" },
+ keys: { value: [{ keyCode: "B".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Underline = Object.create(MM.Command.Style, {
+ command: { value: "underline" },
+ label: { value: "Underline" },
+ keys: { value: [{ keyCode: "U".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Italic = Object.create(MM.Command.Style, {
+ command: { value: "italic" },
+ label: { value: "Italic" },
+ keys: { value: [{ keyCode: "I".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Strikethrough = Object.create(MM.Command.Style, {
+ command: { value: "strikeThrough" },
+ label: { value: "Strike-through" },
+ keys: { value: [{ keyCode: "S".charCodeAt(0), ctrlKey: true }] }
+ });
+ MM.Command.Value = Object.create(MM.Command, {
+ label: { value: "Set value" },
+ keys: { value: [{ charCode: "v".charCodeAt(0), ctrlKey: false, metaKey: false }] }
+ });
+ MM.Command.Value.execute = function() {
+ var item = MM.App.current;
+ var oldValue = item.getValue();
+ var newValue = prompt("Set item value", oldValue);
+ if (newValue == null) {
+ return;
+ }
+ if (!newValue.length) {
+ newValue = null;
+ }
+ var numValue = parseFloat(newValue);
+ var action = new MM.Action.SetValue(item, isNaN(numValue) ? newValue : numValue);
+ MM.App.action(action);
+ };
+ MM.Command.Yes = Object.create(MM.Command, {
+ label: { value: "Yes" },
+ keys: { value: [{ charCode: "y".charCodeAt(0), ctrlKey: false }] }
+ });
+ MM.Command.Yes.execute = function() {
+ var item = MM.App.current;
+ var status = item.getStatus() == "yes" ? null : "yes";
+ var action = new MM.Action.SetStatus(item, status);
+ MM.App.action(action);
+ };
+ MM.Command.No = Object.create(MM.Command, {
+ label: { value: "No" },
+ keys: { value: [{ charCode: "n".charCodeAt(0), ctrlKey: false }] }
+ });
+ MM.Command.No.execute = function() {
+ var item = MM.App.current;
+ var status = item.getStatus() == "no" ? null : "no";
+ var action = new MM.Action.SetStatus(item, status);
+ MM.App.action(action);
+ };
+ MM.Command.Computed = Object.create(MM.Command, {
+ label: { value: "Computed" },
+ keys: { value: [{ charCode: "c".charCodeAt(0), ctrlKey: false, metaKey: false }] }
+ });
+ MM.Command.Computed.execute = function() {
+ var item = MM.App.current;
+ var status = item.getStatus() == "computed" ? null : "computed";
+ var action = new MM.Action.SetStatus(item, status);
+ MM.App.action(action);
+ };
+
+ // .js/command/command.select.js
+ MM.Command.Select = Object.create(MM.Command, {
+ label: { value: "Move selection" },
+ keys: { value: [
+ { keyCode: 38, ctrlKey: false },
+ { keyCode: 37, ctrlKey: false },
+ { keyCode: 40, ctrlKey: false },
+ { keyCode: 39, ctrlKey: false }
+ ] }
+ });
+ MM.Command.Select.execute = function(e) {
+ var dirs = {
+ 37: "left",
+ 38: "top",
+ 39: "right",
+ 40: "bottom"
+ };
+ var dir = dirs[e.keyCode];
+ var layout = MM.App.current.getLayout();
+ var item = layout.pick(MM.App.current, dir);
+ MM.App.select(item);
+ };
+ MM.Command.SelectRoot = Object.create(MM.Command, {
+ label: { value: "Select root" },
+ keys: { value: [{ keyCode: 36 }] }
+ });
+ MM.Command.SelectRoot.execute = function() {
+ var item = MM.App.current;
+ while (!item.isRoot()) {
+ item = item.getParent();
+ }
+ MM.App.select(item);
+ };
+ if (!MM.isMac()) {
+ MM.Command.SelectParent = Object.create(MM.Command, {
+ label: { value: "Select parent" },
+ keys: { value: [{ keyCode: 8 }] }
+ });
+ MM.Command.SelectParent.execute = function() {
+ if (MM.App.current.isRoot()) {
+ return;
+ }
+ MM.App.select(MM.App.current.getParent());
+ };
+ }
+
+ // .js/layout/layout.js
+ MM.Layout = Object.create(MM.Repo, {
+ ALL: { value: [] },
+ SPACING_RANK: { value: 4 },
+ SPACING_CHILD: { value: 4 }
+ });
+ MM.Layout.getAll = function() {
+ return this.ALL;
+ };
+ MM.Layout.update = function(item) {
+ return this;
+ };
+ MM.Layout.getChildDirection = function(child) {
+ return "";
+ };
+ MM.Layout.pick = function(item, dir) {
+ var opposite = {
+ left: "right",
+ right: "left",
+ top: "bottom",
+ bottom: "top"
+ };
+ if (!item.isCollapsed()) {
+ var children = item.getChildren();
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ if (this.getChildDirection(child) == dir) {
+ return child;
+ }
+ }
+ }
+ if (item.isRoot()) {
+ return item;
+ }
+ var parentLayout = item.getParent().getLayout();
+ var thisChildDirection = parentLayout.getChildDirection(item);
+ if (thisChildDirection == dir) {
+ return item;
+ } else if (thisChildDirection == opposite[dir]) {
+ return item.getParent();
+ } else {
+ return parentLayout.pickSibling(item, dir == "left" || dir == "top" ? -1 : 1);
+ }
+ };
+ MM.Layout.pickSibling = function(item, dir) {
+ if (item.isRoot()) {
+ return item;
+ }
+ var children = item.getParent().getChildren();
+ var index = children.indexOf(item);
+ index += dir;
+ index = (index + children.length) % children.length;
+ return children[index];
+ };
+ MM.Layout._anchorCanvas = function(item) {
+ var dom = item.getDOM();
+ dom.canvas.width = dom.node.offsetWidth;
+ dom.canvas.height = dom.node.offsetHeight;
+ };
+ MM.Layout._anchorToggle = function(item, x, y, side) {
+ var node = item.getDOM().toggle;
+ var w = node.offsetWidth;
+ var h = node.offsetHeight;
+ var l = x;
+ var t = y;
+ switch (side) {
+ case "left":
+ t -= h / 2;
+ l -= w;
+ break;
+ case "right":
+ t -= h / 2;
+ break;
+ case "top":
+ l -= w / 2;
+ t -= h;
+ break;
+ case "bottom":
+ l -= w / 2;
+ break;
+ }
+ node.style.left = Math.round(l) + "px";
+ node.style.top = Math.round(t) + "px";
+ };
+ MM.Layout._getChildAnchor = function(item, side) {
+ var dom = item.getDOM();
+ if (side == "left" || side == "right") {
+ var pos = dom.node.offsetLeft + dom.content.offsetLeft;
+ if (side == "left") {
+ pos += dom.content.offsetWidth;
+ }
+ } else {
+ var pos = dom.node.offsetTop + dom.content.offsetTop;
+ if (side == "top") {
+ pos += dom.content.offsetHeight;
+ }
+ }
+ return pos;
+ };
+ MM.Layout._computeChildrenBBox = function(children, childIndex) {
+ var bbox = [0, 0];
+ var rankIndex = (childIndex + 1) % 2;
+ children.forEach(function(child, index) {
+ var node = child.getDOM().node;
+ var childSize = [node.offsetWidth, node.offsetHeight];
+ bbox[rankIndex] = Math.max(bbox[rankIndex], childSize[rankIndex]);
+ bbox[childIndex] += childSize[childIndex];
+ }, this);
+ if (children.length > 1) {
+ bbox[childIndex] += this.SPACING_CHILD * (children.length - 1);
+ }
+ return bbox;
+ };
+ MM.Layout._alignItem = function(item, side) {
+ var dom = item.getDOM();
+ switch (side) {
+ case "left":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
+ dom.content.appendChild(dom.value);
+ dom.content.appendChild(dom.status);
+ break;
+ case "right":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
+ dom.content.insertBefore(dom.value, dom.content.firstChild);
+ dom.content.insertBefore(dom.status, dom.content.firstChild);
+ break;
+ }
+ };
+
+ // .js/layout/layout.graph.js
+ MM.Layout.Graph = Object.create(MM.Layout, {
+ SPACING_RANK: { value: 16 },
+ childDirection: { value: "" }
+ });
+ MM.Layout.Graph.getChildDirection = function(child) {
+ return this.childDirection;
+ };
+ MM.Layout.Graph.create = function(direction, id, label) {
+ var layout = Object.create(this, {
+ childDirection: { value: direction },
+ id: { value: id },
+ label: { value: label }
+ });
+ MM.Layout.ALL.push(layout);
+ return layout;
+ };
+ MM.Layout.Graph.update = function(item) {
+ var side = this.childDirection;
+ if (!item.isRoot()) {
+ side = item.getParent().getLayout().getChildDirection(item);
+ }
+ this._alignItem(item, side);
+ this._layoutItem(item, this.childDirection);
+ if (this.childDirection == "left" || this.childDirection == "right") {
+ this._drawLinesHorizontal(item, this.childDirection);
+ } else {
+ this._drawLinesVertical(item, this.childDirection);
+ }
+ return this;
+ };
+ MM.Layout.Graph._layoutItem = function(item, rankDirection) {
+ var sizeProps = ["width", "height"];
+ var posProps = ["left", "top"];
+ var rankIndex = rankDirection == "left" || rankDirection == "right" ? 0 : 1;
+ var childIndex = (rankIndex + 1) % 2;
+ var rankPosProp = posProps[rankIndex];
+ var childPosProp = posProps[childIndex];
+ var rankSizeProp = sizeProps[rankIndex];
+ var childSizeProp = sizeProps[childIndex];
+ var dom = item.getDOM();
+ var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ var bbox = this._computeChildrenBBox(item.getChildren(), childIndex);
+ var rankSize = contentSize[rankIndex];
+ if (bbox[rankIndex]) {
+ rankSize += bbox[rankIndex] + this.SPACING_RANK;
+ }
+ var childSize = Math.max(bbox[childIndex], contentSize[childIndex]);
+ dom.node.style[rankSizeProp] = rankSize + "px";
+ dom.node.style[childSizeProp] = childSize + "px";
+ var offset = [0, 0];
+ if (rankDirection == "right") {
+ offset[0] = contentSize[0] + this.SPACING_RANK;
+ }
+ if (rankDirection == "bottom") {
+ offset[1] = contentSize[1] + this.SPACING_RANK;
+ }
+ offset[childIndex] = Math.round((childSize - bbox[childIndex]) / 2);
+ this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
+ var labelPos = 0;
+ if (rankDirection == "left") {
+ labelPos = rankSize - contentSize[0];
+ }
+ if (rankDirection == "top") {
+ labelPos = rankSize - contentSize[1];
+ }
+ dom.content.style[childPosProp] = Math.round((childSize - contentSize[childIndex]) / 2) + "px";
+ dom.content.style[rankPosProp] = labelPos + "px";
+ return this;
+ };
+ MM.Layout.Graph._layoutChildren = function(children, rankDirection, offset, bbox) {
+ var posProps = ["left", "top"];
+ var rankIndex = rankDirection == "left" || rankDirection == "right" ? 0 : 1;
+ var childIndex = (rankIndex + 1) % 2;
+ var rankPosProp = posProps[rankIndex];
+ var childPosProp = posProps[childIndex];
+ children.forEach(function(child, index) {
+ var node = child.getDOM().node;
+ var childSize = [node.offsetWidth, node.offsetHeight];
+ if (rankDirection == "left") {
+ offset[0] = bbox[0] - childSize[0];
+ }
+ if (rankDirection == "top") {
+ offset[1] = bbox[1] - childSize[1];
+ }
+ node.style[childPosProp] = offset[childIndex] + "px";
+ node.style[rankPosProp] = offset[rankIndex] + "px";
+ offset[childIndex] += childSize[childIndex] + this.SPACING_CHILD;
+ }, this);
+ return bbox;
+ };
+ MM.Layout.Graph._drawLinesHorizontal = function(item, side) {
+ this._anchorCanvas(item);
+ this._drawHorizontalConnectors(item, side, item.getChildren());
+ };
+ MM.Layout.Graph._drawLinesVertical = function(item, side) {
+ this._anchorCanvas(item);
+ this._drawVerticalConnectors(item, side, item.getChildren());
+ };
+ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
+ if (children.length == 0) {
+ return;
+ }
+ var dom = item.getDOM();
+ var canvas = dom.canvas;
+ var ctx = canvas.getContext("2d");
+ ctx.strokeStyle = item.getColor();
+ var R = this.SPACING_RANK / 2;
+ var y1 = item.getShape().getVerticalAnchor(item);
+ if (side == "left") {
+ var x1 = dom.content.offsetLeft - 0.5;
+ } else {
+ var x1 = dom.content.offsetWidth + dom.content.offsetLeft + 0.5;
+ }
+ this._anchorToggle(item, x1, y1, side);
+ if (item.isCollapsed()) {
+ return;
+ }
+ if (children.length == 1) {
+ var child = children[0];
+ var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ var x2 = this._getChildAnchor(child, side);
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.bezierCurveTo((x1 + x2) / 2, y1, (x1 + x2) / 2, y2, x2, y2);
+ ctx.stroke();
+ return;
+ }
+ if (side == "left") {
+ var x2 = x1 - R;
+ } else {
+ var x2 = x1 + R;
+ }
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y1);
+ ctx.stroke();
+ var c1 = children[0];
+ var c2 = children[children.length - 1];
+ var x = x2;
+ var xx = x + (side == "left" ? -R : R);
+ var y1 = c1.getShape().getVerticalAnchor(c1) + c1.getDOM().node.offsetTop;
+ var y2 = c2.getShape().getVerticalAnchor(c2) + c2.getDOM().node.offsetTop;
+ var x1 = this._getChildAnchor(c1, side);
+ var x2 = this._getChildAnchor(c2, side);
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(xx, y1);
+ ctx.arcTo(x, y1, x, y1 + R, R);
+ ctx.lineTo(x, y2 - R);
+ ctx.arcTo(x, y2, xx, y2, R);
+ ctx.lineTo(x2, y2);
+ for (var i = 1; i < children.length - 1; i++) {
+ var c = children[i];
+ var y = c.getShape().getVerticalAnchor(c) + c.getDOM().node.offsetTop;
+ ctx.moveTo(x, y);
+ ctx.lineTo(this._getChildAnchor(c, side), y);
+ }
+ ctx.stroke();
+ };
+ MM.Layout.Graph._drawVerticalConnectors = function(item, side, children) {
+ if (children.length == 0) {
+ return;
+ }
+ var dom = item.getDOM();
+ var canvas = dom.canvas;
+ var ctx = canvas.getContext("2d");
+ ctx.strokeStyle = item.getColor();
+ var R = this.SPACING_RANK / 2;
+ var x = item.getShape().getHorizontalAnchor(item);
+ var height = children.length == 1 ? 2 * R : R;
+ if (side == "top") {
+ var y1 = canvas.height - dom.content.offsetHeight;
+ var y2 = y1 - height;
+ this._anchorToggle(item, x, y1, side);
+ } else {
+ var y1 = item.getShape().getVerticalAnchor(item);
+ var y2 = dom.content.offsetHeight + height;
+ this._anchorToggle(item, x, dom.content.offsetHeight, side);
+ }
+ ctx.beginPath();
+ ctx.moveTo(x, y1);
+ ctx.lineTo(x, y2);
+ ctx.stroke();
+ if (children.length == 1) {
+ return;
+ }
+ var c1 = children[0];
+ var c2 = children[children.length - 1];
+ var offset = dom.content.offsetHeight + height;
+ var y = Math.round(side == "top" ? canvas.height - offset : offset) + 0.5;
+ var x1 = c1.getShape().getHorizontalAnchor(c1) + c1.getDOM().node.offsetLeft;
+ var x2 = c2.getShape().getHorizontalAnchor(c2) + c2.getDOM().node.offsetLeft;
+ var y1 = this._getChildAnchor(c1, side);
+ var y2 = this._getChildAnchor(c2, side);
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.arcTo(x1, y, x1 + R, y, R);
+ ctx.lineTo(x2 - R, y);
+ ctx.arcTo(x2, y, x2, y2, R);
+ for (var i = 1; i < children.length - 1; i++) {
+ var c = children[i];
+ var x = c.getShape().getHorizontalAnchor(c) + c.getDOM().node.offsetLeft;
+ ctx.moveTo(x, y);
+ ctx.lineTo(x, this._getChildAnchor(c, side));
+ }
+ ctx.stroke();
+ };
+ MM.Layout.Graph.Down = MM.Layout.Graph.create("bottom", "graph-bottom", "Bottom");
+ MM.Layout.Graph.Up = MM.Layout.Graph.create("top", "graph-top", "Top");
+ MM.Layout.Graph.Left = MM.Layout.Graph.create("left", "graph-left", "Left");
+ MM.Layout.Graph.Right = MM.Layout.Graph.create("right", "graph-right", "Right");
+
+ // .js/layout/layout.tree.js
+ MM.Layout.Tree = Object.create(MM.Layout, {
+ SPACING_RANK: { value: 32 },
+ childDirection: { value: "" }
+ });
+ MM.Layout.Tree.getChildDirection = function(child) {
+ return this.childDirection;
+ };
+ MM.Layout.Tree.create = function(direction, id, label) {
+ var layout = Object.create(this, {
+ childDirection: { value: direction },
+ id: { value: id },
+ label: { value: label }
+ });
+ MM.Layout.ALL.push(layout);
+ return layout;
+ };
+ MM.Layout.Tree.update = function(item) {
+ var side = this.childDirection;
+ if (!item.isRoot()) {
+ side = item.getParent().getLayout().getChildDirection(item);
+ }
+ this._alignItem(item, side);
+ this._layoutItem(item, this.childDirection);
+ this._anchorCanvas(item);
+ this._drawLines(item, this.childDirection);
+ return this;
+ };
+ MM.Layout.Tree._layoutItem = function(item, rankDirection) {
+ var dom = item.getDOM();
+ var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ var bbox = this._computeChildrenBBox(item.getChildren(), 1);
+ var rankSize = contentSize[0];
+ var childSize = bbox[1] + contentSize[1];
+ if (bbox[0]) {
+ rankSize = Math.max(rankSize, bbox[0] + this.SPACING_RANK);
+ childSize += this.SPACING_CHILD;
+ }
+ dom.node.style.width = rankSize + "px";
+ dom.node.style.height = childSize + "px";
+ var offset = [this.SPACING_RANK, contentSize[1] + this.SPACING_CHILD];
+ if (rankDirection == "left") {
+ offset[0] = rankSize - bbox[0] - this.SPACING_RANK;
+ }
+ this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
+ var labelPos = 0;
+ if (rankDirection == "left") {
+ labelPos = rankSize - contentSize[0];
+ }
+ dom.content.style.left = labelPos + "px";
+ dom.content.style.top = 0;
+ return this;
+ };
+ MM.Layout.Tree._layoutChildren = function(children, rankDirection, offset, bbox) {
+ children.forEach(function(child, index) {
+ var node = child.getDOM().node;
+ var childSize = [node.offsetWidth, node.offsetHeight];
+ var left = offset[0];
+ if (rankDirection == "left") {
+ left += bbox[0] - childSize[0];
+ }
+ node.style.left = left + "px";
+ node.style.top = offset[1] + "px";
+ offset[1] += childSize[1] + this.SPACING_CHILD;
+ }, this);
+ return bbox;
+ };
+ MM.Layout.Tree._drawLines = function(item, side) {
+ var dom = item.getDOM();
+ var canvas = dom.canvas;
+ var R = this.SPACING_RANK / 4;
+ var x = (side == "left" ? canvas.width - 2 * R : 2 * R) + 0.5;
+ this._anchorToggle(item, x, dom.content.offsetHeight, "bottom");
+ var children = item.getChildren();
+ if (children.length == 0 || item.isCollapsed()) {
+ return;
+ }
+ var ctx = canvas.getContext("2d");
+ ctx.strokeStyle = item.getColor();
+ var y1 = item.getShape().getVerticalAnchor(item);
+ var last = children[children.length - 1];
+ var y2 = last.getShape().getVerticalAnchor(last) + last.getDOM().node.offsetTop;
+ ctx.beginPath();
+ ctx.moveTo(x, y1);
+ ctx.lineTo(x, y2 - R);
+ for (var i = 0; i < children.length; i++) {
+ var c = children[i];
+ var y = c.getShape().getVerticalAnchor(c) + c.getDOM().node.offsetTop;
+ var anchor = this._getChildAnchor(c, side);
+ ctx.moveTo(x, y - R);
+ ctx.arcTo(x, y, anchor, y, R);
+ ctx.lineTo(anchor, y);
+ }
+ ctx.stroke();
+ };
+ MM.Layout.Tree.Left = MM.Layout.Tree.create("left", "tree-left", "Left");
+ MM.Layout.Tree.Right = MM.Layout.Tree.create("right", "tree-right", "Right");
+
+ // .js/layout/layout.map.js
+ MM.Layout.Map = Object.create(MM.Layout.Graph, {
+ id: { value: "map" },
+ label: { value: "Map" },
+ LINE_THICKNESS: { value: 8 }
+ });
+ MM.Layout.ALL.push(MM.Layout.Map);
+ MM.Layout.Map.update = function(item) {
+ if (item.isRoot()) {
+ this._layoutRoot(item);
+ } else {
+ var side = this.getChildDirection(item);
+ var name = side.charAt(0).toUpperCase() + side.substring(1);
+ MM.Layout.Graph[name].update(item);
+ }
+ };
+ MM.Layout.Map.getChildDirection = function(child) {
+ while (!child.getParent().isRoot()) {
+ child = child.getParent();
+ }
+ var side = child.getSide();
+ if (side) {
+ return side;
+ }
+ var counts = { left: 0, right: 0 };
+ var children = child.getParent().getChildren();
+ for (var i = 0; i < children.length; i++) {
+ var side = children[i].getSide();
+ if (!side) {
+ side = counts.right > counts.left ? "left" : "right";
+ children[i].setSide(side);
+ }
+ counts[side]++;
+ }
+ return child.getSide();
+ };
+ MM.Layout.Map.pickSibling = function(item, dir) {
+ if (item.isRoot()) {
+ return item;
+ }
+ var parent = item.getParent();
+ var children = parent.getChildren();
+ if (parent.isRoot()) {
+ var side = this.getChildDirection(item);
+ children = children.filter(function(child) {
+ return this.getChildDirection(child) == side;
+ }, this);
+ }
+ var index = children.indexOf(item);
+ index += dir;
+ index = (index + children.length) % children.length;
+ return children[index];
+ };
+ MM.Layout.Map._layoutRoot = function(item) {
+ this._alignItem(item, "right");
+ var dom = item.getDOM();
+ var children = item.getChildren();
+ var childrenLeft = [];
+ var childrenRight = [];
+ children.forEach(function(child, index) {
+ var node = child.getDOM().node;
+ var side = this.getChildDirection(child);
+ if (side == "left") {
+ childrenLeft.push(child);
+ } else {
+ childrenRight.push(child);
+ }
+ }, this);
+ var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
+ var bboxRight = this._computeChildrenBBox(childrenRight, 1);
+ var height = Math.max(bboxLeft[1], bboxRight[1], dom.content.offsetHeight);
+ var left = 0;
+ this._layoutChildren(childrenLeft, "left", [left, Math.round((height - bboxLeft[1]) / 2)], bboxLeft);
+ left += bboxLeft[0];
+ if (childrenLeft.length) {
+ left += this.SPACING_RANK;
+ }
+ dom.content.style.left = left + "px";
+ left += dom.content.offsetWidth;
+ if (childrenRight.length) {
+ left += this.SPACING_RANK;
+ }
+ this._layoutChildren(childrenRight, "right", [left, Math.round((height - bboxRight[1]) / 2)], bboxRight);
+ left += bboxRight[0];
+ dom.content.style.top = Math.round((height - dom.content.offsetHeight) / 2) + "px";
+ dom.node.style.height = height + "px";
+ dom.node.style.width = left + "px";
+ this._anchorCanvas(item);
+ this._drawRootConnectors(item, "left", childrenLeft);
+ this._drawRootConnectors(item, "right", childrenRight);
+ };
+ MM.Layout.Map._drawRootConnectors = function(item, side, children) {
+ if (children.length == 0 || item.isCollapsed()) {
+ return;
+ }
+ var dom = item.getDOM();
+ var canvas = dom.canvas;
+ var ctx = canvas.getContext("2d");
+ var R = this.SPACING_RANK / 2;
+ var x1 = dom.content.offsetLeft + dom.content.offsetWidth / 2;
+ var y1 = item.getShape().getVerticalAnchor(item);
+ var half = this.LINE_THICKNESS / 2;
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var x2 = this._getChildAnchor(child, side);
+ var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ var angle = Math.atan2(y2 - y1, x2 - x1) + Math.PI / 2;
+ var dx = Math.cos(angle) * half;
+ var dy = Math.sin(angle) * half;
+ ctx.fillStyle = ctx.strokeStyle = child.getColor();
+ ctx.beginPath();
+ ctx.moveTo(x1 - dx, y1 - dy);
+ ctx.quadraticCurveTo((x2 + x1) / 2, y2, x2, y2);
+ ctx.quadraticCurveTo((x2 + x1) / 2, y2, x1 + dx, y1 + dy);
+ ctx.fill();
+ ctx.stroke();
+ }
+ };
+
+ // .js/shape/shape.js
+ MM.Shape = Object.create(MM.Repo, {
+ VERTICAL_OFFSET: { value: 0.5 }
+ });
+ MM.Shape.set = function(item) {
+ item.getDOM().node.classList.add("shape-" + this.id);
+ return this;
+ };
+ MM.Shape.unset = function(item) {
+ item.getDOM().node.classList.remove("shape-" + this.id);
+ return this;
+ };
+ MM.Shape.update = function(item) {
+ item.getDOM().content.style.borderColor = item.getColor();
+ return this;
+ };
+ MM.Shape.getHorizontalAnchor = function(item) {
+ var node = item.getDOM().content;
+ return Math.round(node.offsetLeft + node.offsetWidth / 2) + 0.5;
+ };
+ MM.Shape.getVerticalAnchor = function(item) {
+ var node = item.getDOM().content;
+ return node.offsetTop + Math.round(node.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
+ };
+
+ // .js/shape/shape.underline.js
+ MM.Shape.Underline = Object.create(MM.Shape, {
+ id: { value: "underline" },
+ label: { value: "Underline" },
+ VERTICAL_OFFSET: { value: -3 }
+ });
+ MM.Shape.Underline.update = function(item) {
+ var dom = item.getDOM();
+ var ctx = dom.canvas.getContext("2d");
+ ctx.strokeStyle = item.getColor();
+ var left = dom.content.offsetLeft;
+ var right = left + dom.content.offsetWidth;
+ var top = this.getVerticalAnchor(item);
+ ctx.beginPath();
+ ctx.moveTo(left, top);
+ ctx.lineTo(right, top);
+ ctx.stroke();
+ };
+ MM.Shape.Underline.getVerticalAnchor = function(item) {
+ var node = item.getDOM().content;
+ return node.offsetTop + node.offsetHeight + this.VERTICAL_OFFSET + 0.5;
+ };
+
+ // .js/shape/shape.box.js
+ MM.Shape.Box = Object.create(MM.Shape, {
+ id: { value: "box" },
+ label: { value: "Box" }
+ });
+
+ // .js/shape/shape.ellipse.js
+ MM.Shape.Ellipse = Object.create(MM.Shape, {
+ id: { value: "ellipse" },
+ label: { value: "Ellipse" }
+ });
+
+ // .js/format/format.js
+ MM.Format = Object.create(MM.Repo, {
+ extension: { value: "" },
+ mime: { value: "" }
+ });
+ MM.Format.getByName = function(name) {
+ var index = name.lastIndexOf(".");
+ if (index == -1) {
+ return null;
+ }
+ var extension = name.substring(index + 1).toLowerCase();
+ return this.getByProperty("extension", extension);
+ };
+ MM.Format.getByMime = function(mime) {
+ return this.getByProperty("mime", mime);
+ };
+ MM.Format.to = function(data) {
+ };
+ MM.Format.from = function(data) {
+ };
+ MM.Format.nl2br = function(str) {
+ return str.replace(/\n/g, "
");
+ };
+ MM.Format.br2nl = function(str) {
+ return str.replace(/
/g, "\n");
+ };
+
+ // .js/format/format.json.js
+ MM.Format.JSON = Object.create(MM.Format, {
+ id: { value: "json" },
+ label: { value: "Native (JSON)" },
+ extension: { value: "mymind" },
+ mime: { value: "application/vnd.mymind+json" }
+ });
+ MM.Format.JSON.to = function(data) {
+ return JSON.stringify(data, null, " ") + "\n";
+ };
+ MM.Format.JSON.from = function(data) {
+ return JSON.parse(data);
+ };
+
+ // .js/format/format.freemind.js
+ MM.Format.FreeMind = Object.create(MM.Format, {
+ id: { value: "freemind" },
+ label: { value: "FreeMind" },
+ extension: { value: "mm" },
+ mime: { value: "application/x-freemind" }
+ });
+ MM.Format.FreeMind.to = function(data) {
+ var doc = document.implementation.createDocument(null, null, null);
+ var map = doc.createElement("map");
+ map.setAttribute("version", "1.0.1");
+ map.appendChild(this._serializeItem(doc, data.root));
+ doc.appendChild(map);
+ var serializer = new XMLSerializer();
+ return serializer.serializeToString(doc);
+ };
+ MM.Format.FreeMind.from = function(data) {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(data, "application/xml");
+ if (doc.documentElement.nodeName.toLowerCase() == "parsererror") {
+ throw new Error(doc.documentElement.textContent);
+ }
+ var root = doc.documentElement.getElementsByTagName("node")[0];
+ if (!root) {
+ throw new Error("No root node found");
+ }
+ var json = {
+ root: this._parseNode(root, { shape: "underline" })
+ };
+ json.root.layout = "map";
+ json.root.shape = "ellipse";
+ return json;
+ };
+ MM.Format.FreeMind._serializeItem = function(doc, json) {
+ var elm = this._serializeAttributes(doc, json);
+ (json.children || []).forEach(function(child) {
+ elm.appendChild(this._serializeItem(doc, child));
+ }, this);
+ return elm;
+ };
+ MM.Format.FreeMind._serializeAttributes = function(doc, json) {
+ var elm = doc.createElement("node");
+ elm.setAttribute("TEXT", MM.Format.br2nl(json.text));
+ elm.setAttribute("ID", json.id);
+ if (json.side) {
+ elm.setAttribute("POSITION", json.side);
+ }
+ if (json.shape == "box") {
+ elm.setAttribute("STYLE", "bubble");
+ }
+ if (json.collapsed) {
+ elm.setAttribute("FOLDED", "true");
+ }
+ if (json.notes) {
+ var notesElm = doc.createElement("richcontent");
+ notesElm.setAttribute("TYPE", "NOTE");
+ notesElm.appendChild(doc.createCDATASection("" + json.notes + ""));
+ elm.appendChild(notesElm);
+ }
+ return elm;
+ };
+ MM.Format.FreeMind._parseNode = function(node, parent) {
+ var json = this._parseAttributes(node, parent);
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeName.toLowerCase() == "node") {
+ json.children.push(this._parseNode(child, json));
+ }
+ }
+ return json;
+ };
+ MM.Format.FreeMind._parseAttributes = function(node, parent) {
+ var json = {
+ children: [],
+ text: MM.Format.nl2br(node.getAttribute("TEXT") || ""),
+ id: node.getAttribute("ID")
+ };
+ var position = node.getAttribute("POSITION");
+ if (position) {
+ json.side = position;
+ }
+ var style = node.getAttribute("STYLE");
+ if (style == "bubble") {
+ json.shape = "box";
+ } else {
+ json.shape = parent.shape;
+ }
+ if (node.getAttribute("FOLDED") == "true") {
+ json.collapsed = 1;
+ }
+ var children = node.children;
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ switch (child.nodeName.toLowerCase()) {
+ case "richcontent":
+ if (child.getAttribute("TYPE") == "NOTE") {
+ var body = child.querySelector("body > *");
+ if (body) {
+ var serializer = new XMLSerializer();
+ json.notes = serializer.serializeToString(body).trim();
+ }
+ }
+ break;
+ case "font":
+ if (child.getAttribute("ITALIC") == "true") {
+ json.text = "" + json.text + "";
+ }
+ if (child.getAttribute("BOLD") == "true") {
+ json.text = "" + json.text + "";
+ }
+ break;
+ }
+ }
+ return json;
+ };
+
+ // .js/format/format.mma.js
+ MM.Format.MMA = Object.create(MM.Format.FreeMind, {
+ id: { value: "mma" },
+ label: { value: "Mind Map Architect" },
+ extension: { value: "mma" }
+ });
+ MM.Format.MMA._parseAttributes = function(node, parent) {
+ var json = {
+ children: [],
+ text: MM.Format.nl2br(node.getAttribute("title") || ""),
+ shape: "box"
+ };
+ if (node.getAttribute("expand") == "false") {
+ json.collapsed = 1;
+ }
+ var direction = node.getAttribute("direction");
+ if (direction == "0") {
+ json.side = "left";
+ }
+ if (direction == "1") {
+ json.side = "right";
+ }
+ var color = node.getAttribute("color");
+ if (color) {
+ var re = color.match(/^#(....)(....)(....)$/);
+ if (re) {
+ var r = parseInt(re[1], 16) >> 8;
+ var g = parseInt(re[2], 16) >> 8;
+ var b = parseInt(re[3], 16) >> 8;
+ r = Math.round(r / 17).toString(16);
+ g = Math.round(g / 17).toString(16);
+ b = Math.round(b / 17).toString(16);
+ }
+ json.color = "#" + [r, g, b].join("");
+ }
+ json.icon = node.getAttribute("icon");
+ return json;
+ };
+ MM.Format.MMA._serializeAttributes = function(doc, json) {
+ var elm = doc.createElement("node");
+ elm.setAttribute("title", MM.Format.br2nl(json.text));
+ elm.setAttribute("expand", json.collapsed ? "false" : "true");
+ if (json.side) {
+ elm.setAttribute("direction", json.side == "left" ? "0" : "1");
+ }
+ if (json.color) {
+ var parts = json.color.match(/^#(.)(.)(.)$/);
+ var r = new Array(5).join(parts[1]);
+ var g = new Array(5).join(parts[2]);
+ var b = new Array(5).join(parts[3]);
+ elm.setAttribute("color", "#" + [r, g, b].join(""));
+ }
+ if (json.icon) {
+ elm.setAttribute("icon", json.icon);
+ }
+ return elm;
+ };
+
+ // .js/format/format.mup.js
+ MM.Format.Mup = Object.create(MM.Format, {
+ id: { value: "mup" },
+ label: { value: "MindMup" },
+ extension: { value: "mup" }
+ });
+ MM.Format.Mup.to = function(data) {
+ var root = this._MMtoMup(data.root);
+ return JSON.stringify(root, null, 2);
+ };
+ MM.Format.Mup.from = function(data) {
+ var source = JSON.parse(data);
+ var root = this._MupToMM(source);
+ root.layout = "map";
+ var map = {
+ root
+ };
+ return map;
+ };
+ MM.Format.Mup._MupToMM = function(item) {
+ var json = {
+ text: MM.Format.nl2br(item.title),
+ id: item.id,
+ shape: "box",
+ icon: item.icon
+ };
+ if (item.attr && item.attr.style && item.attr.style.background) {
+ json.color = item.attr.style.background;
+ }
+ if (item.attr && item.attr.collapsed) {
+ json.collapsed = 1;
+ }
+ if (item.ideas) {
+ var data = [];
+ for (var key in item.ideas) {
+ var child = this._MupToMM(item.ideas[key]);
+ var num = parseFloat(key);
+ child.side = num < 0 ? "left" : "right";
+ data.push({
+ child,
+ num
+ });
+ }
+ data.sort(function(a, b) {
+ return a.num - b.num;
+ });
+ json.children = data.map(function(item2) {
+ return item2.child;
+ });
+ }
+ return json;
+ };
+ MM.Format.Mup._MMtoMup = function(item, side) {
+ var result = {
+ id: item.id,
+ title: MM.Format.br2nl(item.text),
+ icon: item.icon,
+ attr: {}
+ };
+ if (item.color) {
+ result.attr.style = { background: item.color };
+ }
+ if (item.collapsed) {
+ result.attr.collapsed = true;
+ }
+ if (item.children) {
+ result.ideas = {};
+ for (var i = 0; i < item.children.length; i++) {
+ var child = item.children[i];
+ var childSide = side || child.side;
+ var key = i + 1;
+ if (childSide == "left") {
+ key *= -1;
+ }
+ result.ideas[key] = this._MMtoMup(child, childSide);
+ }
+ }
+ return result;
+ };
+
+ // .js/format/format.plaintext.js
+ MM.Format.Plaintext = Object.create(MM.Format, {
+ id: { value: "plaintext" },
+ label: { value: "Plain text" },
+ extension: { value: "txt" },
+ mime: { value: "application/vnd.mymind+txt" }
+ });
+ MM.Format.Plaintext.to = function(data) {
+ return this._serializeItem(data.root || data);
+ };
+ MM.Format.Plaintext.from = function(data) {
+ var lines = data.split("\n").filter(function(line) {
+ return line.match(/\S/);
+ });
+ var items = this._parseItems(lines);
+ if (items.length == 1) {
+ var result = {
+ root: items[0]
+ };
+ } else {
+ var result = {
+ root: {
+ text: "",
+ children: items
+ }
+ };
+ }
+ result.root.layout = "map";
+ return result;
+ };
+ MM.Format.Plaintext._serializeItem = function(item, depth) {
+ depth = depth || 0;
+ var lines = (item.children || []).map(function(child) {
+ return this._serializeItem(child, depth + 1);
+ }, this);
+ var prefix = new Array(depth + 1).join(" ");
+ lines.unshift(prefix + item.text.replace(/\n/g, ""));
+ return lines.join("\n") + (depth ? "" : "\n");
+ };
+ MM.Format.Plaintext._parseItems = function(lines) {
+ var items = [];
+ if (!lines.length) {
+ return items;
+ }
+ var firstPrefix = this._parsePrefix(lines[0]);
+ var currentItem = null;
+ var childLines = [];
+ var convertChildLinesToChildren = function() {
+ if (!currentItem || !childLines.length) {
+ return;
+ }
+ var children = this._parseItems(childLines);
+ if (children.length) {
+ currentItem.children = children;
+ }
+ childLines = [];
+ };
+ lines.forEach(function(line, index) {
+ if (this._parsePrefix(line) == firstPrefix) {
+ convertChildLinesToChildren.call(this);
+ currentItem = { text: line.match(/^\s*(.*)/)[1] };
+ items.push(currentItem);
+ } else {
+ childLines.push(line);
+ }
+ }, this);
+ convertChildLinesToChildren.call(this);
+ return items;
+ };
+ MM.Format.Plaintext._parsePrefix = function(line) {
+ return line.match(/^\s*/)[0];
+ };
+
+ // .js/backend/backend.js
+ MM.Backend = Object.create(MM.Repo);
+ MM.Backend.reset = function() {
+ };
+ MM.Backend.save = function(data, name) {
+ };
+ MM.Backend.load = function(name) {
+ };
+
+ // .js/backend/backend.local.js
+ MM.Backend.Local = Object.create(MM.Backend, {
+ label: { value: "Browser storage" },
+ id: { value: "local" },
+ prefix: { value: "mm.map." }
+ });
+ MM.Backend.Local.save = function(data, id, name) {
+ localStorage.setItem(this.prefix + id, data);
+ var names = this.list();
+ names[id] = name;
+ localStorage.setItem(this.prefix + "names", JSON.stringify(names));
+ };
+ MM.Backend.Local.load = function(id) {
+ var data = localStorage.getItem(this.prefix + id);
+ if (!data) {
+ throw new Error("There is no such saved map");
+ }
+ return data;
+ };
+ MM.Backend.Local.remove = function(id) {
+ localStorage.removeItem(this.prefix + id);
+ var names = this.list();
+ delete names[id];
+ localStorage.setItem(this.prefix + "names", JSON.stringify(names));
+ };
+ MM.Backend.Local.list = function() {
+ try {
+ var data = localStorage.getItem(this.prefix + "names") || "{}";
+ return JSON.parse(data);
+ } catch (e) {
+ return {};
+ }
+ };
+
+ // .js/backend/backend.webdav.js
+ MM.Backend.WebDAV = Object.create(MM.Backend, {
+ id: { value: "webdav" },
+ label: { value: "Generic WebDAV" }
+ });
+ MM.Backend.WebDAV.save = function(data, url) {
+ return this._request("put", url, data);
+ };
+ MM.Backend.WebDAV.load = function(url) {
+ return this._request("get", url);
+ };
+ MM.Backend.WebDAV._request = function(method, url, data) {
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.withCredentials = true;
+ var promise = new Promise();
+ Promise.send(xhr, data).then(function(xhr2) {
+ promise.fulfill(xhr2.responseText);
+ }, function(xhr2) {
+ promise.reject(new Error("HTTP/" + xhr2.status + "\n\n" + xhr2.responseText));
+ });
+ return promise;
+ };
+
+ // .js/backend/backend.image.js
+ MM.Backend.Image = Object.create(MM.Backend, {
+ id: { value: "image" },
+ label: { value: "Image" },
+ url: { value: "", writable: true }
+ });
+ MM.Backend.Image.save = function(data, name) {
+ var form = document.createElement("form");
+ form.action = this.url;
+ form.method = "post";
+ form.target = "_blank";
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.name = "data";
+ input.value = data;
+ form.appendChild(input);
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.name = "name";
+ input.value = name;
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();
+ form.parentNode.removeChild(form);
+ };
+
+ // .js/backend/backend.file.js
+ MM.Backend.File = Object.create(MM.Backend, {
+ id: { value: "file" },
+ label: { value: "File" },
+ input: { value: document.createElement("input") }
+ });
+ MM.Backend.File.save = function(data, name) {
+ var link = document.createElement("a");
+ link.download = name;
+ link.href = "data:text/plain;base64," + btoa(unescape(encodeURIComponent(data)));
+ document.body.appendChild(link);
+ link.click();
+ link.parentNode.removeChild(link);
+ var promise = new Promise().fulfill();
+ return promise;
+ };
+ MM.Backend.File.load = function() {
+ var promise = new Promise();
+ this.input.type = "file";
+ this.input.onchange = function(e) {
+ var file = e.target.files[0];
+ if (!file) {
+ return;
+ }
+ var reader = new FileReader();
+ reader.onload = function() {
+ promise.fulfill({ data: reader.result, name: file.name });
+ };
+ reader.onerror = function() {
+ promise.reject(reader.error);
+ };
+ reader.readAsText(file);
+ }.bind(this);
+ this.input.click();
+ return promise;
+ };
+
+ // .js/backend/backend.firebase.js
+ MM.Backend.Firebase = Object.create(MM.Backend, {
+ label: { value: "Firebase" },
+ id: { value: "firebase" },
+ ref: { value: null, writable: true },
+ _current: { value: {
+ id: null,
+ name: null,
+ data: null
+ } }
+ });
+ MM.Backend.Firebase.connect = function(server, auth) {
+ var config = {
+ apiKey: "AIzaSyBO_6uCK8pHjoz1c9htVwZi6Skpm8o4LtQ",
+ authDomain: "my-mind.firebaseapp.com",
+ databaseURL: "https://" + server + ".firebaseio.com",
+ projectId: "firebase-my-mind",
+ storageBucket: "firebase-my-mind.appspot.com",
+ messagingSenderId: "666556281676"
+ };
+ firebase.initializeApp(config);
+ this.ref = firebase.database().ref();
+ this.ref.child("names").on("value", function(snap) {
+ MM.publish("firebase-list", this, snap.val() || {});
+ }, this);
+ if (auth) {
+ return this._login(auth);
+ } else {
+ return new Promise().fulfill();
+ }
+ };
+ MM.Backend.Firebase.save = function(data, id, name) {
+ var promise = new Promise();
+ try {
+ this.ref.child("names/" + id).set(name);
+ this.ref.child("data/" + id).set(data, function(result) {
+ if (result) {
+ promise.reject(result);
+ } else {
+ promise.fulfill();
+ this._listenStart(data, id);
+ }
+ }.bind(this));
+ } catch (e) {
+ promise.reject(e);
+ }
+ return promise;
+ };
+ MM.Backend.Firebase.load = function(id) {
+ var promise = new Promise();
+ this.ref.child("data/" + id).once("value", function(snap) {
+ var data = snap.val();
+ if (data) {
+ promise.fulfill(data);
+ this._listenStart(data, id);
+ } else {
+ promise.reject(new Error("There is no such saved map"));
+ }
+ }, this);
+ return promise;
+ };
+ MM.Backend.Firebase.remove = function(id) {
+ var promise = new Promise();
+ try {
+ this.ref.child("names/" + id).remove();
+ this.ref.child("data/" + id).remove(function(result) {
+ if (result) {
+ promise.reject(result);
+ } else {
+ promise.fulfill();
+ }
+ });
+ } catch (e) {
+ promise.reject(e);
+ }
+ return promise;
+ };
+ MM.Backend.Firebase.reset = function() {
+ this._listenStop();
+ };
+ MM.Backend.Firebase.mergeWith = function(data, name) {
+ var id = this._current.id;
+ if (name != this._current.name) {
+ this._current.name = name;
+ this.ref.child("names/" + id).set(name);
+ }
+ var dataRef = this.ref.child("data/" + id);
+ var oldData = this._current.data;
+ this._listenStop();
+ this._recursiveRefMerge(dataRef, oldData, data);
+ this._listenStart(data, id);
+ };
+ MM.Backend.Firebase._recursiveRefMerge = function(ref, oldData, newData) {
+ var updateObject = {};
+ if (newData instanceof Array) {
+ for (var i = 0; i < newData.length; i++) {
+ var newValue = newData[i];
+ if (!(i in oldData)) {
+ updateObject[i] = newValue;
+ } else if (typeof newValue == "object") {
+ this._recursiveRefMerge(ref.child(i), oldData[i], newValue);
+ } else if (newValue !== oldData[i]) {
+ updateObject[i] = newValue;
+ }
+ }
+ for (var i = newData.length; i < oldData.length; i++) {
+ updateObject[i] = null;
+ }
+ } else {
+ for (var p in newData) {
+ var newValue = newData[p];
+ if (!(p in oldData)) {
+ updateObject[p] = newValue;
+ } else if (typeof newValue == "object") {
+ this._recursiveRefMerge(ref.child(p), oldData[p], newValue);
+ } else if (newValue !== oldData[p]) {
+ updateObject[p] = newValue;
+ }
+ }
+ for (var p in oldData) {
+ if (!(p in newData)) {
+ updateObject[p] = null;
+ }
+ }
+ }
+ if (Object.keys(updateObject).length) {
+ ref.update(updateObject);
+ }
+ };
+ MM.Backend.Firebase._listenStart = function(data, id) {
+ if (this._current.id && this._current.id == id) {
+ return;
+ }
+ this._listenStop();
+ this._current.id = id;
+ this._current.data = data;
+ this.ref.child("data/" + id).on("value", this._valueChange, this);
+ };
+ MM.Backend.Firebase._listenStop = function() {
+ if (!this._current.id) {
+ return;
+ }
+ this.ref.child("data/" + this._current.id).off("value");
+ this._current.id = null;
+ this._current.name = null;
+ this._current.data = null;
+ };
+ MM.Backend.Firebase._valueChange = function(snap) {
+ this._current.data = snap.val();
+ if (this._changeTimeout) {
+ clearTimeout(this._changeTimeout);
+ }
+ this._changeTimeout = setTimeout(function() {
+ MM.publish("firebase-change", this, this._current.data);
+ }.bind(this), 200);
+ };
+ MM.Backend.Firebase._login = function(type) {
+ var provider;
+ switch (type) {
+ case "github":
+ provider = new firebase.auth.GithubAuthProvider();
+ break;
+ case "facebook":
+ provider = new firebase.auth.FacebookAuthProvider();
+ break;
+ case "twitter":
+ provider = new firebase.auth.TwitterAuthProvider();
+ break;
+ case "google":
+ provider = new firebase.auth.GoogleAuthProvider();
+ break;
+ }
+ return firebase.auth().signInWithPopup(provider).then(function(result) {
+ return result.user;
+ });
+ };
+
+ // .js/backend/backend.gdrive.js
+ MM.Backend.GDrive = Object.create(MM.Backend, {
+ id: { value: "gdrive" },
+ label: { value: "Google Drive" },
+ scope: { value: "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.install" },
+ clientId: { value: "767837575056-h87qmlhmhb3djhaaqta5gv2v3koa9hii.apps.googleusercontent.com" },
+ apiKey: { value: "AIzaSyCzu1qVxlgufneOYpBgDJXN6Z9SNVcHYWM" },
+ fileId: { value: null, writable: true }
+ });
+ MM.Backend.GDrive.reset = function() {
+ this.fileId = null;
+ };
+ MM.Backend.GDrive.save = function(data, name, mime) {
+ return this._connect().then(function() {
+ return this._send(data, name, mime);
+ }.bind(this));
+ };
+ MM.Backend.GDrive._send = function(data, name, mime) {
+ var promise = new Promise();
+ var path = "/upload/drive/v2/files";
+ var method = "POST";
+ if (this.fileId) {
+ path += "/" + this.fileId;
+ method = "PUT";
+ }
+ var boundary = "b" + Math.random();
+ var delimiter = "--" + boundary;
+ var body = [
+ delimiter,
+ "Content-Type: application/json",
+ "",
+ JSON.stringify({ title: name }),
+ delimiter,
+ "Content-Type: " + mime,
+ "",
+ data,
+ delimiter + "--"
+ ].join("\r\n");
+ var request = gapi.client.request({
+ path,
+ method,
+ headers: {
+ "Content-Type": "multipart/mixed; boundary='" + boundary + "'"
+ },
+ body
+ });
+ request.execute(function(response) {
+ if (!response) {
+ promise.reject(new Error("Failed to upload to Google Drive"));
+ } else if (response.error) {
+ promise.reject(response.error);
+ } else {
+ this.fileId = response.id;
+ promise.fulfill();
+ }
+ }.bind(this));
+ return promise;
+ };
+ MM.Backend.GDrive.load = function(id) {
+ return this._connect().then(this._load.bind(this, id));
+ };
+ MM.Backend.GDrive._load = function(id) {
+ this.fileId = id;
+ var promise = new Promise();
+ var request = gapi.client.request({
+ path: "/drive/v2/files/" + this.fileId,
+ method: "GET"
+ });
+ request.execute(function(response) {
+ if (response && response.id) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", "https://www.googleapis.com/drive/v2/files/" + response.id + "?alt=media", true);
+ xhr.setRequestHeader("Authorization", "Bearer " + gapi.auth.getToken().access_token);
+ Promise.send(xhr).then(function(xhr2) {
+ promise.fulfill({ data: xhr2.responseText, name: response.title, mime: response.mimeType });
+ }, function(xhr2) {
+ promise.reject(xhr2.responseText);
+ });
+ } else {
+ promise.reject(response && response.error || new Error("Failed to download file"));
+ }
+ }.bind(this));
+ return promise;
+ };
+ MM.Backend.GDrive.pick = function() {
+ return this._connect().then(this._pick.bind(this));
+ };
+ MM.Backend.GDrive._pick = function() {
+ var promise = new Promise();
+ var token = gapi.auth.getToken();
+ var formats = MM.Format.getAll();
+ var mimeTypes = ["application/json; charset=UTF-8", "application/json"];
+ formats.forEach(function(format) {
+ if (format.mime) {
+ mimeTypes.unshift(format.mime);
+ }
+ });
+ var view = new google.picker.DocsView(google.picker.ViewId.DOCS).setMimeTypes(mimeTypes.join(",")).setMode(google.picker.DocsViewMode.LIST);
+ var picker = new google.picker.PickerBuilder().enableFeature(google.picker.Feature.NAV_HIDDEN).addView(view).setOAuthToken(token.access_token).setDeveloperKey(this.apiKey).setCallback(function(data) {
+ switch (data[google.picker.Response.ACTION]) {
+ case google.picker.Action.PICKED:
+ var doc = data[google.picker.Response.DOCUMENTS][0];
+ promise.fulfill(doc.id);
+ break;
+ case google.picker.Action.CANCEL:
+ promise.fulfill(null);
+ break;
+ }
+ }).build();
+ picker.setVisible(true);
+ return promise;
+ };
+ MM.Backend.GDrive._connect = function() {
+ if (window.gapi && window.gapi.auth.getToken()) {
+ return new Promise().fulfill();
+ } else {
+ return this._loadGapi().then(this._auth.bind(this));
+ }
+ };
+ MM.Backend.GDrive._loadGapi = function() {
+ var promise = new Promise();
+ if (window.gapi) {
+ return promise.fulfill();
+ }
+ var script = document.createElement("script");
+ var name = ("cb" + Math.random()).replace(".", "");
+ window[name] = promise.fulfill.bind(promise);
+ script.src = "https://apis.google.com/js/client:picker.js?onload=" + name;
+ document.body.appendChild(script);
+ return promise;
+ };
+ MM.Backend.GDrive._auth = function(forceUI) {
+ var promise = new Promise();
+ gapi.auth.authorize({
+ "client_id": this.clientId,
+ "scope": this.scope,
+ "immediate": !forceUI
+ }, function(token) {
+ if (token && !token.error) {
+ promise.fulfill();
+ } else if (!forceUI) {
+ this._auth(true).then(promise.fulfill.bind(promise), promise.reject.bind(promise));
+ } else {
+ promise.reject(token && token.error || new Error("Failed to authorize with Google"));
+ }
+ }.bind(this));
+ return promise;
+ };
+
+ // .js/ui/ui.js
+ MM.UI = function() {
+ this._node = document.querySelector(".ui");
+ this._toggle = this._node.querySelector("#toggle");
+ this._layout = new MM.UI.Layout();
+ this._shape = new MM.UI.Shape();
+ this._icon = new MM.UI.Icon();
+ this._color = new MM.UI.Color();
+ this._value = new MM.UI.Value();
+ this._status = new MM.UI.Status();
+ MM.subscribe("item-select", this);
+ MM.subscribe("item-change", this);
+ this._node.addEventListener("click", this);
+ this._node.addEventListener("change", this);
+ this.toggle();
+ };
+ MM.UI.prototype.handleMessage = function(message, publisher) {
+ switch (message) {
+ case "item-select":
+ this._update();
+ break;
+ case "item-change":
+ if (publisher == MM.App.current) {
+ this._update();
+ }
+ break;
+ }
+ };
+ MM.UI.prototype.handleEvent = function(e) {
+ switch (e.type) {
+ case "click":
+ if (e.target.nodeName.toLowerCase() != "select") {
+ MM.Clipboard.focus();
+ }
+ if (e.target == this._toggle) {
+ this.toggle();
+ return;
+ }
+ var node = e.target;
+ while (node != document) {
+ var command = node.getAttribute("data-command");
+ if (command) {
+ MM.Command[command].execute();
+ return;
+ }
+ node = node.parentNode;
+ }
+ break;
+ case "change":
+ MM.Clipboard.focus();
+ break;
+ }
+ };
+ MM.UI.prototype.toggle = function() {
+ this._node.classList.toggle("visible");
+ MM.publish("ui-change", this);
+ };
+ MM.UI.prototype.getWidth = function() {
+ return this._node.classList.contains("visible") ? this._node.offsetWidth : 0;
+ };
+ MM.UI.prototype._update = function() {
+ this._layout.update();
+ this._shape.update();
+ this._icon.update();
+ this._value.update();
+ this._status.update();
+ };
+
+ // .js/ui/ui.layout.js
+ MM.UI.Layout = function() {
+ this._select = document.querySelector("#layout");
+ this._select.appendChild(MM.Layout.Map.buildOption());
+ var label = this._buildGroup("Graph");
+ label.appendChild(MM.Layout.Graph.Right.buildOption());
+ label.appendChild(MM.Layout.Graph.Left.buildOption());
+ label.appendChild(MM.Layout.Graph.Down.buildOption());
+ label.appendChild(MM.Layout.Graph.Up.buildOption());
+ var label = this._buildGroup("Tree");
+ label.appendChild(MM.Layout.Tree.Right.buildOption());
+ label.appendChild(MM.Layout.Tree.Left.buildOption());
+ this._select.addEventListener("change", this);
+ };
+ MM.UI.Layout.prototype.update = function() {
+ var value = "";
+ var layout = MM.App.current.getOwnLayout();
+ if (layout) {
+ value = layout.id;
+ }
+ this._select.value = value;
+ this._getOption("").disabled = MM.App.current.isRoot();
+ this._getOption(MM.Layout.Map.id).disabled = !MM.App.current.isRoot();
+ };
+ MM.UI.Layout.prototype.handleEvent = function(e) {
+ var layout = MM.Layout.getById(this._select.value);
+ var action = new MM.Action.SetLayout(MM.App.current, layout);
+ MM.App.action(action);
+ };
+ MM.UI.Layout.prototype._getOption = function(value) {
+ return this._select.querySelector("option[value='" + value + "']");
+ };
+ MM.UI.Layout.prototype._buildGroup = function(label) {
+ var node = document.createElement("optgroup");
+ node.label = label;
+ this._select.appendChild(node);
+ return node;
+ };
+
+ // .js/ui/ui.shape.js
+ MM.UI.Shape = function() {
+ this._select = document.querySelector("#shape");
+ this._select.appendChild(MM.Shape.Box.buildOption());
+ this._select.appendChild(MM.Shape.Ellipse.buildOption());
+ this._select.appendChild(MM.Shape.Underline.buildOption());
+ this._select.addEventListener("change", this);
+ };
+ MM.UI.Shape.prototype.update = function() {
+ var value = "";
+ var shape = MM.App.current.getOwnShape();
+ if (shape) {
+ value = shape.id;
+ }
+ this._select.value = value;
+ };
+ MM.UI.Shape.prototype.handleEvent = function(e) {
+ var shape = MM.Shape.getById(this._select.value);
+ var action = new MM.Action.SetShape(MM.App.current, shape);
+ MM.App.action(action);
+ };
- MM.Tip.init();
- MM.Keyboard.init();
- MM.Menu.init(this._port);
- MM.Mouse.init(this._port);
- MM.Clipboard.init();
+ // .js/ui/ui.value.js
+ MM.UI.Value = function() {
+ this._select = document.querySelector("#value");
+ this._select.addEventListener("change", this);
+ };
+ MM.UI.Value.prototype.update = function() {
+ var value = MM.App.current.getValue();
+ if (value === null) {
+ value = "";
+ }
+ if (typeof value == "number") {
+ value = "num";
+ }
+ this._select.value = value;
+ };
+ MM.UI.Value.prototype.handleEvent = function(e) {
+ var value = this._select.value;
+ if (value == "num") {
+ MM.Command.Value.execute();
+ } else {
+ var action = new MM.Action.SetValue(MM.App.current, value || null);
+ MM.App.action(action);
+ }
+ };
- window.addEventListener("resize", this);
- window.addEventListener("beforeunload", this);
- window.addEventListener("keyup", this);
- window.addEventListener("message", this, false);
- MM.subscribe("ui-change", this);
- MM.subscribe("item-change", this);
-
- this._syncPort();
- this.setMap(new MM.Map());
- },
+ // .js/ui/ui.status.js
+ MM.UI.Status = function() {
+ this._select = document.querySelector("#status");
+ this._select.addEventListener("change", this);
+ };
+ MM.UI.Status.prototype.update = function() {
+ this._select.value = MM.App.current.getStatus() || "";
+ };
+ MM.UI.Status.prototype.handleEvent = function(e) {
+ var action = new MM.Action.SetStatus(MM.App.current, this._select.value || null);
+ MM.App.action(action);
+ };
+
+ // .js/ui/ui.color.js
+ MM.UI.Color = function() {
+ this._node = document.querySelector("#color");
+ this._node.addEventListener("click", this);
+ var items = this._node.querySelectorAll("[data-color]");
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ item.style.backgroundColor = item.getAttribute("data-color");
+ }
+ };
+ MM.UI.Color.prototype.handleEvent = function(e) {
+ e.preventDefault();
+ if (!e.target.hasAttribute("data-color")) {
+ return;
+ }
+ var color = e.target.getAttribute("data-color") || null;
+ var action = new MM.Action.SetColor(MM.App.current, color);
+ MM.App.action(action);
+ };
- _syncPort: function() {
- this.portSize = [window.innerWidth - this.ui.getWidth(), window.innerHeight];
- this._port.style.width = this.portSize[0] + "px";
- this._port.style.height = this.portSize[1] + "px";
- this._throbber.style.right = (20 + this.ui.getWidth())+ "px";
- if (this.map) { this.map.ensureItemVisibility(this.current); }
- }
-}
+ // .js/ui/ui.icon.js
+ MM.UI.Icon = function() {
+ this._select = document.querySelector("#icons");
+ this._select.addEventListener("change", this);
+ };
+ MM.UI.Icon.prototype.update = function() {
+ this._select.value = MM.App.current.getIcon() || "";
+ };
+ MM.UI.Icon.prototype.handleEvent = function(e) {
+ var action = new MM.Action.SetIcon(MM.App.current, this._select.value || null);
+ MM.App.action(action);
+ };
+
+ // .js/ui/ui.help.js
+ MM.UI.Help = function() {
+ this._node = document.querySelector("#help");
+ this._map = {
+ 8: "Backspace",
+ 9: "Tab",
+ 13: "\u21A9",
+ 32: "Spacebar",
+ 33: "PgUp",
+ 34: "PgDown",
+ 35: "End",
+ 36: "Home",
+ 37: "\u2190",
+ 38: "\u2191",
+ 39: "\u2192",
+ 40: "\u2193",
+ 45: "Insert",
+ 46: "Delete",
+ 65: "A",
+ 68: "D",
+ 83: "S",
+ 87: "W",
+ 112: "F1",
+ 113: "F2",
+ 114: "F3",
+ 115: "F4",
+ 116: "F5",
+ 117: "F6",
+ 118: "F7",
+ 119: "F8",
+ 120: "F9",
+ 121: "F10",
+ "-": "−"
+ };
+ this._build();
+ };
+ MM.UI.Help.prototype.toggle = function() {
+ this._node.classList.toggle("visible");
+ };
+ MM.UI.Help.prototype._build = function() {
+ var t = this._node.querySelector(".navigation");
+ this._buildRow(t, "Pan");
+ this._buildRow(t, "Select");
+ this._buildRow(t, "SelectRoot");
+ this._buildRow(t, "SelectParent");
+ this._buildRow(t, "Center");
+ this._buildRow(t, "ZoomIn", "ZoomOut");
+ this._buildRow(t, "Fold");
+ var t = this._node.querySelector(".manipulation");
+ this._buildRow(t, "InsertSibling");
+ this._buildRow(t, "InsertChild");
+ this._buildRow(t, "Swap");
+ this._buildRow(t, "Side");
+ this._buildRow(t, "Delete");
+ this._buildRow(t, "Copy");
+ this._buildRow(t, "Cut");
+ this._buildRow(t, "Paste");
+ var t = this._node.querySelector(".editing");
+ this._buildRow(t, "Value");
+ this._buildRow(t, "Yes", "No", "Computed");
+ this._buildRow(t, "Edit");
+ this._buildRow(t, "Newline");
+ this._buildRow(t, "Bold");
+ this._buildRow(t, "Italic");
+ this._buildRow(t, "Underline");
+ this._buildRow(t, "Strikethrough");
+ var t = this._node.querySelector(".other");
+ this._buildRow(t, "Undo", "Redo");
+ this._buildRow(t, "Save");
+ this._buildRow(t, "SaveAs");
+ this._buildRow(t, "Load");
+ this._buildRow(t, "Help");
+ this._buildRow(t, "Notes");
+ this._buildRow(t, "UI");
+ };
+ MM.UI.Help.prototype._buildRow = function(table, commandName) {
+ var row = table.insertRow(-1);
+ var labels = [];
+ var keys = [];
+ for (var i = 1; i < arguments.length; i++) {
+ var command = MM.Command[arguments[i]];
+ if (!command) {
+ continue;
+ }
+ labels.push(command.label);
+ keys = keys.concat(command.keys.map(this._formatKey, this));
+ }
+ row.insertCell(-1).innerHTML = labels.join("/");
+ row.insertCell(-1).innerHTML = keys.join("/");
+ };
+ MM.UI.Help.prototype._formatKey = function(key) {
+ var str = "";
+ if (key.ctrlKey) {
+ str += "Ctrl+";
+ }
+ if (key.altKey) {
+ str += "Alt+";
+ }
+ if (key.shiftKey) {
+ str += "Shift+";
+ }
+ if (key.charCode) {
+ var ch = String.fromCharCode(key.charCode);
+ str += this._map[ch] || ch.toUpperCase();
+ }
+ if (key.keyCode) {
+ str += this._map[key.keyCode] || String.fromCharCode(key.keyCode);
+ }
+ return str;
+ };
+ MM.UI.Help.prototype.close = function() {
+ if (this._node.classList.contains("visible")) {
+ this._node.classList.toggle("visible");
+ }
+ };
+
+ // .js/ui/ui.notes.js
+ MM.UI.Notes = function() {
+ this._node = document.querySelector("#notes");
+ };
+ MM.UI.Notes.prototype.toggle = function() {
+ this._node.classList.toggle("visible");
+ };
+ MM.UI.Notes.prototype.close = function() {
+ if (this._node.classList.contains("visible")) {
+ this._node.classList.toggle("visible");
+ MM.Clipboard.focus();
+ }
+ };
+ MM.UI.Notes.prototype.update = function(html) {
+ if (html.trim().length === 0) {
+ MM.App.current._notes = null;
+ } else {
+ MM.App.current._notes = html;
+ }
+ MM.App.current.update();
+ };
+
+ // .js/ui/ui.io.js
+ MM.UI.IO = function() {
+ this._prefix = "mm.app.";
+ this._mode = "";
+ this._node = document.querySelector("#io");
+ this._heading = this._node.querySelector("h3");
+ this._backend = this._node.querySelector("#backend");
+ this._currentBackend = null;
+ this._backends = {};
+ var ids = ["local", "firebase", "gdrive", "file", "webdav", "image"];
+ ids.forEach(function(id) {
+ var ui = MM.UI.Backend.getById(id);
+ ui.init(this._backend);
+ this._backends[id] = ui;
+ }, this);
+ this._backend.value = localStorage.getItem(this._prefix + "backend") || MM.Backend.File.id;
+ this._backend.addEventListener("change", this);
+ MM.subscribe("map-new", this);
+ MM.subscribe("save-done", this);
+ MM.subscribe("load-done", this);
+ };
+ MM.UI.IO.prototype.restore = function() {
+ var parts = {};
+ location.search.substring(1).split("&").forEach(function(item) {
+ var keyvalue = item.split("=");
+ parts[decodeURIComponent(keyvalue[0])] = decodeURIComponent(keyvalue[1]);
+ });
+ if ("map" in parts) {
+ parts.url = parts.map;
+ }
+ if ("url" in parts && !("b" in parts)) {
+ parts.b = "webdav";
+ }
+ var backend = MM.UI.Backend.getById(parts.b);
+ if (backend) {
+ backend.setState(parts);
+ return;
+ }
+ if (parts.state) {
+ try {
+ var state = JSON.parse(parts.state);
+ if (state.action == "open") {
+ state = {
+ b: "gdrive",
+ id: state.ids[0]
+ };
+ MM.UI.Backend.GDrive.setState(state);
+ } else {
+ history.replaceState(null, "", ".");
+ }
+ return;
+ } catch (e) {
+ }
+ }
+ };
+ MM.UI.IO.prototype.handleMessage = function(message, publisher) {
+ switch (message) {
+ case "map-new":
+ this._setCurrentBackend(null);
+ break;
+ case "save-done":
+ case "load-done":
+ this.hide();
+ this._setCurrentBackend(publisher);
+ break;
+ }
+ };
+ MM.UI.IO.prototype.show = function(mode) {
+ this._mode = mode;
+ this._node.classList.add("visible");
+ this._heading.innerHTML = mode;
+ this._syncBackend();
+ window.addEventListener("keydown", this);
+ };
+ MM.UI.IO.prototype.hide = function() {
+ if (!this._node.classList.contains("visible")) {
+ return;
+ }
+ this._node.classList.remove("visible");
+ MM.Clipboard.focus();
+ window.removeEventListener("keydown", this);
+ };
+ MM.UI.IO.prototype.quickSave = function() {
+ if (this._currentBackend) {
+ this._currentBackend.save();
+ } else {
+ this.show("save");
+ }
+ };
+ MM.UI.IO.prototype.handleEvent = function(e) {
+ switch (e.type) {
+ case "keydown":
+ if (e.keyCode == 27) {
+ this.hide();
+ }
+ break;
+ case "change":
+ this._syncBackend();
+ break;
+ }
+ };
+ MM.UI.IO.prototype._syncBackend = function() {
+ var all = this._node.querySelectorAll("div[id]");
+ [].slice.apply(all).forEach(function(node) {
+ node.style.display = "none";
+ });
+ this._node.querySelector("#" + this._backend.value).style.display = "";
+ this._backends[this._backend.value].show(this._mode);
+ };
+ MM.UI.IO.prototype._setCurrentBackend = function(backend) {
+ if (this._currentBackend && this._currentBackend != backend) {
+ this._currentBackend.reset();
+ }
+ if (backend) {
+ localStorage.setItem(this._prefix + "backend", backend.id);
+ }
+ this._currentBackend = backend;
+ try {
+ this._updateURL();
+ } catch (e) {
+ }
+ };
+ MM.UI.IO.prototype._updateURL = function() {
+ var data = this._currentBackend && this._currentBackend.getState();
+ if (!data) {
+ history.replaceState(null, "", ".");
+ } else {
+ var arr = Object.keys(data).map(function(key) {
+ return encodeURIComponent(key) + "=" + encodeURIComponent(data[key]);
+ });
+ history.replaceState(null, "", "?" + arr.join("&"));
+ }
+ };
+
+ // .js/ui/backend/ui.backend.js
+ MM.UI.Backend = Object.create(MM.Repo);
+ MM.UI.Backend.init = function(select) {
+ this._backend = MM.Backend.getById(this.id);
+ this._mode = "";
+ this._prefix = "mm.app." + this.id + ".";
+ this._node = document.querySelector("#" + this.id);
+ this._cancel = this._node.querySelector(".cancel");
+ this._cancel.addEventListener("click", this);
+ this._go = this._node.querySelector(".go");
+ this._go.addEventListener("click", this);
+ select.appendChild(this._backend.buildOption());
+ };
+ MM.UI.Backend.reset = function() {
+ this._backend.reset();
+ };
+ MM.UI.Backend.setState = function(data) {
+ };
+ MM.UI.Backend.getState = function() {
+ return null;
+ };
+ MM.UI.Backend.handleEvent = function(e) {
+ switch (e.target) {
+ case this._cancel:
+ MM.App.io.hide();
+ break;
+ case this._go:
+ this._action();
+ break;
+ }
+ };
+ MM.UI.Backend.save = function() {
+ };
+ MM.UI.Backend.load = function() {
+ };
+ MM.UI.Backend.show = function(mode) {
+ this._mode = mode;
+ this._go.innerHTML = mode.charAt(0).toUpperCase() + mode.substring(1);
+ var all = this._node.querySelectorAll("[data-for]");
+ [].concat.apply([], all).forEach(function(node) {
+ node.style.display = "none";
+ });
+ var visible = this._node.querySelectorAll("[data-for~=" + mode + "]");
+ [].concat.apply([], visible).forEach(function(node) {
+ node.style.display = "";
+ });
+ this._go.focus();
+ };
+ MM.UI.Backend._action = function() {
+ switch (this._mode) {
+ case "save":
+ this.save();
+ break;
+ case "load":
+ this.load();
+ break;
+ }
+ };
+ MM.UI.Backend._saveDone = function() {
+ MM.App.setThrobber(false);
+ MM.publish("save-done", this);
+ };
+ MM.UI.Backend._loadDone = function(json) {
+ MM.App.setThrobber(false);
+ try {
+ MM.App.setMap(MM.Map.fromJSON(json));
+ MM.publish("load-done", this);
+ } catch (e) {
+ this._error(e);
+ }
+ };
+ MM.UI.Backend._error = function(e) {
+ MM.App.setThrobber(false);
+ alert("IO error: " + e.message);
+ };
+ MM.UI.Backend._buildList = function(list, select) {
+ var data = [];
+ for (var id in list) {
+ data.push({ id, name: list[id] });
+ }
+ data.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+ data.forEach(function(item) {
+ var o = document.createElement("option");
+ o.value = item.id;
+ o.innerHTML = item.name;
+ select.appendChild(o);
+ });
+ };
+
+ // .js/ui/backend/ui.backend.file.js
+ MM.UI.Backend.File = Object.create(MM.UI.Backend, {
+ id: { value: "file" }
+ });
+ MM.UI.Backend.File.init = function(select) {
+ MM.UI.Backend.init.call(this, select);
+ this._format = this._node.querySelector(".format");
+ this._format.appendChild(MM.Format.JSON.buildOption());
+ this._format.appendChild(MM.Format.FreeMind.buildOption());
+ this._format.appendChild(MM.Format.MMA.buildOption());
+ this._format.appendChild(MM.Format.Mup.buildOption());
+ this._format.appendChild(MM.Format.Plaintext.buildOption());
+ this._format.value = localStorage.getItem(this._prefix + "format") || MM.Format.JSON.id;
+ };
+ MM.UI.Backend.File.show = function(mode) {
+ MM.UI.Backend.show.call(this, mode);
+ this._go.innerHTML = mode == "save" ? "Save" : "Browse";
+ };
+ MM.UI.Backend.File._action = function() {
+ localStorage.setItem(this._prefix + "format", this._format.value);
+ MM.UI.Backend._action.call(this);
+ };
+ MM.UI.Backend.File.save = function() {
+ var format = MM.Format.getById(this._format.value);
+ var json = MM.App.map.toJSON();
+ var data = format.to(json);
+ var name = MM.App.map.getName() + "." + format.extension;
+ this._backend.save(data, name).then(this._saveDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.File.load = function() {
+ this._backend.load().then(this._loadDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.File._loadDone = function(data) {
+ try {
+ var format = MM.Format.getByName(data.name) || MM.Format.JSON;
+ var json = format.from(data.data);
+ } catch (e) {
+ this._error(e);
+ }
+ MM.UI.Backend._loadDone.call(this, json);
+ };
+
+ // .js/ui/backend/ui.backend.webdav.js
+ MM.UI.Backend.WebDAV = Object.create(MM.UI.Backend, {
+ id: { value: "webdav" }
+ });
+ MM.UI.Backend.WebDAV.init = function(select) {
+ MM.UI.Backend.init.call(this, select);
+ this._url = this._node.querySelector(".url");
+ this._url.value = localStorage.getItem(this._prefix + "url") || "";
+ this._current = "";
+ };
+ MM.UI.Backend.WebDAV.getState = function() {
+ var data = {
+ url: this._current
+ };
+ return data;
+ };
+ MM.UI.Backend.WebDAV.setState = function(data) {
+ this._load(data.url);
+ };
+ MM.UI.Backend.WebDAV.save = function() {
+ MM.App.setThrobber(true);
+ var map = MM.App.map;
+ var url = this._url.value;
+ localStorage.setItem(this._prefix + "url", url);
+ if (url.match(/\.mymind$/)) {
+ } else {
+ if (url.charAt(url.length - 1) != "/") {
+ url += "/";
+ }
+ url += map.getName() + "." + MM.Format.JSON.extension;
+ }
+ this._current = url;
+ var json = map.toJSON();
+ var data = MM.Format.JSON.to(json);
+ this._backend.save(data, url).then(this._saveDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.WebDAV.load = function() {
+ this._load(this._url.value);
+ };
+ MM.UI.Backend.WebDAV._load = function(url) {
+ this._current = url;
+ MM.App.setThrobber(true);
+ var lastIndex = url.lastIndexOf("/");
+ this._url.value = url.substring(0, lastIndex);
+ localStorage.setItem(this._prefix + "url", this._url.value);
+ this._backend.load(url).then(this._loadDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.WebDAV._loadDone = function(data) {
+ try {
+ var json = MM.Format.JSON.from(data);
+ } catch (e) {
+ this._error(e);
+ }
+ MM.UI.Backend._loadDone.call(this, json);
+ };
+
+ // .js/ui/backend/ui.backend.image.js
+ MM.UI.Backend.Image = Object.create(MM.UI.Backend, {
+ id: { value: "image" }
+ });
+ MM.UI.Backend.Image.save = function() {
+ var name = MM.App.map.getName();
+ var json = MM.App.map.toJSON();
+ var data = MM.Format.JSON.to(json);
+ this._backend.save(data, name);
+ };
+ MM.UI.Backend.Image.load = null;
+
+ // .js/ui/backend/ui.backend.local.js
+ MM.UI.Backend.Local = Object.create(MM.UI.Backend, {
+ id: { value: "local" }
+ });
+ MM.UI.Backend.Local.init = function(select) {
+ MM.UI.Backend.init.call(this, select);
+ this._list = this._node.querySelector(".list");
+ this._remove = this._node.querySelector(".remove");
+ this._remove.addEventListener("click", this);
+ };
+ MM.UI.Backend.Local.handleEvent = function(e) {
+ MM.UI.Backend.handleEvent.call(this, e);
+ switch (e.target) {
+ case this._remove:
+ var id = this._list.value;
+ if (!id) {
+ break;
+ }
+ this._backend.remove(id);
+ this.show(this._mode);
+ break;
+ }
+ };
+ MM.UI.Backend.Local.show = function(mode) {
+ MM.UI.Backend.show.call(this, mode);
+ this._go.disabled = false;
+ if (mode == "load") {
+ var list = this._backend.list();
+ this._list.innerHTML = "";
+ if (Object.keys(list).length) {
+ this._go.disabled = false;
+ this._remove.disabled = false;
+ this._buildList(list, this._list);
+ } else {
+ this._go.disabled = true;
+ this._remove.disabled = true;
+ var o = document.createElement("option");
+ o.innerHTML = "(no maps saved)";
+ this._list.appendChild(o);
+ }
+ }
+ };
+ MM.UI.Backend.Local.setState = function(data) {
+ this._load(data.id);
+ };
+ MM.UI.Backend.Local.getState = function() {
+ var data = {
+ b: this.id,
+ id: MM.App.map.getId()
+ };
+ return data;
+ };
+ MM.UI.Backend.Local.save = function() {
+ var json = MM.App.map.toJSON();
+ var data = MM.Format.JSON.to(json);
+ try {
+ this._backend.save(data, MM.App.map.getId(), MM.App.map.getName());
+ this._saveDone();
+ } catch (e) {
+ this._error(e);
+ }
+ };
+ MM.UI.Backend.Local.load = function() {
+ this._load(this._list.value);
+ };
+ MM.UI.Backend.Local._load = function(id) {
+ try {
+ var data = this._backend.load(id);
+ var json = MM.Format.JSON.from(data);
+ this._loadDone(json);
+ } catch (e) {
+ this._error(e);
+ }
+ };
+
+ // .js/ui/backend/ui.backend.firebase.js
+ MM.UI.Backend.Firebase = Object.create(MM.UI.Backend, {
+ id: { value: "firebase" }
+ });
+ MM.UI.Backend.Firebase.init = function(select) {
+ MM.UI.Backend.init.call(this, select);
+ this._online = false;
+ this._itemChangeTimeout = null;
+ this._list = this._node.querySelector(".list");
+ this._server = this._node.querySelector(".server");
+ this._server.value = localStorage.getItem(this._prefix + "server") || "my-mind";
+ this._auth = this._node.querySelector(".auth");
+ this._auth.value = localStorage.getItem(this._prefix + "auth") || "";
+ this._remove = this._node.querySelector(".remove");
+ this._remove.addEventListener("click", this);
+ this._go.disabled = false;
+ MM.subscribe("firebase-list", this);
+ MM.subscribe("firebase-change", this);
+ };
+ MM.UI.Backend.Firebase.setState = function(data) {
+ this._connect(data.s, data.a).then(this._load.bind(this, data.id), this._error.bind(this));
+ };
+ MM.UI.Backend.Firebase.getState = function() {
+ var data = {
+ id: MM.App.map.getId(),
+ b: this.id,
+ s: this._server.value
+ };
+ if (this._auth.value) {
+ data.a = this._auth.value;
+ }
+ return data;
+ };
+ MM.UI.Backend.Firebase.show = function(mode) {
+ MM.UI.Backend.show.call(this, mode);
+ this._sync();
+ };
+ MM.UI.Backend.Firebase.handleEvent = function(e) {
+ MM.UI.Backend.handleEvent.call(this, e);
+ switch (e.target) {
+ case this._remove:
+ var id = this._list.value;
+ if (!id) {
+ break;
+ }
+ MM.App.setThrobber(true);
+ this._backend.remove(id).then(function() {
+ MM.App.setThrobber(false);
+ }, this._error.bind(this));
+ break;
+ }
+ };
+ MM.UI.Backend.Firebase.handleMessage = function(message, publisher, data) {
+ switch (message) {
+ case "firebase-list":
+ this._list.innerHTML = "";
+ if (Object.keys(data).length) {
+ this._buildList(data, this._list);
+ } else {
+ var o = document.createElement("option");
+ o.innerHTML = "(no maps saved)";
+ this._list.appendChild(o);
+ }
+ this._sync();
+ break;
+ case "firebase-change":
+ if (data) {
+ MM.unsubscribe("item-change", this);
+ MM.App.map.mergeWith(data);
+ MM.subscribe("item-change", this);
+ } else {
+ console.log("remote data disappeared");
+ }
+ break;
+ case "item-change":
+ if (this._itemChangeTimeout) {
+ clearTimeout(this._itemChangeTimeout);
+ }
+ this._itemChangeTimeout = setTimeout(this._itemChange.bind(this), 200);
+ break;
+ }
+ };
+ MM.UI.Backend.Firebase.reset = function() {
+ this._backend.reset();
+ MM.unsubscribe("item-change", this);
+ };
+ MM.UI.Backend.Firebase._itemChange = function() {
+ var map = MM.App.map;
+ this._backend.mergeWith(map.toJSON(), map.getName());
+ };
+ MM.UI.Backend.Firebase._action = function() {
+ if (!this._online) {
+ this._connect(this._server.value, this._auth.value);
+ return;
+ }
+ MM.UI.Backend._action.call(this);
+ };
+ MM.UI.Backend.Firebase.save = function() {
+ MM.App.setThrobber(true);
+ var map = MM.App.map;
+ this._backend.save(map.toJSON(), map.getId(), map.getName()).then(this._saveDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.Firebase.load = function() {
+ this._load(this._list.value);
+ };
+ MM.UI.Backend.Firebase._load = function(id) {
+ MM.App.setThrobber(true);
+ this._backend.load(id).then(this._loadDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.Firebase._connect = function(server, auth) {
+ var promise = new Promise();
+ this._server.value = server;
+ this._auth.value = auth;
+ this._server.disabled = true;
+ this._auth.disabled = true;
+ localStorage.setItem(this._prefix + "server", server);
+ localStorage.setItem(this._prefix + "auth", auth || "");
+ this._go.disabled = true;
+ MM.App.setThrobber(true);
+ this._backend.connect(server, auth).then(function() {
+ this._connected();
+ promise.fulfill();
+ }.bind(this), promise.reject.bind(promise));
+ return promise;
+ };
+ MM.UI.Backend.Firebase._connected = function() {
+ MM.App.setThrobber(false);
+ this._online = true;
+ this._sync();
+ };
+ MM.UI.Backend.Firebase._sync = function() {
+ if (!this._online) {
+ this._go.innerHTML = "Connect";
+ return;
+ }
+ this._go.disabled = false;
+ if (this._mode == "load" && !this._list.value) {
+ this._go.disabled = true;
+ }
+ this._go.innerHTML = this._mode.charAt(0).toUpperCase() + this._mode.substring(1);
+ };
+ MM.UI.Backend.Firebase._loadDone = function() {
+ MM.subscribe("item-change", this);
+ MM.UI.Backend._loadDone.apply(this, arguments);
+ };
+ MM.UI.Backend.Firebase._saveDone = function() {
+ MM.subscribe("item-change", this);
+ MM.UI.Backend._saveDone.apply(this, arguments);
+ };
+
+ // .js/ui/backend/ui.backend.gdrive.js
+ MM.UI.Backend.GDrive = Object.create(MM.UI.Backend, {
+ id: { value: "gdrive" }
+ });
+ MM.UI.Backend.GDrive.init = function(select) {
+ MM.UI.Backend.init.call(this, select);
+ this._format = this._node.querySelector(".format");
+ this._format.appendChild(MM.Format.JSON.buildOption());
+ this._format.appendChild(MM.Format.FreeMind.buildOption());
+ this._format.appendChild(MM.Format.MMA.buildOption());
+ this._format.appendChild(MM.Format.Mup.buildOption());
+ this._format.appendChild(MM.Format.Plaintext.buildOption());
+ this._format.value = localStorage.getItem(this._prefix + "format") || MM.Format.JSON.id;
+ };
+ MM.UI.Backend.GDrive.save = function() {
+ MM.App.setThrobber(true);
+ var format = MM.Format.getById(this._format.value);
+ var json = MM.App.map.toJSON();
+ var data = format.to(json);
+ var name = MM.App.map.getName();
+ var mime = "text/plain";
+ if (format.mime) {
+ mime = format.mime;
+ } else {
+ name += "." + format.extension;
+ }
+ this._backend.save(data, name, mime).then(this._saveDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.GDrive.load = function() {
+ MM.App.setThrobber(true);
+ this._backend.pick().then(this._picked.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.GDrive._picked = function(id) {
+ MM.App.setThrobber(false);
+ if (!id) {
+ return;
+ }
+ MM.App.setThrobber(true);
+ this._backend.load(id).then(this._loadDone.bind(this), this._error.bind(this));
+ };
+ MM.UI.Backend.GDrive.setState = function(data) {
+ this._picked(data.id);
+ };
+ MM.UI.Backend.GDrive.getState = function() {
+ var data = {
+ b: this.id,
+ id: this._backend.fileId
+ };
+ return data;
+ };
+ MM.UI.Backend.GDrive._loadDone = function(data) {
+ try {
+ var format = MM.Format.getByMime(data.mime) || MM.Format.getByName(data.name) || MM.Format.JSON;
+ var json = format.from(data.data);
+ } catch (e) {
+ this._error(e);
+ }
+ MM.UI.Backend._loadDone.call(this, json);
+ };
+
+ // .js/mouse.js
+ MM.Mouse = {
+ TOUCH_DELAY: 500,
+ _port: null,
+ _cursor: [0, 0],
+ _pos: [0, 0],
+ _mode: "",
+ _item: null,
+ _ghost: null,
+ _oldDragState: null,
+ _touchTimeout: null
+ };
+ MM.Mouse.init = function(port) {
+ this._port = port;
+ this._port.addEventListener("touchstart", this);
+ this._port.addEventListener("mousedown", this);
+ this._port.addEventListener("click", this);
+ this._port.addEventListener("dblclick", this);
+ this._port.addEventListener("wheel", this);
+ this._port.addEventListener("mousewheel", this);
+ this._port.addEventListener("contextmenu", this);
+ };
+ MM.Mouse.handleEvent = function(e) {
+ switch (e.type) {
+ case "click":
+ var item = MM.App.map.getItemFor(e.target);
+ if (MM.App.editing && item == MM.App.current) {
+ return;
+ }
+ if (item) {
+ MM.App.select(item);
+ }
+ break;
+ case "dblclick":
+ var item = MM.App.map.getItemFor(e.target);
+ if (item) {
+ MM.Command.Edit.execute();
+ }
+ break;
+ case "contextmenu":
+ this._endDrag();
+ e.preventDefault();
+ var item = MM.App.map.getItemFor(e.target);
+ item && MM.App.select(item);
+ MM.Menu.open(e.clientX, e.clientY);
+ break;
+ case "touchstart":
+ if (e.touches.length > 1) {
+ return;
+ }
+ e.clientX = e.touches[0].clientX;
+ e.clientY = e.touches[0].clientY;
+ case "mousedown":
+ var item = MM.App.map.getItemFor(e.target);
+ if (MM.App.editing) {
+ if (item == MM.App.current) {
+ return;
+ }
+ MM.Command.Finish.execute();
+ }
+ if (e.type == "mousedown") {
+ e.preventDefault();
+ }
+ if (e.type == "touchstart") {
+ this._touchTimeout = setTimeout(function() {
+ item && MM.App.select(item);
+ MM.Menu.open(e.clientX, e.clientY);
+ }, this.TOUCH_DELAY);
+ }
+ this._startDrag(e, item);
+ break;
+ case "touchmove":
+ if (e.touches.length > 1) {
+ return;
+ }
+ e.clientX = e.touches[0].clientX;
+ e.clientY = e.touches[0].clientY;
+ clearTimeout(this._touchTimeout);
+ case "mousemove":
+ this._processDrag(e);
+ break;
+ case "touchend":
+ clearTimeout(this._touchTimeout);
+ case "mouseup":
+ this._endDrag();
+ break;
+ case "wheel":
+ case "mousewheel":
+ var dir = 0;
+ if (e.wheelDelta) {
+ if (e.wheelDelta < 0) {
+ dir = -1;
+ } else if (e.wheelDelta > 0) {
+ dir = 1;
+ }
+ }
+ if (e.deltaY) {
+ if (e.deltaY > 0) {
+ dir = -1;
+ } else if (e.deltaY < 0) {
+ dir = 1;
+ }
+ }
+ if (dir) {
+ MM.App.adjustFontSize(dir);
+ }
+ break;
+ }
+ };
+ MM.Mouse._startDrag = function(e, item) {
+ if (e.type == "mousedown") {
+ e.preventDefault();
+ this._port.addEventListener("mousemove", this);
+ this._port.addEventListener("mouseup", this);
+ } else {
+ this._port.addEventListener("touchmove", this);
+ this._port.addEventListener("touchend", this);
+ }
+ this._cursor[0] = e.clientX;
+ this._cursor[1] = e.clientY;
+ if (item && !item.isRoot()) {
+ this._mode = "drag";
+ this._item = item;
+ } else {
+ this._mode = "pan";
+ this._port.style.cursor = "move";
+ }
+ };
+ MM.Mouse._processDrag = function(e) {
+ e.preventDefault();
+ var dx = e.clientX - this._cursor[0];
+ var dy = e.clientY - this._cursor[1];
+ this._cursor[0] = e.clientX;
+ this._cursor[1] = e.clientY;
+ switch (this._mode) {
+ case "drag":
+ if (!this._ghost) {
+ this._port.style.cursor = "move";
+ this._buildGhost(dx, dy);
+ }
+ this._moveGhost(dx, dy);
+ var state = this._computeDragState();
+ this._visualizeDragState(state);
+ break;
+ case "pan":
+ MM.App.map.moveBy(dx, dy);
+ break;
+ }
+ };
+ MM.Mouse._endDrag = function() {
+ this._port.style.cursor = "";
+ this._port.removeEventListener("mousemove", this);
+ this._port.removeEventListener("mouseup", this);
+ if (this._mode == "pan") {
+ return;
+ }
+ if (this._ghost) {
+ var state = this._computeDragState();
+ this._finishDragDrop(state);
+ this._ghost.parentNode.removeChild(this._ghost);
+ this._ghost = null;
+ }
+ this._item = null;
+ };
+ MM.Mouse._buildGhost = function() {
+ var content = this._item.getDOM().content;
+ this._ghost = content.cloneNode(true);
+ this._ghost.classList.add("ghost");
+ this._pos[0] = content.offsetLeft;
+ this._pos[1] = content.offsetTop;
+ content.parentNode.appendChild(this._ghost);
+ };
+ MM.Mouse._moveGhost = function(dx, dy) {
+ this._pos[0] += dx;
+ this._pos[1] += dy;
+ this._ghost.style.left = this._pos[0] + "px";
+ this._ghost.style.top = this._pos[1] + "px";
+ var state = this._computeDragState();
+ };
+ MM.Mouse._finishDragDrop = function(state) {
+ this._visualizeDragState(null);
+ var target = state.item;
+ switch (state.result) {
+ case "append":
+ var action = new MM.Action.MoveItem(this._item, target);
+ break;
+ case "sibling":
+ var index = target.getParent().getChildren().indexOf(target);
+ var targetIndex = index + (state.direction == "right" || state.direction == "bottom" ? 1 : 0);
+ var action = new MM.Action.MoveItem(this._item, target.getParent(), targetIndex, target.getSide());
+ break;
+ default:
+ return;
+ break;
+ }
+ MM.App.action(action);
+ };
+ MM.Mouse._computeDragState = function() {
+ var rect = this._ghost.getBoundingClientRect();
+ var closest = MM.App.map.getClosestItem(rect.left + rect.width / 2, rect.top + rect.height / 2);
+ var target = closest.item;
+ var state = {
+ result: "",
+ item: target,
+ direction: ""
+ };
+ var tmp = target;
+ while (!tmp.isRoot()) {
+ if (tmp == this._item) {
+ return state;
+ }
+ tmp = tmp.getParent();
+ }
+ var w1 = this._item.getDOM().content.offsetWidth;
+ var w2 = target.getDOM().content.offsetWidth;
+ var w = Math.max(w1, w2);
+ var h1 = this._item.getDOM().content.offsetHeight;
+ var h2 = target.getDOM().content.offsetHeight;
+ var h = Math.max(h1, h2);
+ if (target.isRoot()) {
+ state.result = "append";
+ } else if (Math.abs(closest.dx) < w && Math.abs(closest.dy) < h) {
+ state.result = "append";
+ } else {
+ state.result = "sibling";
+ var childDirection = target.getParent().getLayout().getChildDirection(target);
+ var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
+ if (childDirection == "left" || childDirection == "right") {
+ state.direction = closest.dy < 0 ? "bottom" : "top";
+ } else {
+ state.direction = closest.dx < 0 ? "right" : "left";
+ }
+ }
+ return state;
+ };
+ MM.Mouse._visualizeDragState = function(state) {
+ if (this._oldState && state && this._oldState.item == state.item && this._oldState.result == state.result) {
+ return;
+ }
+ if (this._oldDragState) {
+ var item = this._oldDragState.item;
+ var node = item.getDOM().content;
+ node.style.boxShadow = "";
+ }
+ this._oldDragState = state;
+ if (state) {
+ var item = state.item;
+ var node = item.getDOM().content;
+ var x = 0;
+ var y = 0;
+ var offset = 5;
+ if (state.result == "sibling") {
+ if (state.direction == "left") {
+ x = -1;
+ }
+ if (state.direction == "right") {
+ x = 1;
+ }
+ if (state.direction == "top") {
+ y = -1;
+ }
+ if (state.direction == "bottom") {
+ y = 1;
+ }
+ }
+ var spread = x || y ? -2 : 2;
+ node.style.boxShadow = x * offset + "px " + y * offset + "px 2px " + spread + "px #000";
+ }
+ };
+
+ // .js/my-mind.js
+ MM.App = {
+ keyboard: null,
+ current: null,
+ editing: false,
+ history: [],
+ historyIndex: 0,
+ portSize: [0, 0],
+ map: null,
+ ui: null,
+ io: null,
+ help: null,
+ _port: null,
+ _throbber: null,
+ _drag: {
+ pos: [0, 0],
+ item: null,
+ ghost: null
+ },
+ _fontSize: 100,
+ action: function(action) {
+ if (this.historyIndex < this.history.length) {
+ this.history.splice(this.historyIndex, this.history.length - this.historyIndex);
+ }
+ this.history.push(action);
+ this.historyIndex++;
+ action.perform();
+ return this;
+ },
+ setMap: function(map) {
+ if (this.map) {
+ this.map.hide();
+ }
+ this.history = [];
+ this.historyIndex = 0;
+ this.map = map;
+ this.map.show(this._port);
+ },
+ select: function(item) {
+ if (this.current && this.current != item) {
+ this.current.deselect();
+ }
+ this.current = item;
+ this.current.select();
+ },
+ adjustFontSize: function(diff) {
+ this._fontSize = Math.max(30, this._fontSize + 10 * diff);
+ this._port.style.fontSize = this._fontSize + "%";
+ this.map.update();
+ this.map.ensureItemVisibility(this.current);
+ },
+ handleMessage: function(message, publisher) {
+ switch (message) {
+ case "ui-change":
+ this._syncPort();
+ break;
+ case "item-change":
+ if (publisher.isRoot() && publisher.getMap() == this.map) {
+ document.title = this.map.getName() + " :: My Mind";
+ }
+ break;
+ }
+ },
+ handleEvent: function(e) {
+ switch (e.type) {
+ case "resize":
+ this._syncPort();
+ break;
+ case "keyup":
+ if (e.key === "Escape") {
+ MM.App.notes.close();
+ MM.App.help.close();
+ }
+ break;
+ case "message":
+ if (e.data && e.data.action) {
+ switch (e.data.action) {
+ case "setContent":
+ MM.App.notes.update(e.data.value);
+ break;
+ case "closeEditor":
+ MM.App.notes.close();
+ break;
+ }
+ }
+ break;
+ case "beforeunload":
+ e.preventDefault();
+ return "";
+ break;
+ }
+ },
+ setThrobber: function(visible) {
+ this._throbber.classList[visible ? "add" : "remove"]("visible");
+ },
+ init: function() {
+ this._port = document.querySelector("#port");
+ this._throbber = document.querySelector("#throbber");
+ this.ui = new MM.UI();
+ this.io = new MM.UI.IO();
+ this.help = new MM.UI.Help();
+ this.notes = new MM.UI.Notes();
+ MM.Tip.init();
+ MM.Keyboard.init();
+ MM.Menu.init(this._port);
+ MM.Mouse.init(this._port);
+ MM.Clipboard.init();
+ window.addEventListener("resize", this);
+ window.addEventListener("beforeunload", this);
+ window.addEventListener("keyup", this);
+ window.addEventListener("message", this, false);
+ MM.subscribe("ui-change", this);
+ MM.subscribe("item-change", this);
+ this._syncPort();
+ this.setMap(new MM.Map());
+ },
+ _syncPort: function() {
+ this.portSize = [window.innerWidth - this.ui.getWidth(), window.innerHeight];
+ this._port.style.width = this.portSize[0] + "px";
+ this._port.style.height = this.portSize[1] + "px";
+ this._throbber.style.right = 20 + this.ui.getWidth() + "px";
+ if (this.map) {
+ this.map.ensureItemVisibility(this.current);
+ }
+ }
+ };
+})();
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..5d0c9051
--- /dev/null
+++ b/package.json
@@ -0,0 +1,6 @@
+{
+ "devDependencies": {
+ "esbuild": "^0.13.7",
+ "typescript": "^4.4.4"
+ }
+}
diff --git a/src/action.js b/src/action.js
index 7e69738b..36099d29 100644
--- a/src/action.js
+++ b/src/action.js
@@ -93,7 +93,7 @@ MM.Action.Swap = function(item, diff) {
var children = this._parent.getChildren();
var sibling = this._parent.getLayout().pickSibling(this._item, diff);
-
+
this._sourceIndex = children.indexOf(this._item);
this._targetIndex = children.indexOf(sibling);
}
diff --git a/src/backend.file.js b/src/backend/backend.file.js
similarity index 100%
rename from src/backend.file.js
rename to src/backend/backend.file.js
diff --git a/src/backend.firebase.js b/src/backend/backend.firebase.js
similarity index 100%
rename from src/backend.firebase.js
rename to src/backend/backend.firebase.js
diff --git a/src/backend.gdrive.js b/src/backend/backend.gdrive.js
similarity index 100%
rename from src/backend.gdrive.js
rename to src/backend/backend.gdrive.js
diff --git a/src/backend.image.js b/src/backend/backend.image.js
similarity index 100%
rename from src/backend.image.js
rename to src/backend/backend.image.js
diff --git a/src/backend.js b/src/backend/backend.js
similarity index 100%
rename from src/backend.js
rename to src/backend/backend.js
diff --git a/src/backend.local.js b/src/backend/backend.local.js
similarity index 100%
rename from src/backend.local.js
rename to src/backend/backend.local.js
diff --git a/src/backend.webdav.js b/src/backend/backend.webdav.js
similarity index 100%
rename from src/backend.webdav.js
rename to src/backend/backend.webdav.js
diff --git a/src/command.edit.js b/src/command/command.edit.js
similarity index 100%
rename from src/command.edit.js
rename to src/command/command.edit.js
diff --git a/src/command.js b/src/command/command.js
similarity index 100%
rename from src/command.js
rename to src/command/command.js
diff --git a/src/command.select.js b/src/command/command.select.js
similarity index 100%
rename from src/command.select.js
rename to src/command/command.select.js
diff --git a/src/format.freemind.js b/src/format/format.freemind.js
similarity index 100%
rename from src/format.freemind.js
rename to src/format/format.freemind.js
diff --git a/src/format.js b/src/format/format.js
similarity index 100%
rename from src/format.js
rename to src/format/format.js
diff --git a/src/format.json.js b/src/format/format.json.js
similarity index 100%
rename from src/format.json.js
rename to src/format/format.json.js
diff --git a/src/format.mma.js b/src/format/format.mma.js
similarity index 100%
rename from src/format.mma.js
rename to src/format/format.mma.js
diff --git a/src/format.mup.js b/src/format/format.mup.js
similarity index 100%
rename from src/format.mup.js
rename to src/format/format.mup.js
diff --git a/src/format.plaintext.js b/src/format/format.plaintext.js
similarity index 100%
rename from src/format.plaintext.js
rename to src/format/format.plaintext.js
diff --git a/src/layout.graph.js b/src/layout/layout.graph.js
similarity index 100%
rename from src/layout.graph.js
rename to src/layout/layout.graph.js
diff --git a/src/layout.js b/src/layout/layout.js
similarity index 100%
rename from src/layout.js
rename to src/layout/layout.js
diff --git a/src/layout.map.js b/src/layout/layout.map.js
similarity index 100%
rename from src/layout.map.js
rename to src/layout/layout.map.js
diff --git a/src/layout.tree.js b/src/layout/layout.tree.js
similarity index 100%
rename from src/layout.tree.js
rename to src/layout/layout.tree.js
diff --git a/src/mm.js b/src/mm.js
index b7a53a81..90c3f281 100644
--- a/src/mm.js
+++ b/src/mm.js
@@ -8,7 +8,7 @@ if (!Function.prototype.bind) {
}
};
-var MM = {
+window.MM = {
_subscribers: {},
publish: function(message, publisher, data) {
diff --git a/src/app.js b/src/my-mind.js
similarity index 71%
rename from src/app.js
rename to src/my-mind.js
index 4a6e73b5..59f69fd3 100644
--- a/src/app.js
+++ b/src/my-mind.js
@@ -1,3 +1,58 @@
+import "./mm.js";
+import "./promise.js";
+import "./promise-addons.js";
+import "./repo.js";
+import "./item.js";
+import "./map.js";
+import "./keyboard.js";
+import "./tip.js";
+import "./action.js";
+import "./clipboard.js";
+import "./menu.js";
+import "./command/command.js";
+import "./command/command.edit.js";
+import "./command/command.select.js";
+import "./layout/layout.js";
+import "./layout/layout.graph.js";
+import "./layout/layout.tree.js";
+import "./layout/layout.map.js";
+import "./shape/shape.js";
+import "./shape/shape.underline.js";
+import "./shape/shape.box.js";
+import "./shape/shape.ellipse.js";
+import "./format/format.js";
+import "./format/format.json.js";
+import "./format/format.freemind.js";
+import "./format/format.mma.js";
+import "./format/format.mup.js";
+import "./format/format.plaintext.js";
+import "./backend/backend.js";
+import "./backend/backend.local.js";
+import "./backend/backend.webdav.js";
+import "./backend/backend.image.js";
+import "./backend/backend.file.js";
+import "./backend/backend.firebase.js";
+import "./backend/backend.gdrive.js";
+import "./ui/ui.js";
+import "./ui/ui.layout.js";
+import "./ui/ui.shape.js";
+import "./ui/ui.value.js";
+import "./ui/ui.status.js";
+import "./ui/ui.color.js";
+import "./ui/ui.icon.js";
+import "./ui/ui.help.js";
+import "./ui/ui.notes.js";
+import "./ui/ui.io.js";
+import "./ui/backend/ui.backend.js";
+import "./ui/backend/ui.backend.file.js";
+import "./ui/backend/ui.backend.webdav.js";
+import "./ui/backend/ui.backend.image.js";
+import "./ui/backend/ui.backend.local.js";
+import "./ui/backend/ui.backend.firebase.js";
+import "./ui/backend/ui.backend.gdrive.js";
+import "./mouse.js";
+
+
/*
setInterval(function() {
console.log(document.activeElement);
@@ -7,17 +62,17 @@ setInterval(function() {
/*
* Notes regarding app state/modes, activeElements, focusing etc.
* ==============================================================
- *
- * 1) There is always exactly one item selected. All executed commands
+ *
+ * 1) There is always exactly one item selected. All executed commands
* operate on this item.
- *
+ *
* 2) The app distinguishes three modes with respect to focus:
- * 2a) One of the UI panes has focus (inputs, buttons, selects).
+ * 2a) One of the UI panes has focus (inputs, buttons, selects).
* Keyboard shortcuts are disabled.
- * 2b) Current item is being edited. It is contentEditable and focused.
+ * 2b) Current item is being edited. It is contentEditable and focused.
* Blurring ends the edit mode.
* 2c) ELSE the Clipboard is focused (its invisible textarea)
- *
+ *
* In 2a, we try to lose focus as soon as possible
* (after clicking, after changing select's value), switching to 2c.
*
@@ -30,7 +85,7 @@ setInterval(function() {
* this calls MM.Command.Finish (3b).
* 3b) By blurring the currentElement;
* this calls MM.Command.Finish (3b).
- *
+ *
*/
MM.App = {
keyboard: null,
@@ -51,19 +106,19 @@ MM.App = {
ghost: null
},
_fontSize: 100,
-
+
action: function(action) {
if (this.historyIndex < this.history.length) { /* remove undoed actions */
this.history.splice(this.historyIndex, this.history.length-this.historyIndex);
}
-
+
this.history.push(action);
this.historyIndex++;
-
+
action.perform();
return this;
},
-
+
setMap: function(map) {
if (this.map) { this.map.hide(); }
@@ -73,7 +128,7 @@ MM.App = {
this.map = map;
this.map.show(this._port);
},
-
+
select: function(item) {
if (this.current && this.current != item) { this.current.deselect(); }
this.current = item;
@@ -86,7 +141,7 @@ MM.App = {
this.map.update();
this.map.ensureItemVisibility(this.current);
},
-
+
handleMessage: function(message, publisher) {
switch (message) {
case "ui-change":
@@ -135,7 +190,7 @@ MM.App = {
break;
}
},
-
+
setThrobber: function(visible) {
this._throbber.classList[visible ? "add" : "remove"]("visible");
},
@@ -160,7 +215,7 @@ MM.App = {
window.addEventListener("message", this, false);
MM.subscribe("ui-change", this);
MM.subscribe("item-change", this);
-
+
this._syncPort();
this.setMap(new MM.Map());
},
diff --git a/src/shape.box.js b/src/shape/shape.box.js
similarity index 100%
rename from src/shape.box.js
rename to src/shape/shape.box.js
diff --git a/src/shape.ellipse.js b/src/shape/shape.ellipse.js
similarity index 100%
rename from src/shape.ellipse.js
rename to src/shape/shape.ellipse.js
diff --git a/src/shape.js b/src/shape/shape.js
similarity index 100%
rename from src/shape.js
rename to src/shape/shape.js
diff --git a/src/shape.underline.js b/src/shape/shape.underline.js
similarity index 100%
rename from src/shape.underline.js
rename to src/shape/shape.underline.js
diff --git a/src/ui.backend.file.js b/src/ui/backend/ui.backend.file.js
similarity index 100%
rename from src/ui.backend.file.js
rename to src/ui/backend/ui.backend.file.js
diff --git a/src/ui.backend.firebase.js b/src/ui/backend/ui.backend.firebase.js
similarity index 100%
rename from src/ui.backend.firebase.js
rename to src/ui/backend/ui.backend.firebase.js
diff --git a/src/ui.backend.gdrive.js b/src/ui/backend/ui.backend.gdrive.js
similarity index 100%
rename from src/ui.backend.gdrive.js
rename to src/ui/backend/ui.backend.gdrive.js
diff --git a/src/ui.backend.image.js b/src/ui/backend/ui.backend.image.js
similarity index 100%
rename from src/ui.backend.image.js
rename to src/ui/backend/ui.backend.image.js
diff --git a/src/ui.backend.js b/src/ui/backend/ui.backend.js
similarity index 100%
rename from src/ui.backend.js
rename to src/ui/backend/ui.backend.js
diff --git a/src/ui.backend.local.js b/src/ui/backend/ui.backend.local.js
similarity index 100%
rename from src/ui.backend.local.js
rename to src/ui/backend/ui.backend.local.js
diff --git a/src/ui.backend.webdav.js b/src/ui/backend/ui.backend.webdav.js
similarity index 100%
rename from src/ui.backend.webdav.js
rename to src/ui/backend/ui.backend.webdav.js
diff --git a/src/ui.color.js b/src/ui/ui.color.js
similarity index 100%
rename from src/ui.color.js
rename to src/ui/ui.color.js
diff --git a/src/ui.help.js b/src/ui/ui.help.js
similarity index 100%
rename from src/ui.help.js
rename to src/ui/ui.help.js
diff --git a/src/ui.icon.js b/src/ui/ui.icon.js
similarity index 100%
rename from src/ui.icon.js
rename to src/ui/ui.icon.js
diff --git a/src/ui.io.js b/src/ui/ui.io.js
similarity index 100%
rename from src/ui.io.js
rename to src/ui/ui.io.js
diff --git a/src/ui.js b/src/ui/ui.js
similarity index 100%
rename from src/ui.js
rename to src/ui/ui.js
diff --git a/src/ui.layout.js b/src/ui/ui.layout.js
similarity index 100%
rename from src/ui.layout.js
rename to src/ui/ui.layout.js
diff --git a/src/ui.notes.js b/src/ui/ui.notes.js
similarity index 100%
rename from src/ui.notes.js
rename to src/ui/ui.notes.js
diff --git a/src/ui.shape.js b/src/ui/ui.shape.js
similarity index 100%
rename from src/ui.shape.js
rename to src/ui/ui.shape.js
diff --git a/src/ui.status.js b/src/ui/ui.status.js
similarity index 100%
rename from src/ui.status.js
rename to src/ui/ui.status.js
diff --git a/src/ui.value.js b/src/ui/ui.value.js
similarity index 100%
rename from src/ui.value.js
rename to src/ui/ui.value.js
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..2e8725aa
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": ".js",
+ "target": "es2019",
+ "allowJs": true,
+ "noEmitOnError": true,
+ "incremental": true,
+ }
+}
From d8dda2d6765a61e8f70e8fbf1b0cb685aadbe64b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sat, 16 Oct 2021 20:06:09 +0200
Subject: [PATCH 02/42] Update README.md
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index c80510c9..227c3736 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,16 @@
My Mind is a web application for creating and managing Mind maps. It is free to use and you can fork its source code. It is distributed under the terms of the MIT license.
-New to Mind maps? They are useful, aesthetic and cool! Read more about these special diagrams in [the Wikipedia article](http://en.wikipedia.org/wiki/Mind_map).
+New to Mind maps? They are useful, aesthetic and cool! Read more about these special diagrams in [the Wikipedia article](https://en.wikipedia.org/wiki/Mind_map).
-* [Official web page](http://my-mind.github.io/)
-* [Sample mind map](http://my-mind.github.io/?map=examples/features.mymind) showcasing many features
+* [Official web page](https://my-mind.github.io/)
+* [Sample mind map](https://my-mind.github.io/?map=examples/features.mymind) showcasing many features
* [News / Changelog](https://github.com/ondras/my-mind/wiki/News)
* [Documentation](https://github.com/ondras/my-mind/wiki)
*
## Installation
-Note: there is also an online version, which can be found at [my-mind.github.io](http://my-mind.github.io/)
+Note: there is also an online version, which can be found at [my-mind.github.io](https://my-mind.github.io/)
* Download the zip by clicking [here](archive/master.zip) and extract the archive, or clone the repository using git
* Open index.html in your webbrowser
From 454b9417997685adbfe8a97a11dc7e2513858e84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sat, 16 Oct 2021 20:09:44 +0200
Subject: [PATCH 03/42] back with non-compatible Promise
---
my-mind.js | 26 +++++++++++++-------------
src/promise.js | 10 +++++-----
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/my-mind.js b/my-mind.js
index 60bc4e7f..2083de74 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -46,7 +46,7 @@
};
// .js/promise.js
- var Promise2 = function(executor) {
+ window.Promise = function(executor) {
this._state = 0;
this._value = null;
this._cb = {
@@ -56,23 +56,23 @@
this._thenPromises = [];
executor && executor(this.fulfill.bind(this), this.reject.bind(this));
};
- Promise2.resolve = function(value) {
- return new Promise2().fulfill(value);
+ Promise.resolve = function(value) {
+ return new Promise().fulfill(value);
};
- Promise2.reject = function(value) {
- return new Promise2().reject(value);
+ Promise.reject = function(value) {
+ return new Promise().reject(value);
};
- Promise2.prototype.then = function(onFulfilled, onRejected) {
+ Promise.prototype.then = function(onFulfilled, onRejected) {
this._cb.fulfilled.push(onFulfilled);
this._cb.rejected.push(onRejected);
- var thenPromise = new Promise2();
+ var thenPromise = new Promise();
this._thenPromises.push(thenPromise);
if (this._state > 0) {
setTimeout(this._processQueue.bind(this), 0);
}
return thenPromise;
};
- Promise2.prototype.fulfill = function(value) {
+ Promise.prototype.fulfill = function(value) {
if (this._state != 0) {
return this;
}
@@ -81,7 +81,7 @@
this._processQueue();
return this;
};
- Promise2.prototype.reject = function(value) {
+ Promise.prototype.reject = function(value) {
if (this._state != 0) {
return this;
}
@@ -90,20 +90,20 @@
this._processQueue();
return this;
};
- Promise2.prototype.chain = function(promise) {
+ Promise.prototype.chain = function(promise) {
return this.then(promise.fulfill.bind(promise), promise.reject.bind(promise));
};
- Promise2.prototype["catch"] = function(onRejected) {
+ Promise.prototype["catch"] = function(onRejected) {
return this.then(null, onRejected);
};
- Promise2.prototype._processQueue = function() {
+ Promise.prototype._processQueue = function() {
while (this._thenPromises.length) {
var onFulfilled = this._cb.fulfilled.shift();
var onRejected = this._cb.rejected.shift();
this._executeCallback(this._state == 1 ? onFulfilled : onRejected);
}
};
- Promise2.prototype._executeCallback = function(cb) {
+ Promise.prototype._executeCallback = function(cb) {
var thenPromise = this._thenPromises.shift();
if (typeof cb != "function") {
if (this._state == 1) {
diff --git a/src/promise.js b/src/promise.js
index e0759a40..849fd246 100644
--- a/src/promise.js
+++ b/src/promise.js
@@ -7,7 +7,7 @@
* @class A promise - value to be resolved in the future.
* Implements the "Promises/A+" specification.
*/
-var Promise = function(executor) {
+window.Promise = function(executor) {
this._state = 0; /* 0 = pending, 1 = fulfilled, 2 = rejected */
this._value = null; /* fulfillment / rejection value */
@@ -47,7 +47,7 @@ Promise.prototype.then = function(onFulfilled, onRejected) {
}
/* 3.2.6. then must return a promise. */
- return thenPromise;
+ return thenPromise;
}
/**
@@ -127,14 +127,14 @@ Promise.prototype._executeCallback = function(cb) {
var rejectThenPromise = function(value) { thenPromise.reject(value); }
returned.then(fulfillThenPromise, rejectThenPromise);
} else {
- /* 3.2.6.1. If either onFulfilled or onRejected returns a value that is not a promise, promise2 must be fulfilled with that value. */
+ /* 3.2.6.1. If either onFulfilled or onRejected returns a value that is not a promise, promise2 must be fulfilled with that value. */
thenPromise.fulfill(returned);
}
} catch (e) {
/* 3.2.6.2. If either onFulfilled or onRejected throws an exception, promise2 must be rejected with the thrown exception as the reason. */
- thenPromise.reject(e);
+ thenPromise.reject(e);
}
-}
+}
From d5aefacc7318684d3ef5e6747828837e4de60173 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sun, 17 Oct 2021 20:05:53 +0200
Subject: [PATCH 04/42] item converted to ts
---
Makefile | 10 +-
my-mind.js | 1648 ++++++++++++-------------
src/action.js | 18 +-
src/backend/backend.firebase.js | 11 +-
src/clipboard.js | 10 +-
src/command/command.edit.js | 4 +-
src/command/command.js | 28 +-
src/command/command.select.js | 9 +-
src/html.ts | 5 +
src/item.js | 659 ----------
src/item.ts | 704 +++++++++++
src/layout/layout.graph.js | 69 +-
src/layout/layout.js | 46 +-
src/layout/layout.map.js | 31 +-
src/layout/layout.tree.js | 40 +-
src/map.js | 81 +-
src/mm.js | 46 -
src/mm.ts | 1 +
src/mouse.js | 22 +-
src/my-mind.js | 8 +-
src/pubsub.ts | 26 +
src/repo.js | 2 +-
src/shape/shape.js | 10 +-
src/shape/shape.underline.js | 4 +-
src/svg.ts | 6 +
src/tip.js | 11 +-
tsconfig.json => src/tsconfig.json | 5 +-
src/ui/backend/ui.backend.firebase.js | 21 +-
src/ui/backend/ui.backend.js | 28 +-
src/ui/ui.io.js | 32 +-
src/ui/ui.js | 15 +-
31 files changed, 1829 insertions(+), 1781 deletions(-)
create mode 100644 src/html.ts
delete mode 100644 src/item.js
create mode 100644 src/item.ts
delete mode 100644 src/mm.js
create mode 100644 src/mm.ts
create mode 100644 src/pubsub.ts
create mode 100644 src/svg.ts
rename tsconfig.json => src/tsconfig.json (66%)
diff --git a/Makefile b/Makefile
index df5a8bc1..3808722d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,4 @@
-MAKEOPTS = "-r"
-
+MAKEOPTS := "-r"
BIN := $(shell npm bin)
TSC := $(BIN)/tsc
LESSC := $(BIN)/lessc
@@ -16,10 +15,13 @@ $(APP): $(FLAG)
$(ESBUILD) --bundle $(JS)/$(APP) > $@
$(FLAG): $(shell find src -type f)
- $(TSC)
+ $(TSC) -p src
touch $@
+watch: all
+ while inotifywait -e MODIFY -r src ; do $(MAKE) $^ ; done
+
clean:
rm -rf $(JS) $(APP)
-.PHONY: all clean
+.PHONY: all clean watch
diff --git a/my-mind.js b/my-mind.js
index 2083de74..1744ad1b 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -1,49 +1,6 @@
(() => {
// .js/mm.js
- if (!Function.prototype.bind) {
- Function.prototype.bind = function(thisObj) {
- var fn = this;
- var args = Array.prototype.slice.call(arguments, 1);
- return function() {
- return fn.apply(thisObj, args.concat(Array.prototype.slice.call(arguments)));
- };
- };
- }
- window.MM = {
- _subscribers: {},
- publish: function(message, publisher, data) {
- var subscribers = this._subscribers[message] || [];
- subscribers.forEach(function(subscriber) {
- subscriber.handleMessage(message, publisher, data);
- });
- },
- subscribe: function(message, subscriber) {
- if (!(message in this._subscribers)) {
- this._subscribers[message] = [];
- }
- var index = this._subscribers[message].indexOf(subscriber);
- if (index == -1) {
- this._subscribers[message].push(subscriber);
- }
- },
- unsubscribe: function(message, subscriber) {
- var index = this._subscribers[message].indexOf(subscriber);
- if (index > -1) {
- this._subscribers[message].splice(index, 1);
- }
- },
- generateId: function() {
- var str = "";
- for (var i = 0; i < 8; i++) {
- var code = Math.floor(Math.random() * 26);
- str += String.fromCharCode("a".charCodeAt(0) + code);
- }
- return str;
- },
- isMac: function() {
- return !!navigator.platform.match(/mac/i);
- }
- };
+ window.MM = {};
// .js/promise.js
window.Promise = function(executor) {
@@ -219,7 +176,7 @@
return all;
},
getByProperty: function(property, value) {
- return this.getAll().filter(function(item) {
+ return this.getAll().filter((item) => {
return item[property] == value;
})[0] || null;
},
@@ -234,577 +191,621 @@
}
};
- // .js/item.js
- MM.Item = function() {
- this._parent = null;
- this._children = [];
- this._collapsed = false;
- this._layout = null;
- this._shape = null;
- this._autoShape = true;
- this._color = null;
- this._value = null;
- this._status = null;
- this._side = null;
- this._icon = null;
- this._notes = null;
- this._id = MM.generateId();
- this._oldText = "";
- this._computed = {
- value: 0,
- status: null
- };
- this._dom = {
- node: document.createElement("li"),
- content: document.createElement("div"),
- notes: document.createElement("div"),
- status: document.createElement("span"),
- icon: document.createElement("span"),
- value: document.createElement("span"),
- text: document.createElement("div"),
- children: document.createElement("ul"),
- toggle: document.createElement("div"),
- canvas: document.createElement("canvas")
- };
- this._dom.node.classList.add("item");
- this._dom.content.classList.add("content");
- this._dom.notes.classList.add("notes-indicator");
- this._dom.status.classList.add("status");
- this._dom.icon.classList.add("icon");
- this._dom.value.classList.add("value");
- this._dom.text.classList.add("text");
- this._dom.toggle.classList.add("toggle");
- this._dom.children.classList.add("children");
- this._dom.content.appendChild(this._dom.text);
- this._dom.node.appendChild(this._dom.canvas);
- this._dom.node.appendChild(this._dom.content);
- this._dom.content.appendChild(this._dom.notes);
- this._dom.toggle.addEventListener("click", this);
- };
- MM.Item.COLOR = "#999";
- MM.Item.RE = /\b(([a-z][\w-]+:\/\/\w)|(([\w-]+\.){2,}[a-z][\w-]+)|([\w-]+\.[a-z][\w-]+\/))[^\s]*([^\s,.;:?!<>\(\)\[\]'"])?($|\b)/i;
- MM.Item.fromJSON = function(data) {
- return new this().fromJSON(data);
- };
- MM.Item.prototype.toJSON = function() {
- var data = {
- id: this._id,
- text: this.getText(),
- notes: this.getNotes()
- };
- if (this._side) {
- data.side = this._side;
+ // .js/html.js
+ function node(name, attrs) {
+ let node2 = document.createElement(name);
+ Object.assign(node2, attrs);
+ return node2;
+ }
+
+ // .js/pubsub.js
+ var subscribers = new Map();
+ function publish(message, publisher, data) {
+ let subs = subscribers.get(message) || [];
+ subs.forEach(function(subscriber) {
+ subscriber.handleMessage(message, publisher, data);
+ });
+ }
+ function subscribe(message, subscriber) {
+ if (!subscribers.has(message)) {
+ subscribers.set(message, []);
}
- if (this._color) {
- data.color = this._color;
+ let subs = subscribers.get(message) || [];
+ let index = subs.indexOf(subscriber);
+ if (index == -1) {
+ subs.push(subscriber);
}
- if (this._icon) {
- data.icon = this._icon;
+ }
+ function unsubscribe(message, subscriber) {
+ let subs = subscribers.get(message) || [];
+ let index = subs.indexOf(subscriber);
+ if (index > -1) {
+ subs.splice(index, 1);
}
- if (this._value) {
- data.value = this._value;
+ }
+
+ // .js/item.js
+ var COLOR = "#999";
+ var RE = /\b(([a-z][\w-]+:\/\/\w)|(([\w-]+\.){2,}[a-z][\w-]+)|([\w-]+\.[a-z][\w-]+\/))[^\s]*([^\s,.;:?!<>\(\)\[\]'"])?($|\b)/i;
+ var UPDATE_OPTIONS = {
+ parent: true,
+ children: false
+ };
+ var Item = class {
+ constructor() {
+ this._id = generateId();
+ this._parent = null;
+ this._collapsed = false;
+ this.dom = {
+ node: node("li"),
+ content: node("div"),
+ notes: node("div"),
+ status: node("span"),
+ icon: node("span"),
+ value: node("span"),
+ text: node("div"),
+ children: node("ul"),
+ toggle: node("div"),
+ canvas: node("canvas")
+ };
+ this.children = [];
+ this._layout = null;
+ this._shape = null;
+ this._autoShape = true;
+ this._color = null;
+ this._value = null;
+ this._status = null;
+ this._side = null;
+ this._icon = null;
+ this._notes = null;
+ this._oldText = "";
+ this._computed = {
+ value: 0,
+ status: null
+ };
+ const { dom } = this;
+ dom.node.classList.add("item");
+ dom.content.classList.add("content");
+ dom.notes.classList.add("notes-indicator");
+ dom.status.classList.add("status");
+ dom.icon.classList.add("icon");
+ dom.value.classList.add("value");
+ dom.text.classList.add("text");
+ dom.toggle.classList.add("toggle");
+ dom.children.classList.add("children");
+ dom.node.append(dom.canvas, dom.content);
+ dom.content.append(dom.text, dom.notes);
+ dom.toggle.addEventListener("click", this);
+ }
+ static fromJSON(data) {
+ return new this().fromJSON(data);
+ }
+ get id() {
+ return this._id;
+ }
+ get parent() {
+ return this._parent;
+ }
+ set parent(parent) {
+ this._parent = parent;
+ this.update({ children: true });
+ }
+ get metrics() {
+ const { dom } = this, { node: node2 } = dom;
+ return {
+ left: node2.offsetLeft,
+ top: node2.offsetTop,
+ width: node2.offsetWidth,
+ height: node2.offsetHeight
+ };
}
- if (this._status) {
- data.status = this._status;
+ get size() {
+ const { dom } = this;
+ return [dom.node.offsetWidth, dom.node.offsetHeight];
+ }
+ set size(size) {
+ const { dom } = this;
+ dom.node.style.width = `${size[0]}px`;
+ dom.node.style.height = `${size[1]}px`;
+ dom.canvas.width = size[0];
+ dom.canvas.height = size[1];
+ }
+ get position() {
+ const { dom } = this;
+ return [dom.node.offsetLeft, dom.node.offsetTop];
+ }
+ set position(position) {
+ const { dom } = this;
+ dom.node.style.left = `${position[0]}px`;
+ dom.node.style.top = `${position[1]}px`;
+ }
+ toJSON() {
+ let data = {
+ id: this.id,
+ text: this.getText(),
+ notes: this.getNotes()
+ };
+ if (this._side) {
+ data.side = this._side;
+ }
+ if (this._color) {
+ data.color = this._color;
+ }
+ if (this._icon) {
+ data.icon = this._icon;
+ }
+ if (this._value) {
+ data.value = this._value;
+ }
+ if (this._status) {
+ data.status = this._status;
+ }
+ if (this._layout) {
+ data.layout = this._layout.id;
+ }
+ if (!this._autoShape) {
+ data.shape = this._shape.id;
+ }
+ if (this._collapsed) {
+ data.collapsed = 1;
+ }
+ if (this.children.length) {
+ data.children = this.children.map((child) => child.toJSON());
+ }
+ return data;
}
- if (this._layout) {
- data.layout = this._layout.id;
+ fromJSON(data) {
+ this.setText(data.text);
+ if (data.notes) {
+ this.setNotes(data.notes);
+ }
+ if (data.id) {
+ this._id = data.id;
+ }
+ if (data.side) {
+ this._side = data.side;
+ }
+ if (data.color) {
+ this._color = data.color;
+ }
+ if (data.icon) {
+ this._icon = data.icon;
+ }
+ if (data.value) {
+ this._value = data.value;
+ }
+ if (data.status) {
+ this._status = data.status;
+ if (this._status == "maybe") {
+ this._status = "computed";
+ }
+ }
+ if (data.collapsed) {
+ this.collapse();
+ }
+ if (data.layout) {
+ this._layout = MM.Layout.getById(data.layout);
+ }
+ if (data.shape) {
+ this.setShape(MM.Shape.getById(data.shape));
+ }
+ (data.children || []).forEach(function(child) {
+ this.insertChild(Item.fromJSON(child));
+ }, this);
+ return this;
}
- if (!this._autoShape) {
- data.shape = this._shape.id;
+ mergeWith(data) {
+ var dirty = 0;
+ if (this.getText() != data.text && !this.dom.text.contentEditable) {
+ this.setText(data.text);
+ }
+ if (this._side != data.side) {
+ this._side = data.side;
+ dirty = 1;
+ }
+ if (this._color != data.color) {
+ this._color = data.color;
+ dirty = 2;
+ }
+ if (this._icon != data.icon) {
+ this._icon = data.icon;
+ dirty = 1;
+ }
+ if (this._value != data.value) {
+ this._value = data.value;
+ dirty = 1;
+ }
+ if (this._status != data.status) {
+ this._status = data.status;
+ dirty = 1;
+ }
+ if (this._collapsed != !!data.collapsed) {
+ this[this._collapsed ? "expand" : "collapse"]();
+ }
+ if (this.getOwnLayout() != data.layout) {
+ this._layout = MM.Layout.getById(data.layout);
+ dirty = 2;
+ }
+ var s = this._autoShape ? null : this._shape.id;
+ if (s != data.shape) {
+ this.setShape(MM.Shape.getById(data.shape));
+ }
+ (data.children || []).forEach(function(child, index) {
+ if (index >= this.children.length) {
+ this.insertChild(Item.fromJSON(child));
+ } else {
+ var myChild = this.children[index];
+ if (myChild.id == child.id) {
+ myChild.mergeWith(child);
+ } else {
+ this.removeChild(this.children[index]);
+ this.insertChild(Item.fromJSON(child), index);
+ }
+ }
+ }, this);
+ var newLength = (data.children || []).length;
+ while (this.children.length > newLength) {
+ this.removeChild(this.children[this.children.length - 1]);
+ }
+ if (dirty == 1) {
+ this.update({ children: false });
+ }
+ if (dirty == 2) {
+ this.update({ children: true });
+ }
}
- if (this._collapsed) {
- data.collapsed = 1;
+ clone() {
+ var data = this.toJSON();
+ var removeId = function(obj) {
+ delete obj.id;
+ obj.children && obj.children.forEach(removeId);
+ };
+ removeId(data);
+ return Item.fromJSON(data);
+ }
+ select() {
+ this.dom.node.classList.add("current");
+ if (window.editor) {
+ if (this._notes) {
+ window.editor.setContent(this._notes);
+ } else {
+ window.editor.setContent("");
+ }
+ }
+ this.map.ensureItemVisibility(this);
+ MM.Clipboard.focus();
+ publish("item-select", this);
}
- if (this._children.length) {
- data.children = this._children.map(function(child) {
- return child.toJSON();
- });
+ deselect() {
+ if (MM.App.editing) {
+ MM.Command.Finish.execute();
+ }
+ this.dom.node.classList.remove("current");
}
- return data;
- };
- MM.Item.prototype.fromJSON = function(data) {
- this.setText(data.text);
- if (data.notes) {
- this.setNotes(data.notes);
+ update(options = {}) {
+ options = Object.assign({}, UPDATE_OPTIONS, options);
+ var map = this.map;
+ if (!map || !map.isVisible()) {
+ return;
+ }
+ if (options.children) {
+ let childUpdateOptions = { parent: false, children: true };
+ this.children.forEach((child) => child.update(childUpdateOptions));
+ }
+ publish("item-change", this);
+ if (this._autoShape) {
+ var autoShape = this._getAutoShape();
+ if (autoShape != this._shape) {
+ if (this._shape) {
+ this._shape.unset(this);
+ }
+ this._shape = autoShape;
+ this._shape.set(this);
+ }
+ }
+ this._updateStatus();
+ this._updateIcon();
+ this._updateNotesIndicator();
+ this._updateValue();
+ this.dom.node.classList.toggle("collapsed", this._collapsed);
+ this.getLayout().update(this);
+ this.getShape().update(this);
+ if (options.parent && !this.isRoot()) {
+ this.parent.update();
+ }
}
- if (data.id) {
- this._id = data.id;
+ setText(text) {
+ this.dom.text.innerHTML = text;
+ findLinks(this.dom.text);
+ return this.update();
}
- if (data.side) {
- this._side = data.side;
+ setNotes(notes) {
+ this._notes = notes;
+ return this.update();
}
- if (data.color) {
- this._color = data.color;
+ getText() {
+ return this.dom.text.innerHTML;
}
- if (data.icon) {
- this._icon = data.icon;
+ getNotes() {
+ return this._notes;
}
- if (data.value) {
- this._value = data.value;
+ collapse() {
+ if (this._collapsed) {
+ return;
+ }
+ this._collapsed = true;
+ return this.update();
}
- if (data.status) {
- this._status = data.status;
- if (this._status == "maybe") {
- this._status = "computed";
+ expand() {
+ if (!this._collapsed) {
+ return;
}
+ this._collapsed = false;
+ this.update();
+ return this.update({ children: true });
}
- if (data.collapsed) {
- this.collapse();
+ isCollapsed() {
+ return this._collapsed;
}
- if (data.layout) {
- this._layout = MM.Layout.getById(data.layout);
+ setValue(value) {
+ this._value = value;
+ return this.update();
}
- if (data.shape) {
- this.setShape(MM.Shape.getById(data.shape));
+ getValue() {
+ return this._value;
}
- (data.children || []).forEach(function(child) {
- this.insertChild(MM.Item.fromJSON(child));
- }, this);
- return this;
- };
- MM.Item.prototype.mergeWith = function(data) {
- var dirty = 0;
- if (this.getText() != data.text && !this._dom.text.contentEditable) {
- this.setText(data.text);
+ getComputedValue() {
+ return this._computed.value;
+ }
+ setStatus(status) {
+ this._status = status;
+ return this.update();
}
- if (this._side != data.side) {
- this._side = data.side;
- dirty = 1;
+ getStatus() {
+ return this._status;
}
- if (this._color != data.color) {
- this._color = data.color;
- dirty = 2;
+ setIcon(icon) {
+ this._icon = icon;
+ return this.update();
}
- if (this._icon != data.icon) {
- this._icon = data.icon;
- dirty = 1;
+ getIcon() {
+ return this._icon;
}
- if (this._value != data.value) {
- this._value = data.value;
- dirty = 1;
+ getComputedStatus() {
+ return this._computed.status;
}
- if (this._status != data.status) {
- this._status = data.status;
- dirty = 1;
+ setSide(side) {
+ this._side = side;
+ return this;
}
- if (this._collapsed != !!data.collapsed) {
- this[this._collapsed ? "expand" : "collapse"]();
+ getSide() {
+ return this._side;
}
- if (this.getOwnLayout() != data.layout) {
- this._layout = MM.Layout.getById(data.layout);
- dirty = 2;
+ setColor(color) {
+ this._color = color;
+ return this.update({ children: true });
}
- var s = this._autoShape ? null : this._shape.id;
- if (s != data.shape) {
- this.setShape(MM.Shape.getById(data.shape));
+ getColor() {
+ return this._color || (this.isRoot() ? COLOR : this.parent.getColor());
}
- (data.children || []).forEach(function(child, index) {
- if (index >= this._children.length) {
- this.insertChild(MM.Item.fromJSON(child));
- } else {
- var myChild = this._children[index];
- if (myChild.getId() == child.id) {
- myChild.mergeWith(child);
- } else {
- this.removeChild(this._children[index]);
- this.insertChild(MM.Item.fromJSON(child), index);
- }
- }
- }, this);
- var newLength = (data.children || []).length;
- while (this._children.length > newLength) {
- this.removeChild(this._children[this._children.length - 1]);
+ getOwnColor() {
+ return this._color;
}
- if (dirty == 1) {
- this.update();
+ getLayout() {
+ return this._layout || this.parent.getLayout();
}
- if (dirty == 2) {
- this.updateSubtree();
+ getOwnLayout() {
+ return this._layout;
}
- };
- MM.Item.prototype.clone = function() {
- var data = this.toJSON();
- var removeId = function(obj) {
- delete obj.id;
- obj.children && obj.children.forEach(removeId);
- };
- removeId(data);
- return this.constructor.fromJSON(data);
- };
- MM.Item.prototype.select = function() {
- this._dom.node.classList.add("current");
- if (window.editor) {
- if (this._notes) {
- window.editor.setContent(this._notes);
- } else {
- window.editor.setContent("");
- }
+ setLayout(layout) {
+ this._layout = layout;
+ return this.update({ children: true });
}
- this.getMap().ensureItemVisibility(this);
- MM.Clipboard.focus();
- MM.publish("item-select", this);
- };
- MM.Item.prototype.deselect = function() {
- if (MM.App.editing) {
- MM.Command.Finish.execute();
+ getShape() {
+ return this._shape;
}
- this._dom.node.classList.remove("current");
- };
- MM.Item.prototype.update = function(doNotRecurse) {
- var map = this.getMap();
- if (!map || !map.isVisible()) {
- return this;
+ getOwnShape() {
+ return this._autoShape ? null : this._shape;
}
- MM.publish("item-change", this);
- if (this._autoShape) {
- var autoShape = this._getAutoShape();
- if (autoShape != this._shape) {
- if (this._shape) {
- this._shape.unset(this);
+ setShape(shape) {
+ if (this._shape) {
+ this._shape.unset(this);
+ }
+ if (shape) {
+ this._autoShape = false;
+ this._shape = shape;
+ } else {
+ this._autoShape = true;
+ this._shape = this._getAutoShape();
+ }
+ this._shape.set(this);
+ return this.update();
+ }
+ get map() {
+ let item = this.parent;
+ while (item) {
+ if (item instanceof MM.Map) {
+ return item;
}
- this._shape = autoShape;
- this._shape.set(this);
+ item = item.parent;
}
+ return null;
}
- this._updateStatus();
- this._updateIcon();
- this._updateNotesIndicator();
- this._updateValue();
- this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
- this.getLayout().update(this);
- this.getShape().update(this);
- if (!this.isRoot() && !doNotRecurse) {
- this._parent.update();
- }
- return this;
- };
- MM.Item.prototype.updateSubtree = function(isSubChild) {
- this._children.forEach(function(child) {
- child.updateSubtree(true);
- });
- return this.update(isSubChild);
- };
- MM.Item.prototype.setText = function(text) {
- this._dom.text.innerHTML = text;
- this._findLinks(this._dom.text);
- return this.update();
- };
- MM.Item.prototype.setNotes = function(notes) {
- this._notes = notes;
- return this.update();
- };
- MM.Item.prototype.getId = function() {
- return this._id;
- };
- MM.Item.prototype.getText = function() {
- return this._dom.text.innerHTML;
- };
- MM.Item.prototype.getNotes = function() {
- return this._notes;
- };
- MM.Item.prototype.collapse = function() {
- if (this._collapsed) {
- return;
- }
- this._collapsed = true;
- return this.update();
- };
- MM.Item.prototype.expand = function() {
- if (!this._collapsed) {
- return;
- }
- this._collapsed = false;
- this.update();
- return this.updateSubtree();
- };
- MM.Item.prototype.isCollapsed = function() {
- return this._collapsed;
- };
- MM.Item.prototype.setValue = function(value) {
- this._value = value;
- return this.update();
- };
- MM.Item.prototype.getValue = function() {
- return this._value;
- };
- MM.Item.prototype.getComputedValue = function() {
- return this._computed.value;
- };
- MM.Item.prototype.setStatus = function(status) {
- this._status = status;
- return this.update();
- };
- MM.Item.prototype.getStatus = function() {
- return this._status;
- };
- MM.Item.prototype.setIcon = function(icon) {
- this._icon = icon;
- return this.update();
- };
- MM.Item.prototype.getIcon = function() {
- return this._icon;
- };
- MM.Item.prototype.getComputedStatus = function() {
- return this._computed.status;
- };
- MM.Item.prototype.setSide = function(side) {
- this._side = side;
- return this;
- };
- MM.Item.prototype.getSide = function() {
- return this._side;
- };
- MM.Item.prototype.getChildren = function() {
- return this._children;
- };
- MM.Item.prototype.setColor = function(color) {
- this._color = color;
- return this.updateSubtree();
- };
- MM.Item.prototype.getColor = function() {
- return this._color || (this.isRoot() ? MM.Item.COLOR : this._parent.getColor());
- };
- MM.Item.prototype.getOwnColor = function() {
- return this._color;
- };
- MM.Item.prototype.getLayout = function() {
- return this._layout || this._parent.getLayout();
- };
- MM.Item.prototype.getOwnLayout = function() {
- return this._layout;
- };
- MM.Item.prototype.setLayout = function(layout) {
- this._layout = layout;
- return this.updateSubtree();
- };
- MM.Item.prototype.getShape = function() {
- return this._shape;
- };
- MM.Item.prototype.getOwnShape = function() {
- return this._autoShape ? null : this._shape;
- };
- MM.Item.prototype.setShape = function(shape) {
- if (this._shape) {
- this._shape.unset(this);
- }
- if (shape) {
- this._autoShape = false;
- this._shape = shape;
- } else {
- this._autoShape = true;
- this._shape = this._getAutoShape();
+ isRoot() {
+ return this.parent instanceof MM.Map;
}
- this._shape.set(this);
- return this.update();
- };
- MM.Item.prototype.getDOM = function() {
- return this._dom;
- };
- MM.Item.prototype.getMap = function() {
- var item = this._parent;
- while (item) {
- if (item instanceof MM.Map) {
- return item;
+ insertChild(child, index) {
+ var newChild = false;
+ if (!child) {
+ child = new Item();
+ newChild = true;
+ } else if (child.parent && child.parent.removeChild) {
+ child.parent.removeChild(child);
}
- item = item.getParent();
- }
- return null;
- };
- MM.Item.prototype.getParent = function() {
- return this._parent;
- };
- MM.Item.prototype.isRoot = function() {
- return this._parent instanceof MM.Map;
- };
- MM.Item.prototype.setParent = function(parent) {
- this._parent = parent;
- return this.updateSubtree();
- };
- MM.Item.prototype.insertChild = function(child, index) {
- var newChild = false;
- if (!child) {
- child = new MM.Item();
- newChild = true;
- } else if (child.getParent() && child.getParent().removeChild) {
- child.getParent().removeChild(child);
- }
- if (!this._children.length) {
- this._dom.node.appendChild(this._dom.toggle);
- this._dom.node.appendChild(this._dom.children);
- }
- if (arguments.length < 2) {
- index = this._children.length;
- }
- var next = null;
- if (index < this._children.length) {
- next = this._children[index].getDOM().node;
- }
- this._dom.children.insertBefore(child.getDOM().node, next);
- this._children.splice(index, 0, child);
- return child.setParent(this);
- };
- MM.Item.prototype.removeChild = function(child) {
- var index = this._children.indexOf(child);
- this._children.splice(index, 1);
- var node = child.getDOM().node;
- node.parentNode.removeChild(node);
- child.setParent(null);
- if (!this._children.length) {
- this._dom.toggle.parentNode.removeChild(this._dom.toggle);
- this._dom.children.parentNode.removeChild(this._dom.children);
- }
- return this.update();
- };
- MM.Item.prototype.startEditing = function() {
- this._oldText = this.getText();
- this._dom.text.contentEditable = true;
- this._dom.text.focus();
- document.execCommand("styleWithCSS", null, false);
- this._dom.text.addEventListener("input", this);
- this._dom.text.addEventListener("keydown", this);
- this._dom.text.addEventListener("blur", this);
- return this;
- };
- MM.Item.prototype.stopEditing = function() {
- this._dom.text.removeEventListener("input", this);
- this._dom.text.removeEventListener("keydown", this);
- this._dom.text.removeEventListener("blur", this);
- this._dom.text.blur();
- this._dom.text.contentEditable = false;
- var result = this._dom.text.innerHTML;
- this._dom.text.innerHTML = this._oldText;
- this._oldText = "";
- this.update();
- MM.Clipboard.focus();
- return result;
- };
- MM.Item.prototype.handleEvent = function(e) {
- switch (e.type) {
- case "input":
- this.update();
- this.getMap().ensureItemVisibility(this);
- break;
- case "keydown":
- if (e.keyCode == 9) {
- e.preventDefault();
- }
- break;
- case "blur":
- MM.Command.Finish.execute();
- break;
- case "click":
- if (this._collapsed) {
- this.expand();
- } else {
- this.collapse();
- }
- MM.App.select(this);
- break;
- }
- };
- MM.Item.prototype._getAutoShape = function() {
- var depth = 0;
- var node = this;
- while (!node.isRoot()) {
- depth++;
- node = node.getParent();
+ if (!this.children.length) {
+ this.dom.node.appendChild(this.dom.toggle);
+ this.dom.node.appendChild(this.dom.children);
+ }
+ if (arguments.length < 2) {
+ index = this.children.length;
+ }
+ var next = null;
+ if (index < this.children.length) {
+ next = this.children[index].dom.node;
+ }
+ this.dom.children.insertBefore(child.dom.node, next);
+ this.children.splice(index, 0, child);
+ child.parent = this;
+ }
+ removeChild(child) {
+ var index = this.children.indexOf(child);
+ this.children.splice(index, 1);
+ var node2 = child.dom.node;
+ node2.parentNode.removeChild(node2);
+ child.parent = null;
+ if (!this.children.length) {
+ this.dom.toggle.parentNode.removeChild(this.dom.toggle);
+ this.dom.children.parentNode.removeChild(this.dom.children);
+ }
+ this.update();
}
- switch (depth) {
- case 0:
- return MM.Shape.Ellipse;
- case 1:
- return MM.Shape.Box;
- default:
- return MM.Shape.Underline;
+ startEditing() {
+ this._oldText = this.getText();
+ this.dom.text.contentEditable = "true";
+ this.dom.text.focus();
+ document.execCommand("styleWithCSS", null, "false");
+ this.dom.text.addEventListener("input", this);
+ this.dom.text.addEventListener("keydown", this);
+ this.dom.text.addEventListener("blur", this);
+ }
+ stopEditing() {
+ this.dom.text.removeEventListener("input", this);
+ this.dom.text.removeEventListener("keydown", this);
+ this.dom.text.removeEventListener("blur", this);
+ this.dom.text.blur();
+ this.dom.text.contentEditable = "false";
+ var result = this.dom.text.innerHTML;
+ this.dom.text.innerHTML = this._oldText;
+ this._oldText = "";
+ this.update();
+ MM.Clipboard.focus();
+ return result;
}
- };
- MM.Item.prototype._updateStatus = function() {
- this._dom.status.className = "status";
- this._dom.status.style.display = "";
- var status = this._status;
- if (this._status == "computed") {
- var childrenStatus = this._children.every(function(child) {
- return child.getComputedStatus() !== false;
- });
- status = childrenStatus ? "yes" : "no";
+ handleEvent(e) {
+ switch (e.type) {
+ case "input":
+ this.update();
+ this.map.ensureItemVisibility(this);
+ break;
+ case "keydown":
+ if (e.keyCode == 9) {
+ e.preventDefault();
+ }
+ break;
+ case "blur":
+ MM.Command.Finish.execute();
+ break;
+ case "click":
+ if (this._collapsed) {
+ this.expand();
+ } else {
+ this.collapse();
+ }
+ MM.App.select(this);
+ break;
+ }
}
- switch (status) {
- case "yes":
- this._dom.status.classList.add("yes");
- this._computed.status = true;
- break;
- case "no":
- this._dom.status.classList.add("no");
- this._computed.status = false;
- break;
- default:
- this._computed.status = null;
- this._dom.status.style.display = "none";
- break;
+ _getAutoShape() {
+ let depth = 0;
+ let node2 = this;
+ while (!node2.isRoot()) {
+ depth++;
+ node2 = node2.parent;
+ }
+ switch (depth) {
+ case 0:
+ return MM.Shape.Ellipse;
+ case 1:
+ return MM.Shape.Box;
+ default:
+ return MM.Shape.Underline;
+ }
}
- };
- MM.Item.prototype._updateIcon = function() {
- this._dom.icon.className = "icon";
- this._dom.icon.style.display = "";
- var icon = this._icon;
- if (icon) {
- this._dom.icon.classList.add("fa");
- this._dom.icon.classList.add(icon);
- this._computed.icon = true;
- } else {
- this._computed.icon = null;
- this._dom.icon.style.display = "none";
+ _updateStatus() {
+ this.dom.status.className = "status";
+ this.dom.status.hidden = false;
+ var status = this._status;
+ if (this._status == "computed") {
+ var childrenStatus = this.children.every(function(child) {
+ return child.getComputedStatus() !== false;
+ });
+ status = childrenStatus ? "yes" : "no";
+ }
+ switch (status) {
+ case "yes":
+ this.dom.status.classList.add("yes");
+ this._computed.status = true;
+ break;
+ case "no":
+ this.dom.status.classList.add("no");
+ this._computed.status = false;
+ break;
+ default:
+ this._computed.status = null;
+ this.dom.status.hidden = true;
+ break;
+ }
}
- };
- MM.Item.prototype._updateNotesIndicator = function() {
- if (this._notes) {
- this._dom.notes.classList.add("notes-indicator-visible");
- } else {
- this._dom.notes.classList.remove("notes-indicator-visible");
+ _updateIcon() {
+ var icon = this._icon;
+ this.dom.icon.className = "icon";
+ this.dom.icon.hidden = !icon;
+ if (icon) {
+ this.dom.icon.classList.add("fa");
+ this.dom.icon.classList.add(icon);
+ }
}
- };
- MM.Item.prototype._updateValue = function() {
- this._dom.value.style.display = "";
- if (typeof this._value == "number") {
- this._computed.value = this._value;
- this._dom.value.innerHTML = this._value;
- return;
+ _updateNotesIndicator() {
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
}
- var childValues = this._children.map(function(child) {
- return child.getComputedValue();
- });
- var result = 0;
- switch (this._value) {
- case "sum":
- result = childValues.reduce(function(prev, cur) {
- return prev + cur;
- }, 0);
- break;
- case "avg":
- var sum = childValues.reduce(function(prev, cur) {
- return prev + cur;
- }, 0);
- result = childValues.length ? sum / childValues.length : 0;
- break;
- case "max":
- result = Math.max.apply(Math, childValues);
- break;
- case "min":
- result = Math.min.apply(Math, childValues);
- break;
- default:
- this._computed.value = 0;
- this._dom.value.innerHTML = "";
- this._dom.value.style.display = "none";
+ _updateValue() {
+ this.dom.value.hidden = false;
+ if (typeof this._value == "number") {
+ this._computed.value = this._value;
+ this.dom.value.textContent = String(this._value);
return;
- break;
+ }
+ var childValues = this.children.map(function(child) {
+ return child.getComputedValue();
+ });
+ var result = 0;
+ switch (this._value) {
+ case "sum":
+ result = childValues.reduce((prev, cur) => prev + cur, 0);
+ break;
+ case "avg":
+ var sum = childValues.reduce((prev, cur) => prev + cur, 0);
+ result = childValues.length ? sum / childValues.length : 0;
+ break;
+ case "max":
+ result = Math.max(...childValues);
+ break;
+ case "min":
+ result = Math.min(...childValues);
+ break;
+ default:
+ this._computed.value = 0;
+ this.dom.value.innerHTML = "";
+ this.dom.value.hidden = true;
+ return;
+ break;
+ }
+ this._computed.value = result;
+ this.dom.value.innerHTML = String(Math.round(result) == result ? result : result.toFixed(3));
}
- this._computed.value = result;
- this._dom.value.innerHTML = Math.round(result) == result ? result : result.toFixed(3);
};
- MM.Item.prototype._findLinks = function(node) {
- var children = [].slice.call(node.childNodes);
+ function findLinks(node2) {
+ var children = [].slice.call(node2.childNodes);
for (var i = 0; i < children.length; i++) {
var child = children[i];
switch (child.nodeType) {
@@ -812,30 +813,39 @@
if (child.nodeName.toLowerCase() == "a") {
continue;
}
- this._findLinks(child);
+ findLinks(child);
break;
case 3:
- var result = child.nodeValue.match(this.constructor.RE);
+ var result = child.nodeValue.match(RE);
if (result) {
var before = child.nodeValue.substring(0, result.index);
var after = child.nodeValue.substring(result.index + result[0].length);
var link = document.createElement("a");
link.innerHTML = link.href = result[0];
if (before) {
- node.insertBefore(document.createTextNode(before), child);
+ node2.insertBefore(document.createTextNode(before), child);
}
- node.insertBefore(link, child);
+ node2.insertBefore(link, child);
if (after) {
child.nodeValue = after;
i--;
} else {
- node.removeChild(child);
+ node2.removeChild(child);
}
}
break;
}
}
- };
+ }
+ function generateId() {
+ let str = "";
+ for (var i = 0; i < 8; i++) {
+ let code = Math.floor(Math.random() * 26);
+ str += String.fromCharCode("a".charCodeAt(0) + code);
+ }
+ return str;
+ }
+ MM.Item = Item;
// .js/map.js
MM.Map = function(options) {
@@ -849,7 +859,10 @@
this._root = null;
this._visible = false;
this._position = [0, 0];
- this._setRoot(new MM.Item().setText(o.root).setLayout(o.layout));
+ let root = new Item();
+ root.setText(o.root);
+ root.setLayout(o.layout);
+ this._setRoot(root);
};
MM.Map.fromJSON = function(data) {
return new this().fromJSON(data);
@@ -861,26 +874,26 @@
return data;
};
MM.Map.prototype.fromJSON = function(data) {
- this._setRoot(MM.Item.fromJSON(data.root));
+ this._setRoot(Item.fromJSON(data.root));
return this;
};
MM.Map.prototype.mergeWith = function(data) {
var ids = [];
var current = MM.App.current;
- var node = current;
- while (node != this) {
- ids.push(node.getId());
- node = node.getParent();
+ var node2 = current;
+ while (node2 != this) {
+ ids.push(node2.id);
+ node2 = node2.parent;
}
this._root.mergeWith(data.root);
- if (current.getMap()) {
- var node = current.getParent();
+ if (current.map) {
+ var node2 = current.parent;
var hidden = false;
- while (node != this) {
- if (node.isCollapsed()) {
+ while (node2 != this) {
+ if (node2.isCollapsed()) {
hidden = true;
}
- node = node.getParent();
+ node2 = node2.parent;
}
if (!hidden) {
return;
@@ -891,8 +904,8 @@
}
var idMap = {};
var scan = function(item) {
- idMap[item.getId()] = item;
- item.getChildren().forEach(scan);
+ idMap[item.id] = item;
+ item.children.forEach(scan);
};
scan(this._root);
while (ids.length) {
@@ -907,29 +920,29 @@
return this._visible;
};
MM.Map.prototype.update = function() {
- this._root.updateSubtree();
+ this._root.update({ parent: true, children: true });
return this;
};
MM.Map.prototype.show = function(where) {
- var node = this._root.getDOM().node;
- where.appendChild(node);
+ var node2 = this._root.dom.node;
+ where.appendChild(node2);
this._visible = true;
- this._root.updateSubtree();
+ this._root.update({ parent: true, children: true });
this.center();
MM.App.select(this._root);
return this;
};
MM.Map.prototype.hide = function() {
- var node = this._root.getDOM().node;
- node.parentNode.removeChild(node);
+ var node2 = this._root.dom.node;
+ node2.parentNode.removeChild(node2);
this._visible = false;
return this;
};
MM.Map.prototype.center = function() {
- var node = this._root.getDOM().node;
+ let { size } = this._root;
var port = MM.App.portSize;
- var left = (port[0] - node.offsetWidth) / 2;
- var top = (port[1] - node.offsetHeight) / 2;
+ var left = (port[0] - size[0]) / 2;
+ var top = (port[1] - size[1]) / 2;
this._moveTo(Math.round(left), Math.round(top));
return this;
};
@@ -939,7 +952,7 @@
MM.Map.prototype.getClosestItem = function(x, y) {
var all = [];
var scan = function(item) {
- var rect = item.getDOM().content.getBoundingClientRect();
+ var rect = item.dom.content.getBoundingClientRect();
var dx = rect.left + rect.width / 2 - x;
var dy = rect.top + rect.height / 2 - y;
all.push({
@@ -948,7 +961,7 @@
dy
});
if (!item.isCollapsed()) {
- item.getChildren().forEach(scan);
+ item.children.forEach(scan);
}
};
scan(this._root);
@@ -959,34 +972,34 @@
});
return all[0];
};
- MM.Map.prototype.getItemFor = function(node) {
- var port = this._root.getDOM().node.parentNode;
- while (node != port && !node.classList.contains("content")) {
- node = node.parentNode;
+ MM.Map.prototype.getItemFor = function(node2) {
+ var port = this._root.dom.node.parentNode;
+ while (node2 != port && !node2.classList.contains("content")) {
+ node2 = node2.parentNode;
}
- if (node == port) {
+ if (node2 == port) {
return null;
}
- var scan = function(item, node2) {
- if (item.getDOM().content == node2) {
+ var scan = function(item, node3) {
+ if (item.dom.content == node3) {
return item;
}
- var children = item.getChildren();
+ var children = item.children;
for (var i = 0; i < children.length; i++) {
- var result = scan(children[i], node2);
+ var result = scan(children[i], node3);
if (result) {
return result;
}
}
return null;
};
- return scan(this._root, node);
+ return scan(this._root, node2);
};
MM.Map.prototype.ensureItemVisibility = function(item) {
var padding = 10;
- var node = item.getDOM().content;
- var itemRect = node.getBoundingClientRect();
- var root = this._root.getDOM().node;
+ var node2 = item.dom.content;
+ var itemRect = node2.getBoundingClientRect();
+ var root = this._root.dom.node;
var parentRect = root.parentNode.getBoundingClientRect();
var delta = [0, 0];
var dx = parentRect.left - itemRect.left + padding;
@@ -1020,11 +1033,11 @@
return MM.Format.br2nl(name).replace(/\n/g, " ").replace(/<.*?>/g, "").trim();
};
MM.Map.prototype.getId = function() {
- return this._root.getId();
+ return this._root.id;
};
MM.Map.prototype.pick = function(item, direction) {
var candidates = [];
- var currentRect = item.getDOM().content.getBoundingClientRect();
+ var currentRect = item.dom.content.getBoundingClientRect();
this._getPickCandidates(currentRect, this._root, direction, candidates);
if (!candidates.length) {
return item;
@@ -1036,12 +1049,12 @@
};
MM.Map.prototype._getPickCandidates = function(currentRect, item, direction, candidates) {
if (!item.isCollapsed()) {
- item.getChildren().forEach(function(child) {
+ item.children.forEach(function(child) {
this._getPickCandidates(currentRect, child, direction, candidates);
}, this);
}
- var node = item.getDOM().content;
- var rect = node.getBoundingClientRect();
+ var node2 = item.dom.content;
+ var rect = node2.getBoundingClientRect();
if (direction == "left" || direction == "right") {
var x1 = currentRect.left + currentRect.width / 2;
var x2 = rect.left + rect.width / 2;
@@ -1078,13 +1091,11 @@
};
MM.Map.prototype._moveTo = function(left, top) {
this._position = [left, top];
- var node = this._root.getDOM().node;
- node.style.left = left + "px";
- node.style.top = top + "px";
+ this._root.position = this._position;
};
MM.Map.prototype._setRoot = function(item) {
this._root = item;
- this._root.setParent(this);
+ this._root.parent = this;
};
// .js/keyboard.js
@@ -1094,12 +1105,12 @@
window.addEventListener("keypress", this);
};
MM.Keyboard.handleEvent = function(e) {
- var node = document.activeElement;
- while (node && node != document) {
- if (node.classList.contains("ui")) {
+ var node2 = document.activeElement;
+ while (node2 && node2 != document) {
+ if (node2.classList.contains("ui")) {
return;
}
- node = node.parentNode;
+ node2 = node2.parentNode;
}
var commands = MM.Command.getAll();
for (var i = 0; i < commands.length; i++) {
@@ -1144,12 +1155,12 @@
init: function() {
this._node = document.querySelector("#tip");
this._node.addEventListener("click", this);
- MM.subscribe("command-child", this);
- MM.subscribe("command-sibling", this);
+ subscribe("command-child", this);
+ subscribe("command-sibling", this);
},
_hide: function() {
- MM.unsubscribe("command-child", this);
- MM.unsubscribe("command-sibling", this);
+ unsubscribe("command-child", this);
+ unsubscribe("command-sibling", this);
this._node.removeEventListener("click", this);
this._node.classList.add("hidden");
this._node = null;
@@ -1185,7 +1196,7 @@
MM.Action.InsertNewItem.prototype = Object.create(MM.Action.prototype);
MM.Action.InsertNewItem.prototype.perform = function() {
this._parent.expand();
- this._item = this._parent.insertChild(this._item, this._index);
+ this._parent.insertChild(this._item, this._index);
MM.App.select(this._item);
};
MM.Action.InsertNewItem.prototype.undo = function() {
@@ -1207,8 +1218,8 @@
};
MM.Action.RemoveItem = function(item) {
this._item = item;
- this._parent = item.getParent();
- this._index = this._parent.getChildren().indexOf(this._item);
+ this._parent = item.parent;
+ this._index = this._parent.children.indexOf(this._item);
};
MM.Action.RemoveItem.prototype = Object.create(MM.Action.prototype);
MM.Action.RemoveItem.prototype.perform = function() {
@@ -1224,8 +1235,8 @@
this._newParent = newParent;
this._newIndex = arguments.length < 3 ? null : newIndex;
this._newSide = newSide || "";
- this._oldParent = item.getParent();
- this._oldIndex = this._oldParent.getChildren().indexOf(item);
+ this._oldParent = item.parent;
+ this._oldIndex = this._oldParent.children.indexOf(item);
this._oldSide = item.getSide();
};
MM.Action.MoveItem.prototype = Object.create(MM.Action.prototype);
@@ -1245,8 +1256,8 @@
};
MM.Action.Swap = function(item, diff) {
this._item = item;
- this._parent = item.getParent();
- var children = this._parent.getChildren();
+ this._parent = item.parent;
+ var children = this._parent.children;
var sibling = this._parent.getLayout().pickSibling(this._item, diff);
this._sourceIndex = children.indexOf(this._item);
this._targetIndex = children.indexOf(sibling);
@@ -1356,11 +1367,11 @@
MM.Action.SetSide.prototype = Object.create(MM.Action.prototype);
MM.Action.SetSide.prototype.perform = function() {
this._item.setSide(this._side);
- this._item.getMap().update();
+ this._item.map.update();
};
MM.Action.SetSide.prototype.undo = function() {
this._item.setSide(this._oldSide);
- this._item.getMap().update();
+ this._item.map.update();
};
// .js/clipboard.js
@@ -1405,7 +1416,7 @@
MM.Clipboard._pasteItem = function(sourceItem, targetItem) {
switch (this._mode) {
case "cut":
- if (sourceItem == targetItem || sourceItem.getParent() == targetItem) {
+ if (sourceItem == targetItem || sourceItem.parent == targetItem) {
this._endCut();
return;
}
@@ -1414,7 +1425,7 @@
if (item == sourceItem) {
return;
}
- item = item.getParent();
+ item = item.parent;
}
var action = new MM.Action.MoveItem(sourceItem, targetItem);
MM.App.action(action);
@@ -1437,7 +1448,7 @@
var action = new MM.Action.AppendItem(targetItem, root);
MM.App.action(action);
} else {
- var actions = root.getChildren().map(function(item) {
+ var actions = root.children.map(function(item) {
return new MM.Action.AppendItem(targetItem, item);
});
var action = new MM.Action.Multi(actions);
@@ -1447,7 +1458,7 @@
MM.Clipboard.cut = function(sourceItem) {
this._endCut();
this._item = sourceItem;
- this._item.getDOM().node.classList.add("cut");
+ this._item.dom.node.classList.add("cut");
this._mode = "cut";
this._expose();
};
@@ -1468,7 +1479,7 @@
if (this._mode != "cut") {
return;
}
- this._item.getDOM().node.classList.remove("cut");
+ this._item.dom.node.classList.remove("cut");
this._item = null;
this._mode = "";
};
@@ -1527,6 +1538,9 @@
};
// .js/command/command.js
+ function isMac() {
+ return !!navigator.platform.match(/mac/i);
+ }
MM.Command = Object.create(MM.Repo, {
keys: { value: [] },
editMode: { value: false },
@@ -1577,15 +1591,15 @@
MM.Command.InsertSibling.execute = function() {
var item = MM.App.current;
if (item.isRoot()) {
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ var action = new MM.Action.InsertNewItem(item, item.children.length);
} else {
- var parent = item.getParent();
- var index = parent.getChildren().indexOf(item);
+ var parent = item.parent;
+ var index = parent.children.indexOf(item);
var action = new MM.Action.InsertNewItem(parent, index + 1);
}
MM.App.action(action);
MM.Command.Edit.execute();
- MM.publish("command-sibling");
+ publish("command-sibling");
};
MM.Command.InsertChild = Object.create(MM.Command, {
label: { value: "Insert a child" },
@@ -1596,14 +1610,14 @@
});
MM.Command.InsertChild.execute = function() {
var item = MM.App.current;
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ var action = new MM.Action.InsertNewItem(item, item.children.length);
MM.App.action(action);
MM.Command.Edit.execute();
- MM.publish("command-child");
+ publish("command-child");
};
MM.Command.Delete = Object.create(MM.Command, {
label: { value: "Delete an item" },
- keys: { value: [{ keyCode: MM.isMac() ? 8 : 46 }] }
+ keys: { value: [{ keyCode: isMac() ? 8 : 46 }] }
});
MM.Command.Delete.isValid = function() {
return MM.Command.isValid.call(this) && !MM.App.current.isRoot();
@@ -1621,7 +1635,7 @@
});
MM.Command.Swap.execute = function(e) {
var current = MM.App.current;
- if (current.isRoot() || current.getParent().getChildren().length < 2) {
+ if (current.isRoot() || current.parent.children.length < 2) {
return;
}
var diff = e.keyCode == 38 ? -1 : 1;
@@ -1637,7 +1651,7 @@
});
MM.Command.Side.execute = function(e) {
var current = MM.App.current;
- if (current.isRoot() || !current.getParent().isRoot()) {
+ if (current.isRoot() || !current.parent.isRoot()) {
return;
}
var side = e.keyCode == 37 ? "left" : "right";
@@ -1682,7 +1696,7 @@
}
var map = new MM.Map();
MM.App.setMap(map);
- MM.publish("map-new", this);
+ publish("map-new", this);
};
MM.Command.ZoomIn = Object.create(MM.Command, {
label: { value: "Zoom in" },
@@ -1846,7 +1860,7 @@
var br = document.createElement("br");
range.insertNode(br);
range.setStartAfter(br);
- MM.App.current.updateSubtree();
+ MM.App.current.update({ parent: true, children: true });
};
MM.Command.Cancel = Object.create(MM.Command, {
editMode: { value: true },
@@ -1872,7 +1886,7 @@
MM.Command.Edit.execute();
var selection = getSelection();
var range = selection.getRangeAt(0);
- range.selectNodeContents(MM.App.current.getDOM().text);
+ range.selectNodeContents(MM.App.current.dom.text);
selection.removeAllRanges();
selection.addRange(range);
this.execute();
@@ -1977,11 +1991,11 @@
MM.Command.SelectRoot.execute = function() {
var item = MM.App.current;
while (!item.isRoot()) {
- item = item.getParent();
+ item = item.parent;
}
MM.App.select(item);
};
- if (!MM.isMac()) {
+ if (!isMac()) {
MM.Command.SelectParent = Object.create(MM.Command, {
label: { value: "Select parent" },
keys: { value: [{ keyCode: 8 }] }
@@ -1990,7 +2004,7 @@
if (MM.App.current.isRoot()) {
return;
}
- MM.App.select(MM.App.current.getParent());
+ MM.App.select(MM.App.current.parent);
};
}
@@ -2017,7 +2031,7 @@
bottom: "top"
};
if (!item.isCollapsed()) {
- var children = item.getChildren();
+ var children = item.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (this.getChildDirection(child) == dir) {
@@ -2028,12 +2042,12 @@
if (item.isRoot()) {
return item;
}
- var parentLayout = item.getParent().getLayout();
+ var parentLayout = item.parent.getLayout();
var thisChildDirection = parentLayout.getChildDirection(item);
if (thisChildDirection == dir) {
return item;
} else if (thisChildDirection == opposite[dir]) {
- return item.getParent();
+ return item.parent;
} else {
return parentLayout.pickSibling(item, dir == "left" || dir == "top" ? -1 : 1);
}
@@ -2042,21 +2056,16 @@
if (item.isRoot()) {
return item;
}
- var children = item.getParent().getChildren();
+ var children = item.parent.children;
var index = children.indexOf(item);
index += dir;
index = (index + children.length) % children.length;
return children[index];
};
- MM.Layout._anchorCanvas = function(item) {
- var dom = item.getDOM();
- dom.canvas.width = dom.node.offsetWidth;
- dom.canvas.height = dom.node.offsetHeight;
- };
MM.Layout._anchorToggle = function(item, x, y, side) {
- var node = item.getDOM().toggle;
- var w = node.offsetWidth;
- var h = node.offsetHeight;
+ var node2 = item.dom.toggle;
+ var w = node2.offsetWidth;
+ var h = node2.offsetHeight;
var l = x;
var t = y;
switch (side) {
@@ -2075,18 +2084,18 @@
l -= w / 2;
break;
}
- node.style.left = Math.round(l) + "px";
- node.style.top = Math.round(t) + "px";
+ node2.style.left = Math.round(l) + "px";
+ node2.style.top = Math.round(t) + "px";
};
MM.Layout._getChildAnchor = function(item, side) {
- var dom = item.getDOM();
+ let { position, dom } = item;
if (side == "left" || side == "right") {
- var pos = dom.node.offsetLeft + dom.content.offsetLeft;
+ var pos = position[0] + dom.content.offsetLeft;
if (side == "left") {
pos += dom.content.offsetWidth;
}
} else {
- var pos = dom.node.offsetTop + dom.content.offsetTop;
+ var pos = position[1] + dom.content.offsetTop;
if (side == "top") {
pos += dom.content.offsetHeight;
}
@@ -2096,19 +2105,18 @@
MM.Layout._computeChildrenBBox = function(children, childIndex) {
var bbox = [0, 0];
var rankIndex = (childIndex + 1) % 2;
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var childSize = [node.offsetWidth, node.offsetHeight];
- bbox[rankIndex] = Math.max(bbox[rankIndex], childSize[rankIndex]);
- bbox[childIndex] += childSize[childIndex];
- }, this);
+ children.forEach((child) => {
+ const { size } = child;
+ bbox[rankIndex] = Math.max(bbox[rankIndex], size[rankIndex]);
+ bbox[childIndex] += size[childIndex];
+ });
if (children.length > 1) {
bbox[childIndex] += this.SPACING_CHILD * (children.length - 1);
}
return bbox;
};
MM.Layout._alignItem = function(item, side) {
- var dom = item.getDOM();
+ var dom = item.dom;
switch (side) {
case "left":
dom.content.insertBefore(dom.icon, dom.content.firstChild);
@@ -2143,7 +2151,7 @@
MM.Layout.Graph.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.getParent().getLayout().getChildDirection(item);
+ side = item.parent.getLayout().getChildDirection(item);
}
this._alignItem(item, side);
this._layoutItem(item, this.childDirection);
@@ -2155,24 +2163,20 @@
return this;
};
MM.Layout.Graph._layoutItem = function(item, rankDirection) {
- var sizeProps = ["width", "height"];
var posProps = ["left", "top"];
var rankIndex = rankDirection == "left" || rankDirection == "right" ? 0 : 1;
var childIndex = (rankIndex + 1) % 2;
var rankPosProp = posProps[rankIndex];
var childPosProp = posProps[childIndex];
- var rankSizeProp = sizeProps[rankIndex];
- var childSizeProp = sizeProps[childIndex];
- var dom = item.getDOM();
+ var dom = item.dom;
var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
- var bbox = this._computeChildrenBBox(item.getChildren(), childIndex);
+ var bbox = this._computeChildrenBBox(item.children, childIndex);
var rankSize = contentSize[rankIndex];
if (bbox[rankIndex]) {
rankSize += bbox[rankIndex] + this.SPACING_RANK;
}
var childSize = Math.max(bbox[childIndex], contentSize[childIndex]);
- dom.node.style[rankSizeProp] = rankSize + "px";
- dom.node.style[childSizeProp] = childSize + "px";
+ item.size = rankIndex == 0 ? [rankSize, childSize] : [childSize, rankSize];
var offset = [0, 0];
if (rankDirection == "right") {
offset[0] = contentSize[0] + this.SPACING_RANK;
@@ -2181,7 +2185,7 @@
offset[1] = contentSize[1] + this.SPACING_RANK;
}
offset[childIndex] = Math.round((childSize - bbox[childIndex]) / 2);
- this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
+ this._layoutChildren(item.children, rankDirection, offset, bbox);
var labelPos = 0;
if (rankDirection == "left") {
labelPos = rankSize - contentSize[0];
@@ -2199,34 +2203,31 @@
var childIndex = (rankIndex + 1) % 2;
var rankPosProp = posProps[rankIndex];
var childPosProp = posProps[childIndex];
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var childSize = [node.offsetWidth, node.offsetHeight];
+ children.forEach((child) => {
+ const { size, dom } = child;
if (rankDirection == "left") {
- offset[0] = bbox[0] - childSize[0];
+ offset[0] = bbox[0] - size[0];
}
if (rankDirection == "top") {
- offset[1] = bbox[1] - childSize[1];
+ offset[1] = bbox[1] - size[1];
}
- node.style[childPosProp] = offset[childIndex] + "px";
- node.style[rankPosProp] = offset[rankIndex] + "px";
- offset[childIndex] += childSize[childIndex] + this.SPACING_CHILD;
- }, this);
+ dom.node.style[childPosProp] = offset[childIndex] + "px";
+ dom.node.style[rankPosProp] = offset[rankIndex] + "px";
+ offset[childIndex] += size[childIndex] + this.SPACING_CHILD;
+ });
return bbox;
};
MM.Layout.Graph._drawLinesHorizontal = function(item, side) {
- this._anchorCanvas(item);
- this._drawHorizontalConnectors(item, side, item.getChildren());
+ this._drawHorizontalConnectors(item, side, item.children);
};
MM.Layout.Graph._drawLinesVertical = function(item, side) {
- this._anchorCanvas(item);
- this._drawVerticalConnectors(item, side, item.getChildren());
+ this._drawVerticalConnectors(item, side, item.children);
};
MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
if (children.length == 0) {
return;
}
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var ctx = canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
@@ -2243,7 +2244,8 @@
}
if (children.length == 1) {
var child = children[0];
- var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ const { position } = child;
+ var y2 = child.getShape().getVerticalAnchor(child) + position[1];
var x2 = this._getChildAnchor(child, side);
ctx.beginPath();
ctx.moveTo(x1, y1);
@@ -2264,8 +2266,10 @@
var c2 = children[children.length - 1];
var x = x2;
var xx = x + (side == "left" ? -R : R);
- var y1 = c1.getShape().getVerticalAnchor(c1) + c1.getDOM().node.offsetTop;
- var y2 = c2.getShape().getVerticalAnchor(c2) + c2.getDOM().node.offsetTop;
+ let p1 = c1.position;
+ let p2 = c2.position;
+ var y1 = c1.getShape().getVerticalAnchor(c1) + p1[1];
+ var y2 = c2.getShape().getVerticalAnchor(c2) + p2[1];
var x1 = this._getChildAnchor(c1, side);
var x2 = this._getChildAnchor(c2, side);
ctx.beginPath();
@@ -2277,7 +2281,8 @@
ctx.lineTo(x2, y2);
for (var i = 1; i < children.length - 1; i++) {
var c = children[i];
- var y = c.getShape().getVerticalAnchor(c) + c.getDOM().node.offsetTop;
+ const { position } = c;
+ var y = c.getShape().getVerticalAnchor(c) + position[1];
ctx.moveTo(x, y);
ctx.lineTo(this._getChildAnchor(c, side), y);
}
@@ -2287,7 +2292,7 @@
if (children.length == 0) {
return;
}
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var ctx = canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
@@ -2314,8 +2319,10 @@
var c2 = children[children.length - 1];
var offset = dom.content.offsetHeight + height;
var y = Math.round(side == "top" ? canvas.height - offset : offset) + 0.5;
- var x1 = c1.getShape().getHorizontalAnchor(c1) + c1.getDOM().node.offsetLeft;
- var x2 = c2.getShape().getHorizontalAnchor(c2) + c2.getDOM().node.offsetLeft;
+ const p1 = c1.position;
+ const p2 = c2.position;
+ var x1 = c1.getShape().getHorizontalAnchor(c1) + p1[0];
+ var x2 = c2.getShape().getHorizontalAnchor(c2) + p2[0];
var y1 = this._getChildAnchor(c1, side);
var y2 = this._getChildAnchor(c2, side);
ctx.beginPath();
@@ -2325,7 +2332,8 @@
ctx.arcTo(x2, y, x2, y2, R);
for (var i = 1; i < children.length - 1; i++) {
var c = children[i];
- var x = c.getShape().getHorizontalAnchor(c) + c.getDOM().node.offsetLeft;
+ const { position } = c;
+ var x = c.getShape().getHorizontalAnchor(c) + position[0];
ctx.moveTo(x, y);
ctx.lineTo(x, this._getChildAnchor(c, side));
}
@@ -2356,31 +2364,29 @@
MM.Layout.Tree.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.getParent().getLayout().getChildDirection(item);
+ side = item.parent.getLayout().getChildDirection(item);
}
this._alignItem(item, side);
this._layoutItem(item, this.childDirection);
- this._anchorCanvas(item);
this._drawLines(item, this.childDirection);
return this;
};
MM.Layout.Tree._layoutItem = function(item, rankDirection) {
- var dom = item.getDOM();
+ var dom = item.dom;
var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
- var bbox = this._computeChildrenBBox(item.getChildren(), 1);
+ var bbox = this._computeChildrenBBox(item.children, 1);
var rankSize = contentSize[0];
var childSize = bbox[1] + contentSize[1];
if (bbox[0]) {
rankSize = Math.max(rankSize, bbox[0] + this.SPACING_RANK);
childSize += this.SPACING_CHILD;
}
- dom.node.style.width = rankSize + "px";
- dom.node.style.height = childSize + "px";
+ item.size = [rankSize, childSize];
var offset = [this.SPACING_RANK, contentSize[1] + this.SPACING_CHILD];
if (rankDirection == "left") {
offset[0] = rankSize - bbox[0] - this.SPACING_RANK;
}
- this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
+ this._layoutChildren(item.children, rankDirection, offset, bbox);
var labelPos = 0;
if (rankDirection == "left") {
labelPos = rankSize - contentSize[0];
@@ -2390,26 +2396,24 @@
return this;
};
MM.Layout.Tree._layoutChildren = function(children, rankDirection, offset, bbox) {
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var childSize = [node.offsetWidth, node.offsetHeight];
+ children.forEach((child) => {
+ const { size } = child;
var left = offset[0];
if (rankDirection == "left") {
- left += bbox[0] - childSize[0];
+ left += bbox[0] - size[0];
}
- node.style.left = left + "px";
- node.style.top = offset[1] + "px";
- offset[1] += childSize[1] + this.SPACING_CHILD;
- }, this);
+ child.position = [left, offset[1]];
+ offset[1] += size[1] + this.SPACING_CHILD;
+ });
return bbox;
};
MM.Layout.Tree._drawLines = function(item, side) {
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var R = this.SPACING_RANK / 4;
var x = (side == "left" ? canvas.width - 2 * R : 2 * R) + 0.5;
this._anchorToggle(item, x, dom.content.offsetHeight, "bottom");
- var children = item.getChildren();
+ var children = item.children;
if (children.length == 0 || item.isCollapsed()) {
return;
}
@@ -2417,13 +2421,13 @@
ctx.strokeStyle = item.getColor();
var y1 = item.getShape().getVerticalAnchor(item);
var last = children[children.length - 1];
- var y2 = last.getShape().getVerticalAnchor(last) + last.getDOM().node.offsetTop;
+ var y2 = last.getShape().getVerticalAnchor(last) + last.position[1];
ctx.beginPath();
ctx.moveTo(x, y1);
ctx.lineTo(x, y2 - R);
for (var i = 0; i < children.length; i++) {
var c = children[i];
- var y = c.getShape().getVerticalAnchor(c) + c.getDOM().node.offsetTop;
+ var y = c.getShape().getVerticalAnchor(c) + c.position[1];
var anchor = this._getChildAnchor(c, side);
ctx.moveTo(x, y - R);
ctx.arcTo(x, y, anchor, y, R);
@@ -2451,15 +2455,15 @@
}
};
MM.Layout.Map.getChildDirection = function(child) {
- while (!child.getParent().isRoot()) {
- child = child.getParent();
+ while (!child.parent.isRoot()) {
+ child = child.parent;
}
var side = child.getSide();
if (side) {
return side;
}
var counts = { left: 0, right: 0 };
- var children = child.getParent().getChildren();
+ var children = child.parent.children;
for (var i = 0; i < children.length; i++) {
var side = children[i].getSide();
if (!side) {
@@ -2474,8 +2478,8 @@
if (item.isRoot()) {
return item;
}
- var parent = item.getParent();
- var children = parent.getChildren();
+ var parent = item.parent;
+ var children = parent.children;
if (parent.isRoot()) {
var side = this.getChildDirection(item);
children = children.filter(function(child) {
@@ -2489,19 +2493,18 @@
};
MM.Layout.Map._layoutRoot = function(item) {
this._alignItem(item, "right");
- var dom = item.getDOM();
- var children = item.getChildren();
+ var dom = item.dom;
+ var children = item.children;
var childrenLeft = [];
var childrenRight = [];
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
+ children.forEach((child) => {
var side = this.getChildDirection(child);
if (side == "left") {
childrenLeft.push(child);
} else {
childrenRight.push(child);
}
- }, this);
+ });
var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
var bboxRight = this._computeChildrenBBox(childrenRight, 1);
var height = Math.max(bboxLeft[1], bboxRight[1], dom.content.offsetHeight);
@@ -2519,9 +2522,7 @@
this._layoutChildren(childrenRight, "right", [left, Math.round((height - bboxRight[1]) / 2)], bboxRight);
left += bboxRight[0];
dom.content.style.top = Math.round((height - dom.content.offsetHeight) / 2) + "px";
- dom.node.style.height = height + "px";
- dom.node.style.width = left + "px";
- this._anchorCanvas(item);
+ item.size = [left, height];
this._drawRootConnectors(item, "left", childrenLeft);
this._drawRootConnectors(item, "right", childrenRight);
};
@@ -2529,7 +2530,7 @@
if (children.length == 0 || item.isCollapsed()) {
return;
}
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var ctx = canvas.getContext("2d");
var R = this.SPACING_RANK / 2;
@@ -2539,7 +2540,7 @@
for (var i = 0; i < children.length; i++) {
var child = children[i];
var x2 = this._getChildAnchor(child, side);
- var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ var y2 = child.getShape().getVerticalAnchor(child) + child.position[1];
var angle = Math.atan2(y2 - y1, x2 - x1) + Math.PI / 2;
var dx = Math.cos(angle) * half;
var dy = Math.sin(angle) * half;
@@ -2558,24 +2559,24 @@
VERTICAL_OFFSET: { value: 0.5 }
});
MM.Shape.set = function(item) {
- item.getDOM().node.classList.add("shape-" + this.id);
+ item.dom.node.classList.add("shape-" + this.id);
return this;
};
MM.Shape.unset = function(item) {
- item.getDOM().node.classList.remove("shape-" + this.id);
+ item.dom.node.classList.remove("shape-" + this.id);
return this;
};
MM.Shape.update = function(item) {
- item.getDOM().content.style.borderColor = item.getColor();
+ item.dom.content.style.borderColor = item.getColor();
return this;
};
MM.Shape.getHorizontalAnchor = function(item) {
- var node = item.getDOM().content;
- return Math.round(node.offsetLeft + node.offsetWidth / 2) + 0.5;
+ var node2 = item.dom.content;
+ return Math.round(node2.offsetLeft + node2.offsetWidth / 2) + 0.5;
};
MM.Shape.getVerticalAnchor = function(item) {
- var node = item.getDOM().content;
- return node.offsetTop + Math.round(node.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
+ var node2 = item.dom.content;
+ return node2.offsetTop + Math.round(node2.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
};
// .js/shape/shape.underline.js
@@ -2585,7 +2586,7 @@
VERTICAL_OFFSET: { value: -3 }
});
MM.Shape.Underline.update = function(item) {
- var dom = item.getDOM();
+ var dom = item.dom;
var ctx = dom.canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
var left = dom.content.offsetLeft;
@@ -2597,8 +2598,8 @@
ctx.stroke();
};
MM.Shape.Underline.getVerticalAnchor = function(item) {
- var node = item.getDOM().content;
- return node.offsetTop + node.offsetHeight + this.VERTICAL_OFFSET + 0.5;
+ var node2 = item.dom.content;
+ return node2.offsetTop + node2.offsetHeight + this.VERTICAL_OFFSET + 0.5;
};
// .js/shape/shape.box.js
@@ -2715,36 +2716,36 @@
}
return elm;
};
- MM.Format.FreeMind._parseNode = function(node, parent) {
- var json = this._parseAttributes(node, parent);
- for (var i = 0; i < node.childNodes.length; i++) {
- var child = node.childNodes[i];
+ MM.Format.FreeMind._parseNode = function(node2, parent) {
+ var json = this._parseAttributes(node2, parent);
+ for (var i = 0; i < node2.childNodes.length; i++) {
+ var child = node2.childNodes[i];
if (child.nodeName.toLowerCase() == "node") {
json.children.push(this._parseNode(child, json));
}
}
return json;
};
- MM.Format.FreeMind._parseAttributes = function(node, parent) {
+ MM.Format.FreeMind._parseAttributes = function(node2, parent) {
var json = {
children: [],
- text: MM.Format.nl2br(node.getAttribute("TEXT") || ""),
- id: node.getAttribute("ID")
+ text: MM.Format.nl2br(node2.getAttribute("TEXT") || ""),
+ id: node2.getAttribute("ID")
};
- var position = node.getAttribute("POSITION");
+ var position = node2.getAttribute("POSITION");
if (position) {
json.side = position;
}
- var style = node.getAttribute("STYLE");
+ var style = node2.getAttribute("STYLE");
if (style == "bubble") {
json.shape = "box";
} else {
json.shape = parent.shape;
}
- if (node.getAttribute("FOLDED") == "true") {
+ if (node2.getAttribute("FOLDED") == "true") {
json.collapsed = 1;
}
- var children = node.children;
+ var children = node2.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
switch (child.nodeName.toLowerCase()) {
@@ -2776,23 +2777,23 @@
label: { value: "Mind Map Architect" },
extension: { value: "mma" }
});
- MM.Format.MMA._parseAttributes = function(node, parent) {
+ MM.Format.MMA._parseAttributes = function(node2, parent) {
var json = {
children: [],
- text: MM.Format.nl2br(node.getAttribute("title") || ""),
+ text: MM.Format.nl2br(node2.getAttribute("title") || ""),
shape: "box"
};
- if (node.getAttribute("expand") == "false") {
+ if (node2.getAttribute("expand") == "false") {
json.collapsed = 1;
}
- var direction = node.getAttribute("direction");
+ var direction = node2.getAttribute("direction");
if (direction == "0") {
json.side = "left";
}
if (direction == "1") {
json.side = "right";
}
- var color = node.getAttribute("color");
+ var color = node2.getAttribute("color");
if (color) {
var re = color.match(/^#(....)(....)(....)$/);
if (re) {
@@ -2805,7 +2806,7 @@
}
json.color = "#" + [r, g, b].join("");
}
- json.icon = node.getAttribute("icon");
+ json.icon = node2.getAttribute("icon");
return json;
};
MM.Format.MMA._serializeAttributes = function(doc, json) {
@@ -3134,7 +3135,7 @@
firebase.initializeApp(config);
this.ref = firebase.database().ref();
this.ref.child("names").on("value", function(snap) {
- MM.publish("firebase-list", this, snap.val() || {});
+ publish("firebase-list", this, snap.val() || {});
}, this);
if (auth) {
return this._login(auth);
@@ -3264,7 +3265,7 @@
clearTimeout(this._changeTimeout);
}
this._changeTimeout = setTimeout(function() {
- MM.publish("firebase-change", this, this._current.data);
+ publish("firebase-change", this, this._current.data);
}.bind(this), 200);
};
MM.Backend.Firebase._login = function(type) {
@@ -3447,8 +3448,8 @@
this._color = new MM.UI.Color();
this._value = new MM.UI.Value();
this._status = new MM.UI.Status();
- MM.subscribe("item-select", this);
- MM.subscribe("item-change", this);
+ subscribe("item-select", this);
+ subscribe("item-change", this);
this._node.addEventListener("click", this);
this._node.addEventListener("change", this);
this.toggle();
@@ -3475,14 +3476,14 @@
this.toggle();
return;
}
- var node = e.target;
- while (node != document) {
- var command = node.getAttribute("data-command");
+ var node2 = e.target;
+ while (node2 != document) {
+ var command = node2.getAttribute("data-command");
if (command) {
MM.Command[command].execute();
return;
}
- node = node.parentNode;
+ node2 = node2.parentNode;
}
break;
case "change":
@@ -3492,7 +3493,7 @@
};
MM.UI.prototype.toggle = function() {
this._node.classList.toggle("visible");
- MM.publish("ui-change", this);
+ publish("ui-change", this);
};
MM.UI.prototype.getWidth = function() {
return this._node.classList.contains("visible") ? this._node.offsetWidth : 0;
@@ -3538,10 +3539,10 @@
return this._select.querySelector("option[value='" + value + "']");
};
MM.UI.Layout.prototype._buildGroup = function(label) {
- var node = document.createElement("optgroup");
- node.label = label;
- this._select.appendChild(node);
- return node;
+ var node2 = document.createElement("optgroup");
+ node2.label = label;
+ this._select.appendChild(node2);
+ return node2;
};
// .js/ui/ui.shape.js
@@ -3766,11 +3767,11 @@
MM.Clipboard.focus();
}
};
- MM.UI.Notes.prototype.update = function(html) {
- if (html.trim().length === 0) {
+ MM.UI.Notes.prototype.update = function(html2) {
+ if (html2.trim().length === 0) {
MM.App.current._notes = null;
} else {
- MM.App.current._notes = html;
+ MM.App.current._notes = html2;
}
MM.App.current.update();
};
@@ -3792,9 +3793,9 @@
}, this);
this._backend.value = localStorage.getItem(this._prefix + "backend") || MM.Backend.File.id;
this._backend.addEventListener("change", this);
- MM.subscribe("map-new", this);
- MM.subscribe("save-done", this);
- MM.subscribe("load-done", this);
+ subscribe("map-new", this);
+ subscribe("save-done", this);
+ subscribe("load-done", this);
};
MM.UI.IO.prototype.restore = function() {
var parts = {};
@@ -3877,11 +3878,8 @@
}
};
MM.UI.IO.prototype._syncBackend = function() {
- var all = this._node.querySelectorAll("div[id]");
- [].slice.apply(all).forEach(function(node) {
- node.style.display = "none";
- });
- this._node.querySelector("#" + this._backend.value).style.display = "";
+ [...this._node.querySelectorAll("div[id]")].forEach((node2) => node2.hidden = true);
+ this._node.querySelector("#" + this._backend.value).hidden = false;
this._backends[this._backend.value].show(this._mode);
};
MM.UI.IO.prototype._setCurrentBackend = function(backend) {
@@ -3947,14 +3945,8 @@
MM.UI.Backend.show = function(mode) {
this._mode = mode;
this._go.innerHTML = mode.charAt(0).toUpperCase() + mode.substring(1);
- var all = this._node.querySelectorAll("[data-for]");
- [].concat.apply([], all).forEach(function(node) {
- node.style.display = "none";
- });
- var visible = this._node.querySelectorAll("[data-for~=" + mode + "]");
- [].concat.apply([], visible).forEach(function(node) {
- node.style.display = "";
- });
+ [...this._node.querySelectorAll("[data-for]")].forEach((node2) => node2.hidden = true);
+ [...this._node.querySelectorAll(`[data-for~=${mode}]`)].forEach((node2) => node2.hidden = false);
this._go.focus();
};
MM.UI.Backend._action = function() {
@@ -3969,13 +3961,13 @@
};
MM.UI.Backend._saveDone = function() {
MM.App.setThrobber(false);
- MM.publish("save-done", this);
+ publish("save-done", this);
};
MM.UI.Backend._loadDone = function(json) {
MM.App.setThrobber(false);
try {
MM.App.setMap(MM.Map.fromJSON(json));
- MM.publish("load-done", this);
+ publish("load-done", this);
} catch (e) {
this._error(e);
}
@@ -4201,8 +4193,8 @@
this._remove = this._node.querySelector(".remove");
this._remove.addEventListener("click", this);
this._go.disabled = false;
- MM.subscribe("firebase-list", this);
- MM.subscribe("firebase-change", this);
+ subscribe("firebase-list", this);
+ subscribe("firebase-change", this);
};
MM.UI.Backend.Firebase.setState = function(data) {
this._connect(data.s, data.a).then(this._load.bind(this, data.id), this._error.bind(this));
@@ -4252,9 +4244,9 @@
break;
case "firebase-change":
if (data) {
- MM.unsubscribe("item-change", this);
+ unsubscribe("item-change", this);
MM.App.map.mergeWith(data);
- MM.subscribe("item-change", this);
+ subscribe("item-change", this);
} else {
console.log("remote data disappeared");
}
@@ -4269,7 +4261,7 @@
};
MM.UI.Backend.Firebase.reset = function() {
this._backend.reset();
- MM.unsubscribe("item-change", this);
+ unsubscribe("item-change", this);
};
MM.UI.Backend.Firebase._itemChange = function() {
var map = MM.App.map;
@@ -4327,11 +4319,11 @@
this._go.innerHTML = this._mode.charAt(0).toUpperCase() + this._mode.substring(1);
};
MM.UI.Backend.Firebase._loadDone = function() {
- MM.subscribe("item-change", this);
+ subscribe("item-change", this);
MM.UI.Backend._loadDone.apply(this, arguments);
};
MM.UI.Backend.Firebase._saveDone = function() {
- MM.subscribe("item-change", this);
+ subscribe("item-change", this);
MM.UI.Backend._saveDone.apply(this, arguments);
};
@@ -4560,7 +4552,7 @@
this._item = null;
};
MM.Mouse._buildGhost = function() {
- var content = this._item.getDOM().content;
+ var content = this._item.dom.content;
this._ghost = content.cloneNode(true);
this._ghost.classList.add("ghost");
this._pos[0] = content.offsetLeft;
@@ -4582,9 +4574,9 @@
var action = new MM.Action.MoveItem(this._item, target);
break;
case "sibling":
- var index = target.getParent().getChildren().indexOf(target);
+ var index = target.parent.children.indexOf(target);
var targetIndex = index + (state.direction == "right" || state.direction == "bottom" ? 1 : 0);
- var action = new MM.Action.MoveItem(this._item, target.getParent(), targetIndex, target.getSide());
+ var action = new MM.Action.MoveItem(this._item, target.parent, targetIndex, target.getSide());
break;
default:
return;
@@ -4606,13 +4598,13 @@
if (tmp == this._item) {
return state;
}
- tmp = tmp.getParent();
+ tmp = tmp.parent;
}
- var w1 = this._item.getDOM().content.offsetWidth;
- var w2 = target.getDOM().content.offsetWidth;
+ var w1 = this._item.dom.content.offsetWidth;
+ var w2 = target.dom.content.offsetWidth;
var w = Math.max(w1, w2);
- var h1 = this._item.getDOM().content.offsetHeight;
- var h2 = target.getDOM().content.offsetHeight;
+ var h1 = this._item.dom.content.offsetHeight;
+ var h2 = target.dom.content.offsetHeight;
var h = Math.max(h1, h2);
if (target.isRoot()) {
state.result = "append";
@@ -4620,7 +4612,7 @@
state.result = "append";
} else {
state.result = "sibling";
- var childDirection = target.getParent().getLayout().getChildDirection(target);
+ var childDirection = target.parent.getLayout().getChildDirection(target);
var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
if (childDirection == "left" || childDirection == "right") {
state.direction = closest.dy < 0 ? "bottom" : "top";
@@ -4636,13 +4628,13 @@
}
if (this._oldDragState) {
var item = this._oldDragState.item;
- var node = item.getDOM().content;
- node.style.boxShadow = "";
+ var node2 = item.dom.content;
+ node2.style.boxShadow = "";
}
this._oldDragState = state;
if (state) {
var item = state.item;
- var node = item.getDOM().content;
+ var node2 = item.dom.content;
var x = 0;
var y = 0;
var offset = 5;
@@ -4661,7 +4653,7 @@
}
}
var spread = x || y ? -2 : 2;
- node.style.boxShadow = x * offset + "px " + y * offset + "px 2px " + spread + "px #000";
+ node2.style.boxShadow = x * offset + "px " + y * offset + "px 2px " + spread + "px #000";
}
};
@@ -4722,7 +4714,7 @@
this._syncPort();
break;
case "item-change":
- if (publisher.isRoot() && publisher.getMap() == this.map) {
+ if (publisher.isRoot() && publisher.map == this.map) {
document.title = this.map.getName() + " :: My Mind";
}
break;
@@ -4776,8 +4768,8 @@
window.addEventListener("beforeunload", this);
window.addEventListener("keyup", this);
window.addEventListener("message", this, false);
- MM.subscribe("ui-change", this);
- MM.subscribe("item-change", this);
+ subscribe("ui-change", this);
+ subscribe("item-change", this);
this._syncPort();
this.setMap(new MM.Map());
},
diff --git a/src/action.js b/src/action.js
index 36099d29..83c427ad 100644
--- a/src/action.js
+++ b/src/action.js
@@ -25,7 +25,7 @@ MM.Action.InsertNewItem = function(parent, index) {
MM.Action.InsertNewItem.prototype = Object.create(MM.Action.prototype);
MM.Action.InsertNewItem.prototype.perform = function() {
this._parent.expand(); /* FIXME remember? */
- this._item = this._parent.insertChild(this._item, this._index);
+ this._parent.insertChild(this._item, this._index);
MM.App.select(this._item);
}
MM.Action.InsertNewItem.prototype.undo = function() {
@@ -49,8 +49,8 @@ MM.Action.AppendItem.prototype.undo = function() {
MM.Action.RemoveItem = function(item) {
this._item = item;
- this._parent = item.getParent();
- this._index = this._parent.getChildren().indexOf(this._item);
+ this._parent = item.parent;
+ this._index = this._parent.children.indexOf(this._item);
}
MM.Action.RemoveItem.prototype = Object.create(MM.Action.prototype);
MM.Action.RemoveItem.prototype.perform = function() {
@@ -67,8 +67,8 @@ MM.Action.MoveItem = function(item, newParent, newIndex, newSide) {
this._newParent = newParent;
this._newIndex = (arguments.length < 3 ? null : newIndex);
this._newSide = newSide || "";
- this._oldParent = item.getParent();
- this._oldIndex = this._oldParent.getChildren().indexOf(item);
+ this._oldParent = item.parent;
+ this._oldIndex = this._oldParent.children.indexOf(item);
this._oldSide = item.getSide();
}
MM.Action.MoveItem.prototype = Object.create(MM.Action.prototype);
@@ -89,9 +89,9 @@ MM.Action.MoveItem.prototype.undo = function() {
MM.Action.Swap = function(item, diff) {
this._item = item;
- this._parent = item.getParent();
+ this._parent = item.parent;
- var children = this._parent.getChildren();
+ var children = this._parent.children;
var sibling = this._parent.getLayout().pickSibling(this._item, diff);
this._sourceIndex = children.indexOf(this._item);
@@ -208,9 +208,9 @@ MM.Action.SetSide = function(item, side) {
MM.Action.SetSide.prototype = Object.create(MM.Action.prototype);
MM.Action.SetSide.prototype.perform = function() {
this._item.setSide(this._side);
- this._item.getMap().update();
+ this._item.map.update();
}
MM.Action.SetSide.prototype.undo = function() {
this._item.setSide(this._oldSide);
- this._item.getMap().update();
+ this._item.map.update();
}
diff --git a/src/backend/backend.firebase.js b/src/backend/backend.firebase.js
index 8eb96e10..57a97d8a 100644
--- a/src/backend/backend.firebase.js
+++ b/src/backend/backend.firebase.js
@@ -1,3 +1,6 @@
+import * as pubsub from "../pubsub.js";
+
+
MM.Backend.Firebase = Object.create(MM.Backend, {
label: {value: "Firebase"},
id: {value: "firebase"},
@@ -22,9 +25,9 @@ MM.Backend.Firebase.connect = function(server, auth) {
firebase.initializeApp(config);
this.ref = firebase.database().ref();
-
+
this.ref.child("names").on("value", function(snap) {
- MM.publish("firebase-list", this, snap.val() || {});
+ pubsub.publish("firebase-list", this, snap.val() || {});
}, this);
if (auth) {
@@ -55,7 +58,7 @@ MM.Backend.Firebase.save = function(data, id, name) {
MM.Backend.Firebase.load = function(id) {
var promise = new Promise();
-
+
this.ref.child("data/" + id).once("value", function(snap) {
var data = snap.val();
if (data) {
@@ -187,7 +190,7 @@ MM.Backend.Firebase._valueChange = function(snap) {
this._current.data = snap.val();
if (this._changeTimeout) { clearTimeout(this._changeTimeout); }
this._changeTimeout = setTimeout(function() {
- MM.publish("firebase-change", this, this._current.data);
+ pubsub.publish("firebase-change", this, this._current.data);
}.bind(this), 200);
}
diff --git a/src/clipboard.js b/src/clipboard.js
index a871e225..34d7f0ab 100644
--- a/src/clipboard.js
+++ b/src/clipboard.js
@@ -45,7 +45,7 @@ MM.Clipboard.paste = function(targetItem) {
MM.Clipboard._pasteItem = function(sourceItem, targetItem) {
switch (this._mode) {
case "cut":
- if (sourceItem == targetItem || sourceItem.getParent() == targetItem) { /* abort by pasting on the same node or the parent */
+ if (sourceItem == targetItem || sourceItem.parent == targetItem) { /* abort by pasting on the same node or the parent */
this._endCut();
return;
}
@@ -53,7 +53,7 @@ MM.Clipboard._pasteItem = function(sourceItem, targetItem) {
var item = targetItem;
while (!item.isRoot()) {
if (item == sourceItem) { return; } /* moving to a child => forbidden */
- item = item.getParent();
+ item = item.parent;
}
var action = new MM.Action.MoveItem(sourceItem, targetItem);
@@ -80,7 +80,7 @@ MM.Clipboard._pastePlaintext = function(plaintext, targetItem) {
var action = new MM.Action.AppendItem(targetItem, root);
MM.App.action(action);
} else {
- var actions = root.getChildren().map(function(item) {
+ var actions = root.children.map(function(item) {
return new MM.Action.AppendItem(targetItem, item);
});
var action = new MM.Action.Multi(actions);
@@ -92,7 +92,7 @@ MM.Clipboard.cut = function(sourceItem) {
this._endCut();
this._item = sourceItem;
- this._item.getDOM().node.classList.add("cut");
+ this._item.dom.node.classList.add("cut");
this._mode = "cut";
this._expose();
@@ -120,7 +120,7 @@ MM.Clipboard._empty = function() {
MM.Clipboard._endCut = function() {
if (this._mode != "cut") { return; }
- this._item.getDOM().node.classList.remove("cut");
+ this._item.dom.node.classList.remove("cut");
this._item = null;
this._mode = "";
}
diff --git a/src/command/command.edit.js b/src/command/command.edit.js
index 94af92b4..dea522ce 100644
--- a/src/command/command.edit.js
+++ b/src/command/command.edit.js
@@ -38,7 +38,7 @@ MM.Command.Newline.execute = function() {
var br = document.createElement("br");
range.insertNode(br);
range.setStartAfter(br);
- MM.App.current.updateSubtree();
+ MM.App.current.update({parent:true, children:true});
}
MM.Command.Cancel = Object.create(MM.Command, {
@@ -67,7 +67,7 @@ MM.Command.Style.execute = function() {
MM.Command.Edit.execute();
var selection = getSelection();
var range = selection.getRangeAt(0);
- range.selectNodeContents(MM.App.current.getDOM().text);
+ range.selectNodeContents(MM.App.current.dom.text);
selection.removeAllRanges();
selection.addRange(range);
this.execute();
diff --git a/src/command/command.js b/src/command/command.js
index 87622432..6f0a195f 100644
--- a/src/command/command.js
+++ b/src/command/command.js
@@ -1,3 +1,11 @@
+import * as pubsub from "../pubsub.js";
+
+
+export function isMac() {
+ return !!navigator.platform.match(/mac/i);
+}
+
+
MM.Command = Object.create(MM.Repo, {
keys: {value: []},
editMode: {value: false},
@@ -54,17 +62,17 @@ MM.Command.InsertSibling = Object.create(MM.Command, {
MM.Command.InsertSibling.execute = function() {
var item = MM.App.current;
if (item.isRoot()) {
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ var action = new MM.Action.InsertNewItem(item, item.children.length);
} else {
- var parent = item.getParent();
- var index = parent.getChildren().indexOf(item);
+ var parent = item.parent;
+ var index = parent.children.indexOf(item);
var action = new MM.Action.InsertNewItem(parent, index+1);
}
MM.App.action(action);
MM.Command.Edit.execute();
- MM.publish("command-sibling");
+ pubsub.publish("command-sibling");
}
MM.Command.InsertChild = Object.create(MM.Command, {
@@ -76,17 +84,17 @@ MM.Command.InsertChild = Object.create(MM.Command, {
});
MM.Command.InsertChild.execute = function() {
var item = MM.App.current;
- var action = new MM.Action.InsertNewItem(item, item.getChildren().length);
+ var action = new MM.Action.InsertNewItem(item, item.children.length);
MM.App.action(action);
MM.Command.Edit.execute();
- MM.publish("command-child");
+ pubsub.publish("command-child");
}
MM.Command.Delete = Object.create(MM.Command, {
label: {value: "Delete an item"},
- keys: {value: [{keyCode: MM.isMac() ? 8 : 46}]} // Mac keyboards' "delete" button generates 8 (backspace)
+ keys: {value: [{keyCode: isMac() ? 8 : 46}]} // Mac keyboards' "delete" button generates 8 (backspace)
});
MM.Command.Delete.isValid = function() {
return MM.Command.isValid.call(this) && !MM.App.current.isRoot();
@@ -105,7 +113,7 @@ MM.Command.Swap = Object.create(MM.Command, {
});
MM.Command.Swap.execute = function(e) {
var current = MM.App.current;
- if (current.isRoot() || current.getParent().getChildren().length < 2) { return; }
+ if (current.isRoot() || current.parent.children.length < 2) { return; }
var diff = (e.keyCode == 38 ? -1 : 1);
var action = new MM.Action.Swap(MM.App.current, diff);
@@ -121,7 +129,7 @@ MM.Command.Side = Object.create(MM.Command, {
});
MM.Command.Side.execute = function(e) {
var current = MM.App.current;
- if (current.isRoot() || !current.getParent().isRoot()) { return; }
+ if (current.isRoot() || !current.parent.isRoot()) { return; }
var side = (e.keyCode == 37 ? "left" : "right");
var action = new MM.Action.SetSide(MM.App.current, side);
@@ -168,7 +176,7 @@ MM.Command.New.execute = function() {
if (!confirm("Throw away your current map and start a new one?")) { return; }
var map = new MM.Map();
MM.App.setMap(map);
- MM.publish("map-new", this);
+ pubsub.publish("map-new", this);
}
MM.Command.ZoomIn = Object.create(MM.Command, {
diff --git a/src/command/command.select.js b/src/command/command.select.js
index a8d12d44..5beef60f 100644
--- a/src/command/command.select.js
+++ b/src/command/command.select.js
@@ -1,3 +1,6 @@
+import { isMac } from "./command.js";
+
+
MM.Command.Select = Object.create(MM.Command, {
label: {value: "Move selection"},
keys: {value: [
@@ -27,18 +30,18 @@ MM.Command.SelectRoot = Object.create(MM.Command, {
});
MM.Command.SelectRoot.execute = function() {
var item = MM.App.current;
- while (!item.isRoot()) { item = item.getParent(); }
+ while (!item.isRoot()) { item = item.parent; }
MM.App.select(item);
}
// Macs use keyCode 8 to delete instead
-if (!MM.isMac()) {
+if (!isMac()) {
MM.Command.SelectParent = Object.create(MM.Command, {
label: {value: "Select parent"},
keys: {value: [{keyCode: 8}]}
});
MM.Command.SelectParent.execute = function() {
if (MM.App.current.isRoot()) { return; }
- MM.App.select(MM.App.current.getParent());
+ MM.App.select(MM.App.current.parent);
}
}
diff --git a/src/html.ts b/src/html.ts
new file mode 100644
index 00000000..df18fc0f
--- /dev/null
+++ b/src/html.ts
@@ -0,0 +1,5 @@
+export function node(name: T, attrs?: Record): HTMLElementTagNameMap[T] {
+ let node = document.createElement(name);
+ Object.assign(node, attrs);
+ return node;
+}
diff --git a/src/item.js b/src/item.js
deleted file mode 100644
index 2a1ad25b..00000000
--- a/src/item.js
+++ /dev/null
@@ -1,659 +0,0 @@
-MM.Item = function() {
- this._parent = null;
- this._children = [];
- this._collapsed = false;
-
- this._layout = null;
- this._shape = null;
- this._autoShape = true;
- this._color = null;
- this._value = null;
- this._status = null;
- this._side = null; /* side preference */
- this._icon = null;
- this._notes = null;
- this._id = MM.generateId();
- this._oldText = "";
-
- this._computed = {
- value: 0,
- status: null
- }
-
- this._dom = {
- node: document.createElement("li"),
- content: document.createElement("div"),
- notes: document.createElement("div"),
- status: document.createElement("span"),
- icon: document.createElement("span"),
- value: document.createElement("span"),
- text: document.createElement("div"),
- children: document.createElement("ul"),
- toggle: document.createElement("div"),
- canvas: document.createElement("canvas")
- }
- this._dom.node.classList.add("item");
- this._dom.content.classList.add("content");
- this._dom.notes.classList.add("notes-indicator");
- this._dom.status.classList.add("status");
- this._dom.icon.classList.add("icon");
- this._dom.value.classList.add("value");
- this._dom.text.classList.add("text");
- this._dom.toggle.classList.add("toggle");
- this._dom.children.classList.add("children");
-
- this._dom.content.appendChild(this._dom.text); /* status+value are appended in layout */
- this._dom.node.appendChild(this._dom.canvas);
- this._dom.node.appendChild(this._dom.content);
- this._dom.content.appendChild(this._dom.notes);
- /* toggle+children are appended when children exist */
-
- this._dom.toggle.addEventListener("click", this);
-}
-
-MM.Item.COLOR = "#999";
-
- /* RE explanation:
- * _________________________________________________________________________ One of the three possible variants
- * ____________________ scheme://x
- * ___________________________ aa.bb.cc
- * _______________________ aa.bb/
- * ______ path, search
- * __________________________ end with a non-forbidden char
- * ______ end of word or end of string
- */
-MM.Item.RE = /\b(([a-z][\w-]+:\/\/\w)|(([\w-]+\.){2,}[a-z][\w-]+)|([\w-]+\.[a-z][\w-]+\/))[^\s]*([^\s,.;:?!<>\(\)\[\]'"])?($|\b)/i;
-
-MM.Item.fromJSON = function(data) {
- return new this().fromJSON(data);
-}
-
-MM.Item.prototype.toJSON = function() {
- var data = {
- id: this._id,
- text: this.getText(),
- notes: this.getNotes()
- }
-
-
- if (this._side) { data.side = this._side; }
- if (this._color) { data.color = this._color; }
- if (this._icon) { data.icon = this._icon; }
- if (this._value) { data.value = this._value; }
- if (this._status) { data.status = this._status; }
- if (this._layout) { data.layout = this._layout.id; }
- if (!this._autoShape) { data.shape = this._shape.id; }
- if (this._collapsed) { data.collapsed = 1; }
- if (this._children.length) {
- data.children = this._children.map(function(child) { return child.toJSON(); });
- }
-
- return data;
-}
-
-/**
- * Only when creating a new item. To merge existing items, use .mergeWith().
- */
-MM.Item.prototype.fromJSON = function(data) {
- this.setText(data.text);
- if (data.notes) {
- this.setNotes(data.notes);
- }
- if (data.id) { this._id = data.id; }
- if (data.side) { this._side = data.side; }
- if (data.color) { this._color = data.color; }
- if (data.icon) { this._icon = data.icon; }
- if (data.value) { this._value = data.value; }
- if (data.status) {
- this._status = data.status;
- if (this._status == "maybe") { this._status = "computed"; }
- }
- if (data.collapsed) { this.collapse(); }
- if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
- if (data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
-
- (data.children || []).forEach(function(child) {
- this.insertChild(MM.Item.fromJSON(child));
- }, this);
-
- return this;
-}
-
-MM.Item.prototype.mergeWith = function(data) {
- var dirty = 0;
-
- if (this.getText() != data.text && !this._dom.text.contentEditable) { this.setText(data.text); }
-
- if (this._side != data.side) {
- this._side = data.side;
- dirty = 1;
- }
-
- if (this._color != data.color) {
- this._color = data.color;
- dirty = 2;
- }
-
- if (this._icon != data.icon) {
- this._icon = data.icon;
- dirty = 1;
- }
-
- if (this._value != data.value) {
- this._value = data.value;
- dirty = 1;
- }
-
- if (this._status != data.status) {
- this._status = data.status;
- dirty = 1;
- }
-
- if (this._collapsed != !!data.collapsed) { this[this._collapsed ? "expand" : "collapse"](); }
-
- if (this.getOwnLayout() != data.layout) {
- this._layout = MM.Layout.getById(data.layout);
- dirty = 2;
- }
-
- var s = (this._autoShape ? null : this._shape.id);
- if (s != data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
-
- (data.children || []).forEach(function(child, index) {
- if (index >= this._children.length) { /* new child */
- this.insertChild(MM.Item.fromJSON(child));
- } else { /* existing child */
- var myChild = this._children[index];
- if (myChild.getId() == child.id) { /* recursive merge */
- myChild.mergeWith(child);
- } else { /* changed; replace */
- this.removeChild(this._children[index]);
- this.insertChild(MM.Item.fromJSON(child), index);
- }
- }
- }, this);
-
- /* remove dead children */
- var newLength = (data.children || []).length;
- while (this._children.length > newLength) { this.removeChild(this._children[this._children.length-1]); }
-
- if (dirty == 1) { this.update(); }
- if (dirty == 2) { this.updateSubtree(); }
-}
-
-MM.Item.prototype.clone = function() {
- var data = this.toJSON();
-
- var removeId = function(obj) {
- delete obj.id;
- obj.children && obj.children.forEach(removeId);
- }
- removeId(data);
-
- return this.constructor.fromJSON(data);
-}
-
-MM.Item.prototype.select = function() {
- this._dom.node.classList.add("current");
- if (window.editor) {
- if (this._notes) {
- window.editor.setContent(this._notes);
- } else {
- window.editor.setContent('');
- }
- }
- this.getMap().ensureItemVisibility(this);
- MM.Clipboard.focus(); /* going to mode 2c */
- MM.publish("item-select", this);
-}
-
-MM.Item.prototype.deselect = function() {
- /* we were in 2b; finish that via 3b */
- if (MM.App.editing) { MM.Command.Finish.execute(); }
- this._dom.node.classList.remove("current");
-}
-
-MM.Item.prototype.update = function(doNotRecurse) {
- var map = this.getMap();
- if (!map || !map.isVisible()) { return this; }
-
- MM.publish("item-change", this);
-
- if (this._autoShape) { /* check for changed auto-shape */
- var autoShape = this._getAutoShape();
- if (autoShape != this._shape) {
- if (this._shape) { this._shape.unset(this); }
- this._shape = autoShape;
- this._shape.set(this);
- }
- }
-
- this._updateStatus();
- this._updateIcon();
- this._updateNotesIndicator();
- this._updateValue();
-
- this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
-
- this.getLayout().update(this);
- this.getShape().update(this);
- if (!this.isRoot() && !doNotRecurse) { this._parent.update(); }
-
- return this;
-}
-
-MM.Item.prototype.updateSubtree = function(isSubChild) {
- this._children.forEach(function(child) {
- child.updateSubtree(true);
- });
- return this.update(isSubChild);
-}
-
-MM.Item.prototype.setText = function(text) {
- this._dom.text.innerHTML = text;
- this._findLinks(this._dom.text);
- return this.update();
-}
-
-MM.Item.prototype.setNotes = function(notes) {
- this._notes = notes;
- return this.update();
-}
-
-MM.Item.prototype.getId = function() {
- return this._id;
-}
-
-MM.Item.prototype.getText = function() {
- return this._dom.text.innerHTML;
-}
-
-MM.Item.prototype.getNotes = function() {
- return this._notes;
-}
-
-MM.Item.prototype.collapse = function() {
- if (this._collapsed) { return; }
- this._collapsed = true;
- return this.update();
-}
-
-MM.Item.prototype.expand = function() {
- if (!this._collapsed) { return; }
- this._collapsed = false;
- this.update();
- return this.updateSubtree();
-}
-
-MM.Item.prototype.isCollapsed = function() {
- return this._collapsed;
-}
-
-MM.Item.prototype.setValue = function(value) {
- this._value = value;
- return this.update();
-}
-
-MM.Item.prototype.getValue = function() {
- return this._value;
-}
-
-MM.Item.prototype.getComputedValue = function() {
- return this._computed.value;
-}
-
-MM.Item.prototype.setStatus = function(status) {
- this._status = status;
- return this.update();
-}
-
-MM.Item.prototype.getStatus = function() {
- return this._status;
-}
-
-MM.Item.prototype.setIcon = function(icon) {
- this._icon = icon;
- return this.update();
-}
-
-MM.Item.prototype.getIcon = function() {
- return this._icon;
-}
-
-MM.Item.prototype.getComputedStatus = function() {
- return this._computed.status;
-}
-
-MM.Item.prototype.setSide = function(side) {
- this._side = side;
- return this;
-}
-
-MM.Item.prototype.getSide = function() {
- return this._side;
-}
-
-MM.Item.prototype.getChildren = function() {
- return this._children;
-}
-
-MM.Item.prototype.setColor = function(color) {
- this._color = color;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.getColor = function() {
- return this._color || (this.isRoot() ? MM.Item.COLOR : this._parent.getColor());
-}
-
-MM.Item.prototype.getOwnColor = function() {
- return this._color;
-}
-
-MM.Item.prototype.getLayout = function() {
- return this._layout || this._parent.getLayout();
-}
-
-MM.Item.prototype.getOwnLayout = function() {
- return this._layout;
-}
-
-MM.Item.prototype.setLayout = function(layout) {
- this._layout = layout;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.getShape = function() {
- return this._shape;
-}
-
-MM.Item.prototype.getOwnShape = function() {
- return (this._autoShape ? null : this._shape);
-}
-
-MM.Item.prototype.setShape = function(shape) {
- if (this._shape) { this._shape.unset(this); }
-
- if (shape) {
- this._autoShape = false;
- this._shape = shape;
- } else {
- this._autoShape = true;
- this._shape = this._getAutoShape();
- }
-
- this._shape.set(this);
- return this.update();
-}
-
-MM.Item.prototype.getDOM = function() {
- return this._dom;
-}
-
-MM.Item.prototype.getMap = function() {
- var item = this._parent;
- while (item) {
- if (item instanceof MM.Map) { return item; }
- item = item.getParent();
- }
- return null;
-}
-
-MM.Item.prototype.getParent = function() {
- return this._parent;
-}
-
-MM.Item.prototype.isRoot = function() {
- return (this._parent instanceof MM.Map);
-}
-
-MM.Item.prototype.setParent = function(parent) {
- this._parent = parent;
- return this.updateSubtree();
-}
-
-MM.Item.prototype.insertChild = function(child, index) {
- /* Create or remove child as necessary. This must be done before computing the index (inserting own child) */
- var newChild = false;
- if (!child) {
- child = new MM.Item();
- newChild = true;
- } else if (child.getParent() && child.getParent().removeChild) { /* only when the child has non-map parent */
- child.getParent().removeChild(child);
- }
-
- if (!this._children.length) {
- this._dom.node.appendChild(this._dom.toggle);
- this._dom.node.appendChild(this._dom.children);
- }
-
- if (arguments.length < 2) { index = this._children.length; }
-
- var next = null;
- if (index < this._children.length) { next = this._children[index].getDOM().node; }
- this._dom.children.insertBefore(child.getDOM().node, next);
- this._children.splice(index, 0, child);
-
- return child.setParent(this);
-}
-
-MM.Item.prototype.removeChild = function(child) {
- var index = this._children.indexOf(child);
- this._children.splice(index, 1);
- var node = child.getDOM().node;
- node.parentNode.removeChild(node);
-
- child.setParent(null);
-
- if (!this._children.length) {
- this._dom.toggle.parentNode.removeChild(this._dom.toggle);
- this._dom.children.parentNode.removeChild(this._dom.children);
- }
-
- return this.update();
-}
-
-MM.Item.prototype.startEditing = function() {
- this._oldText = this.getText();
- this._dom.text.contentEditable = true;
- this._dom.text.focus(); /* switch to 2b */
- document.execCommand("styleWithCSS", null, false);
-
- this._dom.text.addEventListener("input", this);
- this._dom.text.addEventListener("keydown", this);
- this._dom.text.addEventListener("blur", this);
- return this;
-}
-
-MM.Item.prototype.stopEditing = function() {
- this._dom.text.removeEventListener("input", this);
- this._dom.text.removeEventListener("keydown", this);
- this._dom.text.removeEventListener("blur", this);
-
- this._dom.text.blur();
- this._dom.text.contentEditable = false;
- var result = this._dom.text.innerHTML;
- this._dom.text.innerHTML = this._oldText;
- this._oldText = "";
-
- this.update(); /* text changed */
-
- MM.Clipboard.focus();
-
- return result;
-}
-
-MM.Item.prototype.handleEvent = function(e) {
- switch (e.type) {
- case "input":
- this.update();
- this.getMap().ensureItemVisibility(this);
- break;
-
- case "keydown":
- if (e.keyCode == 9) { e.preventDefault(); } /* TAB has a special meaning in this app, do not use it to change focus */
- break;
-
- case "blur": /* 3d */
- MM.Command.Finish.execute();
- break;
-
- case "click":
- if (this._collapsed) { this.expand(); } else { this.collapse(); }
- MM.App.select(this);
- break;
- }
-}
-
-MM.Item.prototype._getAutoShape = function() {
- var depth = 0;
- var node = this;
- while (!node.isRoot()) {
- depth++;
- node = node.getParent();
- }
- switch (depth) {
- case 0: return MM.Shape.Ellipse;
- case 1: return MM.Shape.Box;
- default: return MM.Shape.Underline;
- }
-}
-
-MM.Item.prototype._updateStatus = function() {
- this._dom.status.className = "status";
- this._dom.status.style.display = "";
-
- var status = this._status;
- if (this._status == "computed") {
- var childrenStatus = this._children.every(function(child) {
- return (child.getComputedStatus() !== false);
- });
- status = (childrenStatus ? "yes" : "no");
- }
-
- switch (status) {
- case "yes":
- this._dom.status.classList.add("yes");
- this._computed.status = true;
- break;
-
- case "no":
- this._dom.status.classList.add("no");
- this._computed.status = false;
- break;
-
- default:
- this._computed.status = null;
- this._dom.status.style.display = "none";
- break;
- }
-}
-MM.Item.prototype._updateIcon = function() {
- this._dom.icon.className = "icon";
- this._dom.icon.style.display = "";
-
- var icon = this._icon;
- if (icon)
- {
- this._dom.icon.classList.add('fa');
- this._dom.icon.classList.add(icon);
- this._computed.icon = true;
- } else {
- this._computed.icon = null;
- this._dom.icon.style.display = "none";
- }
-}
-
-MM.Item.prototype._updateNotesIndicator = function() {
- if (this._notes)
- {
- this._dom.notes.classList.add("notes-indicator-visible");
- } else {
- this._dom.notes.classList.remove("notes-indicator-visible");
- }
-}
-
-MM.Item.prototype._updateValue = function() {
- this._dom.value.style.display = "";
-
- if (typeof(this._value) == "number") {
- this._computed.value = this._value;
- this._dom.value.innerHTML = this._value;
- return;
- }
-
- var childValues = this._children.map(function(child) {
- return child.getComputedValue();
- });
-
- var result = 0;
- switch (this._value) {
- case "sum":
- result = childValues.reduce(function(prev, cur) {
- return prev+cur;
- }, 0);
- break;
-
- case "avg":
- var sum = childValues.reduce(function(prev, cur) {
- return prev+cur;
- }, 0);
- result = (childValues.length ? sum/childValues.length : 0);
- break;
-
- case "max":
- result = Math.max.apply(Math, childValues);
- break;
-
- case "min":
- result = Math.min.apply(Math, childValues);
- break;
-
- default:
- this._computed.value = 0;
- this._dom.value.innerHTML = "";
- this._dom.value.style.display = "none";
- return;
- break;
- }
-
- this._computed.value = result;
- this._dom.value.innerHTML = (Math.round(result) == result ? result : result.toFixed(3));
-}
-
-MM.Item.prototype._findLinks = function(node) {
-
- var children = [].slice.call(node.childNodes);
- for (var i=0;i\(\)\[\]'"])?($|\b)/i;
+
+const UPDATE_OPTIONS = {
+ parent: true,
+ children: false
+}
+
+export default class Item {
+ // these have getters/setters
+ protected _id = generateId();
+ protected _parent: Item | null = null;
+ protected _collapsed = false;
+
+ dom = {
+ node: html.node("li"),
+ content: html.node("div"),
+ notes: html.node("div"),
+ status: html.node("span"),
+ icon: html.node("span"),
+ value: html.node("span"),
+ text: html.node("div"),
+ children: html.node("ul"),
+ toggle: html.node("div"),
+ canvas: html.node("canvas")
+ }
+
+ readonly children: Item[] = [];
+
+ // todo
+ protected _layout = null;
+ protected _shape = null;
+ protected _autoShape = true;
+ protected _color = null;
+ protected _value = null;
+ protected _status = null;
+ protected _side = null; /* side preference */
+ protected _icon = null;
+ protected _notes = null;
+ protected _oldText = "";
+ protected _computed = {
+ value: 0,
+ status: null
+ }
+
+ static fromJSON(data) {
+ return new this().fromJSON(data);
+ }
+
+ constructor() {
+ const { dom } = this;
+ dom.node.classList.add("item");
+ dom.content.classList.add("content");
+ dom.notes.classList.add("notes-indicator");
+ dom.status.classList.add("status");
+ dom.icon.classList.add("icon");
+ dom.value.classList.add("value");
+ dom.text.classList.add("text");
+ dom.toggle.classList.add("toggle");
+ dom.children.classList.add("children");
+
+ dom.node.append(dom.canvas, dom.content);
+ dom.content.append(dom.text, dom.notes); /* status+value are appended in layout */
+ /* toggle+children are appended when children exist */
+
+ dom.toggle.addEventListener("click", this);
+ }
+
+ get id() { return this._id; }
+
+ get parent() { return this._parent; }
+ set parent(parent: Item | null) {
+ this._parent = parent;
+ this.update({children:true});
+ }
+
+ get metrics() { // FIXME bud tohle, nebo size/position
+ const { dom } = this, { node } = dom;
+ return {
+ left: node.offsetLeft,
+ top: node.offsetTop,
+ width: node.offsetWidth,
+ height: node.offsetHeight,
+ }
+ }
+
+ get size() {
+ const { dom } = this;
+ return [dom.node.offsetWidth, dom.node.offsetHeight];
+ }
+ set size(size: number[]) {
+ const { dom } = this;
+ dom.node.style.width = `${size[0]}px`;
+ dom.node.style.height = `${size[1]}px`;
+
+ dom.canvas.width = size[0];
+ dom.canvas.height = size[1];
+ }
+
+ get position() {
+ const { dom } = this;
+ return [dom.node.offsetLeft, dom.node.offsetTop];
+ }
+ set position(position: number[]) {
+ const { dom } = this;
+ dom.node.style.left = `${position[0]}px`;
+ dom.node.style.top = `${position[1]}px`;
+ }
+
+ toJSON() {
+ let data: Record = {
+ id: this.id,
+ text: this.getText(),
+ notes: this.getNotes()
+ }
+
+ if (this._side) { data.side = this._side; }
+ if (this._color) { data.color = this._color; }
+ if (this._icon) { data.icon = this._icon; }
+ if (this._value) { data.value = this._value; }
+ if (this._status) { data.status = this._status; }
+ if (this._layout) { data.layout = this._layout.id; }
+ if (!this._autoShape) { data.shape = this._shape.id; }
+ if (this._collapsed) { data.collapsed = 1; }
+ if (this.children.length) {
+ data.children = this.children.map(child => child.toJSON());
+ }
+
+ return data;
+ }
+
+ /**
+ * Only when creating a new item. To merge existing items, use .mergeWith().
+ */
+ fromJSON(data) {
+ this.setText(data.text);
+ if (data.notes) {
+ this.setNotes(data.notes);
+ }
+ if (data.id) { this._id = data.id; }
+ if (data.side) { this._side = data.side; }
+ if (data.color) { this._color = data.color; }
+ if (data.icon) { this._icon = data.icon; }
+ if (data.value) { this._value = data.value; }
+ if (data.status) {
+ this._status = data.status;
+ if (this._status == "maybe") { this._status = "computed"; }
+ }
+ if (data.collapsed) { this.collapse(); }
+ if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
+ if (data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
+
+ (data.children || []).forEach(function(child) {
+ this.insertChild(Item.fromJSON(child));
+ }, this);
+
+ return this;
+ }
+
+ mergeWith(data) {
+ var dirty = 0;
+
+ if (this.getText() != data.text && !this.dom.text.contentEditable) { this.setText(data.text); }
+
+ if (this._side != data.side) {
+ this._side = data.side;
+ dirty = 1;
+ }
+
+ if (this._color != data.color) {
+ this._color = data.color;
+ dirty = 2;
+ }
+
+ if (this._icon != data.icon) {
+ this._icon = data.icon;
+ dirty = 1;
+ }
+
+ if (this._value != data.value) {
+ this._value = data.value;
+ dirty = 1;
+ }
+
+ if (this._status != data.status) {
+ this._status = data.status;
+ dirty = 1;
+ }
+
+ if (this._collapsed != !!data.collapsed) { this[this._collapsed ? "expand" : "collapse"](); }
+
+ if (this.getOwnLayout() != data.layout) {
+ this._layout = MM.Layout.getById(data.layout);
+ dirty = 2;
+ }
+
+ var s = (this._autoShape ? null : this._shape.id);
+ if (s != data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
+
+ (data.children || []).forEach(function(child, index) {
+ if (index >= this.children.length) { /* new child */
+ this.insertChild(Item.fromJSON(child));
+ } else { /* existing child */
+ var myChild = this.children[index];
+ if (myChild.id == child.id) { /* recursive merge */
+ myChild.mergeWith(child);
+ } else { /* changed; replace */
+ this.removeChild(this.children[index]);
+ this.insertChild(Item.fromJSON(child), index);
+ }
+ }
+ }, this);
+
+ /* remove dead children */
+ var newLength = (data.children || []).length;
+ while (this.children.length > newLength) { this.removeChild(this.children[this.children.length-1]); }
+
+ if (dirty == 1) { this.update({children:false}); }
+ if (dirty == 2) { this.update({children:true}); }
+ }
+
+ clone() {
+ var data = this.toJSON();
+
+ var removeId = function(obj) {
+ delete obj.id;
+ obj.children && obj.children.forEach(removeId);
+ }
+ removeId(data);
+
+ return Item.fromJSON(data);
+ }
+
+ select() {
+ this.dom.node.classList.add("current");
+ if (window.editor) {
+ if (this._notes) {
+ window.editor.setContent(this._notes);
+ } else {
+ window.editor.setContent('');
+ }
+ }
+ this.map.ensureItemVisibility(this);
+ MM.Clipboard.focus(); /* going to mode 2c */
+ pubsub.publish("item-select", this);
+ }
+
+ deselect() {
+ /* we were in 2b; finish that via 3b */
+ if (MM.App.editing) { MM.Command.Finish.execute(); }
+ this.dom.node.classList.remove("current");
+ }
+
+ /*
+ * This item changed in some way (typically one of its attributes has been changed).
+ * We need to re-render its immediate DOM and also prehaps recurse upwards/downwards.
+ *
+ * Nothing happens if not part of a map (or the map is not visible).
+ */
+ update(options: Partial = {}) {
+ options = Object.assign({}, UPDATE_OPTIONS, options);
+
+ var map = this.map;
+ if (!map || !map.isVisible()) { return; }
+
+ if (options.children) { // recurse downwards?
+ let childUpdateOptions = { parent: false, children: true };
+ this.children.forEach(child => child.update(childUpdateOptions));
+ }
+
+ pubsub.publish("item-change", this);
+
+ if (this._autoShape) { /* check for changed auto-shape */
+ var autoShape = this._getAutoShape();
+ if (autoShape != this._shape) {
+ if (this._shape) { this._shape.unset(this); }
+ this._shape = autoShape;
+ this._shape.set(this);
+ }
+ }
+
+ this._updateStatus();
+ this._updateIcon();
+ this._updateNotesIndicator();
+ this._updateValue();
+
+ this.dom.node.classList.toggle("collapsed", this._collapsed);
+
+ this.getLayout().update(this);
+ this.getShape().update(this);
+
+ // recurse upwards?
+ if (options.parent && !this.isRoot()) { this.parent.update(); }
+ }
+
+ setText(text) {
+ this.dom.text.innerHTML = text;
+ findLinks(this.dom.text);
+ return this.update();
+ }
+
+ setNotes(notes) {
+ this._notes = notes;
+ return this.update();
+ }
+
+ getText() {
+ return this.dom.text.innerHTML;
+ }
+
+ getNotes() {
+ return this._notes;
+ }
+
+ collapse() {
+ if (this._collapsed) { return; }
+ this._collapsed = true;
+ return this.update();
+ }
+
+ expand() {
+ if (!this._collapsed) { return; }
+ this._collapsed = false;
+ this.update();
+ return this.update({children:true});
+ }
+
+ isCollapsed() {
+ return this._collapsed;
+ }
+
+ setValue(value) {
+ this._value = value;
+ return this.update();
+ }
+
+ getValue() {
+ return this._value;
+ }
+
+ getComputedValue() {
+ return this._computed.value;
+ }
+
+ setStatus(status) {
+ this._status = status;
+ return this.update();
+ }
+
+ getStatus() {
+ return this._status;
+ }
+
+ setIcon(icon) {
+ this._icon = icon;
+ return this.update();
+ }
+
+ getIcon() {
+ return this._icon;
+ }
+
+ getComputedStatus() {
+ return this._computed.status;
+ }
+
+ setSide(side) {
+ this._side = side;
+ return this;
+ }
+
+ getSide() {
+ return this._side;
+ }
+
+ setColor(color) {
+ this._color = color;
+ return this.update({children:true});
+ }
+
+ getColor() {
+ return this._color || (this.isRoot() ? COLOR : this.parent.getColor());
+ }
+
+ getOwnColor() {
+ return this._color;
+ }
+
+ getLayout() {
+ return this._layout || this.parent.getLayout();
+ }
+
+ getOwnLayout() {
+ return this._layout;
+ }
+
+ setLayout(layout) {
+ this._layout = layout;
+ return this.update({children:true});
+ }
+
+ getShape() {
+ return this._shape;
+ }
+
+ getOwnShape() {
+ return (this._autoShape ? null : this._shape);
+ }
+
+ setShape(shape) {
+ if (this._shape) { this._shape.unset(this); }
+
+ if (shape) {
+ this._autoShape = false;
+ this._shape = shape;
+ } else {
+ this._autoShape = true;
+ this._shape = this._getAutoShape();
+ }
+
+ this._shape.set(this);
+ return this.update();
+ }
+
+ get map() {
+ let item = this.parent;
+ while (item) {
+ if (item instanceof MM.Map) { return item; }
+ item = item.parent;
+ }
+ return null;
+ }
+
+ isRoot() {
+ return (this.parent instanceof MM.Map);
+ }
+
+ insertChild(child, index) {
+ /* Create or remove child as necessary. This must be done before computing the index (inserting own child) */
+ var newChild = false;
+ if (!child) {
+ child = new Item();
+ newChild = true;
+ } else if (child.parent && child.parent.removeChild) { /* only when the child has non-map parent */
+ child.parent.removeChild(child);
+ }
+
+ if (!this.children.length) {
+ this.dom.node.appendChild(this.dom.toggle);
+ this.dom.node.appendChild(this.dom.children);
+ }
+
+ if (arguments.length < 2) { index = this.children.length; }
+
+ var next = null;
+ if (index < this.children.length) { next = this.children[index].dom.node; }
+ this.dom.children.insertBefore(child.dom.node, next);
+ this.children.splice(index, 0, child);
+
+ child.parent = this;
+ }
+
+ removeChild(child: Item) {
+ var index = this.children.indexOf(child);
+ this.children.splice(index, 1);
+ var node = child.dom.node;
+ node.parentNode.removeChild(node);
+
+ child.parent = null;
+
+ if (!this.children.length) {
+ this.dom.toggle.parentNode.removeChild(this.dom.toggle);
+ this.dom.children.parentNode.removeChild(this.dom.children);
+ }
+
+ this.update();
+ }
+
+ startEditing() {
+ this._oldText = this.getText();
+ this.dom.text.contentEditable = "true";
+ this.dom.text.focus(); /* switch to 2b */
+ document.execCommand("styleWithCSS", null, "false");
+
+ this.dom.text.addEventListener("input", this);
+ this.dom.text.addEventListener("keydown", this);
+ this.dom.text.addEventListener("blur", this);
+ }
+
+ stopEditing() {
+ this.dom.text.removeEventListener("input", this);
+ this.dom.text.removeEventListener("keydown", this);
+ this.dom.text.removeEventListener("blur", this);
+
+ this.dom.text.blur();
+ this.dom.text.contentEditable = "false";
+ var result = this.dom.text.innerHTML;
+ this.dom.text.innerHTML = this._oldText;
+ this._oldText = "";
+
+ this.update(); /* text changed */
+
+ MM.Clipboard.focus();
+
+ return result;
+ }
+
+ handleEvent(e) {
+ switch (e.type) {
+ case "input":
+ this.update();
+ this.map.ensureItemVisibility(this);
+ break;
+
+ case "keydown":
+ if (e.keyCode == 9) { e.preventDefault(); } /* TAB has a special meaning in this app, do not use it to change focus */
+ break;
+
+ case "blur": /* 3d */
+ MM.Command.Finish.execute();
+ break;
+
+ case "click":
+ if (this._collapsed) { this.expand(); } else { this.collapse(); }
+ MM.App.select(this);
+ break;
+ }
+ }
+
+ _getAutoShape() {
+ let depth = 0;
+ let node: Item | null = this;
+ while (!node.isRoot()) {
+ depth++;
+ node = node.parent;
+ }
+ switch (depth) {
+ case 0: return MM.Shape.Ellipse;
+ case 1: return MM.Shape.Box;
+ default: return MM.Shape.Underline;
+ }
+ }
+
+ _updateStatus() {
+ this.dom.status.className = "status";
+ this.dom.status.hidden = false;
+
+ var status = this._status;
+ if (this._status == "computed") {
+ var childrenStatus = this.children.every(function(child) {
+ return (child.getComputedStatus() !== false);
+ });
+ status = (childrenStatus ? "yes" : "no");
+ }
+
+ switch (status) {
+ case "yes":
+ this.dom.status.classList.add("yes");
+ this._computed.status = true;
+ break;
+
+ case "no":
+ this.dom.status.classList.add("no");
+ this._computed.status = false;
+ break;
+
+ default:
+ this._computed.status = null;
+ this.dom.status.hidden = true;
+ break;
+ }
+ }
+
+ _updateIcon() {
+ var icon = this._icon;
+
+ this.dom.icon.className = "icon";
+ this.dom.icon.hidden = !icon;
+
+ if (icon) {
+ this.dom.icon.classList.add('fa');
+ this.dom.icon.classList.add(icon);
+ }
+ }
+
+ _updateNotesIndicator() {
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
+ }
+
+ _updateValue() {
+ this.dom.value.hidden = false;
+
+ if (typeof(this._value) == "number") {
+ this._computed.value = this._value;
+ this.dom.value.textContent = String(this._value);
+ return;
+ }
+
+ var childValues = this.children.map(function(child) {
+ return child.getComputedValue();
+ });
+
+ var result = 0;
+ switch (this._value) {
+ case "sum":
+ result = childValues.reduce((prev, cur) => prev+cur, 0);
+ break;
+
+ case "avg":
+ var sum = childValues.reduce((prev, cur) => prev+cur, 0);
+ result = (childValues.length ? sum/childValues.length : 0);
+ break;
+
+ case "max":
+ result = Math.max(...childValues);
+ break;
+
+ case "min":
+ result = Math.min(...childValues);
+ break;
+
+ default:
+ this._computed.value = 0;
+ this.dom.value.innerHTML = "";
+ this.dom.value.hidden = true;
+ return;
+ break;
+ }
+ this._computed.value = result;
+ this.dom.value.innerHTML = String(Math.round(result) == result ? result : result.toFixed(3));
+ }
+}
+
+
+function findLinks(node) {
+ var children = [].slice.call(node.childNodes);
+ for (var i=0;i {
+ const { size, dom } = child;
- if (rankDirection == "left") { offset[0] = bbox[0] - childSize[0]; }
- if (rankDirection == "top") { offset[1] = bbox[1] - childSize[1]; }
+ if (rankDirection == "left") { offset[0] = bbox[0] - size[0]; }
+ if (rankDirection == "top") { offset[1] = bbox[1] - size[1]; }
- node.style[childPosProp] = offset[childIndex] + "px";
- node.style[rankPosProp] = offset[rankIndex] + "px";
+ dom.node.style[childPosProp] = offset[childIndex] + "px";
+ dom.node.style[rankPosProp] = offset[rankIndex] + "px";
- offset[childIndex] += childSize[childIndex] + this.SPACING_CHILD; /* offset for next child */
- }, this);
+ offset[childIndex] += size[childIndex] + this.SPACING_CHILD; /* offset for next child */
+ });
return bbox;
}
MM.Layout.Graph._drawLinesHorizontal = function(item, side) {
- this._anchorCanvas(item);
- this._drawHorizontalConnectors(item, side, item.getChildren());
+ this._drawHorizontalConnectors(item, side, item.children);
}
MM.Layout.Graph._drawLinesVertical = function(item, side) {
- this._anchorCanvas(item);
- this._drawVerticalConnectors(item, side, item.getChildren());
+ this._drawVerticalConnectors(item, side, item.children);
}
MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
if (children.length == 0) { return; }
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var ctx = canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
@@ -131,13 +125,14 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
} else {
var x1 = dom.content.offsetWidth + dom.content.offsetLeft + 0.5;
}
-
+
this._anchorToggle(item, x1, y1, side);
if (item.isCollapsed()) { return; }
if (children.length == 1) {
var child = children[0];
- var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ const { position } = child;
+ var y2 = child.getShape().getVerticalAnchor(child) + position[1];
var x2 = this._getChildAnchor(child, side);
ctx.beginPath();
ctx.moveTo(x1, y1);
@@ -163,8 +158,11 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
var x = x2;
var xx = x + (side == "left" ? -R : R);
- var y1 = c1.getShape().getVerticalAnchor(c1) + c1.getDOM().node.offsetTop;
- var y2 = c2.getShape().getVerticalAnchor(c2) + c2.getDOM().node.offsetTop;
+ let p1 = c1.position;
+ let p2 = c2.position;
+
+ var y1 = c1.getShape().getVerticalAnchor(c1) + p1[1];
+ var y2 = c2.getShape().getVerticalAnchor(c2) + p2[1];
var x1 = this._getChildAnchor(c1, side);
var x2 = this._getChildAnchor(c2, side);
@@ -178,7 +176,8 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
for (var i=1; i {
+ const { size } = child;
- bbox[rankIndex] = Math.max(bbox[rankIndex], childSize[rankIndex]); /* adjust cardinal size */
- bbox[childIndex] += childSize[childIndex]; /* adjust orthogonal size */
- }, this);
+ bbox[rankIndex] = Math.max(bbox[rankIndex], size[rankIndex]); /* adjust cardinal size */
+ bbox[childIndex] += size[childIndex]; /* adjust orthogonal size */
+ });
if (children.length > 1) { bbox[childIndex] += this.SPACING_CHILD * (children.length-1); } /* child separation */
@@ -132,7 +122,7 @@ MM.Layout._computeChildrenBBox = function(children, childIndex) {
}
MM.Layout._alignItem = function(item, side) {
- var dom = item.getDOM();
+ var dom = item.dom;
switch (side) {
case "left":
diff --git a/src/layout/layout.map.js b/src/layout/layout.map.js
index be0f97e0..87ff6229 100644
--- a/src/layout/layout.map.js
+++ b/src/layout/layout.map.js
@@ -19,8 +19,8 @@ MM.Layout.Map.update = function(item) {
* @param {MM.Item} child Child node
*/
MM.Layout.Map.getChildDirection = function(child) {
- while (!child.getParent().isRoot()) {
- child = child.getParent();
+ while (!child.parent.isRoot()) {
+ child = child.parent;
}
/* child is now the sub-root node */
@@ -28,7 +28,7 @@ MM.Layout.Map.getChildDirection = function(child) {
if (side) { return side; }
var counts = {left:0, right:0};
- var children = child.getParent().getChildren();
+ var children = child.parent.children;
for (var i=0;i {
var side = this.getChildDirection(child);
-
+
if (side == "left") {
childrenLeft.push(child);
} else {
childrenRight.push(child);
}
- }, this);
+ });
var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
var bboxRight = this._computeChildrenBBox(childrenRight, 1);
@@ -96,10 +95,8 @@ MM.Layout.Map._layoutRoot = function(item) {
left += bboxRight[0];
dom.content.style.top = Math.round((height - dom.content.offsetHeight)/2) + "px";
- dom.node.style.height = height + "px";
- dom.node.style.width = left + "px";
- this._anchorCanvas(item);
+ item.size = [left, height];
this._drawRootConnectors(item, "left", childrenLeft);
this._drawRootConnectors(item, "right", childrenRight);
}
@@ -107,7 +104,7 @@ MM.Layout.Map._layoutRoot = function(item) {
MM.Layout.Map._drawRootConnectors = function(item, side, children) {
if (children.length == 0 || item.isCollapsed()) { return; }
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var ctx = canvas.getContext("2d");
var R = this.SPACING_RANK/2;
@@ -120,7 +117,7 @@ MM.Layout.Map._drawRootConnectors = function(item, side, children) {
var child = children[i];
var x2 = this._getChildAnchor(child, side);
- var y2 = child.getShape().getVerticalAnchor(child) + child.getDOM().node.offsetTop;
+ var y2 = child.getShape().getVerticalAnchor(child) + child.position[1];
var angle = Math.atan2(y2-y1, x2-x1) + Math.PI/2;
var dx = Math.cos(angle) * half;
var dy = Math.sin(angle) * half;
diff --git a/src/layout/layout.tree.js b/src/layout/layout.tree.js
index 7fa74b26..b1e96434 100644
--- a/src/layout/layout.tree.js
+++ b/src/layout/layout.tree.js
@@ -20,12 +20,11 @@ MM.Layout.Tree.create = function(direction, id, label) {
MM.Layout.Tree.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.getParent().getLayout().getChildDirection(item);
+ side = item.parent.getLayout().getChildDirection(item);
}
this._alignItem(item, side);
this._layoutItem(item, this.childDirection);
- this._anchorCanvas(item);
this._drawLines(item, this.childDirection);
return this;
}
@@ -34,27 +33,26 @@ MM.Layout.Tree.update = function(item) {
* Generic graph child layout routine. Updates item's orthogonal size according to the sum of its children.
*/
MM.Layout.Tree._layoutItem = function(item, rankDirection) {
- var dom = item.getDOM();
+ var dom = item.dom;
/* content size */
var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
/* children size */
- var bbox = this._computeChildrenBBox(item.getChildren(), 1);
+ var bbox = this._computeChildrenBBox(item.children, 1);
/* node size */
var rankSize = contentSize[0];
var childSize = bbox[1] + contentSize[1];
- if (bbox[0]) {
- rankSize = Math.max(rankSize, bbox[0] + this.SPACING_RANK);
+ if (bbox[0]) {
+ rankSize = Math.max(rankSize, bbox[0] + this.SPACING_RANK);
childSize += this.SPACING_CHILD;
}
- dom.node.style.width = rankSize + "px";
- dom.node.style.height = childSize + "px";
+ item.size = [rankSize, childSize];
var offset = [this.SPACING_RANK, contentSize[1]+this.SPACING_CHILD];
if (rankDirection == "left") { offset[0] = rankSize - bbox[0] - this.SPACING_RANK; }
- this._layoutChildren(item.getChildren(), rankDirection, offset, bbox);
+ this._layoutChildren(item.children, rankDirection, offset, bbox);
/* label position */
var labelPos = 0;
@@ -66,30 +64,30 @@ MM.Layout.Tree._layoutItem = function(item, rankDirection) {
}
MM.Layout.Tree._layoutChildren = function(children, rankDirection, offset, bbox) {
- children.forEach(function(child, index) {
- var node = child.getDOM().node;
- var childSize = [node.offsetWidth, node.offsetHeight];
+ children.forEach(child => {
+ const { size } = child;
+
var left = offset[0];
- if (rankDirection == "left") { left += (bbox[0] - childSize[0]); }
+ if (rankDirection == "left") { left += (bbox[0] - size[0]); }
- node.style.left = left + "px";
- node.style.top = offset[1] + "px";
+ child.position = [left, offset[1]];
- offset[1] += childSize[1] + this.SPACING_CHILD; /* offset for next child */
- }, this);
+ offset[1] += size[1] + this.SPACING_CHILD; /* offset for next child */
+ });
return bbox;
}
MM.Layout.Tree._drawLines = function(item, side) {
- var dom = item.getDOM();
+ var dom = item.dom;
var canvas = dom.canvas;
var R = this.SPACING_RANK/4;
+ // FIXME canvas.width nahradit za item.size[0] ?
var x = (side == "left" ? canvas.width - 2*R : 2*R) + 0.5;
this._anchorToggle(item, x, dom.content.offsetHeight, "bottom");
- var children = item.getChildren();
+ var children = item.children;
if (children.length == 0 || item.isCollapsed()) { return; }
var ctx = canvas.getContext("2d");
@@ -97,7 +95,7 @@ MM.Layout.Tree._drawLines = function(item, side) {
var y1 = item.getShape().getVerticalAnchor(item);
var last = children[children.length-1];
- var y2 = last.getShape().getVerticalAnchor(last) + last.getDOM().node.offsetTop;
+ var y2 = last.getShape().getVerticalAnchor(last) + last.position[1];
ctx.beginPath();
ctx.moveTo(x, y1);
@@ -106,7 +104,7 @@ MM.Layout.Tree._drawLines = function(item, side) {
/* rounded connectors */
for (var i=0; i -1) { this._subscribers[message].splice(index, 1); }
- },
-
- generateId: function() {
- var str = "";
- for (var i=0;i<8;i++) {
- var code = Math.floor(Math.random()*26);
- str += String.fromCharCode("a".charCodeAt(0) + code);
- }
- return str;
- },
-
- isMac: function() {
- return !!navigator.platform.match(/mac/i);
- }
-};
diff --git a/src/mm.ts b/src/mm.ts
new file mode 100644
index 00000000..939984b8
--- /dev/null
+++ b/src/mm.ts
@@ -0,0 +1 @@
+(window as any).MM = {};
diff --git a/src/mouse.js b/src/mouse.js
index c33fc4af..30efad80 100644
--- a/src/mouse.js
+++ b/src/mouse.js
@@ -172,7 +172,7 @@ MM.Mouse._endDrag = function() {
}
MM.Mouse._buildGhost = function() {
- var content = this._item.getDOM().content;
+ var content = this._item.dom.content;
this._ghost = content.cloneNode(true);
this._ghost.classList.add("ghost");
this._pos[0] = content.offsetLeft;
@@ -199,9 +199,9 @@ MM.Mouse._finishDragDrop = function(state) {
break;
case "sibling":
- var index = target.getParent().getChildren().indexOf(target);
+ var index = target.parent.children.indexOf(target);
var targetIndex = index + (state.direction == "right" || state.direction == "bottom" ? 1 : 0);
- var action = new MM.Action.MoveItem(this._item, target.getParent(), targetIndex, target.getSide());
+ var action = new MM.Action.MoveItem(this._item, target.parent, targetIndex, target.getSide());
break;
default:
@@ -229,14 +229,14 @@ MM.Mouse._computeDragState = function() {
var tmp = target;
while (!tmp.isRoot()) {
if (tmp == this._item) { return state; } /* drop on a child or self */
- tmp = tmp.getParent();
+ tmp = tmp.parent;
}
- var w1 = this._item.getDOM().content.offsetWidth;
- var w2 = target.getDOM().content.offsetWidth;
+ var w1 = this._item.dom.content.offsetWidth;
+ var w2 = target.dom.content.offsetWidth;
var w = Math.max(w1, w2);
- var h1 = this._item.getDOM().content.offsetHeight;
- var h2 = target.getDOM().content.offsetHeight;
+ var h1 = this._item.dom.content.offsetHeight;
+ var h2 = target.dom.content.offsetHeight;
var h = Math.max(h1, h2);
if (target.isRoot()) { /* append here */
@@ -245,7 +245,7 @@ MM.Mouse._computeDragState = function() {
state.result = "append";
} else {
state.result = "sibling";
- var childDirection = target.getParent().getLayout().getChildDirection(target);
+ var childDirection = target.parent.getLayout().getChildDirection(target);
var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
if (childDirection == "left" || childDirection == "right") {
@@ -263,7 +263,7 @@ MM.Mouse._visualizeDragState = function(state) {
if (this._oldDragState) { /* remove old vis */
var item = this._oldDragState.item;
- var node = item.getDOM().content;
+ var node = item.dom.content;
node.style.boxShadow = "";
}
@@ -271,7 +271,7 @@ MM.Mouse._visualizeDragState = function(state) {
if (state) { /* show new vis */
var item = state.item;
- var node = item.getDOM().content;
+ var node = item.dom.content;
var x = 0;
var y = 0;
diff --git a/src/my-mind.js b/src/my-mind.js
index 59f69fd3..c877d0ba 100644
--- a/src/my-mind.js
+++ b/src/my-mind.js
@@ -52,6 +52,8 @@ import "./ui/backend/ui.backend.firebase.js";
import "./ui/backend/ui.backend.gdrive.js";
import "./mouse.js";
+import * as pubsub from "./pubsub.js";
+
/*
setInterval(function() {
@@ -149,7 +151,7 @@ MM.App = {
break;
case "item-change":
- if (publisher.isRoot() && publisher.getMap() == this.map) {
+ if (publisher.isRoot() && publisher.map == this.map) {
document.title = this.map.getName() + " :: My Mind";
}
break;
@@ -213,8 +215,8 @@ MM.App = {
window.addEventListener("beforeunload", this);
window.addEventListener("keyup", this);
window.addEventListener("message", this, false);
- MM.subscribe("ui-change", this);
- MM.subscribe("item-change", this);
+ pubsub.subscribe("ui-change", this);
+ pubsub.subscribe("item-change", this);
this._syncPort();
this.setMap(new MM.Map());
diff --git a/src/pubsub.ts b/src/pubsub.ts
new file mode 100644
index 00000000..55233da8
--- /dev/null
+++ b/src/pubsub.ts
@@ -0,0 +1,26 @@
+interface Subscriber {
+ handleMessage(message: string, publisher: any, data?: any): void;
+}
+let subscribers = new Map();
+
+export function publish(message: string, publisher: any, data?: any) {
+ let subs = subscribers.get(message) || [];
+ subs.forEach(function(subscriber) {
+ subscriber.handleMessage(message, publisher, data);
+ });
+}
+
+export function subscribe(message: string, subscriber: Subscriber) {
+ if (!subscribers.has(message)) {
+ subscribers.set(message, []);
+ }
+ let subs = subscribers.get(message) || [];
+ let index = subs.indexOf(subscriber);
+ if (index == -1) { subs.push(subscriber); }
+}
+
+export function unsubscribe(message: string, subscriber: Subscriber) {
+ let subs = subscribers.get(message) || [];
+ let index = subs.indexOf(subscriber);
+ if (index > -1) { subs.splice(index, 1); }
+}
diff --git a/src/repo.js b/src/repo.js
index 7fbbe54e..9508cfb8 100644
--- a/src/repo.js
+++ b/src/repo.js
@@ -13,7 +13,7 @@ MM.Repo = {
return all;
},
getByProperty: function(property, value) {
- return this.getAll().filter(function(item) {
+ return this.getAll().filter(item => {
return item[property] == value;
})[0] || null;
},
diff --git a/src/shape/shape.js b/src/shape/shape.js
index db6d2dd4..cb251632 100644
--- a/src/shape/shape.js
+++ b/src/shape/shape.js
@@ -3,26 +3,26 @@ MM.Shape = Object.create(MM.Repo, {
});
MM.Shape.set = function(item) {
- item.getDOM().node.classList.add("shape-"+this.id);
+ item.dom.node.classList.add("shape-"+this.id);
return this;
}
MM.Shape.unset = function(item) {
- item.getDOM().node.classList.remove("shape-"+this.id);
+ item.dom.node.classList.remove("shape-"+this.id);
return this;
}
MM.Shape.update = function(item) {
- item.getDOM().content.style.borderColor = item.getColor();
+ item.dom.content.style.borderColor = item.getColor();
return this;
}
MM.Shape.getHorizontalAnchor = function(item) {
- var node = item.getDOM().content;
+ var node = item.dom.content;
return Math.round(node.offsetLeft + node.offsetWidth/2) + 0.5;
}
MM.Shape.getVerticalAnchor = function(item) {
- var node = item.getDOM().content;
+ var node = item.dom.content;
return node.offsetTop + Math.round(node.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
}
diff --git a/src/shape/shape.underline.js b/src/shape/shape.underline.js
index 19df00b5..5b9d9f0f 100644
--- a/src/shape/shape.underline.js
+++ b/src/shape/shape.underline.js
@@ -5,7 +5,7 @@ MM.Shape.Underline = Object.create(MM.Shape, {
});
MM.Shape.Underline.update = function(item) {
- var dom = item.getDOM();
+ var dom = item.dom;
var ctx = dom.canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
@@ -22,6 +22,6 @@ MM.Shape.Underline.update = function(item) {
}
MM.Shape.Underline.getVerticalAnchor = function(item) {
- var node = item.getDOM().content;
+ var node = item.dom.content;
return node.offsetTop + node.offsetHeight + this.VERTICAL_OFFSET + 0.5;
}
diff --git a/src/svg.ts b/src/svg.ts
new file mode 100644
index 00000000..c3375511
--- /dev/null
+++ b/src/svg.ts
@@ -0,0 +1,6 @@
+const NS = "http://www.w3.org/2000/svg";
+
+export function node(name: T): SVGElementTagNameMap[T] {
+ let node = document.createElementNS(NS, name);
+ return node;
+}
diff --git a/src/tip.js b/src/tip.js
index 9220f59e..ebc40675 100644
--- a/src/tip.js
+++ b/src/tip.js
@@ -1,3 +1,6 @@
+import * as pubsub from "./pubsub.js";
+
+
MM.Tip = {
_node: null,
@@ -13,13 +16,13 @@ MM.Tip = {
this._node = document.querySelector("#tip");
this._node.addEventListener("click", this);
- MM.subscribe("command-child", this);
- MM.subscribe("command-sibling", this);
+ pubsub.subscribe("command-child", this);
+ pubsub.subscribe("command-sibling", this);
},
_hide: function() {
- MM.unsubscribe("command-child", this);
- MM.unsubscribe("command-sibling", this);
+ pubsub.unsubscribe("command-child", this);
+ pubsub.unsubscribe("command-sibling", this);
this._node.removeEventListener("click", this);
this._node.classList.add("hidden");
diff --git a/tsconfig.json b/src/tsconfig.json
similarity index 66%
rename from tsconfig.json
rename to src/tsconfig.json
index 2e8725aa..a11df330 100644
--- a/tsconfig.json
+++ b/src/tsconfig.json
@@ -1,8 +1,9 @@
{
- "include": ["src"],
+ "include": ["."],
"compilerOptions": {
- "outDir": ".js",
+ "outDir": "../.js",
"target": "es2019",
+ "strict": false,
"allowJs": true,
"noEmitOnError": true,
"incremental": true,
diff --git a/src/ui/backend/ui.backend.firebase.js b/src/ui/backend/ui.backend.firebase.js
index 9817d36f..b686b291 100644
--- a/src/ui/backend/ui.backend.firebase.js
+++ b/src/ui/backend/ui.backend.firebase.js
@@ -1,10 +1,13 @@
+import * as pubsub from "../../pubsub.js";
+
+
MM.UI.Backend.Firebase = Object.create(MM.UI.Backend, {
id: {value: "firebase"}
});
MM.UI.Backend.Firebase.init = function(select) {
MM.UI.Backend.init.call(this, select);
-
+
this._online = false;
this._itemChangeTimeout = null;
this._list = this._node.querySelector(".list");
@@ -18,8 +21,8 @@ MM.UI.Backend.Firebase.init = function(select) {
this._remove.addEventListener("click", this);
this._go.disabled = false;
- MM.subscribe("firebase-list", this);
- MM.subscribe("firebase-change", this);
+ pubsub.subscribe("firebase-list", this);
+ pubsub.subscribe("firebase-change", this);
}
MM.UI.Backend.Firebase.setState = function(data) {
@@ -76,9 +79,9 @@ MM.UI.Backend.Firebase.handleMessage = function(message, publisher, data) {
case "firebase-change":
if (data) {
- MM.unsubscribe("item-change", this);
+ pubsub.unsubscribe("item-change", this);
MM.App.map.mergeWith(data);
- MM.subscribe("item-change", this);
+ pubsub.subscribe("item-change", this);
} else { /* FIXME */
console.log("remote data disappeared");
}
@@ -93,7 +96,7 @@ MM.UI.Backend.Firebase.handleMessage = function(message, publisher, data) {
MM.UI.Backend.Firebase.reset = function() {
this._backend.reset();
- MM.unsubscribe("item-change", this);
+ pubsub.unsubscribe("item-change", this);
}
MM.UI.Backend.Firebase._itemChange = function() {
@@ -106,7 +109,7 @@ MM.UI.Backend.Firebase._action = function() {
this._connect(this._server.value, this._auth.value);
return;
}
-
+
MM.UI.Backend._action.call(this);
}
@@ -176,11 +179,11 @@ MM.UI.Backend.Firebase._sync = function() {
}
MM.UI.Backend.Firebase._loadDone = function() {
- MM.subscribe("item-change", this);
+ pubsub.subscribe("item-change", this);
MM.UI.Backend._loadDone.apply(this, arguments);
}
MM.UI.Backend.Firebase._saveDone = function() {
- MM.subscribe("item-change", this);
+ pubsub.subscribe("item-change", this);
MM.UI.Backend._saveDone.apply(this, arguments);
}
diff --git a/src/ui/backend/ui.backend.js b/src/ui/backend/ui.backend.js
index e1e5465d..3bafdb16 100644
--- a/src/ui/backend/ui.backend.js
+++ b/src/ui/backend/ui.backend.js
@@ -1,3 +1,6 @@
+import * as pubsub from "../../pubsub.js";
+
+
MM.UI.Backend = Object.create(MM.Repo);
MM.UI.Backend.init = function(select) {
@@ -6,13 +9,13 @@ MM.UI.Backend.init = function(select) {
this._prefix = "mm.app." + this.id + ".";
this._node = document.querySelector("#" + this.id);
-
+
this._cancel = this._node.querySelector(".cancel");
this._cancel.addEventListener("click", this);
this._go = this._node.querySelector(".go");
this._go.addEventListener("click", this);
-
+
select.appendChild(this._backend.buildOption());
}
@@ -50,11 +53,8 @@ MM.UI.Backend.show = function(mode) {
this._go.innerHTML = mode.charAt(0).toUpperCase() + mode.substring(1);
- var all = this._node.querySelectorAll("[data-for]");
- [].concat.apply([], all).forEach(function(node) { node.style.display = "none"; });
-
- var visible = this._node.querySelectorAll("[data-for~=" + mode + "]");
- [].concat.apply([], visible).forEach(function(node) { node.style.display = ""; });
+ [...this._node.querySelectorAll("[data-for]")].forEach(node => node.hidden = true);
+ [...this._node.querySelectorAll(`[data-for~=${mode}]`)].forEach(node => node.hidden = false);
/* switch to 2a: steal focus from the current item */
this._go.focus();
@@ -65,7 +65,7 @@ MM.UI.Backend._action = function() {
case "save":
this.save();
break;
-
+
case "load":
this.load();
break;
@@ -74,15 +74,15 @@ MM.UI.Backend._action = function() {
MM.UI.Backend._saveDone = function() {
MM.App.setThrobber(false);
- MM.publish("save-done", this);
+ pubsub.publish("save-done", this);
}
MM.UI.Backend._loadDone = function(json) {
MM.App.setThrobber(false);
try {
MM.App.setMap(MM.Map.fromJSON(json));
- MM.publish("load-done", this);
- } catch (e) {
+ pubsub.publish("load-done", this);
+ } catch (e) {
this._error(e);
}
}
@@ -94,15 +94,15 @@ MM.UI.Backend._error = function(e) {
MM.UI.Backend._buildList = function(list, select) {
var data = [];
-
+
for (var id in list) {
data.push({id:id, name:list[id]});
}
-
+
data.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
-
+
data.forEach(function(item) {
var o = document.createElement("option");
o.value = item.id;
diff --git a/src/ui/ui.io.js b/src/ui/ui.io.js
index ed3fc78a..99b45658 100644
--- a/src/ui/ui.io.js
+++ b/src/ui/ui.io.js
@@ -1,3 +1,6 @@
+import * as pubsub from "../pubsub.js";
+
+
MM.UI.IO = function() {
this._prefix = "mm.app.";
this._mode = "";
@@ -16,10 +19,10 @@ MM.UI.IO = function() {
this._backend.value = localStorage.getItem(this._prefix + "backend") || MM.Backend.File.id;
this._backend.addEventListener("change", this);
-
- MM.subscribe("map-new", this);
- MM.subscribe("save-done", this);
- MM.subscribe("load-done", this);
+
+ pubsub.subscribe("map-new", this);
+ pubsub.subscribe("save-done", this);
+ pubsub.subscribe("load-done", this);
}
MM.UI.IO.prototype.restore = function() {
@@ -28,7 +31,7 @@ MM.UI.IO.prototype.restore = function() {
var keyvalue = item.split("=");
parts[decodeURIComponent(keyvalue[0])] = decodeURIComponent(keyvalue[1]);
});
-
+
/* backwards compatibility */
if ("map" in parts) { parts.url = parts.map; }
@@ -37,7 +40,7 @@ MM.UI.IO.prototype.restore = function() {
var backend = MM.UI.Backend.getById(parts.b);
if (backend) { /* saved backend info */
- backend.setState(parts);
+ backend.setState(parts);
return;
}
@@ -63,7 +66,7 @@ MM.UI.IO.prototype.handleMessage = function(message, publisher) {
case "map-new":
this._setCurrentBackend(null);
break;
-
+
case "save-done":
case "load-done":
this.hide();
@@ -76,7 +79,7 @@ MM.UI.IO.prototype.show = function(mode) {
this._mode = mode;
this._node.classList.add("visible");
this._heading.innerHTML = mode;
-
+
this._syncBackend();
window.addEventListener("keydown", this);
}
@@ -89,7 +92,7 @@ MM.UI.IO.prototype.hide = function() {
}
MM.UI.IO.prototype.quickSave = function() {
- if (this._currentBackend) {
+ if (this._currentBackend) {
this._currentBackend.save();
} else {
this.show("save");
@@ -101,7 +104,7 @@ MM.UI.IO.prototype.handleEvent = function(e) {
case "keydown":
if (e.keyCode == 27) { this.hide(); }
break;
-
+
case "change":
this._syncBackend();
break;
@@ -109,11 +112,8 @@ MM.UI.IO.prototype.handleEvent = function(e) {
}
MM.UI.IO.prototype._syncBackend = function() {
- var all = this._node.querySelectorAll("div[id]");
- [].slice.apply(all).forEach(function(node) { node.style.display = "none"; });
-
- this._node.querySelector("#" + this._backend.value).style.display = "";
-
+ [...this._node.querySelectorAll("div[id]")].forEach(node => node.hidden = true);
+ this._node.querySelector("#" + this._backend.value).hidden = false;
this._backends[this._backend.value].show(this._mode);
}
@@ -122,7 +122,7 @@ MM.UI.IO.prototype._syncBackend = function() {
*/
MM.UI.IO.prototype._setCurrentBackend = function(backend) {
if (this._currentBackend && this._currentBackend != backend) { this._currentBackend.reset(); }
-
+
if (backend) { localStorage.setItem(this._prefix + "backend", backend.id); }
this._currentBackend = backend;
try {
diff --git a/src/ui/ui.js b/src/ui/ui.js
index 6f11eef8..e7b2d5a1 100644
--- a/src/ui/ui.js
+++ b/src/ui/ui.js
@@ -1,6 +1,9 @@
+import * as pubsub from "../pubsub.js";
+
+
MM.UI = function() {
this._node = document.querySelector(".ui");
-
+
this._toggle = this._node.querySelector("#toggle");
this._layout = new MM.UI.Layout();
@@ -9,9 +12,9 @@ MM.UI = function() {
this._color = new MM.UI.Color();
this._value = new MM.UI.Value();
this._status = new MM.UI.Status();
-
- MM.subscribe("item-select", this);
- MM.subscribe("item-change", this);
+
+ pubsub.subscribe("item-select", this);
+ pubsub.subscribe("item-change", this);
this._node.addEventListener("click", this);
this._node.addEventListener("change", this);
@@ -40,7 +43,7 @@ MM.UI.prototype.handleEvent = function(e) {
this.toggle();
return;
}
-
+
var node = e.target;
while (node != document) {
var command = node.getAttribute("data-command");
@@ -60,7 +63,7 @@ MM.UI.prototype.handleEvent = function(e) {
MM.UI.prototype.toggle = function() {
this._node.classList.toggle("visible");
- MM.publish("ui-change", this);
+ pubsub.publish("ui-change", this);
}
From d58bf1176470632ccf13f4ea737aa31e517804b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sun, 17 Oct 2021 22:17:51 +0200
Subject: [PATCH 05/42] content size/position getters, ctx
---
my-mind.js | 211 +++++++++++++++++------------------
src/item.ts | 82 ++++++++------
src/layout/layout.graph.js | 54 ++++-----
src/layout/layout.js | 14 +--
src/layout/layout.map.js | 21 ++--
src/layout/layout.tree.js | 17 +--
src/map.js | 6 +-
src/mouse.js | 14 +--
src/shape/shape.js | 8 +-
src/shape/shape.underline.js | 11 +-
10 files changed, 213 insertions(+), 225 deletions(-)
diff --git a/my-mind.js b/my-mind.js
index 1744ad1b..540cef7f 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -290,6 +290,9 @@
this._parent = parent;
this.update({ children: true });
}
+ get ctx() {
+ return this.dom.canvas.getContext("2d");
+ }
get metrics() {
const { dom } = this, { node: node2 } = dom;
return {
@@ -300,24 +303,37 @@
};
}
get size() {
- const { dom } = this;
- return [dom.node.offsetWidth, dom.node.offsetHeight];
+ const { node: node2 } = this.dom;
+ return [node2.offsetWidth, node2.offsetHeight];
}
set size(size) {
- const { dom } = this;
- dom.node.style.width = `${size[0]}px`;
- dom.node.style.height = `${size[1]}px`;
- dom.canvas.width = size[0];
- dom.canvas.height = size[1];
+ const { node: node2, canvas } = this.dom;
+ node2.style.width = `${size[0]}px`;
+ node2.style.height = `${size[1]}px`;
+ canvas.width = size[0];
+ canvas.height = size[1];
}
get position() {
- const { dom } = this;
- return [dom.node.offsetLeft, dom.node.offsetTop];
+ const { node: node2 } = this.dom;
+ return [node2.offsetLeft, node2.offsetTop];
}
set position(position) {
- const { dom } = this;
- dom.node.style.left = `${position[0]}px`;
- dom.node.style.top = `${position[1]}px`;
+ const { node: node2 } = this.dom;
+ node2.style.left = `${position[0]}px`;
+ node2.style.top = `${position[1]}px`;
+ }
+ get contentSize() {
+ const { content } = this.dom;
+ return [content.offsetWidth, content.offsetHeight];
+ }
+ get contentPosition() {
+ const { content } = this.dom;
+ return [content.offsetLeft, content.offsetTop];
+ }
+ set contentPosition(position) {
+ const { content } = this.dom;
+ content.style.left = `${position[0]}px`;
+ content.style.top = `${position[1]}px`;
}
toJSON() {
let data = {
@@ -389,9 +405,9 @@
if (data.shape) {
this.setShape(MM.Shape.getById(data.shape));
}
- (data.children || []).forEach(function(child) {
+ (data.children || []).forEach((child) => {
this.insertChild(Item.fromJSON(child));
- }, this);
+ });
return this;
}
mergeWith(data) {
@@ -430,7 +446,7 @@
if (s != data.shape) {
this.setShape(MM.Shape.getById(data.shape));
}
- (data.children || []).forEach(function(child, index) {
+ (data.children || []).forEach((child, index) => {
if (index >= this.children.length) {
this.insertChild(Item.fromJSON(child));
} else {
@@ -442,7 +458,7 @@
this.insertChild(Item.fromJSON(child), index);
}
}
- }, this);
+ });
var newLength = (data.children || []).length;
while (this.children.length > newLength) {
this.removeChild(this.children[this.children.length - 1]);
@@ -505,7 +521,7 @@
}
this._updateStatus();
this._updateIcon();
- this._updateNotesIndicator();
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
this._updateValue();
this.dom.node.classList.toggle("collapsed", this._collapsed);
this.getLayout().update(this);
@@ -517,11 +533,11 @@
setText(text) {
this.dom.text.innerHTML = text;
findLinks(this.dom.text);
- return this.update();
+ this.update();
}
setNotes(notes) {
this._notes = notes;
- return this.update();
+ this.update();
}
getText() {
return this.dom.text.innerHTML;
@@ -534,7 +550,7 @@
return;
}
this._collapsed = true;
- return this.update();
+ this.update();
}
expand() {
if (!this._collapsed) {
@@ -542,14 +558,14 @@
}
this._collapsed = false;
this.update();
- return this.update({ children: true });
+ this.update({ children: true });
}
isCollapsed() {
return this._collapsed;
}
setValue(value) {
this._value = value;
- return this.update();
+ this.update();
}
getValue() {
return this._value;
@@ -559,14 +575,14 @@
}
setStatus(status) {
this._status = status;
- return this.update();
+ this.update();
}
getStatus() {
return this._status;
}
setIcon(icon) {
this._icon = icon;
- return this.update();
+ this.update();
}
getIcon() {
return this._icon;
@@ -583,7 +599,7 @@
}
setColor(color) {
this._color = color;
- return this.update({ children: true });
+ this.update({ children: true });
}
getColor() {
return this._color || (this.isRoot() ? COLOR : this.parent.getColor());
@@ -599,7 +615,7 @@
}
setLayout(layout) {
this._layout = layout;
- return this.update({ children: true });
+ this.update({ children: true });
}
getShape() {
return this._shape;
@@ -619,7 +635,7 @@
this._shape = this._getAutoShape();
}
this._shape.set(this);
- return this.update();
+ this.update();
}
get map() {
let item = this.parent;
@@ -635,10 +651,8 @@
return this.parent instanceof MM.Map;
}
insertChild(child, index) {
- var newChild = false;
if (!child) {
child = new Item();
- newChild = true;
} else if (child.parent && child.parent.removeChild) {
child.parent.removeChild(child);
}
@@ -765,9 +779,6 @@
this.dom.icon.classList.add(icon);
}
}
- _updateNotesIndicator() {
- this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
- }
_updateValue() {
this.dom.value.hidden = false;
if (typeof this._value == "number") {
@@ -924,8 +935,7 @@
return this;
};
MM.Map.prototype.show = function(where) {
- var node2 = this._root.dom.node;
- where.appendChild(node2);
+ where.appendChild(this._root.dom.node);
this._visible = true;
this._root.update({ parent: true, children: true });
this.center();
@@ -933,8 +943,7 @@
return this;
};
MM.Map.prototype.hide = function() {
- var node2 = this._root.dom.node;
- node2.parentNode.removeChild(node2);
+ this._root.dom.node.remove();
this._visible = false;
return this;
};
@@ -2088,16 +2097,16 @@
node2.style.top = Math.round(t) + "px";
};
MM.Layout._getChildAnchor = function(item, side) {
- let { position, dom } = item;
+ let { position, contentPosition, contentSize } = item;
if (side == "left" || side == "right") {
- var pos = position[0] + dom.content.offsetLeft;
+ var pos = position[0] + contentPosition[0];
if (side == "left") {
- pos += dom.content.offsetWidth;
+ pos += contentSize[0];
}
} else {
- var pos = position[1] + dom.content.offsetTop;
+ var pos = position[1] + contentPosition[1];
if (side == "top") {
- pos += dom.content.offsetHeight;
+ pos += contentSize[1];
}
}
return pos;
@@ -2163,20 +2172,20 @@
return this;
};
MM.Layout.Graph._layoutItem = function(item, rankDirection) {
- var posProps = ["left", "top"];
var rankIndex = rankDirection == "left" || rankDirection == "right" ? 0 : 1;
var childIndex = (rankIndex + 1) % 2;
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
- var dom = item.dom;
- var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ const { contentSize } = item;
var bbox = this._computeChildrenBBox(item.children, childIndex);
var rankSize = contentSize[rankIndex];
if (bbox[rankIndex]) {
rankSize += bbox[rankIndex] + this.SPACING_RANK;
}
var childSize = Math.max(bbox[childIndex], contentSize[childIndex]);
- item.size = rankIndex == 0 ? [rankSize, childSize] : [childSize, rankSize];
+ let size = [rankSize, childSize];
+ if (rankIndex == 1) {
+ size = size.reverse();
+ }
+ item.size = size;
var offset = [0, 0];
if (rankDirection == "right") {
offset[0] = contentSize[0] + this.SPACING_RANK;
@@ -2193,26 +2202,29 @@
if (rankDirection == "top") {
labelPos = rankSize - contentSize[1];
}
- dom.content.style[childPosProp] = Math.round((childSize - contentSize[childIndex]) / 2) + "px";
- dom.content.style[rankPosProp] = labelPos + "px";
+ let contentPosition = [Math.round((childSize - contentSize[childIndex]) / 2), labelPos];
+ if (rankIndex == 0) {
+ contentPosition = contentPosition.reverse();
+ }
+ item.contentPosition = contentPosition;
return this;
};
MM.Layout.Graph._layoutChildren = function(children, rankDirection, offset, bbox) {
- var posProps = ["left", "top"];
var rankIndex = rankDirection == "left" || rankDirection == "right" ? 0 : 1;
var childIndex = (rankIndex + 1) % 2;
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
children.forEach((child) => {
- const { size, dom } = child;
+ const { size } = child;
if (rankDirection == "left") {
offset[0] = bbox[0] - size[0];
}
if (rankDirection == "top") {
offset[1] = bbox[1] - size[1];
}
- dom.node.style[childPosProp] = offset[childIndex] + "px";
- dom.node.style[rankPosProp] = offset[rankIndex] + "px";
+ let childPosition = offset.slice();
+ if (rankIndex == 1) {
+ childPosition = childPosition.reverse();
+ }
+ child.position = childPosition;
offset[childIndex] += size[childIndex] + this.SPACING_CHILD;
});
return bbox;
@@ -2227,16 +2239,14 @@
if (children.length == 0) {
return;
}
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
+ const { contentPosition, contentSize, ctx } = item;
ctx.strokeStyle = item.getColor();
var R = this.SPACING_RANK / 2;
var y1 = item.getShape().getVerticalAnchor(item);
if (side == "left") {
- var x1 = dom.content.offsetLeft - 0.5;
+ var x1 = contentPosition[0] - 0.5;
} else {
- var x1 = dom.content.offsetWidth + dom.content.offsetLeft + 0.5;
+ var x1 = contentPosition[0] + contentSize[0] + 0.5;
}
this._anchorToggle(item, x1, y1, side);
if (item.isCollapsed()) {
@@ -2292,21 +2302,19 @@
if (children.length == 0) {
return;
}
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
+ const { contentSize, size, ctx } = item;
ctx.strokeStyle = item.getColor();
var R = this.SPACING_RANK / 2;
var x = item.getShape().getHorizontalAnchor(item);
var height = children.length == 1 ? 2 * R : R;
if (side == "top") {
- var y1 = canvas.height - dom.content.offsetHeight;
+ var y1 = size[1] - contentSize[1];
var y2 = y1 - height;
this._anchorToggle(item, x, y1, side);
} else {
var y1 = item.getShape().getVerticalAnchor(item);
- var y2 = dom.content.offsetHeight + height;
- this._anchorToggle(item, x, dom.content.offsetHeight, side);
+ var y2 = contentSize[1] + height;
+ this._anchorToggle(item, x, contentSize[1], side);
}
ctx.beginPath();
ctx.moveTo(x, y1);
@@ -2317,8 +2325,8 @@
}
var c1 = children[0];
var c2 = children[children.length - 1];
- var offset = dom.content.offsetHeight + height;
- var y = Math.round(side == "top" ? canvas.height - offset : offset) + 0.5;
+ var offset = contentSize[1] + height;
+ var y = Math.round(side == "top" ? size[1] - offset : offset) + 0.5;
const p1 = c1.position;
const p2 = c2.position;
var x1 = c1.getShape().getHorizontalAnchor(c1) + p1[0];
@@ -2372,8 +2380,7 @@
return this;
};
MM.Layout.Tree._layoutItem = function(item, rankDirection) {
- var dom = item.dom;
- var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ const { contentSize } = item;
var bbox = this._computeChildrenBBox(item.children, 1);
var rankSize = contentSize[0];
var childSize = bbox[1] + contentSize[1];
@@ -2391,8 +2398,7 @@
if (rankDirection == "left") {
labelPos = rankSize - contentSize[0];
}
- dom.content.style.left = labelPos + "px";
- dom.content.style.top = 0;
+ item.contentPosition = [labelPos, 0];
return this;
};
MM.Layout.Tree._layoutChildren = function(children, rankDirection, offset, bbox) {
@@ -2408,16 +2414,14 @@
return bbox;
};
MM.Layout.Tree._drawLines = function(item, side) {
- var dom = item.dom;
- var canvas = dom.canvas;
+ const { contentSize, size, ctx } = item;
var R = this.SPACING_RANK / 4;
- var x = (side == "left" ? canvas.width - 2 * R : 2 * R) + 0.5;
- this._anchorToggle(item, x, dom.content.offsetHeight, "bottom");
+ var x = (side == "left" ? size[0] - 2 * R : 2 * R) + 0.5;
+ this._anchorToggle(item, x, contentSize[1], "bottom");
var children = item.children;
if (children.length == 0 || item.isCollapsed()) {
return;
}
- var ctx = canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
var y1 = item.getShape().getVerticalAnchor(item);
var last = children[children.length - 1];
@@ -2493,10 +2497,10 @@
};
MM.Layout.Map._layoutRoot = function(item) {
this._alignItem(item, "right");
- var dom = item.dom;
- var children = item.children;
+ const { children, contentSize } = item;
var childrenLeft = [];
var childrenRight = [];
+ let contentPosition = [0, 0];
children.forEach((child) => {
var side = this.getChildDirection(child);
if (side == "left") {
@@ -2507,21 +2511,22 @@
});
var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
var bboxRight = this._computeChildrenBBox(childrenRight, 1);
- var height = Math.max(bboxLeft[1], bboxRight[1], dom.content.offsetHeight);
+ var height = Math.max(bboxLeft[1], bboxRight[1], contentSize[1]);
var left = 0;
this._layoutChildren(childrenLeft, "left", [left, Math.round((height - bboxLeft[1]) / 2)], bboxLeft);
left += bboxLeft[0];
if (childrenLeft.length) {
left += this.SPACING_RANK;
}
- dom.content.style.left = left + "px";
- left += dom.content.offsetWidth;
+ contentPosition[0] = left;
+ left += contentSize[1];
if (childrenRight.length) {
left += this.SPACING_RANK;
}
this._layoutChildren(childrenRight, "right", [left, Math.round((height - bboxRight[1]) / 2)], bboxRight);
left += bboxRight[0];
- dom.content.style.top = Math.round((height - dom.content.offsetHeight) / 2) + "px";
+ contentPosition[1] = Math.round((height - contentSize[1]) / 2);
+ item.contentPosition = contentPosition;
item.size = [left, height];
this._drawRootConnectors(item, "left", childrenLeft);
this._drawRootConnectors(item, "right", childrenRight);
@@ -2530,11 +2535,8 @@
if (children.length == 0 || item.isCollapsed()) {
return;
}
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
- var R = this.SPACING_RANK / 2;
- var x1 = dom.content.offsetLeft + dom.content.offsetWidth / 2;
+ const { contentSize, contentPosition, ctx } = item;
+ var x1 = contentPosition[0] + contentSize[0] / 2;
var y1 = item.getShape().getVerticalAnchor(item);
var half = this.LINE_THICKNESS / 2;
for (var i = 0; i < children.length; i++) {
@@ -2571,12 +2573,12 @@
return this;
};
MM.Shape.getHorizontalAnchor = function(item) {
- var node2 = item.dom.content;
- return Math.round(node2.offsetLeft + node2.offsetWidth / 2) + 0.5;
+ const { contentPosition, contentSize } = item;
+ return Math.round(contentPosition[0] + contentSize[0] / 2) + 0.5;
};
MM.Shape.getVerticalAnchor = function(item) {
- var node2 = item.dom.content;
- return node2.offsetTop + Math.round(node2.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
+ const { contentPosition, contentSize } = item;
+ return contentPosition[1] + Math.round(contentSize[1] * this.VERTICAL_OFFSET) + 0.5;
};
// .js/shape/shape.underline.js
@@ -2586,11 +2588,10 @@
VERTICAL_OFFSET: { value: -3 }
});
MM.Shape.Underline.update = function(item) {
- var dom = item.dom;
- var ctx = dom.canvas.getContext("2d");
+ const { contentPosition, contentSize, ctx } = item;
ctx.strokeStyle = item.getColor();
- var left = dom.content.offsetLeft;
- var right = left + dom.content.offsetWidth;
+ var left = contentPosition[0];
+ var right = left + contentSize[0];
var top = this.getVerticalAnchor(item);
ctx.beginPath();
ctx.moveTo(left, top);
@@ -2598,8 +2599,8 @@
ctx.stroke();
};
MM.Shape.Underline.getVerticalAnchor = function(item) {
- var node2 = item.dom.content;
- return node2.offsetTop + node2.offsetHeight + this.VERTICAL_OFFSET + 0.5;
+ const { contentPosition, contentSize } = item;
+ return contentPosition[1] + contentSize[1] + this.VERTICAL_OFFSET + 0.5;
};
// .js/shape/shape.box.js
@@ -4555,8 +4556,7 @@
var content = this._item.dom.content;
this._ghost = content.cloneNode(true);
this._ghost.classList.add("ghost");
- this._pos[0] = content.offsetLeft;
- this._pos[1] = content.offsetTop;
+ this._pos = this._item.contentPosition;
content.parentNode.appendChild(this._ghost);
};
MM.Mouse._moveGhost = function(dx, dy) {
@@ -4600,12 +4600,10 @@
}
tmp = tmp.parent;
}
- var w1 = this._item.dom.content.offsetWidth;
- var w2 = target.dom.content.offsetWidth;
- var w = Math.max(w1, w2);
- var h1 = this._item.dom.content.offsetHeight;
- var h2 = target.dom.content.offsetHeight;
- var h = Math.max(h1, h2);
+ let itemContentSize = this._item.contentSize;
+ let targetContentSize = target.contentSize;
+ var w = Math.max(itemContentSize[0], targetContentSize[0]);
+ var h = Math.max(itemContentSize[1], targetContentSize[1]);
if (target.isRoot()) {
state.result = "append";
} else if (Math.abs(closest.dx) < w && Math.abs(closest.dy) < h) {
@@ -4613,7 +4611,6 @@
} else {
state.result = "sibling";
var childDirection = target.parent.getLayout().getChildDirection(target);
- var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
if (childDirection == "left" || childDirection == "right") {
state.direction = closest.dy < 0 ? "bottom" : "top";
} else {
diff --git a/src/item.ts b/src/item.ts
index d8fa7276..54d67c5a 100644
--- a/src/item.ts
+++ b/src/item.ts
@@ -95,6 +95,9 @@ export default class Item {
this.update({children:true});
}
+ // fixme zrusit
+ get ctx() { return this.dom.canvas.getContext("2d"); }
+
get metrics() { // FIXME bud tohle, nebo size/position
const { dom } = this, { node } = dom;
return {
@@ -106,26 +109,41 @@ export default class Item {
}
get size() {
- const { dom } = this;
- return [dom.node.offsetWidth, dom.node.offsetHeight];
+ const { node } = this.dom;
+ return [node.offsetWidth, node.offsetHeight];
}
set size(size: number[]) {
- const { dom } = this;
- dom.node.style.width = `${size[0]}px`;
- dom.node.style.height = `${size[1]}px`;
+ const { node, canvas } = this.dom;
+ node.style.width = `${size[0]}px`;
+ node.style.height = `${size[1]}px`;
- dom.canvas.width = size[0];
- dom.canvas.height = size[1];
+ canvas.width = size[0];
+ canvas.height = size[1];
}
get position() {
- const { dom } = this;
- return [dom.node.offsetLeft, dom.node.offsetTop];
+ const { node } = this.dom;
+ return [node.offsetLeft, node.offsetTop];
}
set position(position: number[]) {
- const { dom } = this;
- dom.node.style.left = `${position[0]}px`;
- dom.node.style.top = `${position[1]}px`;
+ const { node } = this.dom;
+ node.style.left = `${position[0]}px`;
+ node.style.top = `${position[1]}px`;
+ }
+
+ get contentSize() {
+ const { content } = this.dom;
+ return [content.offsetWidth, content.offsetHeight];
+ }
+
+ get contentPosition() {
+ const { content } = this.dom;
+ return [content.offsetLeft, content.offsetTop];
+ }
+ set contentPosition(position: number[]) {
+ const { content } = this.dom;
+ content.style.left = `${position[0]}px`;
+ content.style.top = `${position[1]}px`;
}
toJSON() {
@@ -171,9 +189,9 @@ export default class Item {
if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
if (data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
- (data.children || []).forEach(function(child) {
+ (data.children || []).forEach(child => {
this.insertChild(Item.fromJSON(child));
- }, this);
+ });
return this;
}
@@ -218,7 +236,7 @@ export default class Item {
var s = (this._autoShape ? null : this._shape.id);
if (s != data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
- (data.children || []).forEach(function(child, index) {
+ (data.children || []).forEach((child, index) => {
if (index >= this.children.length) { /* new child */
this.insertChild(Item.fromJSON(child));
} else { /* existing child */
@@ -230,7 +248,7 @@ export default class Item {
this.insertChild(Item.fromJSON(child), index);
}
}
- }, this);
+ });
/* remove dead children */
var newLength = (data.children || []).length;
@@ -302,7 +320,8 @@ export default class Item {
this._updateStatus();
this._updateIcon();
- this._updateNotesIndicator();
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
+
this._updateValue();
this.dom.node.classList.toggle("collapsed", this._collapsed);
@@ -317,12 +336,12 @@ export default class Item {
setText(text) {
this.dom.text.innerHTML = text;
findLinks(this.dom.text);
- return this.update();
+ this.update();
}
setNotes(notes) {
this._notes = notes;
- return this.update();
+ this.update();
}
getText() {
@@ -336,14 +355,14 @@ export default class Item {
collapse() {
if (this._collapsed) { return; }
this._collapsed = true;
- return this.update();
+ this.update();
}
expand() {
if (!this._collapsed) { return; }
this._collapsed = false;
this.update();
- return this.update({children:true});
+ this.update({children:true});
}
isCollapsed() {
@@ -352,7 +371,7 @@ export default class Item {
setValue(value) {
this._value = value;
- return this.update();
+ this.update();
}
getValue() {
@@ -365,7 +384,7 @@ export default class Item {
setStatus(status) {
this._status = status;
- return this.update();
+ this.update();
}
getStatus() {
@@ -374,7 +393,7 @@ export default class Item {
setIcon(icon) {
this._icon = icon;
- return this.update();
+ this.update();
}
getIcon() {
@@ -387,6 +406,7 @@ export default class Item {
setSide(side) {
this._side = side;
+ // FIXME no .update() call, because the whole map needs updating?
return this;
}
@@ -396,7 +416,7 @@ export default class Item {
setColor(color) {
this._color = color;
- return this.update({children:true});
+ this.update({children:true});
}
getColor() {
@@ -417,7 +437,7 @@ export default class Item {
setLayout(layout) {
this._layout = layout;
- return this.update({children:true});
+ this.update({children:true});
}
getShape() {
@@ -440,7 +460,7 @@ export default class Item {
}
this._shape.set(this);
- return this.update();
+ this.update();
}
get map() {
@@ -456,12 +476,10 @@ export default class Item {
return (this.parent instanceof MM.Map);
}
- insertChild(child, index) {
+ insertChild(child: Item, index?: number) {
/* Create or remove child as necessary. This must be done before computing the index (inserting own child) */
- var newChild = false;
if (!child) {
child = new Item();
- newChild = true;
} else if (child.parent && child.parent.removeChild) { /* only when the child has non-map parent */
child.parent.removeChild(child);
}
@@ -604,10 +622,6 @@ export default class Item {
}
}
- _updateNotesIndicator() {
- this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
- }
-
_updateValue() {
this.dom.value.hidden = false;
diff --git a/src/layout/layout.graph.js b/src/layout/layout.graph.js
index fd868b0b..fd79ba72 100644
--- a/src/layout/layout.graph.js
+++ b/src/layout/layout.graph.js
@@ -40,17 +40,10 @@ MM.Layout.Graph.update = function(item) {
* Generic graph child layout routine. Updates item's orthogonal size according to the sum of its children.
*/
MM.Layout.Graph._layoutItem = function(item, rankDirection) {
- var posProps = ["left", "top"];
var rankIndex = (rankDirection == "left" || rankDirection == "right" ? 0 : 1);
var childIndex = (rankIndex+1) % 2;
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
-
- var dom = item.dom;
-
- /* content size */
- var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ const { contentSize } = item;
/* children size */
var bbox = this._computeChildrenBBox(item.children, childIndex);
@@ -60,7 +53,9 @@ MM.Layout.Graph._layoutItem = function(item, rankDirection) {
if (bbox[rankIndex]) { rankSize += bbox[rankIndex] + this.SPACING_RANK; }
var childSize = Math.max(bbox[childIndex], contentSize[childIndex]);
- item.size = (rankIndex == 0 ? [rankSize, childSize] : [childSize, rankSize]);
+ let size = [rankSize, childSize];
+ if (rankIndex == 1) { size = size.reverse(); }
+ item.size = size;
var offset = [0, 0];
if (rankDirection == "right") { offset[0] = contentSize[0] + this.SPACING_RANK; }
@@ -72,28 +67,27 @@ MM.Layout.Graph._layoutItem = function(item, rankDirection) {
var labelPos = 0;
if (rankDirection == "left") { labelPos = rankSize - contentSize[0]; }
if (rankDirection == "top") { labelPos = rankSize - contentSize[1]; }
- dom.content.style[childPosProp] = Math.round((childSize - contentSize[childIndex])/2) + "px";
- dom.content.style[rankPosProp] = labelPos + "px";
+
+ let contentPosition = [Math.round((childSize - contentSize[childIndex])/2), labelPos];
+ if (rankIndex == 0) { contentPosition = contentPosition.reverse(); }
+ item.contentPosition = contentPosition;
return this;
}
MM.Layout.Graph._layoutChildren = function(children, rankDirection, offset, bbox) {
- var posProps = ["left", "top"];
-
var rankIndex = (rankDirection == "left" || rankDirection == "right" ? 0 : 1);
var childIndex = (rankIndex+1) % 2;
- var rankPosProp = posProps[rankIndex];
- var childPosProp = posProps[childIndex];
children.forEach(child => {
- const { size, dom } = child;
+ const { size } = child;
if (rankDirection == "left") { offset[0] = bbox[0] - size[0]; }
if (rankDirection == "top") { offset[1] = bbox[1] - size[1]; }
- dom.node.style[childPosProp] = offset[childIndex] + "px";
- dom.node.style[rankPosProp] = offset[rankIndex] + "px";
+ let childPosition = offset.slice(); // could be reversed
+ if (rankIndex == 1) { childPosition = childPosition.reverse(); }
+ child.position = childPosition;
offset[childIndex] += size[childIndex] + this.SPACING_CHILD; /* offset for next child */
});
@@ -112,18 +106,17 @@ MM.Layout.Graph._drawLinesVertical = function(item, side) {
MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
if (children.length == 0) { return; }
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
+ const { contentPosition, contentSize, ctx } = item;
+
ctx.strokeStyle = item.getColor();
var R = this.SPACING_RANK/2;
/* first part */
var y1 = item.getShape().getVerticalAnchor(item);
if (side == "left") {
- var x1 = dom.content.offsetLeft - 0.5;
+ var x1 = contentPosition[0] - 0.5;
} else {
- var x1 = dom.content.offsetWidth + dom.content.offsetLeft + 0.5;
+ var x1 = contentPosition[0] + contentSize[0] + 0.5;
}
this._anchorToggle(item, x1, y1, side);
@@ -187,9 +180,8 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
MM.Layout.Graph._drawVerticalConnectors = function(item, side, children) {
if (children.length == 0) { return; }
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
+ const { contentSize, size, ctx } = item;
+
ctx.strokeStyle = item.getColor();
/* first part */
@@ -199,13 +191,13 @@ MM.Layout.Graph._drawVerticalConnectors = function(item, side, children) {
var height = (children.length == 1 ? 2*R : R);
if (side == "top") {
- var y1 = canvas.height - dom.content.offsetHeight;
+ var y1 = size[1] - contentSize[1];
var y2 = y1 - height;
this._anchorToggle(item, x, y1, side);
} else {
var y1 = item.getShape().getVerticalAnchor(item);
- var y2 = dom.content.offsetHeight + height;
- this._anchorToggle(item, x, dom.content.offsetHeight, side);
+ var y2 = contentSize[1] + height;
+ this._anchorToggle(item, x, contentSize[1], side);
}
ctx.beginPath();
@@ -219,8 +211,8 @@ MM.Layout.Graph._drawVerticalConnectors = function(item, side, children) {
/* rounded connectors */
var c1 = children[0];
var c2 = children[children.length-1];
- var offset = dom.content.offsetHeight + height;
- var y = Math.round(side == "top" ? canvas.height - offset : offset) + 0.5;
+ var offset = contentSize[1] + height;
+ var y = Math.round(side == "top" ? size[1] - offset : offset) + 0.5;
const p1 = c1.position;
const p2 = c2.position;
diff --git a/src/layout/layout.js b/src/layout/layout.js
index 8a45bffe..dc32cd3c 100644
--- a/src/layout/layout.js
+++ b/src/layout/layout.js
@@ -94,13 +94,13 @@ MM.Layout._anchorToggle = function(item, x, y, side) {
}
MM.Layout._getChildAnchor = function(item, side) {
- let { position, dom } = item;
+ let { position, contentPosition, contentSize } = item;
if (side == "left" || side == "right") {
- var pos = position[0] + dom.content.offsetLeft;
- if (side == "left") { pos += dom.content.offsetWidth; }
+ var pos = position[0] + contentPosition[0];
+ if (side == "left") { pos += contentSize[0]; }
} else {
- var pos = position[1] + dom.content.offsetTop;
- if (side == "top") { pos += dom.content.offsetHeight; }
+ var pos = position[1] + contentPosition[1];
+ if (side == "top") { pos += contentSize[1]; }
}
return pos;
}
@@ -125,12 +125,12 @@ MM.Layout._alignItem = function(item, side) {
var dom = item.dom;
switch (side) {
- case "left":
+ case "left": // icon, text, value, status
dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.appendChild(dom.value);
dom.content.appendChild(dom.status);
break;
- case "right":
+ case "right": // status, value, icon, text
dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.insertBefore(dom.value, dom.content.firstChild);
dom.content.insertBefore(dom.status, dom.content.firstChild);
diff --git a/src/layout/layout.map.js b/src/layout/layout.map.js
index 87ff6229..ddf61cfc 100644
--- a/src/layout/layout.map.js
+++ b/src/layout/layout.map.js
@@ -62,11 +62,10 @@ MM.Layout.Map.pickSibling = function(item, dir) {
MM.Layout.Map._layoutRoot = function(item) {
this._alignItem(item, "right");
- var dom = item.dom;
-
- var children = item.children;
+ const { children, contentSize } = item;
var childrenLeft = [];
var childrenRight = [];
+ let contentPosition = [0, 0];
children.forEach(child => {
var side = this.getChildDirection(child);
@@ -80,21 +79,22 @@ MM.Layout.Map._layoutRoot = function(item) {
var bboxLeft = this._computeChildrenBBox(childrenLeft, 1);
var bboxRight = this._computeChildrenBBox(childrenRight, 1);
- var height = Math.max(bboxLeft[1], bboxRight[1], dom.content.offsetHeight);
+ var height = Math.max(bboxLeft[1], bboxRight[1], contentSize[1]);
var left = 0;
this._layoutChildren(childrenLeft, "left", [left, Math.round((height-bboxLeft[1])/2)], bboxLeft);
left += bboxLeft[0];
if (childrenLeft.length) { left += this.SPACING_RANK; }
- dom.content.style.left = left + "px";
- left += dom.content.offsetWidth;
+ contentPosition[0] = left;
+ left += contentSize[1];
if (childrenRight.length) { left += this.SPACING_RANK; }
this._layoutChildren(childrenRight, "right", [left, Math.round((height-bboxRight[1])/2)], bboxRight);
left += bboxRight[0];
- dom.content.style.top = Math.round((height - dom.content.offsetHeight)/2) + "px";
+ contentPosition[1] = Math.round((height - contentSize[1])/2);
+ item.contentPosition = contentPosition;
item.size = [left, height];
this._drawRootConnectors(item, "left", childrenLeft);
@@ -104,12 +104,9 @@ MM.Layout.Map._layoutRoot = function(item) {
MM.Layout.Map._drawRootConnectors = function(item, side, children) {
if (children.length == 0 || item.isCollapsed()) { return; }
- var dom = item.dom;
- var canvas = dom.canvas;
- var ctx = canvas.getContext("2d");
- var R = this.SPACING_RANK/2;
+ const { contentSize, contentPosition, ctx } = item;
- var x1 = dom.content.offsetLeft + dom.content.offsetWidth/2;
+ var x1 = contentPosition[0] + contentSize[0]/2;
var y1 = item.getShape().getVerticalAnchor(item);
var half = this.LINE_THICKNESS/2;
diff --git a/src/layout/layout.tree.js b/src/layout/layout.tree.js
index b1e96434..96d50890 100644
--- a/src/layout/layout.tree.js
+++ b/src/layout/layout.tree.js
@@ -33,10 +33,7 @@ MM.Layout.Tree.update = function(item) {
* Generic graph child layout routine. Updates item's orthogonal size according to the sum of its children.
*/
MM.Layout.Tree._layoutItem = function(item, rankDirection) {
- var dom = item.dom;
-
- /* content size */
- var contentSize = [dom.content.offsetWidth, dom.content.offsetHeight];
+ const { contentSize } = item;
/* children size */
var bbox = this._computeChildrenBBox(item.children, 1);
@@ -57,8 +54,8 @@ MM.Layout.Tree._layoutItem = function(item, rankDirection) {
/* label position */
var labelPos = 0;
if (rankDirection == "left") { labelPos = rankSize - contentSize[0]; }
- dom.content.style.left = labelPos + "px";
- dom.content.style.top = 0;
+
+ item.contentPosition = [labelPos, 0];
return this;
}
@@ -79,18 +76,16 @@ MM.Layout.Tree._layoutChildren = function(children, rankDirection, offset, bbox)
}
MM.Layout.Tree._drawLines = function(item, side) {
- var dom = item.dom;
- var canvas = dom.canvas;
+ const { contentSize, size, ctx } = item;
var R = this.SPACING_RANK/4;
// FIXME canvas.width nahradit za item.size[0] ?
- var x = (side == "left" ? canvas.width - 2*R : 2*R) + 0.5;
- this._anchorToggle(item, x, dom.content.offsetHeight, "bottom");
+ var x = (side == "left" ? size[0] - 2*R : 2*R) + 0.5;
+ this._anchorToggle(item, x, contentSize[1], "bottom");
var children = item.children;
if (children.length == 0 || item.isCollapsed()) { return; }
- var ctx = canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
var y1 = item.getShape().getVerticalAnchor(item);
diff --git a/src/map.js b/src/map.js
index fa4d4459..d127d1fd 100644
--- a/src/map.js
+++ b/src/map.js
@@ -89,8 +89,7 @@ MM.Map.prototype.update = function() {
}
MM.Map.prototype.show = function(where) {
- var node = this._root.dom.node;
- where.appendChild(node);
+ where.appendChild(this._root.dom.node);
this._visible = true;
this._root.update({parent:true, children:true});
this.center();
@@ -99,8 +98,7 @@ MM.Map.prototype.show = function(where) {
}
MM.Map.prototype.hide = function() {
- var node = this._root.dom.node;
- node.parentNode.removeChild(node);
+ this._root.dom.node.remove();
this._visible = false;
return this;
}
diff --git a/src/mouse.js b/src/mouse.js
index 30efad80..669d56df 100644
--- a/src/mouse.js
+++ b/src/mouse.js
@@ -175,8 +175,7 @@ MM.Mouse._buildGhost = function() {
var content = this._item.dom.content;
this._ghost = content.cloneNode(true);
this._ghost.classList.add("ghost");
- this._pos[0] = content.offsetLeft;
- this._pos[1] = content.offsetTop;
+ this._pos = this._item.contentPosition;
content.parentNode.appendChild(this._ghost);
}
@@ -232,12 +231,10 @@ MM.Mouse._computeDragState = function() {
tmp = tmp.parent;
}
- var w1 = this._item.dom.content.offsetWidth;
- var w2 = target.dom.content.offsetWidth;
- var w = Math.max(w1, w2);
- var h1 = this._item.dom.content.offsetHeight;
- var h2 = target.dom.content.offsetHeight;
- var h = Math.max(h1, h2);
+ let itemContentSize = this._item.contentSize;
+ let targetContentSize = target.contentSize;
+ var w = Math.max(itemContentSize[0], targetContentSize[0]);
+ var h = Math.max(itemContentSize[1], targetContentSize[1]);
if (target.isRoot()) { /* append here */
state.result = "append";
@@ -246,7 +243,6 @@ MM.Mouse._computeDragState = function() {
} else {
state.result = "sibling";
var childDirection = target.parent.getLayout().getChildDirection(target);
- var diff = -1 * (childDirection == "top" || childDirection == "bottom" ? closest.dx : closest.dy);
if (childDirection == "left" || childDirection == "right") {
state.direction = (closest.dy < 0 ? "bottom" : "top");
diff --git a/src/shape/shape.js b/src/shape/shape.js
index cb251632..b4a6ebd2 100644
--- a/src/shape/shape.js
+++ b/src/shape/shape.js
@@ -18,11 +18,11 @@ MM.Shape.update = function(item) {
}
MM.Shape.getHorizontalAnchor = function(item) {
- var node = item.dom.content;
- return Math.round(node.offsetLeft + node.offsetWidth/2) + 0.5;
+ const { contentPosition, contentSize } = item;
+ return Math.round(contentPosition[0] + contentSize[0]/2) + 0.5;
}
MM.Shape.getVerticalAnchor = function(item) {
- var node = item.dom.content;
- return node.offsetTop + Math.round(node.offsetHeight * this.VERTICAL_OFFSET) + 0.5;
+ const { contentPosition, contentSize } = item;
+ return contentPosition[1] + Math.round(contentSize[1] * this.VERTICAL_OFFSET) + 0.5;
}
diff --git a/src/shape/shape.underline.js b/src/shape/shape.underline.js
index 5b9d9f0f..caf4bc7f 100644
--- a/src/shape/shape.underline.js
+++ b/src/shape/shape.underline.js
@@ -5,13 +5,12 @@ MM.Shape.Underline = Object.create(MM.Shape, {
});
MM.Shape.Underline.update = function(item) {
- var dom = item.dom;
+ const { contentPosition, contentSize, ctx } = item;
- var ctx = dom.canvas.getContext("2d");
ctx.strokeStyle = item.getColor();
- var left = dom.content.offsetLeft;
- var right = left + dom.content.offsetWidth;
+ var left = contentPosition[0];
+ var right = left + contentSize[0];
var top = this.getVerticalAnchor(item);
@@ -22,6 +21,6 @@ MM.Shape.Underline.update = function(item) {
}
MM.Shape.Underline.getVerticalAnchor = function(item) {
- var node = item.dom.content;
- return node.offsetTop + node.offsetHeight + this.VERTICAL_OFFSET + 0.5;
+ const { contentPosition, contentSize } = item;
+ return contentPosition[1] + contentSize[1] + this.VERTICAL_OFFSET + 0.5;
}
From 6033e91c96d92f36b7858d1f18f4e64c12a95595 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Sun, 17 Oct 2021 23:10:22 +0200
Subject: [PATCH 06/42] removing promises, fixing item
---
my-mind.js | 184 +++++++++++-----------------------
src/action.js | 12 +--
src/backend/backend.webdav.js | 28 +++---
src/clipboard.js | 2 +-
src/command/command.edit.js | 2 +-
src/item.ts | 46 ++++-----
src/map.js | 4 +-
src/my-mind.js | 1 -
src/promise-addons.js | 87 ----------------
src/shape/shape.js | 3 -
src/ui/ui.icon.js | 2 +-
src/ui/ui.notes.js | 4 +-
12 files changed, 102 insertions(+), 273 deletions(-)
delete mode 100644 src/promise-addons.js
diff --git a/my-mind.js b/my-mind.js
index 540cef7f..2092012e 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -88,79 +88,6 @@
}
};
- // .js/promise-addons.js
- Promise.all = Promise.when = function(all) {
- var promise = new this();
- var counter = 0;
- var results = [];
- for (var i = 0; i < all.length; i++) {
- counter++;
- all[i].then(function(index, result) {
- results[index] = result;
- counter--;
- if (!counter) {
- promise.fulfill(results);
- }
- }.bind(null, i), function(reason) {
- counter = 1 / 0;
- promise.reject(reason);
- });
- }
- return promise;
- };
- Promise.setTimeout = function(ms) {
- var promise = new this();
- setTimeout(function() {
- promise.fulfill();
- }, ms);
- return promise;
- };
- Promise.event = function(element, event, capture) {
- var promise = new this();
- var cb = function(e) {
- element.removeEventListener(event, cb, capture);
- promise.fulfill(e);
- };
- element.addEventListener(event, cb, capture);
- return promise;
- };
- Promise.transition = function(element) {
- if ("transition" in element.style) {
- return this.event(element, "transitionend", false);
- } else if ("webkitTransition" in element.style) {
- return this.event(element, "webkitTransitionEnd", false);
- } else {
- return new this().fulfill();
- }
- };
- Promise.send = function(xhr, data) {
- var promise = new this();
- xhr.addEventListener("readystatechange", function(e) {
- if (e.target.readyState != 4) {
- return;
- }
- if (e.target.status.toString().charAt(0) == "2") {
- promise.fulfill(e.target);
- } else {
- promise.reject(e.target);
- }
- });
- xhr.send(data);
- return promise;
- };
- Promise.worker = function(url, message) {
- var promise = new this();
- var worker = new Worker(url);
- Promise.event(worker, "message").then(function(e) {
- promise.fulfill(e.data);
- });
- Promise.event(worker, "error").then(function(e) {
- promise.reject(e.message);
- });
- worker.postMessage(message);
- return promise;
- };
-
// .js/repo.js
MM.Repo = {
id: "",
@@ -236,6 +163,8 @@
this._id = generateId();
this._parent = null;
this._collapsed = false;
+ this._icon = null;
+ this._notes = null;
this.dom = {
node: node("li"),
content: node("div"),
@@ -256,8 +185,6 @@
this._value = null;
this._status = null;
this._side = null;
- this._icon = null;
- this._notes = null;
this._oldText = "";
this._computed = {
value: 0,
@@ -338,8 +265,8 @@
toJSON() {
let data = {
id: this.id,
- text: this.getText(),
- notes: this.getNotes()
+ text: this.text,
+ notes: this.notes
};
if (this._side) {
data.side = this._side;
@@ -371,13 +298,13 @@
return data;
}
fromJSON(data) {
- this.setText(data.text);
- if (data.notes) {
- this.setNotes(data.notes);
- }
+ this.text = data.text;
if (data.id) {
this._id = data.id;
}
+ if (data.notes) {
+ this.notes = data.notes;
+ }
if (data.side) {
this._side = data.side;
}
@@ -412,8 +339,8 @@
}
mergeWith(data) {
var dirty = 0;
- if (this.getText() != data.text && !this.dom.text.contentEditable) {
- this.setText(data.text);
+ if (this.text != data.text && !this.dom.text.contentEditable) {
+ this.text = data.text;
}
if (this._side != data.side) {
this._side = data.side;
@@ -482,8 +409,8 @@
select() {
this.dom.node.classList.add("current");
if (window.editor) {
- if (this._notes) {
- window.editor.setContent(this._notes);
+ if (this.notes) {
+ window.editor.setContent(this.notes);
} else {
window.editor.setContent("");
}
@@ -521,7 +448,7 @@
}
this._updateStatus();
this._updateIcon();
- this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this.notes);
this._updateValue();
this.dom.node.classList.toggle("collapsed", this._collapsed);
this.getLayout().update(this);
@@ -530,21 +457,21 @@
this.parent.update();
}
}
- setText(text) {
+ get text() {
+ return this.dom.text.innerHTML;
+ }
+ set text(text) {
this.dom.text.innerHTML = text;
findLinks(this.dom.text);
this.update();
}
- setNotes(notes) {
+ get notes() {
+ return this._notes;
+ }
+ set notes(notes) {
this._notes = notes;
this.update();
}
- getText() {
- return this.dom.text.innerHTML;
- }
- getNotes() {
- return this._notes;
- }
collapse() {
if (this._collapsed) {
return;
@@ -580,13 +507,13 @@
getStatus() {
return this._status;
}
- setIcon(icon) {
+ get icon() {
+ return this._icon;
+ }
+ set icon(icon) {
this._icon = icon;
this.update();
}
- getIcon() {
- return this._icon;
- }
getComputedStatus() {
return this._computed.status;
}
@@ -684,7 +611,7 @@
this.update();
}
startEditing() {
- this._oldText = this.getText();
+ this._oldText = this.text;
this.dom.text.contentEditable = "true";
this.dom.text.focus();
document.execCommand("styleWithCSS", null, "false");
@@ -871,7 +798,7 @@
this._visible = false;
this._position = [0, 0];
let root = new Item();
- root.setText(o.root);
+ root.text = o.root;
root.setLayout(o.layout);
this._setRoot(root);
};
@@ -1038,7 +965,7 @@
return this._root;
};
MM.Map.prototype.getName = function() {
- var name = this._root.getText();
+ var name = this._root.text;
return MM.Format.br2nl(name).replace(/\n/g, " ").replace(/<.*?>/g, "").trim();
};
MM.Map.prototype.getId = function() {
@@ -1317,19 +1244,19 @@
MM.Action.SetText = function(item, text) {
this._item = item;
this._text = text;
- this._oldText = item.getText();
+ this._oldText = item.text;
this._oldValue = item.getValue();
};
MM.Action.SetText.prototype = Object.create(MM.Action.prototype);
MM.Action.SetText.prototype.perform = function() {
- this._item.setText(this._text);
+ this._item.text = this._text;
var numText = Number(this._text);
if (numText == this._text) {
this._item.setValue(numText);
}
};
MM.Action.SetText.prototype.undo = function() {
- this._item.setText(this._oldText);
+ this._item.text = this._oldText;
this._item.setValue(this._oldValue);
};
MM.Action.SetValue = function(item, value) {
@@ -1359,14 +1286,14 @@
MM.Action.SetIcon = function(item, icon) {
this._item = item;
this._icon = icon;
- this._oldIcon = item.getIcon();
+ this._oldIcon = item.icon;
};
MM.Action.SetIcon.prototype = Object.create(MM.Action.prototype);
MM.Action.SetIcon.prototype.perform = function() {
- this._item.setIcon(this._icon);
+ this._item.icon = this._icon;
};
MM.Action.SetIcon.prototype.undo = function() {
- this._item.setIcon(this._oldIcon);
+ this._item.icon = this._oldIcon;
};
MM.Action.SetSide = function(item, side) {
this._item = item;
@@ -1453,7 +1380,7 @@
var json = MM.Format.Plaintext.from(plaintext);
var map = MM.Map.fromJSON(json);
var root = map.getRoot();
- if (root.getText()) {
+ if (root.text) {
var action = new MM.Action.AppendItem(targetItem, root);
MM.App.action(action);
} else {
@@ -1878,7 +1805,7 @@
MM.Command.Cancel.execute = function() {
MM.App.editing = false;
MM.App.current.stopEditing();
- var oldText = MM.App.current.getText();
+ var oldText = MM.App.current.text;
if (!oldText) {
var action = new MM.Action.RemoveItem(MM.App.current);
MM.App.action(action);
@@ -2562,15 +2489,12 @@
});
MM.Shape.set = function(item) {
item.dom.node.classList.add("shape-" + this.id);
- return this;
};
MM.Shape.unset = function(item) {
item.dom.node.classList.remove("shape-" + this.id);
- return this;
};
MM.Shape.update = function(item) {
item.dom.content.style.borderColor = item.getColor();
- return this;
};
MM.Shape.getHorizontalAnchor = function(item) {
const { contentPosition, contentSize } = item;
@@ -3032,22 +2956,26 @@
label: { value: "Generic WebDAV" }
});
MM.Backend.WebDAV.save = function(data, url) {
- return this._request("put", url, data);
+ return this._request("PUT", url, data);
};
MM.Backend.WebDAV.load = function(url) {
- return this._request("get", url);
+ return this._request("GET", url);
};
- MM.Backend.WebDAV._request = function(method, url, data) {
- var xhr = new XMLHttpRequest();
- xhr.open(method, url, true);
- xhr.withCredentials = true;
- var promise = new Promise();
- Promise.send(xhr, data).then(function(xhr2) {
- promise.fulfill(xhr2.responseText);
- }, function(xhr2) {
- promise.reject(new Error("HTTP/" + xhr2.status + "\n\n" + xhr2.responseText));
- });
- return promise;
+ MM.Backend.WebDAV._request = async function(method, url, data) {
+ let init = {
+ method,
+ credentials: "include"
+ };
+ if (data) {
+ init.body = data;
+ }
+ let response = await fetch(url, init);
+ let text = await response.text();
+ if (response.status == 200) {
+ return text;
+ } else {
+ throw new Error("HTTP/" + response.status + "\n\n" + text);
+ }
};
// .js/backend/backend.image.js
@@ -3632,7 +3560,7 @@
this._select.addEventListener("change", this);
};
MM.UI.Icon.prototype.update = function() {
- this._select.value = MM.App.current.getIcon() || "";
+ this._select.value = MM.App.current.icon || "";
};
MM.UI.Icon.prototype.handleEvent = function(e) {
var action = new MM.Action.SetIcon(MM.App.current, this._select.value || null);
@@ -3770,9 +3698,9 @@
};
MM.UI.Notes.prototype.update = function(html2) {
if (html2.trim().length === 0) {
- MM.App.current._notes = null;
+ MM.App.current.notes = null;
} else {
- MM.App.current._notes = html2;
+ MM.App.current.notes = html2;
}
MM.App.current.update();
};
diff --git a/src/action.js b/src/action.js
index 83c427ad..40d7143d 100644
--- a/src/action.js
+++ b/src/action.js
@@ -147,17 +147,17 @@ MM.Action.SetColor.prototype.undo = function() {
MM.Action.SetText = function(item, text) {
this._item = item;
this._text = text;
- this._oldText = item.getText();
+ this._oldText = item.text;
this._oldValue = item.getValue(); /* adjusting text can also modify value! */
}
MM.Action.SetText.prototype = Object.create(MM.Action.prototype);
MM.Action.SetText.prototype.perform = function() {
- this._item.setText(this._text);
+ this._item.text = this._text;
var numText = Number(this._text);
if (numText == this._text) { this._item.setValue(numText); }
}
MM.Action.SetText.prototype.undo = function() {
- this._item.setText(this._oldText);
+ this._item.text = this._oldText;
this._item.setValue(this._oldValue);
}
@@ -190,14 +190,14 @@ MM.Action.SetStatus.prototype.undo = function() {
MM.Action.SetIcon = function(item, icon) {
this._item = item;
this._icon = icon;
- this._oldIcon = item.getIcon();
+ this._oldIcon = item.icon;
}
MM.Action.SetIcon.prototype = Object.create(MM.Action.prototype);
MM.Action.SetIcon.prototype.perform = function() {
- this._item.setIcon(this._icon);
+ this._item.icon = this._icon;
}
MM.Action.SetIcon.prototype.undo = function() {
- this._item.setIcon(this._oldIcon);
+ this._item.icon = this._oldIcon;
}
MM.Action.SetSide = function(item, side) {
diff --git a/src/backend/backend.webdav.js b/src/backend/backend.webdav.js
index 5a89fe04..9c28ccea 100644
--- a/src/backend/backend.webdav.js
+++ b/src/backend/backend.webdav.js
@@ -4,24 +4,26 @@ MM.Backend.WebDAV = Object.create(MM.Backend, {
});
MM.Backend.WebDAV.save = function(data, url) {
- return this._request("put", url, data);
+ return this._request("PUT", url, data);
}
MM.Backend.WebDAV.load = function(url) {
- return this._request("get", url);
+ return this._request("GET", url);
}
-MM.Backend.WebDAV._request = function(method, url, data) {
- var xhr = new XMLHttpRequest();
- xhr.open(method, url, true);
- xhr.withCredentials = true;
+MM.Backend.WebDAV._request = async function(method, url, data) {
+ let init = {
+ method,
+ credentials: "include"
+ }
+ if (data) { init.body = data; }
- var promise = new Promise();
-
- Promise.send(xhr, data).then(
- function(xhr) { promise.fulfill(xhr.responseText); },
- function(xhr) { promise.reject(new Error("HTTP/" + xhr.status + "\n\n" + xhr.responseText)); }
- );
+ let response = await fetch(url, init);
+ let text = await response.text();
- return promise;
+ if (response.status == 200) {
+ return text;
+ } else {
+ throw new Error("HTTP/" + response.status + "\n\n" + text);
+ }
}
diff --git a/src/clipboard.js b/src/clipboard.js
index 34d7f0ab..69955eec 100644
--- a/src/clipboard.js
+++ b/src/clipboard.js
@@ -76,7 +76,7 @@ MM.Clipboard._pastePlaintext = function(plaintext, targetItem) {
var map = MM.Map.fromJSON(json);
var root = map.getRoot();
- if (root.getText()) {
+ if (root.text) {
var action = new MM.Action.AppendItem(targetItem, root);
MM.App.action(action);
} else {
diff --git a/src/command/command.edit.js b/src/command/command.edit.js
index dea522ce..ab30ab36 100644
--- a/src/command/command.edit.js
+++ b/src/command/command.edit.js
@@ -48,7 +48,7 @@ MM.Command.Cancel = Object.create(MM.Command, {
MM.Command.Cancel.execute = function() {
MM.App.editing = false;
MM.App.current.stopEditing();
- var oldText = MM.App.current.getText();
+ var oldText = MM.App.current.text;
if (!oldText) { /* newly added node */
var action = new MM.Action.RemoveItem(MM.App.current);
MM.App.action(action);
diff --git a/src/item.ts b/src/item.ts
index 54d67c5a..90222608 100644
--- a/src/item.ts
+++ b/src/item.ts
@@ -32,6 +32,8 @@ export default class Item {
protected _id = generateId();
protected _parent: Item | null = null;
protected _collapsed = false;
+ protected _icon: string | null = null;
+ protected _notes: string | null = null;
dom = {
node: html.node("li"),
@@ -56,8 +58,6 @@ export default class Item {
protected _value = null;
protected _status = null;
protected _side = null; /* side preference */
- protected _icon = null;
- protected _notes = null;
protected _oldText = "";
protected _computed = {
value: 0,
@@ -149,8 +149,8 @@ export default class Item {
toJSON() {
let data: Record = {
id: this.id,
- text: this.getText(),
- notes: this.getNotes()
+ text: this.text,
+ notes: this.notes
}
if (this._side) { data.side = this._side; }
@@ -172,11 +172,10 @@ export default class Item {
* Only when creating a new item. To merge existing items, use .mergeWith().
*/
fromJSON(data) {
- this.setText(data.text);
- if (data.notes) {
- this.setNotes(data.notes);
- }
+ this.text = data.text;
+
if (data.id) { this._id = data.id; }
+ if (data.notes) { this.notes = data.notes; }
if (data.side) { this._side = data.side; }
if (data.color) { this._color = data.color; }
if (data.icon) { this._icon = data.icon; }
@@ -199,7 +198,7 @@ export default class Item {
mergeWith(data) {
var dirty = 0;
- if (this.getText() != data.text && !this.dom.text.contentEditable) { this.setText(data.text); }
+ if (this.text != data.text && !this.dom.text.contentEditable) { this.text = data.text; }
if (this._side != data.side) {
this._side = data.side;
@@ -273,8 +272,8 @@ export default class Item {
select() {
this.dom.node.classList.add("current");
if (window.editor) {
- if (this._notes) {
- window.editor.setContent(this._notes);
+ if (this.notes) {
+ window.editor.setContent(this.notes);
} else {
window.editor.setContent('');
}
@@ -320,7 +319,7 @@ export default class Item {
this._updateStatus();
this._updateIcon();
- this.dom.notes.classList.toggle("notes-indicator-visible", !!this._notes);
+ this.dom.notes.classList.toggle("notes-indicator-visible", !!this.notes);
this._updateValue();
@@ -333,25 +332,19 @@ export default class Item {
if (options.parent && !this.isRoot()) { this.parent.update(); }
}
- setText(text) {
+ get text() { return this.dom.text.innerHTML; }
+ set text(text: string) {
this.dom.text.innerHTML = text;
findLinks(this.dom.text);
this.update();
}
- setNotes(notes) {
+ get notes() { return this._notes; }
+ set notes(notes: string) {
this._notes = notes;
this.update();
}
- getText() {
- return this.dom.text.innerHTML;
- }
-
- getNotes() {
- return this._notes;
- }
-
collapse() {
if (this._collapsed) { return; }
this._collapsed = true;
@@ -391,15 +384,12 @@ export default class Item {
return this._status;
}
- setIcon(icon) {
+ get icon() { return this._icon; }
+ set icon(icon: string) {
this._icon = icon;
this.update();
}
- getIcon() {
- return this._icon;
- }
-
getComputedStatus() {
return this._computed.status;
}
@@ -516,7 +506,7 @@ export default class Item {
}
startEditing() {
- this._oldText = this.getText();
+ this._oldText = this.text;
this.dom.text.contentEditable = "true";
this.dom.text.focus(); /* switch to 2b */
document.execCommand("styleWithCSS", null, "false");
diff --git a/src/map.js b/src/map.js
index d127d1fd..d2f83414 100644
--- a/src/map.js
+++ b/src/map.js
@@ -12,7 +12,7 @@ MM.Map = function(options) {
this._position = [0, 0];
let root = new Item();
- root.setText(o.root);
+ root.text = o.root;
root.setLayout(o.layout);
this._setRoot(root);
}
@@ -198,7 +198,7 @@ MM.Map.prototype.getRoot = function() {
}
MM.Map.prototype.getName = function() {
- var name = this._root.getText();
+ var name = this._root.text;
return MM.Format.br2nl(name).replace(/\n/g, " ").replace(/<.*?>/g, "").trim();
}
diff --git a/src/my-mind.js b/src/my-mind.js
index c877d0ba..13a85d97 100644
--- a/src/my-mind.js
+++ b/src/my-mind.js
@@ -1,6 +1,5 @@
import "./mm.js";
import "./promise.js";
-import "./promise-addons.js";
import "./repo.js";
import "./item.js";
import "./map.js";
diff --git a/src/promise-addons.js b/src/promise-addons.js
deleted file mode 100644
index ef548fc0..00000000
--- a/src/promise-addons.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Wait for all these promises to complete. One failed => this fails too.
- */
-Promise.all = Promise.when = function(all) {
- var promise = new this();
- var counter = 0;
- var results = [];
-
- for (var i=0;i
Date: Mon, 18 Oct 2021 10:03:10 +0200
Subject: [PATCH 07/42] value refactored to getters/setters
---
examples/features.mymind | 3 --
my-mind.js | 93 +++++++++++++++++++------------------
src/action.js | 12 ++---
src/command/command.edit.js | 2 +-
src/item.ts | 76 +++++++++++++-----------------
src/layout/layout.map.js | 2 +-
src/ui/ui.value.js | 2 +-
7 files changed, 88 insertions(+), 102 deletions(-)
diff --git a/examples/features.mymind b/examples/features.mymind
index cef13e67..ab9ff4bc 100644
--- a/examples/features.mymind
+++ b/examples/features.mymind
@@ -152,19 +152,16 @@
{
"id": "pjqwdjad",
"text": "Bool node statuses",
- "value": "maybe",
"status": "computed",
"children": [
{
"id": "jsrxnhgy",
"text": "Yes/No",
- "value": "yes",
"status": "no"
},
{
"id": "oqzwbzvf",
"text": "Auto-propagation to parent",
- "value": "no",
"status": "computed"
}
]
diff --git a/my-mind.js b/my-mind.js
index 2092012e..a6564836 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -165,6 +165,7 @@
this._collapsed = false;
this._icon = null;
this._notes = null;
+ this._value = null;
this.dom = {
node: node("li"),
content: node("div"),
@@ -182,7 +183,6 @@
this._shape = null;
this._autoShape = true;
this._color = null;
- this._value = null;
this._status = null;
this._side = null;
this._oldText = "";
@@ -490,15 +490,37 @@
isCollapsed() {
return this._collapsed;
}
- setValue(value) {
- this._value = value;
- this.update();
+ get resolvedValue() {
+ const value = this._value;
+ if (typeof value == "number") {
+ return value;
+ }
+ let childValues = this.children.map((child) => child.resolvedValue);
+ switch (value) {
+ case "max":
+ return Math.max(...childValues);
+ break;
+ case "min":
+ return Math.min(...childValues);
+ break;
+ case "sum":
+ return childValues.reduce((prev, cur) => prev + cur, 0);
+ break;
+ case "avg":
+ var sum = childValues.reduce((prev, cur) => prev + cur, 0);
+ return childValues.length ? sum / childValues.length : 0;
+ break;
+ default:
+ return 0;
+ break;
+ }
}
- getValue() {
+ get value() {
return this._value;
}
- getComputedValue() {
- return this._computed.value;
+ set value(value) {
+ this._value = value;
+ this.update();
}
setStatus(status) {
this._status = status;
@@ -707,39 +729,18 @@
}
}
_updateValue() {
- this.dom.value.hidden = false;
- if (typeof this._value == "number") {
- this._computed.value = this._value;
- this.dom.value.textContent = String(this._value);
+ const { dom, _value } = this;
+ if (_value === null) {
+ dom.value.hidden = true;
return;
}
- var childValues = this.children.map(function(child) {
- return child.getComputedValue();
- });
- var result = 0;
- switch (this._value) {
- case "sum":
- result = childValues.reduce((prev, cur) => prev + cur, 0);
- break;
- case "avg":
- var sum = childValues.reduce((prev, cur) => prev + cur, 0);
- result = childValues.length ? sum / childValues.length : 0;
- break;
- case "max":
- result = Math.max(...childValues);
- break;
- case "min":
- result = Math.min(...childValues);
- break;
- default:
- this._computed.value = 0;
- this.dom.value.innerHTML = "";
- this.dom.value.hidden = true;
- return;
- break;
+ dom.value.hidden = false;
+ if (typeof _value == "number") {
+ dom.value.textContent = String(_value);
+ } else {
+ let resolved = this.resolvedValue;
+ dom.value.textContent = String(Math.round(resolved) == resolved ? resolved : resolved.toFixed(3));
}
- this._computed.value = result;
- this.dom.value.innerHTML = String(Math.round(result) == result ? result : result.toFixed(3));
}
};
function findLinks(node2) {
@@ -1245,31 +1246,31 @@
this._item = item;
this._text = text;
this._oldText = item.text;
- this._oldValue = item.getValue();
+ this._oldValue = item.value;
};
MM.Action.SetText.prototype = Object.create(MM.Action.prototype);
MM.Action.SetText.prototype.perform = function() {
this._item.text = this._text;
var numText = Number(this._text);
if (numText == this._text) {
- this._item.setValue(numText);
+ this._item.value = numText;
}
};
MM.Action.SetText.prototype.undo = function() {
this._item.text = this._oldText;
- this._item.setValue(this._oldValue);
+ this._item.value = this._oldValue;
};
MM.Action.SetValue = function(item, value) {
this._item = item;
this._value = value;
- this._oldValue = item.getValue();
+ this._oldValue = item.value;
};
MM.Action.SetValue.prototype = Object.create(MM.Action.prototype);
MM.Action.SetValue.prototype.perform = function() {
- this._item.setValue(this._value);
+ this._item.value = this._value;
};
MM.Action.SetValue.prototype.undo = function() {
- this._item.setValue(this._oldValue);
+ this._item.value = this._oldValue;
};
MM.Action.SetStatus = function(item, status) {
this._item = item;
@@ -1855,7 +1856,7 @@
});
MM.Command.Value.execute = function() {
var item = MM.App.current;
- var oldValue = item.getValue();
+ var oldValue = item.value;
var newValue = prompt("Set item value", oldValue);
if (newValue == null) {
return;
@@ -2446,7 +2447,7 @@
left += this.SPACING_RANK;
}
contentPosition[0] = left;
- left += contentSize[1];
+ left += contentSize[0];
if (childrenRight.length) {
left += this.SPACING_RANK;
}
@@ -3502,7 +3503,7 @@
this._select.addEventListener("change", this);
};
MM.UI.Value.prototype.update = function() {
- var value = MM.App.current.getValue();
+ var value = MM.App.current.value;
if (value === null) {
value = "";
}
diff --git a/src/action.js b/src/action.js
index 40d7143d..e4d62d6f 100644
--- a/src/action.js
+++ b/src/action.js
@@ -148,30 +148,30 @@ MM.Action.SetText = function(item, text) {
this._item = item;
this._text = text;
this._oldText = item.text;
- this._oldValue = item.getValue(); /* adjusting text can also modify value! */
+ this._oldValue = item.value; /* adjusting text can also modify value! FIXME why/when?*/
}
MM.Action.SetText.prototype = Object.create(MM.Action.prototype);
MM.Action.SetText.prototype.perform = function() {
this._item.text = this._text;
var numText = Number(this._text);
- if (numText == this._text) { this._item.setValue(numText); }
+ if (numText == this._text) { this._item.value = numText; }
}
MM.Action.SetText.prototype.undo = function() {
this._item.text = this._oldText;
- this._item.setValue(this._oldValue);
+ this._item.value = this._oldValue;
}
MM.Action.SetValue = function(item, value) {
this._item = item;
this._value = value;
- this._oldValue = item.getValue();
+ this._oldValue = item.value;
}
MM.Action.SetValue.prototype = Object.create(MM.Action.prototype);
MM.Action.SetValue.prototype.perform = function() {
- this._item.setValue(this._value);
+ this._item.value = this._value;
}
MM.Action.SetValue.prototype.undo = function() {
- this._item.setValue(this._oldValue);
+ this._item.value = this._oldValue;
}
MM.Action.SetStatus = function(item, status) {
diff --git a/src/command/command.edit.js b/src/command/command.edit.js
index ab30ab36..910c344b 100644
--- a/src/command/command.edit.js
+++ b/src/command/command.edit.js
@@ -105,7 +105,7 @@ MM.Command.Value = Object.create(MM.Command, {
});
MM.Command.Value.execute = function() {
var item = MM.App.current;
- var oldValue = item.getValue();
+ var oldValue = item.value;
var newValue = prompt("Set item value", oldValue);
if (newValue == null) { return; }
diff --git a/src/item.ts b/src/item.ts
index 90222608..916f1137 100644
--- a/src/item.ts
+++ b/src/item.ts
@@ -34,6 +34,7 @@ export default class Item {
protected _collapsed = false;
protected _icon: string | null = null;
protected _notes: string | null = null;
+ protected _value: string | number | null = null;
dom = {
node: html.node("li"),
@@ -55,12 +56,10 @@ export default class Item {
protected _shape = null;
protected _autoShape = true;
protected _color = null;
- protected _value = null;
protected _status = null;
protected _side = null; /* side preference */
protected _oldText = "";
protected _computed = {
- value: 0,
status: null
}
@@ -362,17 +361,31 @@ export default class Item {
return this._collapsed;
}
- setValue(value) {
- this._value = value;
- this.update();
- }
+ get resolvedValue(): number {
+ const value = this._value;
+
+ if (typeof(value) == "number") { return value; }
+
+ let childValues = this.children.map(child => child.resolvedValue);
+
+ switch (value) {
+ case "max": return Math.max(...childValues); break;
+ case "min": return Math.min(...childValues); break;
+ case "sum": return childValues.reduce((prev, cur) => prev+cur, 0); break;
+
+ case "avg":
+ var sum = childValues.reduce((prev, cur) => prev+cur, 0);
+ return (childValues.length ? sum/childValues.length : 0);
+ break;
- getValue() {
- return this._value;
+ default: return 0; break;
+ }
}
- getComputedValue() {
- return this._computed.value;
+ get value() { return this._value; }
+ set value(value: string | number | null) {
+ this._value = value;
+ this.update();
}
setStatus(status) {
@@ -613,46 +626,21 @@ export default class Item {
}
_updateValue() {
- this.dom.value.hidden = false;
+ const { dom, _value } = this;
- if (typeof(this._value) == "number") {
- this._computed.value = this._value;
- this.dom.value.textContent = String(this._value);
+ if (_value === null) {
+ dom.value.hidden = true;
return;
}
- var childValues = this.children.map(function(child) {
- return child.getComputedValue();
- });
+ dom.value.hidden = false;
- var result = 0;
- switch (this._value) {
- case "sum":
- result = childValues.reduce((prev, cur) => prev+cur, 0);
- break;
-
- case "avg":
- var sum = childValues.reduce((prev, cur) => prev+cur, 0);
- result = (childValues.length ? sum/childValues.length : 0);
- break;
-
- case "max":
- result = Math.max(...childValues);
- break;
-
- case "min":
- result = Math.min(...childValues);
- break;
-
- default:
- this._computed.value = 0;
- this.dom.value.innerHTML = "";
- this.dom.value.hidden = true;
- return;
- break;
+ if (typeof(_value) == "number") { // exact values are not rounded
+ dom.value.textContent = String(_value);
+ } else {
+ let resolved = this.resolvedValue; // computed values are rounded to 3 decimals if need rounding
+ dom.value.textContent = String(Math.round(resolved) == resolved ? resolved : resolved.toFixed(3));
}
- this._computed.value = result;
- this.dom.value.innerHTML = String(Math.round(result) == result ? result : result.toFixed(3));
}
}
diff --git a/src/layout/layout.map.js b/src/layout/layout.map.js
index ddf61cfc..de833a2c 100644
--- a/src/layout/layout.map.js
+++ b/src/layout/layout.map.js
@@ -87,7 +87,7 @@ MM.Layout.Map._layoutRoot = function(item) {
if (childrenLeft.length) { left += this.SPACING_RANK; }
contentPosition[0] = left;
- left += contentSize[1];
+ left += contentSize[0];
if (childrenRight.length) { left += this.SPACING_RANK; }
this._layoutChildren(childrenRight, "right", [left, Math.round((height-bboxRight[1])/2)], bboxRight);
diff --git a/src/ui/ui.value.js b/src/ui/ui.value.js
index 43641c83..2d98f8ec 100644
--- a/src/ui/ui.value.js
+++ b/src/ui/ui.value.js
@@ -4,7 +4,7 @@ MM.UI.Value = function() {
}
MM.UI.Value.prototype.update = function() {
- var value = MM.App.current.getValue();
+ var value = MM.App.current.value;
if (value === null) { value = ""; }
if (typeof(value) == "number") { value = "num" }
From 0e61ac2fe38c9c7f425cc3be412afd9457e92b4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Mon, 18 Oct 2021 10:19:45 +0200
Subject: [PATCH 08/42] status refactored to getters/setters
---
my-mind.js | 93 ++++++++++++++++++++++---------------
src/action.js | 6 +--
src/command/command.edit.js | 6 +--
src/item.ts | 74 ++++++++++++++---------------
src/ui/ui.status.js | 21 ++++++++-
5 files changed, 114 insertions(+), 86 deletions(-)
diff --git a/my-mind.js b/my-mind.js
index a6564836..70d443f1 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -166,6 +166,7 @@
this._icon = null;
this._notes = null;
this._value = null;
+ this._status = null;
this.dom = {
node: node("li"),
content: node("div"),
@@ -183,11 +184,9 @@
this._shape = null;
this._autoShape = true;
this._color = null;
- this._status = null;
this._side = null;
this._oldText = "";
this._computed = {
- value: 0,
status: null
};
const { dom } = this;
@@ -318,9 +317,12 @@
this._value = data.value;
}
if (data.status) {
- this._status = data.status;
- if (this._status == "maybe") {
- this._status = "computed";
+ if (data.status == "yes") {
+ this._status = true;
+ } else if (data.status == "no") {
+ this._status = false;
+ } else {
+ this._status = data.status;
}
}
if (data.collapsed) {
@@ -522,13 +524,23 @@
this._value = value;
this.update();
}
- setStatus(status) {
- this._status = status;
- this.update();
+ get resolvedStatus() {
+ let status = this._status;
+ if (status == "computed") {
+ return this.children.every((child) => {
+ return child.resolvedStatus !== false;
+ });
+ } else {
+ return status;
+ }
}
- getStatus() {
+ get status() {
return this._status;
}
+ set status(status) {
+ this._status = status;
+ this.update();
+ }
get icon() {
return this._icon;
}
@@ -536,9 +548,6 @@
this._icon = icon;
this.update();
}
- getComputedStatus() {
- return this._computed.status;
- }
setSide(side) {
this._side = side;
return this;
@@ -695,27 +704,18 @@
}
}
_updateStatus() {
- this.dom.status.className = "status";
- this.dom.status.hidden = false;
- var status = this._status;
- if (this._status == "computed") {
- var childrenStatus = this.children.every(function(child) {
- return child.getComputedStatus() !== false;
- });
- status = childrenStatus ? "yes" : "no";
- }
- switch (status) {
- case "yes":
- this.dom.status.classList.add("yes");
- this._computed.status = true;
+ const { resolvedStatus, dom } = this;
+ dom.status.className = "status";
+ dom.status.hidden = false;
+ switch (resolvedStatus) {
+ case true:
+ dom.status.classList.add("yes");
break;
- case "no":
- this.dom.status.classList.add("no");
- this._computed.status = false;
+ case false:
+ dom.status.classList.add("no");
break;
default:
- this._computed.status = null;
- this.dom.status.hidden = true;
+ dom.status.hidden = true;
break;
}
}
@@ -1275,14 +1275,14 @@
MM.Action.SetStatus = function(item, status) {
this._item = item;
this._status = status;
- this._oldStatus = item.getStatus();
+ this._oldStatus = item.status;
};
MM.Action.SetStatus.prototype = Object.create(MM.Action.prototype);
MM.Action.SetStatus.prototype.perform = function() {
- this._item.setStatus(this._status);
+ this._item.status = this._status;
};
MM.Action.SetStatus.prototype.undo = function() {
- this._item.setStatus(this._oldStatus);
+ this._item.status = this._oldStatus;
};
MM.Action.SetIcon = function(item, icon) {
this._item = item;
@@ -1874,7 +1874,7 @@
});
MM.Command.Yes.execute = function() {
var item = MM.App.current;
- var status = item.getStatus() == "yes" ? null : "yes";
+ var status = item.status === true ? null : true;
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
};
@@ -1884,7 +1884,7 @@
});
MM.Command.No.execute = function() {
var item = MM.App.current;
- var status = item.getStatus() == "no" ? null : "no";
+ var status = item.status === false ? null : false;
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
};
@@ -1894,7 +1894,7 @@
});
MM.Command.Computed.execute = function() {
var item = MM.App.current;
- var status = item.getStatus() == "computed" ? null : "computed";
+ var status = item.status == "computed" ? null : "computed";
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
};
@@ -3523,15 +3523,32 @@
};
// .js/ui/ui.status.js
+ var STATUS_MAP = {
+ "yes": true,
+ "no": false,
+ "": null
+ };
+ function statusToString(status) {
+ for (let key in STATUS_MAP) {
+ if (STATUS_MAP[key] === status) {
+ return key;
+ }
+ }
+ return status;
+ }
+ function stringToStatus(str) {
+ return str in STATUS_MAP ? STATUS_MAP[str] : str;
+ }
MM.UI.Status = function() {
this._select = document.querySelector("#status");
this._select.addEventListener("change", this);
};
MM.UI.Status.prototype.update = function() {
- this._select.value = MM.App.current.getStatus() || "";
+ this._select.value = statusToString(MM.App.current.status);
};
MM.UI.Status.prototype.handleEvent = function(e) {
- var action = new MM.Action.SetStatus(MM.App.current, this._select.value || null);
+ let status = stringToStatus(this._select.value);
+ var action = new MM.Action.SetStatus(MM.App.current, status);
MM.App.action(action);
};
diff --git a/src/action.js b/src/action.js
index e4d62d6f..c5b1c9b2 100644
--- a/src/action.js
+++ b/src/action.js
@@ -177,14 +177,14 @@ MM.Action.SetValue.prototype.undo = function() {
MM.Action.SetStatus = function(item, status) {
this._item = item;
this._status = status;
- this._oldStatus = item.getStatus();
+ this._oldStatus = item.status;
}
MM.Action.SetStatus.prototype = Object.create(MM.Action.prototype);
MM.Action.SetStatus.prototype.perform = function() {
- this._item.setStatus(this._status);
+ this._item.status = this._status;
}
MM.Action.SetStatus.prototype.undo = function() {
- this._item.setStatus(this._oldStatus);
+ this._item.status = this._oldStatus;
}
MM.Action.SetIcon = function(item, icon) {
diff --git a/src/command/command.edit.js b/src/command/command.edit.js
index 910c344b..ec37eaba 100644
--- a/src/command/command.edit.js
+++ b/src/command/command.edit.js
@@ -122,7 +122,7 @@ MM.Command.Yes = Object.create(MM.Command, {
});
MM.Command.Yes.execute = function() {
var item = MM.App.current;
- var status = (item.getStatus() == "yes" ? null : "yes");
+ var status = (item.status === true ? null : true);
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
}
@@ -133,7 +133,7 @@ MM.Command.No = Object.create(MM.Command, {
});
MM.Command.No.execute = function() {
var item = MM.App.current;
- var status = (item.getStatus() == "no" ? null : "no");
+ var status = (item.status === false ? null : false);
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
}
@@ -144,7 +144,7 @@ MM.Command.Computed = Object.create(MM.Command, {
});
MM.Command.Computed.execute = function() {
var item = MM.App.current;
- var status = (item.getStatus() == "computed" ? null : "computed");
+ var status = (item.status == "computed" ? null : "computed");
var action = new MM.Action.SetStatus(item, status);
MM.App.action(action);
}
diff --git a/src/item.ts b/src/item.ts
index 916f1137..814ec604 100644
--- a/src/item.ts
+++ b/src/item.ts
@@ -8,6 +8,8 @@ declare global {
}
}
+export type ValueType = string | number | null;
+export type StatusType = "computed" | boolean | null;
const COLOR = "#999";
@@ -34,7 +36,8 @@ export default class Item {
protected _collapsed = false;
protected _icon: string | null = null;
protected _notes: string | null = null;
- protected _value: string | number | null = null;
+ protected _value: ValueType = null;
+ protected _status: StatusType = null;
dom = {
node: html.node("li"),
@@ -56,7 +59,6 @@ export default class Item {
protected _shape = null;
protected _autoShape = true;
protected _color = null;
- protected _status = null;
protected _side = null; /* side preference */
protected _oldText = "";
protected _computed = {
@@ -180,8 +182,14 @@ export default class Item {
if (data.icon) { this._icon = data.icon; }
if (data.value) { this._value = data.value; }
if (data.status) {
- this._status = data.status;
- if (this._status == "maybe") { this._status = "computed"; }
+ // backwards compatibility for yes/no
+ if (data.status == "yes") {
+ this._status = true;
+ } else if (data.status == "no") {
+ this._status = false;
+ } else {
+ this._status = data.status;
+ }
}
if (data.collapsed) { this.collapse(); }
if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
@@ -383,18 +391,26 @@ export default class Item {
}
get value() { return this._value; }
- set value(value: string | number | null) {
+ set value(value: ValueType) {
this._value = value;
this.update();
}
- setStatus(status) {
- this._status = status;
- this.update();
+ get resolvedStatus(): boolean | null {
+ let status = this._status;
+ if (status == "computed") {
+ return this.children.every(child => {
+ return (child.resolvedStatus !== false);
+ });
+ } else {
+ return status;
+ }
}
- getStatus() {
- return this._status;
+ get status() { return this._status; }
+ set status(status: StatusType) {
+ this._status = status;
+ this.update();
}
get icon() { return this._icon; }
@@ -403,10 +419,6 @@ export default class Item {
this.update();
}
- getComputedStatus() {
- return this._computed.status;
- }
-
setSide(side) {
this._side = side;
// FIXME no .update() call, because the whole map needs updating?
@@ -584,32 +596,14 @@ export default class Item {
}
_updateStatus() {
- this.dom.status.className = "status";
- this.dom.status.hidden = false;
-
- var status = this._status;
- if (this._status == "computed") {
- var childrenStatus = this.children.every(function(child) {
- return (child.getComputedStatus() !== false);
- });
- status = (childrenStatus ? "yes" : "no");
- }
-
- switch (status) {
- case "yes":
- this.dom.status.classList.add("yes");
- this._computed.status = true;
- break;
-
- case "no":
- this.dom.status.classList.add("no");
- this._computed.status = false;
- break;
-
- default:
- this._computed.status = null;
- this.dom.status.hidden = true;
- break;
+ const { resolvedStatus, dom } = this;
+ dom.status.className = "status";
+ dom.status.hidden = false;
+
+ switch (resolvedStatus) {
+ case true: dom.status.classList.add("yes"); break;
+ case false: dom.status.classList.add("no"); break;
+ default: dom.status.hidden = true; break;
}
}
diff --git a/src/ui/ui.status.js b/src/ui/ui.status.js
index d7de8615..e24fd5d9 100644
--- a/src/ui/ui.status.js
+++ b/src/ui/ui.status.js
@@ -1,13 +1,30 @@
+const STATUS_MAP = {
+ "yes": true,
+ "no": false,
+ "": null
+}
+
+function statusToString(status) {
+ for (let key in STATUS_MAP) {
+ if (STATUS_MAP[key] === status) { return key; }
+ }
+ return status;
+}
+
+function stringToStatus(str) {
+ return (str in STATUS_MAP ? STATUS_MAP[str] : str);
+}
MM.UI.Status = function() {
this._select = document.querySelector("#status");
this._select.addEventListener("change", this);
}
MM.UI.Status.prototype.update = function() {
- this._select.value = MM.App.current.getStatus() || "";
+ this._select.value = statusToString(MM.App.current.status);
}
MM.UI.Status.prototype.handleEvent = function(e) {
- var action = new MM.Action.SetStatus(MM.App.current, this._select.value || null);
+ let status = stringToStatus(this._select.value);
+ var action = new MM.Action.SetStatus(MM.App.current, status);
MM.App.action(action);
}
From 220cf053997b21d99f535dbe7b3b2d5c5536071a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Mon, 18 Oct 2021 12:43:41 +0200
Subject: [PATCH 09/42] item cleanup complete
---
css/shape.css | 4 +-
my-mind.js | 267 +++++++++++++++-------------------
src/action.js | 28 ++--
src/command/command.select.js | 4 +-
src/item.ts | 166 +++++++++------------
src/layout/layout.graph.js | 30 ++--
src/layout/layout.js | 3 +-
src/layout/layout.map.js | 16 +-
src/layout/layout.tree.js | 12 +-
src/mouse.js | 4 +-
src/shape/shape.js | 10 +-
src/shape/shape.underline.js | 3 +-
src/ui/ui.layout.js | 6 +-
src/ui/ui.shape.js | 6 +-
14 files changed, 247 insertions(+), 312 deletions(-)
diff --git a/css/shape.css b/css/shape.css
index aa21a84f..e937fa6c 100644
--- a/css/shape.css
+++ b/css/shape.css
@@ -1,11 +1,11 @@
-.shape-box > .content {
+[data-shape=box] > .content {
padding: 0.15em 0.4em;
background-color: #fff;
border: 1px solid #666;
border-radius: 3px;
}
-.shape-ellipse > .content {
+[data-shape=ellipse] > .content {
background-color: #fff;
border: 1px solid #666;
border-radius: 50%;
diff --git a/my-mind.js b/my-mind.js
index 70d443f1..0d5c0387 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -167,6 +167,10 @@
this._notes = null;
this._value = null;
this._status = null;
+ this._color = null;
+ this._side = null;
+ this._shape = null;
+ this._layout = null;
this.dom = {
node: node("li"),
content: node("div"),
@@ -180,15 +184,7 @@
canvas: node("canvas")
};
this.children = [];
- this._layout = null;
- this._shape = null;
- this._autoShape = true;
- this._color = null;
- this._side = null;
- this._oldText = "";
- this._computed = {
- status: null
- };
+ this.originalText = "";
const { dom } = this;
dom.node.classList.add("item");
dom.content.classList.add("content");
@@ -199,6 +195,7 @@
dom.text.classList.add("text");
dom.toggle.classList.add("toggle");
dom.children.classList.add("children");
+ dom.icon.classList.add("icon");
dom.node.append(dom.canvas, dom.content);
dom.content.append(dom.text, dom.notes);
dom.toggle.addEventListener("click", this);
@@ -285,7 +282,7 @@
if (this._layout) {
data.layout = this._layout.id;
}
- if (!this._autoShape) {
+ if (this._shape) {
data.shape = this._shape.id;
}
if (this._collapsed) {
@@ -332,7 +329,7 @@
this._layout = MM.Layout.getById(data.layout);
}
if (data.shape) {
- this.setShape(MM.Shape.getById(data.shape));
+ this.shape = MM.Shape.getById(data.shape);
}
(data.children || []).forEach((child) => {
this.insertChild(Item.fromJSON(child));
@@ -367,13 +364,12 @@
if (this._collapsed != !!data.collapsed) {
this[this._collapsed ? "expand" : "collapse"]();
}
- if (this.getOwnLayout() != data.layout) {
+ if (this.layout != data.layout) {
this._layout = MM.Layout.getById(data.layout);
dirty = 2;
}
- var s = this._autoShape ? null : this._shape.id;
- if (s != data.shape) {
- this.setShape(MM.Shape.getById(data.shape));
+ if (this.shape != data.shape) {
+ this.shape = MM.Shape.getById(data.shape);
}
(data.children || []).forEach((child, index) => {
if (index >= this.children.length) {
@@ -438,23 +434,14 @@
this.children.forEach((child) => child.update(childUpdateOptions));
}
publish("item-change", this);
- if (this._autoShape) {
- var autoShape = this._getAutoShape();
- if (autoShape != this._shape) {
- if (this._shape) {
- this._shape.unset(this);
- }
- this._shape = autoShape;
- this._shape.set(this);
- }
- }
- this._updateStatus();
- this._updateIcon();
+ this.updateStatus();
+ this.updateIcon();
+ this.updateValue();
this.dom.notes.classList.toggle("notes-indicator-visible", !!this.notes);
- this._updateValue();
this.dom.node.classList.toggle("collapsed", this._collapsed);
- this.getLayout().update(this);
- this.getShape().update(this);
+ this.dom.node.dataset.shape = this.resolvedShape.id;
+ this.resolvedLayout.update(this);
+ this.resolvedShape.update(this);
if (options.parent && !this.isRoot()) {
this.parent.update();
}
@@ -492,6 +479,13 @@
isCollapsed() {
return this._collapsed;
}
+ get value() {
+ return this._value;
+ }
+ set value(value) {
+ this._value = value;
+ this.update();
+ }
get resolvedValue() {
const value = this._value;
if (typeof value == "number") {
@@ -517,11 +511,11 @@
break;
}
}
- get value() {
- return this._value;
+ get status() {
+ return this._status;
}
- set value(value) {
- this._value = value;
+ set status(status) {
+ this._status = status;
this.update();
}
get resolvedStatus() {
@@ -534,13 +528,6 @@
return status;
}
}
- get status() {
- return this._status;
- }
- set status(status) {
- this._status = status;
- this.update();
- }
get icon() {
return this._icon;
}
@@ -548,52 +535,61 @@
this._icon = icon;
this.update();
}
- setSide(side) {
+ get side() {
+ return this._side;
+ }
+ set side(side) {
this._side = side;
- return this;
}
- getSide() {
- return this._side;
+ get color() {
+ return this._color;
}
- setColor(color) {
+ set color(color) {
this._color = color;
this.update({ children: true });
}
- getColor() {
- return this._color || (this.isRoot() ? COLOR : this.parent.getColor());
+ get resolvedColor() {
+ return this._color || (this.isRoot() ? COLOR : this.parent.resolvedColor);
}
- getOwnColor() {
- return this._color;
+ get layout() {
+ return this._layout;
}
- getLayout() {
- return this._layout || this.parent.getLayout();
+ set layout(layout) {
+ this._layout = layout;
+ this.update({ children: true });
}
- getOwnLayout() {
- return this._layout;
+ get resolvedLayout() {
+ return this._layout || this.parent.resolvedLayout;
}
setLayout(layout) {
this._layout = layout;
this.update({ children: true });
}
- getShape() {
+ get shape() {
return this._shape;
}
- getOwnShape() {
- return this._autoShape ? null : this._shape;
+ set shape(shape) {
+ this._shape = shape;
+ this.update();
}
- setShape(shape) {
+ get resolvedShape() {
if (this._shape) {
- this._shape.unset(this);
+ return this._shape;
}
- if (shape) {
- this._autoShape = false;
- this._shape = shape;
- } else {
- this._autoShape = true;
- this._shape = this._getAutoShape();
+ let depth = 0;
+ let node2 = this;
+ while (!node2.isRoot()) {
+ depth++;
+ node2 = node2.parent;
+ }
+ switch (depth) {
+ case 0:
+ return MM.Shape.Ellipse;
+ case 1:
+ return MM.Shape.Box;
+ default:
+ return MM.Shape.Underline;
}
- this._shape.set(this);
- this.update();
}
get map() {
let item = this.parent;
@@ -642,7 +638,7 @@
this.update();
}
startEditing() {
- this._oldText = this.text;
+ this.originalText = this.text;
this.dom.text.contentEditable = "true";
this.dom.text.focus();
document.execCommand("styleWithCSS", null, "false");
@@ -657,8 +653,8 @@
this.dom.text.blur();
this.dom.text.contentEditable = "false";
var result = this.dom.text.innerHTML;
- this.dom.text.innerHTML = this._oldText;
- this._oldText = "";
+ this.dom.text.innerHTML = this.originalText;
+ this.originalText = "";
this.update();
MM.Clipboard.focus();
return result;
@@ -687,23 +683,7 @@
break;
}
}
- _getAutoShape() {
- let depth = 0;
- let node2 = this;
- while (!node2.isRoot()) {
- depth++;
- node2 = node2.parent;
- }
- switch (depth) {
- case 0:
- return MM.Shape.Ellipse;
- case 1:
- return MM.Shape.Box;
- default:
- return MM.Shape.Underline;
- }
- }
- _updateStatus() {
+ updateStatus() {
const { resolvedStatus, dom } = this;
dom.status.className = "status";
dom.status.hidden = false;
@@ -719,7 +699,7 @@
break;
}
}
- _updateIcon() {
+ updateIcon() {
var icon = this._icon;
this.dom.icon.className = "icon";
this.dom.icon.hidden = !icon;
@@ -728,7 +708,7 @@
this.dom.icon.classList.add(icon);
}
}
- _updateValue() {
+ updateValue() {
const { dom, _value } = this;
if (_value === null) {
dom.value.hidden = true;
@@ -1174,11 +1154,11 @@
this._newSide = newSide || "";
this._oldParent = item.parent;
this._oldIndex = this._oldParent.children.indexOf(item);
- this._oldSide = item.getSide();
+ this._oldSide = item.side;
};
MM.Action.MoveItem.prototype = Object.create(MM.Action.prototype);
MM.Action.MoveItem.prototype.perform = function() {
- this._item.setSide(this._newSide);
+ this._item.side = this._newSide;
if (this._newIndex === null) {
this._newParent.insertChild(this._item);
} else {
@@ -1187,7 +1167,7 @@
MM.App.select(this._item);
};
MM.Action.MoveItem.prototype.undo = function() {
- this._item.setSide(this._oldSide);
+ this._item.side = this._oldSide;
this._oldParent.insertChild(this._item, this._oldIndex);
MM.App.select(this._newParent);
};
@@ -1195,7 +1175,7 @@
this._item = item;
this._parent = item.parent;
var children = this._parent.children;
- var sibling = this._parent.getLayout().pickSibling(this._item, diff);
+ var sibling = this._parent.resolvedLayout.pickSibling(this._item, diff);
this._sourceIndex = children.indexOf(this._item);
this._targetIndex = children.indexOf(sibling);
};
@@ -1209,7 +1189,7 @@
MM.Action.SetLayout = function(item, layout) {
this._item = item;
this._layout = layout;
- this._oldLayout = item.getOwnLayout();
+ this._oldLayout = item.layout;
};
MM.Action.SetLayout.prototype = Object.create(MM.Action.prototype);
MM.Action.SetLayout.prototype.perform = function() {
@@ -1221,26 +1201,26 @@
MM.Action.SetShape = function(item, shape) {
this._item = item;
this._shape = shape;
- this._oldShape = item.getOwnShape();
+ this._oldShape = item.shape;
};
MM.Action.SetShape.prototype = Object.create(MM.Action.prototype);
MM.Action.SetShape.prototype.perform = function() {
- this._item.setShape(this._shape);
+ this._item.shape = this._shape;
};
MM.Action.SetShape.prototype.undo = function() {
- this._item.setShape(this._oldShape);
+ this._item.shape = this._oldShape;
};
MM.Action.SetColor = function(item, color) {
this._item = item;
this._color = color;
- this._oldColor = item.getOwnColor();
+ this._oldColor = item.color;
};
MM.Action.SetColor.prototype = Object.create(MM.Action.prototype);
MM.Action.SetColor.prototype.perform = function() {
- this._item.setColor(this._color);
+ this._item.color = this._color;
};
MM.Action.SetColor.prototype.undo = function() {
- this._item.setColor(this._oldColor);
+ this._item.color = this._oldColor;
};
MM.Action.SetText = function(item, text) {
this._item = item;
@@ -1299,15 +1279,15 @@
MM.Action.SetSide = function(item, side) {
this._item = item;
this._side = side;
- this._oldSide = item.getSide();
+ this._oldSide = item.side;
};
MM.Action.SetSide.prototype = Object.create(MM.Action.prototype);
MM.Action.SetSide.prototype.perform = function() {
- this._item.setSide(this._side);
+ this._item.side = this._side;
this._item.map.update();
};
MM.Action.SetSide.prototype.undo = function() {
- this._item.setSide(this._oldSide);
+ this._item.side = this._oldSide;
this._item.map.update();
};
@@ -1917,7 +1897,7 @@
40: "bottom"
};
var dir = dirs[e.keyCode];
- var layout = MM.App.current.getLayout();
+ var layout = MM.App.current.resolvedLayout;
var item = layout.pick(MM.App.current, dir);
MM.App.select(item);
};
@@ -1979,7 +1959,7 @@
if (item.isRoot()) {
return item;
}
- var parentLayout = item.parent.getLayout();
+ var parentLayout = item.parent.resolvedLayout;
var thisChildDirection = parentLayout.getChildDirection(item);
if (thisChildDirection == dir) {
return item;
@@ -2088,7 +2068,7 @@
MM.Layout.Graph.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.parent.getLayout().getChildDirection(item);
+ side = item.parent.resolvedLayout.getChildDirection(item);
}
this._alignItem(item, side);
this._layoutItem(item, this.childDirection);
@@ -2167,10 +2147,10 @@
if (children.length == 0) {
return;
}
- const { contentPosition, contentSize, ctx } = item;
- ctx.strokeStyle = item.getColor();
+ const { contentPosition, contentSize, ctx, resolvedShape } = item;
+ ctx.strokeStyle = item.resolvedColor;
var R = this.SPACING_RANK / 2;
- var y1 = item.getShape().getVerticalAnchor(item);
+ var y1 = resolvedShape.getVerticalAnchor(item);
if (side == "left") {
var x1 = contentPosition[0] - 0.5;
} else {
@@ -2183,7 +2163,7 @@
if (children.length == 1) {
var child = children[0];
const { position } = child;
- var y2 = child.getShape().getVerticalAnchor(child) + position[1];
+ var y2 = child.resolvedShape.getVerticalAnchor(child) + position[1];
var x2 = this._getChildAnchor(child, side);
ctx.beginPath();
ctx.moveTo(x1, y1);
@@ -2206,8 +2186,8 @@
var xx = x + (side == "left" ? -R : R);
let p1 = c1.position;
let p2 = c2.position;
- var y1 = c1.getShape().getVerticalAnchor(c1) + p1[1];
- var y2 = c2.getShape().getVerticalAnchor(c2) + p2[1];
+ var y1 = c1.resolvedShape.getVerticalAnchor(c1) + p1[1];
+ var y2 = c2.resolvedShape.getVerticalAnchor(c2) + p2[1];
var x1 = this._getChildAnchor(c1, side);
var x2 = this._getChildAnchor(c2, side);
ctx.beginPath();
@@ -2220,7 +2200,7 @@
for (var i = 1; i < children.length - 1; i++) {
var c = children[i];
const { position } = c;
- var y = c.getShape().getVerticalAnchor(c) + position[1];
+ var y = c.resolvedShape.getVerticalAnchor(c) + position[1];
ctx.moveTo(x, y);
ctx.lineTo(this._getChildAnchor(c, side), y);
}
@@ -2230,17 +2210,17 @@
if (children.length == 0) {
return;
}
- const { contentSize, size, ctx } = item;
- ctx.strokeStyle = item.getColor();
+ const { contentSize, size, ctx, resolvedShape } = item;
+ ctx.strokeStyle = item.resolvedColor;
var R = this.SPACING_RANK / 2;
- var x = item.getShape().getHorizontalAnchor(item);
+ var x = resolvedShape.getHorizontalAnchor(item);
var height = children.length == 1 ? 2 * R : R;
if (side == "top") {
var y1 = size[1] - contentSize[1];
var y2 = y1 - height;
this._anchorToggle(item, x, y1, side);
} else {
- var y1 = item.getShape().getVerticalAnchor(item);
+ var y1 = resolvedShape.getVerticalAnchor(item);
var y2 = contentSize[1] + height;
this._anchorToggle(item, x, contentSize[1], side);
}
@@ -2257,8 +2237,8 @@
var y = Math.round(side == "top" ? size[1] - offset : offset) + 0.5;
const p1 = c1.position;
const p2 = c2.position;
- var x1 = c1.getShape().getHorizontalAnchor(c1) + p1[0];
- var x2 = c2.getShape().getHorizontalAnchor(c2) + p2[0];
+ var x1 = c1.resolvedShape.getHorizontalAnchor(c1) + p1[0];
+ var x2 = c2.resolvedShape.getHorizontalAnchor(c2) + p2[0];
var y1 = this._getChildAnchor(c1, side);
var y2 = this._getChildAnchor(c2, side);
ctx.beginPath();
@@ -2269,7 +2249,7 @@
for (var i = 1; i < children.length - 1; i++) {
var c = children[i];
const { position } = c;
- var x = c.getShape().getHorizontalAnchor(c) + position[0];
+ var x = c.resolvedShape.getHorizontalAnchor(c) + position[0];
ctx.moveTo(x, y);
ctx.lineTo(x, this._getChildAnchor(c, side));
}
@@ -2300,7 +2280,7 @@
MM.Layout.Tree.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.parent.getLayout().getChildDirection(item);
+ side = item.parent.resolvedLayout.getChildDirection(item);
}
this._alignItem(item, side);
this._layoutItem(item, this.childDirection);
@@ -2342,7 +2322,7 @@
return bbox;
};
MM.Layout.Tree._drawLines = function(item, side) {
- const { contentSize, size, ctx } = item;
+ const { contentSize, size, ctx, resolvedShape } = item;
var R = this.SPACING_RANK / 4;
var x = (side == "left" ? size[0] - 2 * R : 2 * R) + 0.5;
this._anchorToggle(item, x, contentSize[1], "bottom");
@@ -2350,16 +2330,16 @@
if (children.length == 0 || item.isCollapsed()) {
return;
}
- ctx.strokeStyle = item.getColor();
- var y1 = item.getShape().getVerticalAnchor(item);
+ ctx.strokeStyle = item.resolvedColor;
+ var y1 = resolvedShape.getVerticalAnchor(item);
var last = children[children.length - 1];
- var y2 = last.getShape().getVerticalAnchor(last) + last.position[1];
+ var y2 = last.resolvedShape.getVerticalAnchor(last) + last.position[1];
ctx.beginPath();
ctx.moveTo(x, y1);
ctx.lineTo(x, y2 - R);
for (var i = 0; i < children.length; i++) {
var c = children[i];
- var y = c.getShape().getVerticalAnchor(c) + c.position[1];
+ var y = c.resolvedShape.getVerticalAnchor(c) + c.position[1];
var anchor = this._getChildAnchor(c, side);
ctx.moveTo(x, y - R);
ctx.arcTo(x, y, anchor, y, R);
@@ -2390,21 +2370,21 @@
while (!child.parent.isRoot()) {
child = child.parent;
}
- var side = child.getSide();
+ var side = child.side;
if (side) {
return side;
}
var counts = { left: 0, right: 0 };
var children = child.parent.children;
for (var i = 0; i < children.length; i++) {
- var side = children[i].getSide();
+ var side = children[i].side;
if (!side) {
side = counts.right > counts.left ? "left" : "right";
- children[i].setSide(side);
+ children[i].side = side;
}
counts[side]++;
}
- return child.getSide();
+ return child.side;
};
MM.Layout.Map.pickSibling = function(item, dir) {
if (item.isRoot()) {
@@ -2463,18 +2443,18 @@
if (children.length == 0 || item.isCollapsed()) {
return;
}
- const { contentSize, contentPosition, ctx } = item;
+ const { contentSize, contentPosition, ctx, resolvedShape } = item;
var x1 = contentPosition[0] + contentSize[0] / 2;
- var y1 = item.getShape().getVerticalAnchor(item);
+ var y1 = resolvedShape.getVerticalAnchor(item);
var half = this.LINE_THICKNESS / 2;
for (var i = 0; i < children.length; i++) {
var child = children[i];
var x2 = this._getChildAnchor(child, side);
- var y2 = child.getShape().getVerticalAnchor(child) + child.position[1];
+ var y2 = child.resolvedShape.getVerticalAnchor(child) + child.position[1];
var angle = Math.atan2(y2 - y1, x2 - x1) + Math.PI / 2;
var dx = Math.cos(angle) * half;
var dy = Math.sin(angle) * half;
- ctx.fillStyle = ctx.strokeStyle = child.getColor();
+ ctx.fillStyle = ctx.strokeStyle = child.resolvedColor;
ctx.beginPath();
ctx.moveTo(x1 - dx, y1 - dy);
ctx.quadraticCurveTo((x2 + x1) / 2, y2, x2, y2);
@@ -2488,14 +2468,8 @@
MM.Shape = Object.create(MM.Repo, {
VERTICAL_OFFSET: { value: 0.5 }
});
- MM.Shape.set = function(item) {
- item.dom.node.classList.add("shape-" + this.id);
- };
- MM.Shape.unset = function(item) {
- item.dom.node.classList.remove("shape-" + this.id);
- };
MM.Shape.update = function(item) {
- item.dom.content.style.borderColor = item.getColor();
+ item.dom.content.style.borderColor = item.resolvedColor;
};
MM.Shape.getHorizontalAnchor = function(item) {
const { contentPosition, contentSize } = item;
@@ -2514,10 +2488,11 @@
});
MM.Shape.Underline.update = function(item) {
const { contentPosition, contentSize, ctx } = item;
- ctx.strokeStyle = item.getColor();
+ ctx.strokeStyle = item.resolvedColor;
var left = contentPosition[0];
var right = left + contentSize[0];
var top = this.getVerticalAnchor(item);
+ console.log(left, right, top, item.resolvedColor);
ctx.beginPath();
ctx.moveTo(left, top);
ctx.lineTo(right, top);
@@ -3452,7 +3427,7 @@
};
MM.UI.Layout.prototype.update = function() {
var value = "";
- var layout = MM.App.current.getOwnLayout();
+ var layout = MM.App.current.layout;
if (layout) {
value = layout.id;
}
@@ -3485,7 +3460,7 @@
};
MM.UI.Shape.prototype.update = function() {
var value = "";
- var shape = MM.App.current.getOwnShape();
+ var shape = MM.App.current.shape;
if (shape) {
value = shape.id;
}
@@ -4522,7 +4497,7 @@
case "sibling":
var index = target.parent.children.indexOf(target);
var targetIndex = index + (state.direction == "right" || state.direction == "bottom" ? 1 : 0);
- var action = new MM.Action.MoveItem(this._item, target.parent, targetIndex, target.getSide());
+ var action = new MM.Action.MoveItem(this._item, target.parent, targetIndex, target.side);
break;
default:
return;
@@ -4556,7 +4531,7 @@
state.result = "append";
} else {
state.result = "sibling";
- var childDirection = target.parent.getLayout().getChildDirection(target);
+ var childDirection = target.parent.resolvedLayout.getChildDirection(target);
if (childDirection == "left" || childDirection == "right") {
state.direction = closest.dy < 0 ? "bottom" : "top";
} else {
diff --git a/src/action.js b/src/action.js
index c5b1c9b2..27242c18 100644
--- a/src/action.js
+++ b/src/action.js
@@ -69,11 +69,11 @@ MM.Action.MoveItem = function(item, newParent, newIndex, newSide) {
this._newSide = newSide || "";
this._oldParent = item.parent;
this._oldIndex = this._oldParent.children.indexOf(item);
- this._oldSide = item.getSide();
+ this._oldSide = item.side;
}
MM.Action.MoveItem.prototype = Object.create(MM.Action.prototype);
MM.Action.MoveItem.prototype.perform = function() {
- this._item.setSide(this._newSide);
+ this._item.side = this._newSide;
if (this._newIndex === null) {
this._newParent.insertChild(this._item);
} else {
@@ -82,7 +82,7 @@ MM.Action.MoveItem.prototype.perform = function() {
MM.App.select(this._item);
}
MM.Action.MoveItem.prototype.undo = function() {
- this._item.setSide(this._oldSide);
+ this._item.side = this._oldSide;
this._oldParent.insertChild(this._item, this._oldIndex);
MM.App.select(this._newParent);
}
@@ -92,7 +92,7 @@ MM.Action.Swap = function(item, diff) {
this._parent = item.parent;
var children = this._parent.children;
- var sibling = this._parent.getLayout().pickSibling(this._item, diff);
+ var sibling = this._parent.resolvedLayout.pickSibling(this._item, diff);
this._sourceIndex = children.indexOf(this._item);
this._targetIndex = children.indexOf(sibling);
@@ -108,7 +108,7 @@ MM.Action.Swap.prototype.undo = function() {
MM.Action.SetLayout = function(item, layout) {
this._item = item;
this._layout = layout;
- this._oldLayout = item.getOwnLayout();
+ this._oldLayout = item.layout;
}
MM.Action.SetLayout.prototype = Object.create(MM.Action.prototype);
MM.Action.SetLayout.prototype.perform = function() {
@@ -121,27 +121,27 @@ MM.Action.SetLayout.prototype.undo = function() {
MM.Action.SetShape = function(item, shape) {
this._item = item;
this._shape = shape;
- this._oldShape = item.getOwnShape();
+ this._oldShape = item.shape;
}
MM.Action.SetShape.prototype = Object.create(MM.Action.prototype);
MM.Action.SetShape.prototype.perform = function() {
- this._item.setShape(this._shape);
+ this._item.shape = this._shape;
}
MM.Action.SetShape.prototype.undo = function() {
- this._item.setShape(this._oldShape);
+ this._item.shape = this._oldShape;
}
MM.Action.SetColor = function(item, color) {
this._item = item;
this._color = color;
- this._oldColor = item.getOwnColor();
+ this._oldColor = item.color;
}
MM.Action.SetColor.prototype = Object.create(MM.Action.prototype);
MM.Action.SetColor.prototype.perform = function() {
- this._item.setColor(this._color);
+ this._item.color = this._color;
}
MM.Action.SetColor.prototype.undo = function() {
- this._item.setColor(this._oldColor);
+ this._item.color = this._oldColor;
}
MM.Action.SetText = function(item, text) {
@@ -203,14 +203,14 @@ MM.Action.SetIcon.prototype.undo = function() {
MM.Action.SetSide = function(item, side) {
this._item = item;
this._side = side;
- this._oldSide = item.getSide();
+ this._oldSide = item.side;
}
MM.Action.SetSide.prototype = Object.create(MM.Action.prototype);
MM.Action.SetSide.prototype.perform = function() {
- this._item.setSide(this._side);
+ this._item.side = this._side;
this._item.map.update();
}
MM.Action.SetSide.prototype.undo = function() {
- this._item.setSide(this._oldSide);
+ this._item.side = this._oldSide;
this._item.map.update();
}
diff --git a/src/command/command.select.js b/src/command/command.select.js
index 5beef60f..71e951b5 100644
--- a/src/command/command.select.js
+++ b/src/command/command.select.js
@@ -19,8 +19,8 @@ MM.Command.Select.execute = function(e) {
}
var dir = dirs[e.keyCode];
- var layout = MM.App.current.getLayout();
- var item = /*MM.App.map*/layout.pick(MM.App.current, dir);
+ var layout = MM.App.current.resolvedLayout;
+ var item = layout.pick(MM.App.current, dir);
MM.App.select(item);
}
diff --git a/src/item.ts b/src/item.ts
index 814ec604..69444c42 100644
--- a/src/item.ts
+++ b/src/item.ts
@@ -10,6 +10,10 @@ declare global {
export type ValueType = string | number | null;
export type StatusType = "computed" | boolean | null;
+export type Side = "left" | "right";
+
+type Layout = any | null; // FIXME
+type Shape = any | null; // FIXME
const COLOR = "#999";
@@ -38,6 +42,10 @@ export default class Item {
protected _notes: string | null = null;
protected _value: ValueType = null;
protected _status: StatusType = null;
+ protected _color: string | null = null;
+ protected _side: Side | null = null; // side preference
+ protected _shape: Shape = null;
+ protected _layout: Layout = null;
dom = {
node: html.node("li"),
@@ -55,15 +63,7 @@ export default class Item {
readonly children: Item[] = [];
// todo
- protected _layout = null;
- protected _shape = null;
- protected _autoShape = true;
- protected _color = null;
- protected _side = null; /* side preference */
- protected _oldText = "";
- protected _computed = {
- status: null
- }
+ protected originalText = "";
static fromJSON(data) {
return new this().fromJSON(data);
@@ -80,6 +80,7 @@ export default class Item {
dom.text.classList.add("text");
dom.toggle.classList.add("toggle");
dom.children.classList.add("children");
+ dom.icon.classList.add("icon");
dom.node.append(dom.canvas, dom.content);
dom.content.append(dom.text, dom.notes); /* status+value are appended in layout */
@@ -160,7 +161,7 @@ export default class Item {
if (this._value) { data.value = this._value; }
if (this._status) { data.status = this._status; }
if (this._layout) { data.layout = this._layout.id; }
- if (!this._autoShape) { data.shape = this._shape.id; }
+ if (this._shape) { data.shape = this._shape.id; }
if (this._collapsed) { data.collapsed = 1; }
if (this.children.length) {
data.children = this.children.map(child => child.toJSON());
@@ -193,7 +194,7 @@ export default class Item {
}
if (data.collapsed) { this.collapse(); }
if (data.layout) { this._layout = MM.Layout.getById(data.layout); }
- if (data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
+ if (data.shape) { this.shape = MM.Shape.getById(data.shape); }
(data.children || []).forEach(child => {
this.insertChild(Item.fromJSON(child));
@@ -234,13 +235,12 @@ export default class Item {
if (this._collapsed != !!data.collapsed) { this[this._collapsed ? "expand" : "collapse"](); }
- if (this.getOwnLayout() != data.layout) {
+ if (this.layout != data.layout) {
this._layout = MM.Layout.getById(data.layout);
dirty = 2;
}
- var s = (this._autoShape ? null : this._shape.id);
- if (s != data.shape) { this.setShape(MM.Shape.getById(data.shape)); }
+ if (this.shape != data.shape) { this.shape = MM.Shape.getById(data.shape); }
(data.children || []).forEach((child, index) => {
if (index >= this.children.length) { /* new child */
@@ -256,7 +256,7 @@ export default class Item {
}
});
- /* remove dead children */
+ // remove dead children
var newLength = (data.children || []).length;
while (this.children.length > newLength) { this.removeChild(this.children[this.children.length-1]); }
@@ -315,25 +315,16 @@ export default class Item {
pubsub.publish("item-change", this);
- if (this._autoShape) { /* check for changed auto-shape */
- var autoShape = this._getAutoShape();
- if (autoShape != this._shape) {
- if (this._shape) { this._shape.unset(this); }
- this._shape = autoShape;
- this._shape.set(this);
- }
- }
+ this.updateStatus();
+ this.updateIcon();
+ this.updateValue();
- this._updateStatus();
- this._updateIcon();
this.dom.notes.classList.toggle("notes-indicator-visible", !!this.notes);
-
- this._updateValue();
-
this.dom.node.classList.toggle("collapsed", this._collapsed);
- this.getLayout().update(this);
- this.getShape().update(this);
+ this.dom.node.dataset.shape = this.resolvedShape.id; // applies css => modifies dimensions (necessary for layout)
+ this.resolvedLayout.update(this);
+ this.resolvedShape.update(this); // draws into canvas -> must be called after layout FIXME refactor after svg
// recurse upwards?
if (options.parent && !this.isRoot()) { this.parent.update(); }
@@ -369,6 +360,11 @@ export default class Item {
return this._collapsed;
}
+ get value() { return this._value; }
+ set value(value: ValueType) {
+ this._value = value;
+ this.update();
+ }
get resolvedValue(): number {
const value = this._value;
@@ -390,12 +386,11 @@ export default class Item {
}
}
- get value() { return this._value; }
- set value(value: ValueType) {
- this._value = value;
+ get status() { return this._status; }
+ set status(status: StatusType) {
+ this._status = status;
this.update();
}
-
get resolvedStatus(): boolean | null {
let status = this._status;
if (status == "computed") {
@@ -407,47 +402,34 @@ export default class Item {
}
}
- get status() { return this._status; }
- set status(status: StatusType) {
- this._status = status;
- this.update();
- }
-
get icon() { return this._icon; }
set icon(icon: string) {
this._icon = icon;
this.update();
}
- setSide(side) {
+ get side() { return this._side; }
+ set side(side: Side) {
this._side = side;
- // FIXME no .update() call, because the whole map needs updating?
- return this;
- }
-
- getSide() {
- return this._side;
+ // no .update() call, because the whole map needs updating
}
- setColor(color) {
+ get color() { return this._color; }
+ set color(color: string | null) {
this._color = color;
this.update({children:true});
}
-
- getColor() {
- return this._color || (this.isRoot() ? COLOR : this.parent.getColor());
- }
-
- getOwnColor() {
- return this._color;
+ get resolvedColor(): string {
+ return this._color || (this.isRoot() ? COLOR : this.parent.resolvedColor);
}
- getLayout() {
- return this._layout || this.parent.getLayout();
+ get layout() { return this._layout; }
+ set layout(layout: Layout) {
+ this._layout = layout;
+ this.update({children:true});
}
-
- getOwnLayout() {
- return this._layout;
+ get resolvedLayout() {
+ return this._layout || this.parent.resolvedLayout;
}
setLayout(layout) {
@@ -455,27 +437,25 @@ export default class Item {
this.update({children:true});
}
- getShape() {
- return this._shape;
- }
-
- getOwnShape() {
- return (this._autoShape ? null : this._shape);
+ get shape() { return this._shape; }
+ set shape(shape: Shape) {
+ this._shape = shape;
+ this.update()
}
+ get resolvedShape() {
+ if (this._shape) { return this._shape; }
- setShape(shape) {
- if (this._shape) { this._shape.unset(this); }
-
- if (shape) {
- this._autoShape = false;
- this._shape = shape;
- } else {
- this._autoShape = true;
- this._shape = this._getAutoShape();
+ let depth = 0;
+ let node: Item | null = this;
+ while (!node.isRoot()) {
+ depth++;
+ node = node.parent;
+ }
+ switch (depth) {
+ case 0: return MM.Shape.Ellipse;
+ case 1: return MM.Shape.Box;
+ default: return MM.Shape.Underline;
}
-
- this._shape.set(this);
- this.update();
}
get map() {
@@ -531,7 +511,7 @@ export default class Item {
}
startEditing() {
- this._oldText = this.text;
+ this.originalText = this.text;
this.dom.text.contentEditable = "true";
this.dom.text.focus(); /* switch to 2b */
document.execCommand("styleWithCSS", null, "false");
@@ -549,8 +529,8 @@ export default class Item {
this.dom.text.blur();
this.dom.text.contentEditable = "false";
var result = this.dom.text.innerHTML;
- this.dom.text.innerHTML = this._oldText;
- this._oldText = "";
+ this.dom.text.innerHTML = this.originalText;
+ this.originalText = "";
this.update(); /* text changed */
@@ -581,21 +561,7 @@ export default class Item {
}
}
- _getAutoShape() {
- let depth = 0;
- let node: Item | null = this;
- while (!node.isRoot()) {
- depth++;
- node = node.parent;
- }
- switch (depth) {
- case 0: return MM.Shape.Ellipse;
- case 1: return MM.Shape.Box;
- default: return MM.Shape.Underline;
- }
- }
-
- _updateStatus() {
+ protected updateStatus() {
const { resolvedStatus, dom } = this;
dom.status.className = "status";
dom.status.hidden = false;
@@ -607,19 +573,19 @@ export default class Item {
}
}
- _updateIcon() {
+ protected updateIcon() {
var icon = this._icon;
- this.dom.icon.className = "icon";
+ this.dom.icon.className = "icon"; // completely reset
this.dom.icon.hidden = !icon;
if (icon) {
- this.dom.icon.classList.add('fa');
+ this.dom.icon.classList.add("fa");
this.dom.icon.classList.add(icon);
}
}
- _updateValue() {
+ protected updateValue() {
const { dom, _value } = this;
if (_value === null) {
diff --git a/src/layout/layout.graph.js b/src/layout/layout.graph.js
index fd79ba72..00be9e97 100644
--- a/src/layout/layout.graph.js
+++ b/src/layout/layout.graph.js
@@ -20,7 +20,7 @@ MM.Layout.Graph.create = function(direction, id, label) {
MM.Layout.Graph.update = function(item) {
var side = this.childDirection;
if (!item.isRoot()) {
- side = item.parent.getLayout().getChildDirection(item);
+ side = item.parent.resolvedLayout.getChildDirection(item);
}
this._alignItem(item, side);
@@ -106,13 +106,13 @@ MM.Layout.Graph._drawLinesVertical = function(item, side) {
MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
if (children.length == 0) { return; }
- const { contentPosition, contentSize, ctx } = item;
+ const { contentPosition, contentSize, ctx, resolvedShape } = item;
- ctx.strokeStyle = item.getColor();
+ ctx.strokeStyle = item.resolvedColor;
var R = this.SPACING_RANK/2;
/* first part */
- var y1 = item.getShape().getVerticalAnchor(item);
+ var y1 = resolvedShape.getVerticalAnchor(item);
if (side == "left") {
var x1 = contentPosition[0] - 0.5;
} else {
@@ -125,7 +125,7 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
if (children.length == 1) {
var child = children[0];
const { position } = child;
- var y2 = child.getShape().getVerticalAnchor(child) + position[1];
+ var y2 = child.resolvedShape.getVerticalAnchor(child) + position[1];
var x2 = this._getChildAnchor(child, side);
ctx.beginPath();
ctx.moveTo(x1, y1);
@@ -154,8 +154,8 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
let p1 = c1.position;
let p2 = c2.position;
- var y1 = c1.getShape().getVerticalAnchor(c1) + p1[1];
- var y2 = c2.getShape().getVerticalAnchor(c2) + p2[1];
+ var y1 = c1.resolvedShape.getVerticalAnchor(c1) + p1[1];
+ var y2 = c2.resolvedShape.getVerticalAnchor(c2) + p2[1];
var x1 = this._getChildAnchor(c1, side);
var x2 = this._getChildAnchor(c2, side);
@@ -170,7 +170,7 @@ MM.Layout.Graph._drawHorizontalConnectors = function(item, side, children) {
for (var i=1; i counts.left ? "left" : "right");
- children[i].setSide(side);
+ children[i].side = side;
}
counts[side]++;
}
- return child.getSide();
+ return child.side;
}
MM.Layout.Map.pickSibling = function(item, dir) {
@@ -104,22 +104,22 @@ MM.Layout.Map._layoutRoot = function(item) {
MM.Layout.Map._drawRootConnectors = function(item, side, children) {
if (children.length == 0 || item.isCollapsed()) { return; }
- const { contentSize, contentPosition, ctx } = item;
+ const { contentSize, contentPosition, ctx, resolvedShape } = item;
var x1 = contentPosition[0] + contentSize[0]/2;
- var y1 = item.getShape().getVerticalAnchor(item);
+ var y1 = resolvedShape.getVerticalAnchor(item);
var half = this.LINE_THICKNESS/2;
for (var i=0;i
Date: Mon, 18 Oct 2021 14:04:18 +0200
Subject: [PATCH 10/42] console
---
my-mind.js | 1 -
src/shape/shape.underline.js | 1 -
2 files changed, 2 deletions(-)
diff --git a/my-mind.js b/my-mind.js
index 0d5c0387..6ff3fbf0 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -2492,7 +2492,6 @@
var left = contentPosition[0];
var right = left + contentSize[0];
var top = this.getVerticalAnchor(item);
- console.log(left, right, top, item.resolvedColor);
ctx.beginPath();
ctx.moveTo(left, top);
ctx.lineTo(right, top);
diff --git a/src/shape/shape.underline.js b/src/shape/shape.underline.js
index 2d30c2f2..77b91ab6 100644
--- a/src/shape/shape.underline.js
+++ b/src/shape/shape.underline.js
@@ -13,7 +13,6 @@ MM.Shape.Underline.update = function(item) {
var right = left + contentSize[0];
var top = this.getVerticalAnchor(item);
- console.log(left, right, top, item.resolvedColor)
ctx.beginPath();
ctx.moveTo(left, top);
From 39072e5f8c638a4ee6eccc87148486d7173a40cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=BD=C3=A1ra?=
Date: Mon, 18 Oct 2021 22:54:04 +0200
Subject: [PATCH 11/42] first attempt at svg-based tree
---
css/item.css | 12 +-
css/shape.css | 12 +-
css/style.css | 4 +
index.html | 6 +-
my-mind.js | 292 ++++++++++++++++++++-----------------
src/item.ts | 83 ++++++-----
src/layout/layout.graph.js | 17 +--
src/layout/layout.js | 4 +
src/layout/layout.map.js | 11 +-
src/layout/layout.tree.js | 15 +-
src/map.js | 13 +-
src/mouse.js | 2 -
src/svg.ts | 13 ++
13 files changed, 275 insertions(+), 209 deletions(-)
diff --git a/css/item.css b/css/item.css
index 0fc1de53..910b3ab3 100644
--- a/css/item.css
+++ b/css/item.css
@@ -1,28 +1,24 @@
-.item {
- position: absolute;
-}
-
.item.cut {
opacity: 0.5;
}
-.item.collapsed .children {
+.item.collapsed .item {
display: none;
}
.content {
position: relative;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
white-space: nowrap;
cursor: pointer;
}
.content > * {
- vertical-align: middle;
+ flex: none;
}
.text {
- display: inline-block;
text-align: center;
min-width: 0.5em;
min-height: 1.3em;
diff --git a/css/shape.css b/css/shape.css
index e937fa6c..1c762103 100644
--- a/css/shape.css
+++ b/css/shape.css
@@ -1,11 +1,11 @@
-[data-shape=box] > .content {
+[data-shape=box] > foreignObject > .content {
padding: 0.15em 0.4em;
background-color: #fff;
border: 1px solid #666;
border-radius: 3px;
}
-[data-shape=ellipse] > .content {
+[data-shape=ellipse] > foreignObject > .content {
background-color: #fff;
border: 1px solid #666;
border-radius: 50%;
@@ -14,25 +14,25 @@
/* current */
-.current > .content {
+.current > foreignObject > .content {
background-color: rgba(255, 255, 187, 0.9);
}
/* root */
-#port > .item > .content {
+svg > .item > foreignObject > .content {
font-weight: bold;
border-width: 2px;
font-size: 140%;
}
-#port > .item > .toggle {
+svg > .item > .toggle {
display: none;
}
/* 1st children */
-#port > .item > .children > .item > .content {
+svg > .item > .item > foreignObject > .content {
border-width: 2px;
font-size: 120%;
}
diff --git a/css/style.css b/css/style.css
index a5a32854..b7b8e5c0 100644
--- a/css/style.css
+++ b/css/style.css
@@ -25,6 +25,10 @@ ul {
list-style: none;
}
+svg {
+ position: absolute;
+}
+
#port {
overflow: hidden;
font-size: 15px;
diff --git a/index.html b/index.html
index 47e8d29f..5dca0f49 100644
--- a/index.html
+++ b/index.html
@@ -29,7 +29,7 @@
gtag('config', 'UA-383250-18');
-
+
@@ -904,7 +904,7 @@
Storage
-
+
Local files are suitable for loading/saving files from other mindmapping applications.
@@ -1008,7 +1008,7 @@
Help
Topic Notes
-
+
-
+
Storage
@@ -988,7 +987,7 @@
-
+
Help
Navigation
@@ -1004,7 +1003,7 @@
Help
-
+
Topic Notes
@@ -1025,9 +1024,6 @@
Topic Notes
+
-