From a2d355ac60791ca3d6689f93b8d4afd71c829b89 Mon Sep 17 00:00:00 2001 From: ZitRo Date: Tue, 28 Apr 2015 19:58:56 +0300 Subject: [PATCH 1/3] fixed aggregation for class names without package name, ++COS code quality --- cache/projectTemplate.xml | 79 ++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/cache/projectTemplate.xml b/cache/projectTemplate.xml index 50cb861..a5fa734 100644 --- a/cache/projectTemplate.xml +++ b/cache/projectTemplate.xml @@ -1,10 +1,9 @@ - - + Class contains methods that return structured class data. -63668,60898.691763 +63670,70761.169478 63653,67019.989197 @@ -57,6 +56,8 @@ return structured data about class do oProp.%DispatchSetProperty("readOnly", p.ReadOnly) do oProp.%DispatchSetProperty("type", p.Type) do ..collectAggregation(oData, classDefinition.Name, p.Type) + do ..collectAggregation(oData, classDefinition.Name, basePack _ "." _ p.Type) } set oMethods = ##class(%ZEN.proxyObject).%New() @@ -216,11 +218,11 @@ return structured data about class - + - - - + + + @@ -229,7 +231,7 @@ return structured data about class REST interface for UMLExplorer %CSP.REST -63667,85509.960346 +63670,71431.319061 63648,30450.187229 @@ -286,52 +288,39 @@ Returns all package class trees by given package name ]]> - - -Method to test accessibility of REST interface. -1 -%Status - - - + +Method returns user application CSS. 1 %Status + +Method returns user application JavaScript. 1 %Status -Method returns user application. +Method returns user application HTML. 1 %Status + do ##class(StaticContent).Write("HTML") return $$$OK ]]> @@ -339,40 +328,36 @@ Method returns user application. -63663,76108.945861 +63670,71368.846177 63663,71456.865723 - + -Outputs css code for UMLExplorer application +Write the contents of xData tag 1 +Const:%String %Status - - -Outputs js code for UMLExplorer application -1 -%Status - - + + +]]> + + +]]]]> ]]> From 5e347b2f81bcca7571883516b8b11844fe443cac Mon Sep 17 00:00:00 2001 From: ZitRo Date: Tue, 28 Apr 2015 20:22:39 +0300 Subject: [PATCH 2/3] composition visualizing --- cache/projectTemplate.xml | 16 ++++++----- package.json | 2 +- web/js/ClassView.js | 50 +++++++++++++++-------------------- web/jsLib/joint.shapes.uml.js | 2 +- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/cache/projectTemplate.xml b/cache/projectTemplate.xml index a5fa734..53bb53c 100644 --- a/cache/projectTemplate.xml +++ b/cache/projectTemplate.xml @@ -3,7 +3,7 @@ Class contains methods that return structured class data. -63670,70761.169478 +63670,72515.130814 63653,67019.989197 @@ -70,8 +70,8 @@ return structured data about class do oProp.%DispatchSetProperty("private", p.Private) do oProp.%DispatchSetProperty("readOnly", p.ReadOnly) do oProp.%DispatchSetProperty("type", p.Type) - do ..collectAggregation(oData, classDefinition.Name, p.Type) - do ..collectAggregation(oData, classDefinition.Name, basePack _ "." _ p.Type) + do ..collectAggregation(oData, classDefinition.Name, p.Type, p.Private) + do ..collectAggregation(oData, classDefinition.Name, basePack _ "." _ p.Type, p.Private) } set oMethods = ##class(%ZEN.proxyObject).%New() @@ -143,15 +143,16 @@ return structured data about class 1 -oData:%ZEN.proxyObject,className:%String,type:%String +oData:%ZEN.proxyObject,className:%String,type:%String,private:%String %Status set oData.classes = ##class(%ZEN.proxyObject).%New() set oData.inheritance = ##class(%ZEN.proxyObject).%New() set oData.aggregation = ##class(%ZEN.proxyObject).%New() + set oData.composition = ##class(%ZEN.proxyObject).%New() set classes = ##class(%ResultSet).%New("%Dictionary.ClassDefinition:Summary") do classes.Execute() @@ -218,7 +220,7 @@ return structured data about class - + diff --git a/package.json b/package.json index db8fe84..f010aae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CacheUMLExplorer", - "version": "0.3.0", + "version": "0.4.0", "description": "An UML Class explorer for InterSystems Caché", "directories": { "test": "test" diff --git a/web/js/ClassView.js b/web/js/ClassView.js index 224188d..53a25d4 100644 --- a/web/js/ClassView.js +++ b/web/js/ClassView.js @@ -138,7 +138,7 @@ ClassView.prototype.createClassInstance = function (name, classMetaData) { ClassView.prototype.render = function (data) { - var p, pp, className, classInstance, + var self = this, p, pp, className, classInstance, uml = joint.shapes.uml, relFrom, relTo, classes = {}, connector; @@ -158,37 +158,29 @@ ClassView.prototype.render = function (data) { } - for (p in data["inheritance"]) { - relFrom = (classes[p] || {}).instance; - for (pp in data["inheritance"][p]) { - relTo = (classes[pp] || {}).instance; - if (relFrom && relTo) { - this.graph.addCell(connector = new uml.Generalization({ - source: { id: relFrom.id }, - target: { id: relTo.id }, - router: { name: "manhattan" }, - connector: { name: "rounded" } - })); - this.links.push(connector); + var link = function (type) { + var name = type === "inheritance" ? "Generalization" : + type === "aggregation" ? "Aggregation" : "Composition"; + for (p in data[type]) { + relFrom = (classes[p] || {}).instance; + for (pp in data[type][p]) { + relTo = (classes[pp] || {}).instance; + if (relFrom && relTo) { + self.graph.addCell(connector = new uml[name]({ + source: { id: type === "inheritance" ? relFrom.id : relTo.id }, + target: { id: type === "inheritance" ? relTo.id : relFrom.id }, + router: { name: "manhattan" }, + connector: { name: "rounded" } + })); + self.links.push(connector); + } } } - } + }; - for (p in data["aggregation"]) { - relTo = (classes[p] || {}).instance; - for (pp in data["aggregation"][p]) { - relFrom = (classes[pp] || {}).instance; - if (relFrom && relTo) { - this.graph.addCell(connector = new uml.Aggregation({ - source: { id: relFrom.id }, - target: { id: relTo.id }, - router: { name: "manhattan" }, - connector: { name: "rounded" } - })); - this.links.push(connector); - } - } - } + link("inheritance"); + link("composition"); + link("aggregation"); joint.layout.DirectedGraph.layout(this.graph, { setLinkVertices: false, diff --git a/web/jsLib/joint.shapes.uml.js b/web/jsLib/joint.shapes.uml.js index 603f9f7..2be2234 100644 --- a/web/jsLib/joint.shapes.uml.js +++ b/web/jsLib/joint.shapes.uml.js @@ -190,7 +190,7 @@ joint.shapes.uml.Aggregation = joint.dia.Link.extend({ joint.shapes.uml.Composition = joint.dia.Link.extend({ defaults: { type: 'uml.Composition', - attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'black' }} + attrs: { '.marker-target': { d: 'M 20 10 L 10 15 L 0 10 L 10 5 z', fill: 'black' }} } }); From 6234d8e6f78c00c9000c4de08167f80b80f6b3c6 Mon Sep 17 00:00:00 2001 From: ZitRo Date: Tue, 28 Apr 2015 23:35:08 +0300 Subject: [PATCH 3/3] png image export feature added, various technical fixes and improvements --- gulpfile.js | 3 +- package.json | 2 +- web/css/extras.css | 23 ++++++ web/css/interface.css | 9 ++- web/css/treeView.css | 11 ++- web/index.html | 7 +- web/js/CacheUMLExplorer.js | 2 + web/jsLib/ImageExporter.js | 146 +++++++++++++++++++++++++++++++++++++ 8 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 web/jsLib/ImageExporter.js diff --git a/gulpfile.js b/gulpfile.js index cbe65a1..44c7ac8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -36,7 +36,8 @@ gulp.task("clean", function () { gulp.task("gatherLibs", ["clean"], function () { return gulp.src([ "web/jsLib/joint.js", - "web/jsLib/joint.shapes.uml.js" + "web/jsLib/joint.shapes.uml.js", + "web/jsLib/ImageExporter.js" ]) .pipe(uglify({ output: { diff --git a/package.json b/package.json index f010aae..393c029 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CacheUMLExplorer", - "version": "0.4.0", + "version": "0.5.0", "description": "An UML Class explorer for InterSystems Caché", "directories": { "test": "test" diff --git a/web/css/extras.css b/web/css/extras.css index 13c99ae..0921c8f 100644 --- a/web/css/extras.css +++ b/web/css/extras.css @@ -117,6 +117,29 @@ left: 5px; } +.icon.download:before { + content: ""; + border: 8px solid transparent; + border-right-width: 0; + border-left-color: #fff; + left: 8px; + right: auto; + position: absolute; + top: 16px; + transform: translateY(-50%) rotate(90deg); +} + +.icon.download:after { + content: ""; + background-color: #fff; + width: 6px; + height: 10px; + border-radius: 1px; + position: absolute; + top: 5px; + left: 9px; +} + .icon.scaleNormal:after { content: "1:1"; position: absolute; diff --git a/web/css/interface.css b/web/css/interface.css index 5f4bc80..4076ceb 100644 --- a/web/css/interface.css +++ b/web/css/interface.css @@ -34,13 +34,20 @@ html, body { font-size: 18pt; } -.ui-toolBar { +.ui-rightBottomToolBar { position: absolute; bottom: 0; right: 0; padding: .5em; } +.ui-leftBottomToolBar { + position: absolute; + bottom: 0; + left: 0; + padding: .5em; +} + #className { text-shadow: 1px 1px 0 white, -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white; } \ No newline at end of file diff --git a/web/css/treeView.css b/web/css/treeView.css index 57b928b..01f48f9 100644 --- a/web/css/treeView.css +++ b/web/css/treeView.css @@ -21,9 +21,12 @@ cursor: pointer; border-radius: 5px; -webkit-transition: all .2s ease; - user-select: none; + -moz-transition: all .2s ease; + -o-transition: all .2s ease; + transition: all .2s ease; -webkit-user-select: none; -ms-user-select: none; + user-select: none; } .tv-class-name:hover, .tv-package-name:hover { @@ -54,6 +57,9 @@ border: 1px solid gray; border-radius: 2px 2px 0 2px; -webkit-transition: all .2s ease; + -moz-transition: all .2s ease; + -o-transition: all .2s ease; + transition: all .2s ease; } .tv-package-name:after { @@ -69,6 +75,9 @@ border: 1px solid gray; border-radius: 0 2px 2px 2px; -webkit-transition: all .2s ease; + -moz-transition: all .2s ease; + -o-transition: all .2s ease; + transition: all .2s ease; } .tv-package-name:hover:before { diff --git a/web/index.html b/web/index.html index f014bcf..af5d99f 100644 --- a/web/index.html +++ b/web/index.html @@ -14,6 +14,7 @@ + @@ -32,7 +33,10 @@
-
+
+
+
+
@@ -42,5 +46,6 @@
+ \ No newline at end of file diff --git a/web/js/CacheUMLExplorer.js b/web/js/CacheUMLExplorer.js index e0ceb1a..ffcf0d8 100644 --- a/web/js/CacheUMLExplorer.js +++ b/web/js/CacheUMLExplorer.js @@ -43,4 +43,6 @@ CacheUMLExplorer.prototype.init = function () { } } + enableSVGDownload(this.classTree); + }; \ No newline at end of file diff --git a/web/jsLib/ImageExporter.js b/web/jsLib/ImageExporter.js new file mode 100644 index 0000000..63be399 --- /dev/null +++ b/web/jsLib/ImageExporter.js @@ -0,0 +1,146 @@ +/* + * @see https://github.com/NYTimes/svg-crowbar - A bit of used code from here, thanks to it's author. + */ +var enableSVGDownload = function (classView) { + + var doctype = ''; + + window.URL = (window.URL || window.webkitURL); + + var prefix = { + xmlns: "http://www.w3.org/2000/xmlns/", + xlink: "http://www.w3.org/1999/xlink", + svg: "http://www.w3.org/2000/svg" + }; + + function getSources(doc, emptySvgDeclarationComputed) { + + var svgInfo = [], + svgs = doc.querySelectorAll("svg"); + + [].forEach.call(svgs, function (svg) { + + var par = svg.parentNode; + svg = svg.cloneNode(true); + par.appendChild(svg); + var gGroup = svg.childNodes[0]; + + svg.setAttribute("version", "1.1"); + + // removing attributes so they aren't doubled up + svg.removeAttribute("xmlns"); + svg.removeAttribute("xlink"); + + // These are needed for the svg + if (!svg.hasAttributeNS(prefix.xmlns, "xmlns")) { + svg.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg); + } + + if (!svg.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) { + svg.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink); + } + + svg.setAttribute("width", gGroup.getBBox().width); + svg.setAttribute("height", gGroup.getBBox().height); + gGroup.setAttribute("transform", ""); + + setInlineStyles(svg, emptySvgDeclarationComputed); + + var source = (new XMLSerializer()).serializeToString(svg); + var rect = svg.getBoundingClientRect(); + svgInfo.push({ + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height, + class: svg.getAttribute("class"), + id: svg.getAttribute("id"), + childElementCount: svg.childElementCount, + source: [doctype + source] + }); + + par.removeChild(svg); + + }); + return svgInfo; + } + + document.getElementById("button.downloadSVG").addEventListener("click", function () { + + var emptySvg = window.document.createElementNS(prefix.svg, 'svg'), + emptySvgDeclarationComputed = getComputedStyle(emptySvg), + source = getSources(document, emptySvgDeclarationComputed)[0]; + + var filename = (classView || {}).SELECTED_CLASS_NAME || "classDiagram"; + + var img = new Image(); + var url = window.URL.createObjectURL(new Blob(source.source, { "type" : 'image/svg+xml;charset=utf-8'/*"text\/xml"*/ })); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + canvas.setAttribute("width", source.width); + canvas.setAttribute("height", source.height); + document.body.appendChild(canvas); + + img.onload = function () { + ctx.drawImage(img, 0, 0); + var dataURL = canvas.toDataURL("image/png"); + var a = document.createElement("a"); + a.setAttribute("download", filename + ".png"); + a.setAttribute("href", dataURL/*url*/); + document.body.appendChild(a); + a.click(); + setTimeout(function () { + a.parentNode.removeChild(a); + canvas.parentNode.removeChild(canvas); + window.URL.revokeObjectURL(url); + }, 10); + }; + + img.src = url; + + }); + + function setInlineStyles(svg, emptySvgDeclarationComputed) { + + function explicitlySetStyle (element) { + var cSSStyleDeclarationComputed = getComputedStyle(element); + var i, len, key, value; + var computedStyleStr = ""; + for (i=0, len=cSSStyleDeclarationComputed.length; i