mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 05:07:52 +00:00
Merge branch 'master' into open933
This commit is contained in:
commit
a1331b7bb3
@ -20,6 +20,8 @@
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter3": "^1.2.0",
|
||||
"d3": "~4.1.0"
|
||||
"d3": "~4.1.0",
|
||||
"html2canvas": "^0.4.1",
|
||||
"jspdf": "^1.2.61"
|
||||
}
|
||||
}
|
||||
|
8
main.js
8
main.js
@ -29,6 +29,8 @@ requirejs.config({
|
||||
"csv": "bower_components/comma-separated-values/csv.min",
|
||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||
"EventEmitter": "bower_components/eventemitter3/index",
|
||||
"html2canvas": "bower_components/html2canvas/build/html2canvas.min",
|
||||
"jsPDF": "bower_components/jspdf/dist/jspdf.min",
|
||||
"moment": "bower_components/moment/moment",
|
||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||
@ -45,6 +47,12 @@ requirejs.config({
|
||||
"angular-route": {
|
||||
"deps": ["angular"]
|
||||
},
|
||||
"html2canvas": {
|
||||
"exports": "html2canvas"
|
||||
},
|
||||
"jsPDF": {
|
||||
"exports": "jsPDF"
|
||||
},
|
||||
"EventEmitter": {
|
||||
"exports": "EventEmitter"
|
||||
},
|
||||
|
@ -25,6 +25,7 @@ define([
|
||||
"./src/PlotController",
|
||||
"./src/policies/PlotViewPolicy",
|
||||
"./src/PlotOptionsController",
|
||||
"./src/services/ExportImageService",
|
||||
"text!./res/templates/plot.html",
|
||||
"text!./res/templates/plot-options-browse.html",
|
||||
'legacyRegistry'
|
||||
@ -33,6 +34,7 @@ define([
|
||||
PlotController,
|
||||
PlotViewPolicy,
|
||||
PlotOptionsController,
|
||||
exportImageService,
|
||||
plotTemplate,
|
||||
plotOptionsBrowseTemplate,
|
||||
legacyRegistry
|
||||
@ -70,6 +72,8 @@ define([
|
||||
"implementation": PlotController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$element",
|
||||
"exportImageService",
|
||||
"telemetryFormatter",
|
||||
"telemetryHandler",
|
||||
"throttle",
|
||||
@ -84,12 +88,30 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "exportImageService",
|
||||
"implementation": exportImageService,
|
||||
"depends": [
|
||||
"$q",
|
||||
"$timeout",
|
||||
"$log",
|
||||
"EXPORT_IMAGE_TIMEOUT"
|
||||
]
|
||||
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "PLOT_FIXED_DURATION",
|
||||
"value": 900000,
|
||||
"priority": "fallback",
|
||||
"comment": "Fifteen minutes."
|
||||
},
|
||||
{
|
||||
"key": "EXPORT_IMAGE_TIMEOUT",
|
||||
"value": 500,
|
||||
"priority": "fallback"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
@ -103,6 +125,38 @@ define([
|
||||
"key": "plot-options-browse",
|
||||
"template": plotOptionsBrowseTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "FileSaver.js",
|
||||
"version": "0.0.2",
|
||||
"author": "Eli Grey",
|
||||
"description": "File download initiator (for file exports)",
|
||||
"website": "https://github.com/eligrey/FileSaver.js/",
|
||||
"copyright": "Copyright © 2015 Eli Grey.",
|
||||
"license": "license-mit",
|
||||
"link": "https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md"
|
||||
},
|
||||
{
|
||||
"name": "html2canvas",
|
||||
"version": "0.4.1",
|
||||
"author": "Niklas von Hertzen",
|
||||
"description": "JavaScript HTML renderer",
|
||||
"website": "https://github.com/niklasvh/html2canvas",
|
||||
"copyright": "Copyright © 2012 Niklas von Hertzen.",
|
||||
"license": "license-mit",
|
||||
"link": "https://github.com/niklasvh/html2canvas/blob/master/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "jsPDF",
|
||||
"version": "1.2.61",
|
||||
"author": "James Hall",
|
||||
"description": "JavaScript HTML renderer",
|
||||
"website": "https://github.com/MrRio/jsPDF",
|
||||
"copyright": "Copyright © 2010-2016 James Hall",
|
||||
"license": "license-mit",
|
||||
"link": "https://github.com/MrRio/jsPDF/blob/master/MIT-LICENSE.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
@ -20,7 +20,27 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="PlotController as plot"
|
||||
class="abs holder holder-plot">
|
||||
class="abs holder holder-plot has-control-bar">
|
||||
<div class="l-control-bar" ng-show="!plot.hideExportButtons">
|
||||
<span class="l-btn-set">
|
||||
<a class="s-button t-export icon-download labeled first"
|
||||
ng-click="plot.exportPDF()"
|
||||
title="Export This View's Data as PDF">
|
||||
PDF
|
||||
</a>
|
||||
<a class="s-button t-export labeled"
|
||||
ng-click="plot.exportPNG()"
|
||||
title="Export This View's Data as PNG">
|
||||
PNG
|
||||
</a>
|
||||
<a class="s-button t-export labeled last"
|
||||
ng-click="plot.exportJPG()"
|
||||
title="Export This View's Data as JPG">
|
||||
JPG
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="l-view-section">
|
||||
<div class="gl-plot"
|
||||
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
|
||||
ng-repeat="subplot in plot.getSubPlots()">
|
||||
@ -136,4 +156,5 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -54,7 +54,8 @@ define(
|
||||
* @throws {Error} an error is thrown if WebGL is unavailable.
|
||||
*/
|
||||
function GLChart(canvas) {
|
||||
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"),
|
||||
var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) ||
|
||||
canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }),
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
program,
|
||||
|
@ -63,6 +63,8 @@ define(
|
||||
*/
|
||||
function PlotController(
|
||||
$scope,
|
||||
$element,
|
||||
exportImageService,
|
||||
telemetryFormatter,
|
||||
telemetryHandler,
|
||||
throttle,
|
||||
@ -252,6 +254,8 @@ define(
|
||||
});
|
||||
|
||||
self.pending = true;
|
||||
self.$element = $element;
|
||||
self.exportImageService = exportImageService;
|
||||
|
||||
// Initialize axes; will get repopulated when telemetry
|
||||
// metadata becomes available.
|
||||
@ -376,6 +380,39 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the plot to PDF
|
||||
*/
|
||||
PlotController.prototype.exportPDF = function () {
|
||||
var self = this;
|
||||
self.hideExportButtons = true;
|
||||
self.exportImageService.exportPDF(self.$element[0], "plot.pdf").finally(function () {
|
||||
self.hideExportButtons = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the plot to PNG
|
||||
*/
|
||||
PlotController.prototype.exportPNG = function () {
|
||||
var self = this;
|
||||
self.hideExportButtons = true;
|
||||
self.exportImageService.exportPNG(self.$element[0], "plot.png").finally(function () {
|
||||
self.hideExportButtons = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the plot to JPG
|
||||
*/
|
||||
PlotController.prototype.exportJPG = function () {
|
||||
var self = this;
|
||||
self.hideExportButtons = true;
|
||||
self.exportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () {
|
||||
self.hideExportButtons = false;
|
||||
});
|
||||
};
|
||||
|
||||
return PlotController;
|
||||
}
|
||||
);
|
||||
|
176
platform/features/plot/src/services/ExportImageService.js
Normal file
176
platform/features/plot/src/services/ExportImageService.js
Normal file
@ -0,0 +1,176 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
|
||||
*/
|
||||
define(
|
||||
[
|
||||
"html2canvas",
|
||||
"jsPDF",
|
||||
"saveAs"
|
||||
],
|
||||
function (
|
||||
html2canvas,
|
||||
jsPDF,
|
||||
saveAs
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* The export image service will export any HTML node to
|
||||
* PDF, JPG, or PNG.
|
||||
* @param {object} $q
|
||||
* @param {object} $timeout
|
||||
* @param {object} $log
|
||||
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
|
||||
* @constructor
|
||||
*/
|
||||
function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injJsPDF, injSaveAs, injFileReader) {
|
||||
self.$q = $q;
|
||||
self.$timeout = $timeout;
|
||||
self.$log = $log;
|
||||
self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT;
|
||||
self.html2canvas = injHtml2Canvas || html2canvas;
|
||||
self.jsPDF = injJsPDF || jsPDF;
|
||||
self.saveAs = injSaveAs || saveAs;
|
||||
self.reader = injFileReader || new FileReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an HTML element into a base64 encoded image
|
||||
* as a BLOB, PNG, or JPG.
|
||||
* @param {node} element that will be converted to an image
|
||||
* @param {string} type of image to convert the element to
|
||||
* @returns {promise}
|
||||
*/
|
||||
function renderElement(element, type) {
|
||||
var defer = self.$q.defer(),
|
||||
validTypes = ["png", "jpg", "jpeg"],
|
||||
renderTimeout;
|
||||
|
||||
if (validTypes.indexOf(type) === -1) {
|
||||
self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
renderTimeout = self.$timeout(function () {
|
||||
defer.reject("html2canvas timed out");
|
||||
self.$log.warn("html2canvas timed out");
|
||||
}, self.EXPORT_IMAGE_TIMEOUT);
|
||||
|
||||
try {
|
||||
self.html2canvas(element, {
|
||||
onrendered: function (canvas) {
|
||||
switch (type.toLowerCase()) {
|
||||
case "png":
|
||||
canvas.toBlob(defer.resolve, "image/png");
|
||||
break;
|
||||
|
||||
default:
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
canvas.toBlob(defer.resolve, "image/jpeg");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
defer.reject(e);
|
||||
self.$log.warn("html2canvas failed with error: " + e);
|
||||
}
|
||||
|
||||
defer.promise.finally(renderTimeout.cancel);
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
|
||||
* implements the method in browsers that would not otherwise support it.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
|
||||
*/
|
||||
function polyfillToBlob() {
|
||||
if (!HTMLCanvasElement.prototype.toBlob) {
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
|
||||
value: function (callback, type, quality) {
|
||||
|
||||
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
|
||||
len = binStr.length,
|
||||
arr = new Uint8Array(len);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = binStr.charCodeAt(i);
|
||||
}
|
||||
|
||||
callback(new Blob([arr], {type: type || "image/png"}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to PDF.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.exportPDF = function (element, filename) {
|
||||
return renderElement(element, "jpeg").then(function (img) {
|
||||
self.reader.readAsDataURL(img);
|
||||
self.reader.onloadend = function () {
|
||||
var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]);
|
||||
pdf.addImage(self.reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight);
|
||||
pdf.save(filename);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to JPG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.exportJPG = function (element, filename) {
|
||||
return renderElement(element, "jpeg").then(function (img) {
|
||||
self.saveAs(img, filename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to PNG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.exportPNG = function (element, filename) {
|
||||
return renderElement(element, "png").then(function (img) {
|
||||
self.saveAs(img, filename);
|
||||
});
|
||||
};
|
||||
|
||||
polyfillToBlob();
|
||||
|
||||
return ExportImageService;
|
||||
}
|
||||
);
|
@ -1,3 +1,5 @@
|
||||
/*global angular*/
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
@ -29,6 +31,8 @@ define(
|
||||
|
||||
describe("The plot controller", function () {
|
||||
var mockScope,
|
||||
mockElement,
|
||||
mockExportImageService,
|
||||
mockFormatter,
|
||||
mockHandler,
|
||||
mockThrottle,
|
||||
@ -66,6 +70,11 @@ define(
|
||||
"$scope",
|
||||
["$watch", "$on", "$emit"]
|
||||
);
|
||||
mockElement = angular.element('<div />');
|
||||
mockExportImageService = jasmine.createSpyObj(
|
||||
"ExportImageService",
|
||||
["exportJPG", "exportPNG", "exportPDF"]
|
||||
);
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
["formatDomainValue", "formatRangeValue"]
|
||||
@ -113,6 +122,8 @@ define(
|
||||
|
||||
controller = new PlotController(
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockExportImageService,
|
||||
mockFormatter,
|
||||
mockHandler,
|
||||
mockThrottle
|
||||
|
141
platform/features/plot/test/services/ExportImageServiceSpec.js
Normal file
141
platform/features/plot/test/services/ExportImageServiceSpec.js
Normal file
@ -0,0 +1,141 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* ExportImageServiceSpec. Created by hudsonfoo on 09/03/16.
|
||||
*/
|
||||
define(
|
||||
["../../src/services/ExportImageService"],
|
||||
function (ExportImageService) {
|
||||
var mockQ,
|
||||
mockDeferred,
|
||||
mockPromise,
|
||||
mockTimeout,
|
||||
mockLog,
|
||||
mockHtml2Canvas,
|
||||
mockCanvas,
|
||||
mockJsPDF,
|
||||
mockJsPDFSave,
|
||||
mockSaveAs,
|
||||
mockFileReader,
|
||||
mockExportTimeoutConstant,
|
||||
testElement,
|
||||
exportImageService;
|
||||
|
||||
describe("ExportImageService", function () {
|
||||
beforeEach(function () {
|
||||
mockDeferred = jasmine.createSpyObj(
|
||||
"deferred",
|
||||
["reject", "resolve"]
|
||||
);
|
||||
mockPromise = jasmine.createSpyObj(
|
||||
"promise",
|
||||
["then", "finally"]
|
||||
);
|
||||
mockPromise.then = function (callback) {
|
||||
callback();
|
||||
};
|
||||
mockQ = {
|
||||
"defer": function () {
|
||||
return {
|
||||
"resolve": mockDeferred.resolve,
|
||||
"reject": mockDeferred.reject,
|
||||
"promise": mockPromise
|
||||
};
|
||||
}
|
||||
};
|
||||
mockTimeout = function (fn, time) {
|
||||
return {
|
||||
"cancel": function () {}
|
||||
};
|
||||
};
|
||||
mockLog = jasmine.createSpyObj(
|
||||
"$log",
|
||||
["warn"]
|
||||
);
|
||||
mockHtml2Canvas = jasmine.createSpy("html2canvas").andCallFake(function (element, opts) {
|
||||
opts.onrendered(mockCanvas);
|
||||
});
|
||||
mockCanvas = jasmine.createSpyObj(
|
||||
"canvas",
|
||||
["toBlob"]
|
||||
);
|
||||
mockJsPDFSave = jasmine.createSpy("jsPDFSave");
|
||||
mockJsPDF = function () {
|
||||
return {
|
||||
"addImage": function () {},
|
||||
"save": mockJsPDFSave
|
||||
};
|
||||
};
|
||||
mockSaveAs = jasmine.createSpy("saveAs");
|
||||
mockFileReader = jasmine.createSpyObj(
|
||||
"FileReader",
|
||||
["readAsDataURL", "onloadend"]
|
||||
);
|
||||
mockExportTimeoutConstant = 0;
|
||||
testElement = {};
|
||||
|
||||
exportImageService = new ExportImageService(
|
||||
mockQ,
|
||||
mockTimeout,
|
||||
mockLog,
|
||||
mockExportTimeoutConstant,
|
||||
mockHtml2Canvas,
|
||||
mockJsPDF,
|
||||
mockSaveAs,
|
||||
mockFileReader
|
||||
);
|
||||
});
|
||||
|
||||
it("runs html2canvas and tries to save a pdf", function () {
|
||||
exportImageService.exportPDF(testElement, "plot.pdf");
|
||||
mockFileReader.onloadend();
|
||||
|
||||
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
|
||||
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
|
||||
expect(mockDeferred.reject).not.toHaveBeenCalled();
|
||||
expect(mockJsPDFSave).toHaveBeenCalled();
|
||||
expect(mockPromise.finally).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs html2canvas and tries to save a png", function () {
|
||||
exportImageService.exportPNG(testElement, "plot.png");
|
||||
|
||||
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
|
||||
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/png");
|
||||
expect(mockDeferred.reject).not.toHaveBeenCalled();
|
||||
expect(mockSaveAs).toHaveBeenCalled();
|
||||
expect(mockPromise.finally).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs html2canvas and tries to save a jpg", function () {
|
||||
exportImageService.exportJPG(testElement, "plot.png");
|
||||
|
||||
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
|
||||
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
|
||||
expect(mockDeferred.reject).not.toHaveBeenCalled();
|
||||
expect(mockSaveAs).toHaveBeenCalled();
|
||||
expect(mockPromise.finally).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -55,6 +55,8 @@ requirejs.config({
|
||||
"csv": "bower_components/comma-separated-values/csv.min",
|
||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||
"EventEmitter": "bower_components/eventemitter3/index",
|
||||
"html2canvas": "bower_components/html2canvas/build/html2canvas.min",
|
||||
"jsPDF": "bower_components/jspdf/dist/jspdf.min",
|
||||
"moment": "bower_components/moment/moment",
|
||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||
|
Loading…
Reference in New Issue
Block a user