diff --git a/package.json b/package.json index 0240634..8b5cbdc 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "buefy" ], "dependencies": { + "blueimp-canvas-to-blob": "^3.14.0", "buefy": "^0.6.3", + "file-saver": "^1.3.8", "lodash": "^4.17.5", "uuid": "^3.2.1", "vue": "^2.5.13", diff --git a/src/components/LayoutGrid/LayoutGrid.vue b/src/components/LayoutGrid/LayoutGrid.vue index bbb976a..defae00 100644 --- a/src/components/LayoutGrid/LayoutGrid.vue +++ b/src/components/LayoutGrid/LayoutGrid.vue @@ -89,6 +89,12 @@ }"> + + + + + @@ -101,10 +107,10 @@ import {mapMutations, mapState} from 'vuex'; import Emotion from './Emotion'; import Table from './Table'; + import { getSVGString, svgString2Image } from '../../utils/svg2png'; + import { saveAs } from 'file-saver/FileSaver'; const isObject = o => String(o) === '[object Object]'; - const isString = s => typeof s === 'string'; - const isArray = arr => Array.isArray(arr); const isArrayAndHasLength = arr => Array.isArray(arr) && arr.length > 0; const isEmpty = s => s === '' || s === undefined || s === null; const isFunction = f => typeof f === 'function'; @@ -206,6 +212,17 @@ if (isFunction(component.safeDraw) && isDisplay(el)) component.safeDraw(); }, + download(ev, title) { + const svg = this.getLayoutGridItem(ev).querySelector('svg'), + { width, height } = svg.getBBox(); + + const svgString = getSVGString(svg); + svgString2Image( svgString, 2 * width, 2 * height, 'png', save); + + function save( dataBlob, filesize ){ + saveAs( dataBlob, title); + } + }, onMove(i, x, y) { this.$emit('move', i, x, y); }, @@ -340,8 +357,8 @@ width: 100%; - overflow-x: auto; - overflow-y: auto; + overflow-x: hidden; + overflow-y: hidden; } .icon { diff --git a/src/components/LayoutGrid/Table.vue b/src/components/LayoutGrid/Table.vue index 294d829..3a4a5bd 100644 --- a/src/components/LayoutGrid/Table.vue +++ b/src/components/LayoutGrid/Table.vue @@ -18,18 +18,18 @@
Export: - + - json + Json - + - csv + Csv
diff --git a/src/utils/svg2png.js b/src/utils/svg2png.js new file mode 100644 index 0000000..5b36678 --- /dev/null +++ b/src/utils/svg2png.js @@ -0,0 +1,103 @@ +function getSVGString(svgNode) { + svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink'); + var cssStyleText = getCSSStyles(svgNode); + appendCSS(cssStyleText, svgNode); + + var serializer = new XMLSerializer(); + var svgString = serializer.serializeToString(svgNode); + svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace + svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix + + return svgString; + + function getCSSStyles(parentElement) { + var selectorTextArr = []; + + // Add Parent element Id and Classes to the list + selectorTextArr.push('#' + parentElement.id); + for (var c = 0; c < parentElement.classList.length; c++) + if (!contains('.' + parentElement.classList[c], selectorTextArr)) + selectorTextArr.push('.' + parentElement.classList[c]); + + // Add Children element Ids and Classes to the list + var nodes = parentElement.getElementsByTagName("*"); + for (var i = 0; i < nodes.length; i++) { + var id = nodes[i].id; + if (!contains('#' + id, selectorTextArr)) + selectorTextArr.push('#' + id); + + var classes = nodes[i].classList; + for (var c = 0; c < classes.length; c++) + if (!contains('.' + classes[c], selectorTextArr)) + selectorTextArr.push('.' + classes[c]); + } + + // Extract CSS Rules + var extractedCSSText = ""; + for (var i = 0; i < document.styleSheets.length; i++) { + var s = document.styleSheets[i]; + + try { + if (!s.cssRules) continue; + } catch (e) { + if (e.name !== 'SecurityError') throw e; // for Firefox + continue; + } + + var cssRules = s.cssRules; + for (var r = 0; r < cssRules.length; r++) { + if (contains(cssRules[r].selectorText, selectorTextArr)) + extractedCSSText += cssRules[r].cssText; + } + } + + + return extractedCSSText; + + function contains(str, arr) { + return arr.indexOf(str) === -1 ? false : true; + } + + } + + function appendCSS(cssText, element) { + var styleElement = document.createElement("style"); + styleElement.setAttribute("type", "text/css"); + styleElement.innerHTML = cssText; + var refNode = element.hasChildNodes() ? element.children[0] : null; + element.insertBefore(styleElement, refNode); + } +} + + +function svgString2Image(svgString, width, height, format, callback) { + var format = format ? format : 'png'; + + var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL + + var canvas = document.createElement("canvas"); + var context = canvas.getContext("2d"); + + canvas.width = width; + canvas.height = height; + + var image = new Image(); + image.onload = function () { + context.clearRect(0, 0, width, height); + context.drawImage(image, 0, 0, width, height); + + canvas.toBlob(function (blob) { + var filesize = Math.round(blob.length / 1024) + ' KB'; + if (callback) callback(blob, filesize); + }); + + + }; + + image.src = imgsrc; +} + +export { + getSVGString, + svgString2Image +};