diff --git a/web/js/CacheClassExplorer.js b/web/js/CacheClassExplorer.js
index da4ad5a..bbd3639 100644
--- a/web/js/CacheClassExplorer.js
+++ b/web/js/CacheClassExplorer.js
@@ -22,6 +22,8 @@ var CacheClassExplorer = function (treeViewContainer, classViewContainer) {
showSettingsButton: id("button.showSettings"),
helpButton: id("button.showHelp"),
infoButton: id("button.showInfo"),
+ saveViewButton: id("button.saveView"),
+ saveViewIcon: id("saveViewIcon"),
methodCodeView: id("methodCodeView"),
closeMethodCodeView: id("closeMethodCodeView"),
methodLabel: id("methodLabel"),
@@ -258,4 +260,15 @@ CacheClassExplorer.prototype.init = function () {
enableSVGDownload(this.classTree);
+ // default icon
+ this.elements.saveViewIcon.src = lib.image.pin;
+ this.elements.saveViewButton.addEventListener("click", function () {
+ self.classView.switchViewSave();
+ if (self.classView.viewSaving) {
+ self.classView.saveView();
+ } else {
+ self.source.resetView( self.NAMESPACE + ":" + self.classView.CURRENT_RENDER_NAME );
+ }
+ });
+
};
\ No newline at end of file
diff --git a/web/js/ClassView.js b/web/js/ClassView.js
index 16f03db..970f1f8 100644
--- a/web/js/ClassView.js
+++ b/web/js/ClassView.js
@@ -25,6 +25,16 @@ var ClassView = function (parent, container) {
this.HIGHLIGHTED_VIEW = null;
this.SEARCH_INDEX = 0;
+ this.CURRENT_RENDER_NAME = "";
+
+ this.viewSaving = false;
+
+ /**
+ * Not to perform save too frequentry, this variable is used to control saving frequency.
+ * @type {number}
+ */
+ this.saveTimeout = 0;
+
this.init();
};
@@ -446,9 +456,10 @@ ClassView.prototype.getPropertyHoverText = function (prop, type) {
/**
* @param {string} name
* @param classMetaData
+ * @param saved - Object with saved data.
* @returns {joint.shapes.uml.Class}
*/
-ClassView.prototype.createClassInstance = function (name, classMetaData) {
+ClassView.prototype.createClassInstance = function (name, classMetaData, saved) {
var classParams = classMetaData["parameters"],
classProps = classMetaData["properties"],
@@ -458,7 +469,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
keyWordsArray = [name],
self = this;
- var classInstance = new joint.shapes.uml.Class({
+ var setup = {
name: [{
text: name,
clickHandler: function () {
@@ -549,9 +560,18 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
classSigns: this.getClassSigns(classMetaData),
classType: classMetaData.ClassType || "registered",
SYMBOL_12_WIDTH: self.SYMBOL_12_WIDTH
+ };
+
+ if (saved && saved.position) setup.position = saved.position;
+
+ var classInstance = new joint.shapes.uml.Class(setup);
+
+ classInstance.on("change:position", function () {
+ self.prepareToSave();
});
classInstance.SEARCH_KEYWORDS = keyWordsArray.join(",").toLowerCase();
+ classInstance.NAME = name;
this.objects.push(classInstance);
this.graph.addCell(classInstance);
@@ -559,6 +579,15 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) {
};
+ClassView.prototype.prepareToSave = function () {
+
+ if (!this.viewSaving) return;
+
+ if (this.saveTimeout) clearTimeout(this.saveTimeout);
+ this.saveTimeout = setTimeout(this.saveView.bind(this), 700);
+
+};
+
ClassView.prototype.showMethodCode = function (className, methodName) {
var self = this;
@@ -701,6 +730,8 @@ ClassView.prototype.confirmRender = function (data) {
uml = joint.shapes.uml, relFrom, relTo,
classes = {}, connector;
+ this.switchViewSave(!!data.savedView);
+
this.filterInherits(data);
// Reset view and zoom again because it may cause visual damage to icons.
@@ -715,7 +746,11 @@ ClassView.prototype.confirmRender = function (data) {
for (className in data["classes"]) {
classes[className] = {
- instance: this.createClassInstance(className, data["classes"][className])
+ instance: this.createClassInstance(
+ className,
+ data["classes"][className],
+ ((data.savedView || {}).classes || {})[className]
+ )
};
}
@@ -730,7 +765,11 @@ ClassView.prototype.confirmRender = function (data) {
relTo = (classes[pp] || {}).instance;
if (!relTo) {
classes[pp] = {
- instance: relTo = self.createClassInstance(pp, {})
+ instance: relTo = self.createClassInstance(
+ pp,
+ {},
+ ((data.savedView || {}).classes || {})[pp]
+ )
};
}
if (relFrom && relTo) {
@@ -780,13 +819,15 @@ ClassView.prototype.confirmRender = function (data) {
link("aggregation");
link("association");
- joint.layout.DirectedGraph.layout(this.graph, {
- setLinkVertices: false,
- nodeSep: 100,
- rankSep: 100,
- edgeSep: 20,
- rankDir: data.layoutDirection || "TB"
- });
+ if (!data.savedView) {
+ joint.layout.DirectedGraph.layout(this.graph, {
+ setLinkVertices: false,
+ nodeSep: 100,
+ rankSep: 100,
+ edgeSep: 20,
+ rankDir: data.layoutDirection || "TB"
+ });
+ }
this.updateSizes();
@@ -794,16 +835,66 @@ ClassView.prototype.confirmRender = function (data) {
this.paper.findViewByModel(this.links[i]).update();
}
- var bb = this.paper.getContentBBox(), q = this.paper;
+ var bb = this.paper.getContentBBox(),
+ q = this.paper;
+
this.paper.setOrigin(
q.options.width/2 - bb.width/2,
q.options.height/2 - Math.min(q.options.height/2 - 100, bb.height/2)
);
+ if (data.savedView) this.restoreView(data.savedView);
+
this.onRendered();
};
+ClassView.prototype.switchViewSave = function ( saving ) {
+
+ if (typeof saving === "undefined") saving = !this.viewSaving;
+ this.viewSaving = !!saving;
+ this.cacheClassExplorer.elements.saveViewIcon.src = lib.image["pin" + (saving ? "Active" : "")];
+
+};
+
+ClassView.prototype.saveView = function () {
+
+ if (!this.CURRENT_RENDER_NAME || !this.cacheClassExplorer.NAMESPACE) return;
+
+ var self = this,
+ name = this.cacheClassExplorer.NAMESPACE + ":" + this.CURRENT_RENDER_NAME;
+
+ var saved = {
+ classes: {},
+ zoom: this.PAPER_SCALE,
+ origin: {
+ x: Math.round(self.paper.options.origin.x),
+ y: Math.round(self.paper.options.origin.y)
+ }
+ };
+
+ this.graph.getElements().forEach(function (element) {
+ if (!element.NAME) return;
+ saved.classes[element.NAME] = {
+ position: element.attributes.position
+ }
+ });
+
+ this.cacheClassExplorer.source.saveView(name, saved);
+
+};
+
+ClassView.prototype.restoreView = function (data) {
+
+ // data.classes are parsed during class creation
+ if (data.zoom) { // do not swap with origin set
+ this.PAPER_SCALE = data.zoom;
+ this.zoom(0);
+ }
+ if (data.origin && data.origin.x && data.origin.y) this.paper.setOrigin(data.origin.x, data.origin.y);
+
+};
+
ClassView.prototype.loadClass = function (className) {
var self = this;
@@ -823,6 +914,7 @@ ClassView.prototype.loadClass = function (className) {
});
this.cacheClassExplorer.elements.className.textContent = className;
+ this.CURRENT_RENDER_NAME = "CLASS:" + className;
this.cacheClassExplorer.updateURL();
};
@@ -846,6 +938,7 @@ ClassView.prototype.loadPackage = function (packageName) {
});
this.cacheClassExplorer.elements.className.textContent = packageName;
+ this.CURRENT_RENDER_NAME = "PACKAGE:" + packageName;
this.cacheClassExplorer.updateURL();
};
@@ -881,6 +974,8 @@ ClassView.prototype.zoom = function (delta) {
oy - (sh/2 - oy)*scaleDelta
);
+ if (delta) this.prepareToSave(); // delta = null,0 when restore triggered
+
};
/**
@@ -1047,6 +1142,7 @@ ClassView.prototype.init = function () {
self.paper.options.origin.y + e.pageY - relP.y
);
relP.x = e.pageX; relP.y = e.pageY;
+ self.prepareToSave();
};
this.cacheClassExplorer.elements.classViewContainer.addEventListener("mousemove", moveHandler);
diff --git a/web/js/Lib.js b/web/js/Lib.js
index 952e6f6..a0008c9 100644
--- a/web/js/Lib.js
+++ b/web/js/Lib.js
@@ -12,10 +12,16 @@ Lib.prototype.load = function (url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open(data ? "POST" : "GET", url);
+ if (typeof callback === "undefined") callback = function () {};
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
- return callback(null, JSON.parse(xhr.responseText) || {});
+ try {
+ return callback(null, JSON.parse(xhr.responseText) || {});
+ } catch (e) {
+ console.error(url, "Unable to parse:", { data: xhr.responseText });
+ return {};
+ }
} else if (xhr.readyState === 4) {
callback(xhr.responseText + ", " + xhr.status + ": " + xhr.statusText);
}
@@ -312,5 +318,7 @@ Lib.prototype.image = {
keyRed: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACeklEQVQ4jX2TO0zTURTGv3vv/9F/H4Q2LZRHa4o8fASGGuJjMBEluDAQI0Tj4ICTkUQZ3YyJxsHEyRijgwZjdFBjbHQCnDAGMQqioDYgMUBttdACbf/3HheNoK1nPDnfL/m+cw7r7OxEqbKJdMlYd0QWeryrK00pzZiJu9wPdKVuC6IMAGilxHkiI6hrd08K6orOzkHEp5HK2o2j/pr2683NvVkhDgqiBC8mlkTwm+a5i5tqu1oFIKSN4WAYTy5cgrOjHWdfj0cF0R3JmK8oICeVp6O66kTQ5QQUAbaNkYataOs7g3zfKYSWs9iTTB/IcX6oqAWNs+2tlRU+ZNIAA6Bp2D35FgP9p1HzOY59UqElncVQwBstCmCM5XUh/jSEwN7UPFqvXoFOBmCUQXIGArSiFgpSjY8mU7Pg6yCMwzIMCMMAAIx4PVCMvSseoq75Mh8mJUgB/PcIAURgBRvDwQBeecvGTKXu/2NhhYv6tu+LsfaxF5G1hXk4wrUAAMU4Ug4Lg+EQbtXVzQuiY5xobgNglbGdO+y1x90zUwGLMTgmJoBPX3BtW9P7ly3VybRmuH4YxnNTyvOcKAGsO6Qc0f6IJh4eTibcTiXhFwwoKDzzeXHP5+/XQTGNCJZtb9wYANhERytM4+YRVTA90kbIMJAwLfUoUpeLVdeOWKRinKhYXOCSqNep6wM93nLTbRfQ6PEAysZgoOrNjc0NoTzn3aXEAMAVsGwB0At5RCsrwBjDkumEDgq7C7bkRN9KqgFoUqmP5Q5zqWNLUxlm4oCSuByqnx41Xcct+ZfhYgAOTOmaPvR0IbFrMZ3JZw23EWdameT8q/brZf9XPwERTf+roi2jYwAAAABJRU5ErkJggg==",
keyGreen: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACbElEQVQ4jX2TS0iUURzFz31933wz44yOjviYERTTIjCYkLJFkCW2cSGRUrRoYW1cuHAV7aJFm2odUYvCiFpUREOt1FZGWNEblUwTmxwdfI7OzHfvv8UgIc147upy7/n97zlwWVdXF4qJXFLQrAf1ud5cWbqZp6wZNe1/Qsrch6B1AJDFzCZLlqdKPgz2U/ef2BzmxSR0ym0qH6/pKL3d0ocNcRKCkrzgZE1wKuwrLdci3b5WwAgXwdE6XHxxHW3eTqQvf4hB0ANoFioIcDOmpKGz+kKwygsNgoaLmrF96G8fQG92ACa6BhxZOoEMP1UwgpBsf6S1MpTFCvI5JWbbPuHS0CAWa39AHNNgLSugkXCscAeMZYUSoO0tBNJHE4i33kRAWQjCBjQHCLJgBJ0znxPjqVkJ8Y8JDtuxoISV72msDDDsa0GA1DI0tf5NGxgwbF+h/GI5sNEqsHdl72Gbx/9FYGnRuNm+EJ/oeFPvbiUQ9tTmDwyHTDnwDEeh7jUkSNA5cJrbCdhkh9jBreeLPRNh7jD89HzBMmbgubX3e83bmiWxYvn4svWabH0VnJL5grcfmKHjdr14unk66YdXQ1UwMBjQq3KwR+FBpShOkkCOuzMuABiXzjqV1l3vmZy9UeLCG7WQTjom8Kwh48QjY+SYODihkDhp6rO8aqi2t9Q2/hxCTSUwcOEbrv7ov7MniizvKWbOAwzWuAO4KotIrBKMMchVL0hRHfldDU6LRd0ApNFmyim1Vw90Ngd+YRqARvBG4yQb9503juvuZgYAyTgmpFIjMy+Th38vrGe3NvwWm5YBpvk8ZP7L7qa/51LzXlKvOSsAAAAASUVORK5CYII=",
minusSimple: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAL0lEQVQ4jWP8//8/IwMFgIkSzaMGUMkAFhiDkZHxHyka////z0QVFzCOJqThYAAAIOcKHde8N5QAAAAASUVORK5CYII=",
- plusSimple: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARUlEQVQ4jdWRMQ4AIAjErP9/M+eiiyEyYCTeSjiagiRaIj2zHBYABlgdwR8FrDdGsvZI6ncJ3OGkWte81EusLzhKfEIwANMfFhcD7xBqAAAAAElFTkSuQmCC"
+ plusSimple: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARUlEQVQ4jdWRMQ4AIAjErP9/M+eiiyEyYCTeSjiagiRaIj2zHBYABlgdwR8FrDdGsvZI6ncJ3OGkWte81EusLzhKfEIwANMfFhcD7xBqAAAAAElFTkSuQmCC",
+ pin: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAADBmlDQ1BpY2MAAHjaY2BgnuDo4uTKJMDAUFBUUuQe5BgZERmlwH6egY2BmYGBgYGBITG5uMAxIMCHgYGBIS8/L5UBFTAyMHy7xsDIwMDAcFnX0cXJlYE0wJpcUFTCwMBwgIGBwSgltTiZgYHhCwMDQ3p5SUEJAwNjDAMDg0hSdkEJAwNjAQMDg0h2SJAzAwNjCwMDE09JakUJAwMDg3N+QWVRZnpGiYKhpaWlgmNKflKqQnBlcUlqbrGCZ15yflFBflFiSWoKAwMD1A4GBgYGXpf8EgX3xMw8BSMDVQYqg4jIKAUICxE+CDEESC4tKoMHJQODAIMCgwGDA0MAQyJDPcMChqMMbxjFGV0YSxlXMN5jEmMKYprAdIFZmDmSeSHzGxZLlg6WW6x6rK2s99gs2aaxfWMPZ9/NocTRxfGFM5HzApcj1xZuTe4FPFI8U3mFeCfxCfNN45fhXyygI7BD0FXwilCq0A/hXhEVkb2i4aJfxCaJG4lfkaiQlJM8JpUvLS19QqZMVl32llyfvIv8H4WtioVKekpvldeqFKiaqP5UO6jepRGqqaT5QeuA9iSdVF0rPUG9V/pHDBYY1hrFGNuayJsym740u2C+02KJ5QSrOutcmzjbQDtXe2sHY0cdJzVnJRcFV3k3BXdlD3VPXS8Tbxsfd99gvwT//ID6wIlBS4N3hVwMfRnOFCEXaRUVEV0RMzN2T9yDBLZE3aSw5IaUNak30zkyLDIzs+ZmX8xlz7PPryjYVPiuWLskq3RV2ZsK/cqSql01jLVedVPrHzbqNdU0n22VaytsP9op3VXUfbpXta+x/+5Em0mzJ/+dGj/t8AyNmf2zvs9JmHt6vvmCpYtEFrcu+bYsc/m9lSGrTq9xWbtvveWGbZtMNm/ZarJt+w6rnft3u+45uy9s/4ODOYd+Hmk/Jn58xUnrU+fOJJ/9dX7SRe1LR68kXv13fc5Nm1t379TfU75/4mHeY7En+59lvhB5efB1/lv5dxc+NH0y/fzq64Lv4T8Ffp360/rP8f9/AA0ADzTzG2NJAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAACYktHRAD/h4/MvwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+ABCw0JBgCIkrwAAADNSURBVCjPdZA9DgFRFIVPMCFRSFQTBQ293z1oRWMJLECoLUHsAdEoRERjA0QvEhGNaBUa8inGzDwznFu83HvO/XlHKBQ5ZmTcTD8EHWCH7WQRhfGQVNZCtqQfEywmONhih1dYjPGxJetTcUTU63Yxj0lKqa2G0jooqrp3yVQbobUQBe4EMSZmfrPKNUBbQR9KHD166XabPux18La/9DRc+SiHwIozACcS/gTn6QIjRJErsCPyLWgCfdxzb/RM60SFCy2jlCf5LRhQQ//jDXleGJr4KuKfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTAxLTExVDEzOjA5OjA2KzAxOjAw8Rgr6AAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wMS0xMVQxMzowOTowNiswMTowMIBFk1QAAAA3dEVYdGljYzpjb3B5cmlnaHQAQ29weXJpZ2h0IDE5OTkgQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxbP9tAAAAHHRFWHRpY2M6ZGVzY3JpcHRpb24ARG90IEdhaW4gMjAlk5c01gAAAB10RVh0aWNjOm1hbnVmYWN0dXJlcgBEb3QgR2FpbiAyMCWy7qr9AAAAFnRFWHRpY2M6bW9kZWwARG90IEdhaW4gMjAlMnX1qwAAAABJRU5ErkJggg==",
+ pinActive: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAe1BMVEX/////zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv////a7dsHAAAAJ3RSTlMAQPLg8wreGfQY6utB+GxtU/taZ7ywq+3urK+9aL6trpeZf4BeYC49Q3tiAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+ABCw0HIEwGOs8AAABlSURBVBjTXc1HEoAwCEBRYu+99879b2hGo0b+KrzJAACPKSpPY/Ckq8gz9BfAtBAtG6QcRFeewaVAf3g+ov8dgQCvwmeO4hviRECa5UVZ1U3bSVvaHv4NI4FpJrCsBLadwHGIxwkpXgeBl9dpZwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0xMVQxMzowNzozMyswMTowMDNmMx8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTYtMDEtMTFUMTM6MDc6MzIrMDE6MDDkTIAXAAAAAElFTkSuQmCC"
};
\ No newline at end of file
diff --git a/web/js/Logic.js b/web/js/Logic.js
index 12a3704..1d43d22 100644
--- a/web/js/Logic.js
+++ b/web/js/Logic.js
@@ -7,7 +7,7 @@ var Logic = function (parent) {
/**
* Modify data, add relations, connections, helpers.
*
- * @param {{basePackageName: string, classes: object, restrictPackage: number}} data
+ * @param {*} data
*/
Logic.prototype.process = function (data) {
@@ -16,6 +16,13 @@ Logic.prototype.process = function (data) {
this.data = data;
+ if (data.savedView) try {
+ data.savedView = JSON.parse(data.savedView);
+ } catch (e) {
+ delete data.savedView;
+ console.log("! Unable to deserialize savedView.");
+ }
+
data.classes["%Persistent"] = data.classes["%Library.Persistent"] = {
$classType: "Persistent"
};
diff --git a/web/js/Source.js b/web/js/Source.js
index 1585aef..41085c2 100644
--- a/web/js/Source.js
+++ b/web/js/Source.js
@@ -45,7 +45,8 @@ Source.prototype.getMethod = function (className, methodName, callback) {
+ encodeURIComponent(methodName)
+ (this.cue.NAMESPACE ? "&namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""),
null,
- callback);
+ callback
+ );
};
@@ -60,7 +61,26 @@ Source.prototype.getClassView = function (className, callback) {
this.URL + "/GetClassView?name=" + encodeURIComponent(className)
+ (this.cue.NAMESPACE ? "&namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""),
null,
- callback);
+ callback
+ );
+
+};
+
+Source.prototype.saveView = function (packageName, data) {
+
+ lib.load(
+ this.URL + "/SaveView?name=" + encodeURIComponent(packageName),
+ data,
+ function (e) { console.log("View saved."); }
+ );
+
+};
+
+Source.prototype.resetView = function (packageName) {
+
+ lib.load(
+ this.URL + "/ResetView?name=" + encodeURIComponent(packageName)
+ );
};