file-utils.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // adapted from https://github.com/NYTimes/svg-crowbar
  2. import { each } from 'lodash';
  3. const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
  4. const prefix = {
  5. svg: 'http://www.w3.org/2000/svg',
  6. xlink: 'http://www.w3.org/1999/xlink',
  7. xmlns: 'http://www.w3.org/2000/xmlns/'
  8. };
  9. const cssSkipValues = {
  10. '0px 0px': true,
  11. auto: true,
  12. pointer: true,
  13. visible: true
  14. };
  15. function setInlineStyles(svg, target, emptySvgDeclarationComputed) {
  16. function explicitlySetStyle(element, targetEl) {
  17. const cSSStyleDeclarationComputed = getComputedStyle(element);
  18. let computedStyleStr = '';
  19. each(cSSStyleDeclarationComputed, (key) => {
  20. const value = cSSStyleDeclarationComputed.getPropertyValue(key);
  21. if (value !== emptySvgDeclarationComputed.getPropertyValue(key) && !cssSkipValues[value]) {
  22. computedStyleStr += `${key}:${value};`;
  23. }
  24. });
  25. targetEl.setAttribute('style', computedStyleStr);
  26. targetEl.removeAttribute('data-reactid');
  27. }
  28. function traverse(obj) {
  29. const tree = [];
  30. function visit(node) {
  31. if (node && node.hasChildNodes()) {
  32. let child = node.firstChild;
  33. while (child) {
  34. if (child.nodeType === 1 && child.nodeName !== 'SCRIPT') {
  35. tree.push(child);
  36. visit(child);
  37. }
  38. child = child.nextSibling;
  39. }
  40. }
  41. }
  42. tree.push(obj);
  43. visit(obj);
  44. return tree;
  45. }
  46. // make sure logo shows up
  47. svg.setAttribute('class', 'exported');
  48. // hardcode computed css styles inside svg
  49. const allElements = traverse(svg);
  50. const allTargetElements = traverse(target);
  51. for (let i = allElements.length - 1; i >= 0; i -= 1) {
  52. explicitlySetStyle(allElements[i], allTargetElements[i]);
  53. }
  54. // set font
  55. target.setAttribute('style', 'font-family: Arial;');
  56. // set view box
  57. target.setAttribute('width', svg.clientWidth);
  58. target.setAttribute('height', svg.clientHeight);
  59. }
  60. function download(source, name) {
  61. let filename = 'untitled';
  62. if (name) {
  63. filename = name;
  64. } else if (window.document.title) {
  65. filename = `${window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${(+new Date())}`;
  66. }
  67. const url = window.URL.createObjectURL(new Blob(
  68. source,
  69. { type: 'text/xml' }
  70. ));
  71. const a = document.createElement('a');
  72. document.body.appendChild(a);
  73. a.setAttribute('class', 'svg-crowbar');
  74. a.setAttribute('download', `${filename}.svg`);
  75. a.setAttribute('href', url);
  76. a.style.display = 'none';
  77. a.click();
  78. setTimeout(() => {
  79. window.URL.revokeObjectURL(url);
  80. }, 10);
  81. }
  82. function getSVGElement() {
  83. return document.getElementById('canvas');
  84. }
  85. function getSVG(doc, emptySvgDeclarationComputed) {
  86. const svg = getSVGElement();
  87. const target = svg.cloneNode(true);
  88. target.setAttribute('version', '1.1');
  89. // removing attributes so they aren't doubled up
  90. target.removeAttribute('xmlns');
  91. target.removeAttribute('xlink');
  92. // These are needed for the svg
  93. if (!target.hasAttributeNS(prefix.xmlns, 'xmlns')) {
  94. target.setAttributeNS(prefix.xmlns, 'xmlns', prefix.svg);
  95. }
  96. if (!target.hasAttributeNS(prefix.xmlns, 'xmlns:xlink')) {
  97. target.setAttributeNS(prefix.xmlns, 'xmlns:xlink', prefix.xlink);
  98. }
  99. setInlineStyles(svg, target, emptySvgDeclarationComputed);
  100. const source = (new XMLSerializer()).serializeToString(target);
  101. return [doctype + source];
  102. }
  103. function cleanup() {
  104. const crowbarElements = document.querySelectorAll('.svg-crowbar');
  105. [].forEach.call(crowbarElements, (el) => {
  106. el.parentNode.removeChild(el);
  107. });
  108. // hide embedded logo
  109. const svg = getSVGElement();
  110. svg.setAttribute('class', '');
  111. }
  112. export function saveGraph(filename) {
  113. window.URL = (window.URL || window.webkitURL);
  114. // add empty svg element
  115. const emptySvg = window.document.createElementNS(prefix.svg, 'svg');
  116. window.document.body.appendChild(emptySvg);
  117. const emptySvgDeclarationComputed = getComputedStyle(emptySvg);
  118. const svgSource = getSVG(document, emptySvgDeclarationComputed);
  119. download(svgSource, filename);
  120. cleanup();
  121. }