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