From 852ac3cd550f3173556c168e81677e8d8d7b262c Mon Sep 17 00:00:00 2001 From: David Hudson Date: Wed, 31 Aug 2016 23:03:48 +0900 Subject: [PATCH 01/36] [Enhancement] Quick prototype of PDF export --- bower.json | 4 +++- main.js | 19 +++++++++++++++++++ platform/features/plot/src/GLChart.js | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 7c913754cf3..670cbd4b70c 100644 --- a/bower.json +++ b/bower.json @@ -18,6 +18,8 @@ "node-uuid": "^1.4.7", "comma-separated-values": "^3.6.4", "FileSaver.js": "^0.0.2", - "zepto": "^1.1.6" + "zepto": "^1.1.6", + "html2canvas": "^0.4.1", + "jspdf": "^1.2.61" } } diff --git a/main.js b/main.js index 268c70060d9..668f3409e27 100644 --- a/main.js +++ b/main.js @@ -28,6 +28,8 @@ requirejs.config({ "angular-route": "bower_components/angular-route/angular-route.min", "csv": "bower_components/comma-separated-values/csv.min", "es6-promise": "bower_components/es6-promise/promise.min", + "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", @@ -43,6 +45,9 @@ requirejs.config({ "angular-route": { "deps": ["angular"] }, + "html2canvas": { + "exports": "html2canvas" + }, "moment-duration-format": { "deps": ["moment"] }, @@ -59,6 +64,8 @@ define([ './platform/framework/src/Main', 'legacyRegistry', + 'html2canvas', + 'jspdf', './platform/framework/bundle', './platform/core/bundle', './platform/representation/bundle', @@ -95,6 +102,18 @@ define([ './platform/status/bundle', './platform/commonUI/regions/bundle' ], function (Main, legacyRegistry) { + setTimeout(function() { + var plotEl = document.getElementsByClassName('gl-plot')[0]; + + html2canvas(plotEl, { + onrendered: function(htmlCanvas) { + var pdf = new jsPDF('l', 'px', [plotEl.offsetHeight, plotEl.offsetWidth]); + pdf.addImage(htmlCanvas.toDataURL('image/jpeg', 2.0), 'JPEG', 0, 0, plotEl.offsetWidth, plotEl.offsetHeight); + pdf.save("plot.pdf"); + } + }); + }, 5000); + return { legacyRegistry: legacyRegistry, run: function () { diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js index 56ca29bb3e6..348a64f9230 100644 --- a/platform/features/plot/src/GLChart.js +++ b/platform/features/plot/src/GLChart.js @@ -54,7 +54,7 @@ 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, From 5e0975ddfac3b8d83ac4a66ef270beec2c39f1da Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 02:29:26 +0900 Subject: [PATCH 02/36] [Formatting] Remove test code --- main.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/main.js b/main.js index 668f3409e27..057346d91ea 100644 --- a/main.js +++ b/main.js @@ -102,18 +102,6 @@ define([ './platform/status/bundle', './platform/commonUI/regions/bundle' ], function (Main, legacyRegistry) { - setTimeout(function() { - var plotEl = document.getElementsByClassName('gl-plot')[0]; - - html2canvas(plotEl, { - onrendered: function(htmlCanvas) { - var pdf = new jsPDF('l', 'px', [plotEl.offsetHeight, plotEl.offsetWidth]); - pdf.addImage(htmlCanvas.toDataURL('image/jpeg', 2.0), 'JPEG', 0, 0, plotEl.offsetWidth, plotEl.offsetHeight); - pdf.save("plot.pdf"); - } - }); - }, 5000); - return { legacyRegistry: legacyRegistry, run: function () { From 06a453333adf48f8f50175b39f7c13ad51f4c52a Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 02:31:34 +0900 Subject: [PATCH 03/36] [Enhancement] Pass element to plot controller This will be used for exporting an image of the plot. --- platform/features/plot/bundle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 3ce136abdfd..c09a9651971 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -70,6 +70,7 @@ define([ "implementation": PlotController, "depends": [ "$scope", + "$element", "telemetryFormatter", "telemetryHandler", "throttle", From 219301a85b55fd78840892e75ec7c47a4b7626e5 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 02:32:42 +0900 Subject: [PATCH 04/36] [Frontend] Add export plot buttons --- platform/features/plot/res/templates/plot.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index 6dc3cd71b9e..c4b5321226d 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -21,6 +21,21 @@ --> + + Export as PDF + + + Export as PNG + + + Export as JPG +
From ea8f4299028647b6b381c366f2c5a5beee1d2fa8 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 02:41:43 +0900 Subject: [PATCH 05/36] [Enhancement] Add export functionality to PlotController Extends PlotController by adding three new scoped methods: exportPDF, exportPNG, exportJPG. All three methods use basically the same steps. The HTML node of the plot is passed through html2canvas which generates a canvas. From the canvas we export a blob, PNG, or JPG then save the file. --- platform/features/plot/src/PlotController.js | 41 ++++++++- .../plot/src/services/ExportImageService.js | 92 +++++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 platform/features/plot/src/services/ExportImageService.js diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 88d82ea31bd..1cbccb8cfb8 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -32,7 +32,8 @@ define( "./elements/PlotLimitTracker", "./elements/PlotTelemetryFormatter", "./modes/PlotModeOptions", - "./SubPlotFactory" + "./SubPlotFactory", + "./services/ExportImageService" ], function ( PlotUpdater, @@ -41,7 +42,8 @@ define( PlotLimitTracker, PlotTelemetryFormatter, PlotModeOptions, - SubPlotFactory + SubPlotFactory, + ExportImageService ) { var AXIS_DEFAULTS = [ @@ -63,6 +65,7 @@ define( */ function PlotController( $scope, + $element, telemetryFormatter, telemetryHandler, throttle, @@ -246,6 +249,7 @@ define( }); self.pending = true; + self.$element = $element; // Initialize axes; will get repopulated when telemetry // metadata becomes available. @@ -254,6 +258,18 @@ define( new PlotAxis("ranges", [], AXIS_DEFAULTS[1]) ]; + $scope.exportPDF = function() { + PlotController.prototype.exportPDF(self.$element, 'plot.pdf'); + }; + + $scope.exportPNG = function() { + PlotController.prototype.exportPNG(self.$element, 'plot.png'); + }; + + $scope.exportJPG = function() { + PlotController.prototype.exportJPG(self.$element, 'plot.jpg'); + }; + // Watch for changes to the selected axis $scope.$watch("axes[0].active.key", domainRequery); $scope.$watch("axes[1].active.key", rangeRequery); @@ -364,6 +380,27 @@ define( return this.pending; }; + /** + * Export the plot to PDF + */ + PlotController.prototype.exportPDF = function (element, filename) { + ExportImageService.exportPDF(element[0], filename); + }; + + /** + * Export the plot to PNG + */ + PlotController.prototype.exportPNG = function (element, filename) { + ExportImageService.exportPNG(element, filename); + }; + + /** + * Export the plot to JPG + */ + PlotController.prototype.exportJPG = function (element, filename) { + ExportImageService.exportJPG(element, filename); + }; + return PlotController; } ); diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js new file mode 100644 index 00000000000..b1dc8a51d73 --- /dev/null +++ b/platform/features/plot/src/services/ExportImageService.js @@ -0,0 +1,92 @@ +/***************************************************************************** + * 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( + ['saveAs'], + function (saveAs) { + + /** + * The export image service will export any HTML node to + * PDF, JPG, or PNG. + * @constructor + */ + function ExportImageService() { + } + + /** + * 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 {function} callback for passing the resulting image + * @param {string} type of image to convert the element to + * @returns {string} the color, in #RRGGBB form + */ + function renderElement(element, callback, type) { + type = type || 'jpeg'; + + html2canvas(element, { + onrendered: function(canvas) { + switch (type.toLowerCase()) { + case "blob": + canvas.toBlob(callback); + break; + + case "png": + callback(canvas.toDataURL('image/png', 1.0)); + break; + + default: + case "jpg": + case "jpeg": + callback(canvas.toDataURL('image/jpeg', 1.0)); + break; + } + } + }); + } + + ExportImageService.exportPDF = function(element, filename) { + renderElement(element, function(img) { + var pdf = new jsPDF('l', 'px', [element.offsetHeight, element.offsetWidth]); + pdf.addImage(img, 'JPEG', 0, 0, element.offsetWidth, element.offsetHeight); + pdf.save(filename); + }, 'jpeg'); + }; + + ExportImageService.exportJPG = function(element, filename) { + renderElement(element, function(img) { + saveAs(img, filename); + }, "blob"); + }; + + ExportImageService.exportPNG = function(element, filename) { + renderElement(element, function(img) { + saveAs(img, filename); + }, "blob"); + }; + + return ExportImageService; + } +); From 3a19890be9c3855ec26131c08852bf9953fa6800 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 03:11:26 +0900 Subject: [PATCH 06/36] [Formatting] Linting --- main.js | 7 +++--- platform/features/plot/src/PlotController.js | 6 ++--- .../plot/src/services/ExportImageService.js | 24 +++++++++++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/main.js b/main.js index 057346d91ea..58ea2b1756e 100644 --- a/main.js +++ b/main.js @@ -29,7 +29,7 @@ requirejs.config({ "csv": "bower_components/comma-separated-values/csv.min", "es6-promise": "bower_components/es6-promise/promise.min", "html2canvas": "bower_components/html2canvas/build/html2canvas.min", - "jspdf": "bower_components/jspdf/dist/jspdf.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", @@ -48,6 +48,9 @@ requirejs.config({ "html2canvas": { "exports": "html2canvas" }, + "jsPDF": { + "exports": "jsPDF" + }, "moment-duration-format": { "deps": ["moment"] }, @@ -64,8 +67,6 @@ define([ './platform/framework/src/Main', 'legacyRegistry', - 'html2canvas', - 'jspdf', './platform/framework/bundle', './platform/core/bundle', './platform/representation/bundle', diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 1cbccb8cfb8..0e134c05e97 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -258,15 +258,15 @@ define( new PlotAxis("ranges", [], AXIS_DEFAULTS[1]) ]; - $scope.exportPDF = function() { + $scope.exportPDF = function () { PlotController.prototype.exportPDF(self.$element, 'plot.pdf'); }; - $scope.exportPNG = function() { + $scope.exportPNG = function () { PlotController.prototype.exportPNG(self.$element, 'plot.png'); }; - $scope.exportJPG = function() { + $scope.exportJPG = function () { PlotController.prototype.exportJPG(self.$element, 'plot.jpg'); }; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index b1dc8a51d73..2f55c4eaadd 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -24,15 +24,19 @@ * Module defining ExportImageService. Created by hudsonfoo on 09/02/16 */ define( - ['saveAs'], - function (saveAs) { + [ + 'html2canvas', + 'jsPDF', + 'saveAs' + ], + function (html2canvas, jsPDF, saveAs) { /** * The export image service will export any HTML node to * PDF, JPG, or PNG. * @constructor */ - function ExportImageService() { + function ExportImageService () { } /** @@ -47,7 +51,7 @@ define( type = type || 'jpeg'; html2canvas(element, { - onrendered: function(canvas) { + onrendered: function (canvas) { switch (type.toLowerCase()) { case "blob": canvas.toBlob(callback); @@ -67,22 +71,22 @@ define( }); } - ExportImageService.exportPDF = function(element, filename) { - renderElement(element, function(img) { + ExportImageService.exportPDF = function (element, filename) { + renderElement(element, function (img) { var pdf = new jsPDF('l', 'px', [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, 'JPEG', 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); }, 'jpeg'); }; - ExportImageService.exportJPG = function(element, filename) { - renderElement(element, function(img) { + ExportImageService.exportJPG = function (element, filename) { + renderElement(element, function (img) { saveAs(img, filename); }, "blob"); }; - ExportImageService.exportPNG = function(element, filename) { - renderElement(element, function(img) { + ExportImageService.exportPNG = function (element, filename) { + renderElement(element, function (img) { saveAs(img, filename); }, "blob"); }; From e3702710930eeb8cab1e70e75724ef26068eed86 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 11:54:38 +0900 Subject: [PATCH 07/36] [Code Review] Updates based on code review by @VWoeltjen Set export functions on the ExportImageService prototype. Insantiated ExportImageService in the PlotController for better dependcy injection. --- .../features/plot/res/templates/plot.html | 6 ++--- platform/features/plot/src/PlotController.js | 25 ++++++------------- .../plot/src/services/ExportImageService.js | 6 ++--- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index c4b5321226d..e1a712cbee0 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -22,17 +22,17 @@ Export as PDF Export as PNG Export as JPG diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 0e134c05e97..0c721f01fd2 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -250,6 +250,7 @@ define( self.pending = true; self.$element = $element; + self.ExportImageService = new ExportImageService(); // Initialize axes; will get repopulated when telemetry // metadata becomes available. @@ -258,18 +259,6 @@ define( new PlotAxis("ranges", [], AXIS_DEFAULTS[1]) ]; - $scope.exportPDF = function () { - PlotController.prototype.exportPDF(self.$element, 'plot.pdf'); - }; - - $scope.exportPNG = function () { - PlotController.prototype.exportPNG(self.$element, 'plot.png'); - }; - - $scope.exportJPG = function () { - PlotController.prototype.exportJPG(self.$element, 'plot.jpg'); - }; - // Watch for changes to the selected axis $scope.$watch("axes[0].active.key", domainRequery); $scope.$watch("axes[1].active.key", rangeRequery); @@ -383,22 +372,22 @@ define( /** * Export the plot to PDF */ - PlotController.prototype.exportPDF = function (element, filename) { - ExportImageService.exportPDF(element[0], filename); + PlotController.prototype.exportPDF = function () { + this.ExportImageService.exportPDF(this.$element[0], 'plot.pdf'); }; /** * Export the plot to PNG */ - PlotController.prototype.exportPNG = function (element, filename) { - ExportImageService.exportPNG(element, filename); + PlotController.prototype.exportPNG = function () { + this.ExportImageService.exportPNG(this.$element[0], 'plot.png'); }; /** * Export the plot to JPG */ - PlotController.prototype.exportJPG = function (element, filename) { - ExportImageService.exportJPG(element, filename); + PlotController.prototype.exportJPG = function () { + this.ExportImageService.exportJPG(this.$element[0], 'plot.jpg'); }; return PlotController; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 2f55c4eaadd..7721714b52c 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -71,7 +71,7 @@ define( }); } - ExportImageService.exportPDF = function (element, filename) { + ExportImageService.prototype.exportPDF = function (element, filename) { renderElement(element, function (img) { var pdf = new jsPDF('l', 'px', [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, 'JPEG', 0, 0, element.offsetWidth, element.offsetHeight); @@ -79,13 +79,13 @@ define( }, 'jpeg'); }; - ExportImageService.exportJPG = function (element, filename) { + ExportImageService.prototype.exportJPG = function (element, filename) { renderElement(element, function (img) { saveAs(img, filename); }, "blob"); }; - ExportImageService.exportPNG = function (element, filename) { + ExportImageService.prototype.exportPNG = function (element, filename) { renderElement(element, function (img) { saveAs(img, filename); }, "blob"); From 3e3d3ebcf9c3015e3b5937fa004c2d70ff5fecff Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 11:58:15 +0900 Subject: [PATCH 08/36] [Formatting] Switched to double quote across the board for consistency --- platform/features/plot/src/PlotController.js | 6 +++--- .../plot/src/services/ExportImageService.js | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 0c721f01fd2..3183d3d82fa 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -373,21 +373,21 @@ define( * Export the plot to PDF */ PlotController.prototype.exportPDF = function () { - this.ExportImageService.exportPDF(this.$element[0], 'plot.pdf'); + this.ExportImageService.exportPDF(this.$element[0], "plot.pdf"); }; /** * Export the plot to PNG */ PlotController.prototype.exportPNG = function () { - this.ExportImageService.exportPNG(this.$element[0], 'plot.png'); + this.ExportImageService.exportPNG(this.$element[0], "plot.png"); }; /** * Export the plot to JPG */ PlotController.prototype.exportJPG = function () { - this.ExportImageService.exportJPG(this.$element[0], 'plot.jpg'); + this.ExportImageService.exportJPG(this.$element[0], "plot.jpg"); }; return PlotController; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 7721714b52c..e05dfe47db5 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -25,9 +25,9 @@ */ define( [ - 'html2canvas', - 'jsPDF', - 'saveAs' + "html2canvas", + "jsPDF", + "saveAs" ], function (html2canvas, jsPDF, saveAs) { @@ -48,7 +48,7 @@ define( * @returns {string} the color, in #RRGGBB form */ function renderElement(element, callback, type) { - type = type || 'jpeg'; + type = type || "jpeg"; html2canvas(element, { onrendered: function (canvas) { @@ -58,13 +58,13 @@ define( break; case "png": - callback(canvas.toDataURL('image/png', 1.0)); + callback(canvas.toDataURL("image/png", 1.0)); break; default: case "jpg": case "jpeg": - callback(canvas.toDataURL('image/jpeg', 1.0)); + callback(canvas.toDataURL("image/jpeg", 1.0)); break; } } @@ -73,10 +73,10 @@ define( ExportImageService.prototype.exportPDF = function (element, filename) { renderElement(element, function (img) { - var pdf = new jsPDF('l', 'px', [element.offsetHeight, element.offsetWidth]); - pdf.addImage(img, 'JPEG', 0, 0, element.offsetWidth, element.offsetHeight); + var pdf = new jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); + pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); - }, 'jpeg'); + }, "jpeg"); }; ExportImageService.prototype.exportJPG = function (element, filename) { From e7a7025961c29c56cbbe84bf09d37893cb3c1205 Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Thu, 1 Sep 2016 17:10:21 -0700 Subject: [PATCH 09/36] [Frontend] Markup for #967 Fixes #967 WIP to be integrated with work from hudsonfoo in PR #1164 (cherry picked from commit ebadad61c64cbf57381112bedcb605cdef33b614) --- .../features/plot/res/templates/plot.html | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index e1a712cbee0..192fbaab2eb 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -21,21 +21,23 @@ --> - - Export as PDF - - - Export as PNG - - + - Export as JPG - + title="Export This View's Data as JPG"> + JPG + + + PNG + + + PDF + +
From 3736f84c12edc4bbe07e49a3ca67c0ddfa45e098 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 19:36:39 +0900 Subject: [PATCH 10/36] [Frontend] Set plot export buttons to show/hide on hover It's worth noting that I changed the .gl-plot position from relative to absolute in an attempt to match the results to a similar requirement in MCT Table. Setting to absolute caused no regressions as far as I could tell, but I have not attempted browsers outside of Chrome. --- .../general/res/sass/plots/_plots-main.scss | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index 572b5429e3a..12f148c0d9c 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -20,14 +20,35 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ .abs.holder-plot { + $btnExportH: $btnFrameH; + // Fend off the scrollbar when less than min-height; right: $interiorMargin; + + .s-button.t-export { + @include trans-prop-nice(opacity, $dur: 50ms); + opacity: 0; + } + .gl-plot { + @include trans-prop-nice(top, $dur: 150ms, $delay: 50ms); + top: 0; + } + &:hover { + .s-button.t-export { + @include trans-prop-nice(opacity, 150ms, 100ms); + opacity: 1; + } + .gl-plot { + @include trans-prop-nice(top, $dur: 150ms); + top: $btnExportH + $interiorMargin; + } + } } .gl-plot { color: $colorPlotFg; font-size: 0.7rem; - position: relative; + position: absolute; width: 100%; height: 100%; min-height: $plotMinH; From de85f79ab6ddb3c98b9e1c52a179dc55917ccb91 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 2 Sep 2016 20:41:38 +0900 Subject: [PATCH 11/36] [Enhancement] Hide export buttons during export Buttons temporarily hide until export completes. --- platform/features/plot/res/templates/plot.html | 2 +- platform/features/plot/src/PlotController.js | 18 +++++++++++++++--- .../plot/src/services/ExportImageService.js | 15 ++++++++++++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index 192fbaab2eb..dc230bcfa9c 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -21,7 +21,7 @@ --> - + diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 3183d3d82fa..89b650b0865 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -373,21 +373,33 @@ define( * Export the plot to PDF */ PlotController.prototype.exportPDF = function () { - this.ExportImageService.exportPDF(this.$element[0], "plot.pdf"); + var self = this; + self.hideExportButtons = true; + self.ExportImageService.exportPDF(self.$element[0], "plot.pdf", function() { + self.hideExportButtons = false; + }); }; /** * Export the plot to PNG */ PlotController.prototype.exportPNG = function () { - this.ExportImageService.exportPNG(this.$element[0], "plot.png"); + var self = this; + self.hideExportButtons = true; + self.ExportImageService.exportPNG(self.$element[0], "plot.png", function() { + self.hideExportButtons = false; + }); }; /** * Export the plot to JPG */ PlotController.prototype.exportJPG = function () { - this.ExportImageService.exportJPG(this.$element[0], "plot.jpg"); + var self = this; + self.hideExportButtons = true; + self.ExportImageService.exportJPG(self.$element[0], "plot.jpg", function() { + self.hideExportButtons = false; + }); }; return PlotController; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index e05dfe47db5..d07f40c97fb 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -71,23 +71,32 @@ define( }); } - ExportImageService.prototype.exportPDF = function (element, filename) { + ExportImageService.prototype.exportPDF = function (element, filename, callback) { + callback = typeof callback === "function" ? callback : function () {}; + renderElement(element, function (img) { var pdf = new jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); + callback(); }, "jpeg"); }; - ExportImageService.prototype.exportJPG = function (element, filename) { + ExportImageService.prototype.exportJPG = function (element, filename, callback) { + callback = typeof callback === "function" ? callback : function () {}; + renderElement(element, function (img) { saveAs(img, filename); + callback(); }, "blob"); }; - ExportImageService.prototype.exportPNG = function (element, filename) { + ExportImageService.prototype.exportPNG = function (element, filename, callback) { + callback = typeof callback === "function" ? callback : function () {}; + renderElement(element, function (img) { saveAs(img, filename); + callback(); }, "blob"); }; From 40ad9c47a8bf626d2a3872ab062fdc05f0e3e5c3 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 3 Sep 2016 13:51:16 +0900 Subject: [PATCH 12/36] [Tests] Add jsPDF and html2canvas --- test-main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-main.js b/test-main.js index c329407f931..0e28eb56619 100644 --- a/test-main.js +++ b/test-main.js @@ -54,6 +54,8 @@ requirejs.config({ "angular-route": "bower_components/angular-route/angular-route.min", "csv": "bower_components/comma-separated-values/csv.min", "es6-promise": "bower_components/es6-promise/promise.min", + "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", From 92bfea1773398865cbac575402f7d0ff613e7e2e Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 3 Sep 2016 13:51:37 +0900 Subject: [PATCH 13/36] [Tests] Mock new argument in PlotController --- platform/features/plot/test/PlotControllerSpec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index a3de710bdfe..de3230858c1 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -29,6 +29,7 @@ define( describe("The plot controller", function () { var mockScope, + mockElement, mockFormatter, mockHandler, mockThrottle, @@ -65,6 +66,7 @@ define( "$scope", ["$watch", "$on", "$emit"] ); + mockElement = angular.element('
'); mockFormatter = jasmine.createSpyObj( "formatter", ["formatDomainValue", "formatRangeValue"] @@ -107,6 +109,7 @@ define( controller = new PlotController( mockScope, + mockElement, mockFormatter, mockHandler, mockThrottle From 26c14d2bcadf6f2ce4c0bf1ac1ffa0fa798e5b01 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 3 Sep 2016 14:02:19 +0900 Subject: [PATCH 14/36] [Formatting] Fix styling errors --- platform/features/plot/src/PlotController.js | 6 +++--- platform/features/plot/src/services/ExportImageService.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 89b650b0865..fa7e1dc7266 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -375,7 +375,7 @@ define( PlotController.prototype.exportPDF = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPDF(self.$element[0], "plot.pdf", function() { + self.ExportImageService.exportPDF(self.$element[0], "plot.pdf", function () { self.hideExportButtons = false; }); }; @@ -386,7 +386,7 @@ define( PlotController.prototype.exportPNG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPNG(self.$element[0], "plot.png", function() { + self.ExportImageService.exportPNG(self.$element[0], "plot.png", function () { self.hideExportButtons = false; }); }; @@ -397,7 +397,7 @@ define( PlotController.prototype.exportJPG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportJPG(self.$element[0], "plot.jpg", function() { + self.ExportImageService.exportJPG(self.$element[0], "plot.jpg", function () { self.hideExportButtons = false; }); }; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index d07f40c97fb..633635a2c61 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -29,14 +29,14 @@ define( "jsPDF", "saveAs" ], - function (html2canvas, jsPDF, saveAs) { + function (html2canvas, JsPdf, saveAs) { /** * The export image service will export any HTML node to * PDF, JPG, or PNG. * @constructor */ - function ExportImageService () { + function ExportImageService() { } /** @@ -75,7 +75,7 @@ define( callback = typeof callback === "function" ? callback : function () {}; renderElement(element, function (img) { - var pdf = new jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); + var pdf = new JsPdf("l", "px", [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); callback(); From fecf419f83288d2cf68b4c2679b78994047d8a8a Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 3 Sep 2016 14:44:33 +0900 Subject: [PATCH 15/36] [Testing] Move ExportImageController into constructor for dependency injection --- platform/features/plot/bundle.js | 10 ++++++++++ platform/features/plot/src/PlotController.js | 9 ++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index c09a9651971..bec42694b32 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -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 @@ -71,6 +73,7 @@ define([ "depends": [ "$scope", "$element", + "ExportImageService", "telemetryFormatter", "telemetryHandler", "throttle", @@ -85,6 +88,13 @@ define([ ] } ], + "services": [ + { + "key": "ExportImageService", + "implementation": ExportImageService, + "depends": [] + } + ], "constants": [ { "key": "PLOT_FIXED_DURATION", diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index fa7e1dc7266..074e17f9b6e 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -32,8 +32,7 @@ define( "./elements/PlotLimitTracker", "./elements/PlotTelemetryFormatter", "./modes/PlotModeOptions", - "./SubPlotFactory", - "./services/ExportImageService" + "./SubPlotFactory" ], function ( PlotUpdater, @@ -42,8 +41,7 @@ define( PlotLimitTracker, PlotTelemetryFormatter, PlotModeOptions, - SubPlotFactory, - ExportImageService + SubPlotFactory ) { var AXIS_DEFAULTS = [ @@ -66,6 +64,7 @@ define( function PlotController( $scope, $element, + ExportImageService, telemetryFormatter, telemetryHandler, throttle, @@ -250,7 +249,7 @@ define( self.pending = true; self.$element = $element; - self.ExportImageService = new ExportImageService(); + self.ExportImageService = ExportImageService; // Initialize axes; will get repopulated when telemetry // metadata becomes available. From a566265a72f7ebacfb0ba23d66664d2bcdd3db48 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 3 Sep 2016 14:45:09 +0900 Subject: [PATCH 16/36] [Testing] Add ExportImageService mock --- platform/features/plot/test/PlotControllerSpec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index de3230858c1..73a01d716a7 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -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 @@ -30,6 +32,7 @@ define( describe("The plot controller", function () { var mockScope, mockElement, + mockExportImageService, mockFormatter, mockHandler, mockThrottle, @@ -67,6 +70,10 @@ define( ["$watch", "$on", "$emit"] ); mockElement = angular.element('
'); + mockExportImageService = jasmine.createSpyObj( + "ExportImageService", + ["exportJPG", "exportPNG", "exportPDF"] + ); mockFormatter = jasmine.createSpyObj( "formatter", ["formatDomainValue", "formatRangeValue"] @@ -110,6 +117,7 @@ define( controller = new PlotController( mockScope, mockElement, + mockExportImageService, mockFormatter, mockHandler, mockThrottle From 9bb647e27577f78c281a950bb27f9fd3eebb5a07 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sun, 4 Sep 2016 19:19:14 +0900 Subject: [PATCH 17/36] [Testing] Add better dependency injection and error handling --- platform/features/plot/bundle.js | 13 ++- platform/features/plot/src/PlotController.js | 6 +- .../plot/src/services/ExportImageService.js | 93 +++++++++++-------- 3 files changed, 68 insertions(+), 44 deletions(-) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index bec42694b32..874f8fa2405 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -92,7 +92,13 @@ define([ { "key": "ExportImageService", "implementation": ExportImageService, - "depends": [] + "depends": [ + "$q", + "$timeout", + "$log", + "EXPORT_IMAGE_TIMEOUT" + ] + } ], "constants": [ @@ -101,6 +107,11 @@ define([ "value": 900000, "priority": "fallback", "comment": "Fifteen minutes." + }, + { + "key": "EXPORT_IMAGE_TIMEOUT", + "value": 500, + "priority": "fallback" } ], "policies": [ diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 074e17f9b6e..97e848f66aa 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -374,7 +374,7 @@ define( PlotController.prototype.exportPDF = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPDF(self.$element[0], "plot.pdf", function () { + self.ExportImageService.exportPDF(self.$element[0], "plot.pdf").finally(function () { self.hideExportButtons = false; }); }; @@ -385,7 +385,7 @@ define( PlotController.prototype.exportPNG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPNG(self.$element[0], "plot.png", function () { + self.ExportImageService.exportPNG(self.$element[0], "plot.png").finally(function () { self.hideExportButtons = false; }); }; @@ -396,7 +396,7 @@ define( PlotController.prototype.exportJPG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportJPG(self.$element[0], "plot.jpg", function () { + self.ExportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () { self.hideExportButtons = false; }); }; diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 633635a2c61..611330c2d94 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -29,75 +29,88 @@ define( "jsPDF", "saveAs" ], - function (html2canvas, JsPdf, saveAs) { + function ( + html2canvas, + JsPdf, + saveAs + ) { + var self = this; /** * The export image service will export any HTML node to * PDF, JPG, or PNG. * @constructor */ - function ExportImageService() { + function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT) { + self.$q = $q; + self.$timeout = $timeout; + self.$log = $log; + self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT; } /** * 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 {function} callback for passing the resulting image * @param {string} type of image to convert the element to - * @returns {string} the color, in #RRGGBB form + * @returns {promise} */ - function renderElement(element, callback, type) { - type = type || "jpeg"; + function renderElement(element, type) { + var defer = self.$q.defer(), + renderTimeout; - html2canvas(element, { - onrendered: function (canvas) { - switch (type.toLowerCase()) { - case "blob": - canvas.toBlob(callback); - break; + renderTimeout = self.$timeout(function() { + defer.reject("html2canvas timed out"); + }, self.EXPORT_IMAGE_TIMEOUT); - case "png": - callback(canvas.toDataURL("image/png", 1.0)); - break; + try { + html2canvas(element, { + onrendered: function (canvas) { + switch (type.toLowerCase()) { + case "blob": + canvas.toBlob(defer.resolve); + break; - default: - case "jpg": - case "jpeg": - callback(canvas.toDataURL("image/jpeg", 1.0)); - break; + case "png": + defer.resolve(canvas.toDataURL("image/png", 1.0)); + break; + + default: + case "jpg": + case "jpeg": + defer.resolve(canvas.toDataURL("image/jpeg", 1.0)); + break; + } } - } - }); - } + }); + } catch(e) { + self.$log.warn("html2canvas failed with error: " + e); + defer.reject(e); + } - ExportImageService.prototype.exportPDF = function (element, filename, callback) { - callback = typeof callback === "function" ? callback : function () {}; + defer.promise.finally(renderTimeout.cancel); + + return defer.promise; + } - renderElement(element, function (img) { + ExportImageService.prototype.exportPDF = function (element, filename) { + return renderElement(element, "jpeg").then(function (img) { var pdf = new JsPdf("l", "px", [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); - callback(); - }, "jpeg"); + }); }; - ExportImageService.prototype.exportJPG = function (element, filename, callback) { - callback = typeof callback === "function" ? callback : function () {}; - - renderElement(element, function (img) { + ExportImageService.prototype.exportJPG = function (element, filename) { + return renderElement(element, "blob").then(function (img) { saveAs(img, filename); - callback(); - }, "blob"); + }); }; - ExportImageService.prototype.exportPNG = function (element, filename, callback) { - callback = typeof callback === "function" ? callback : function () {}; - - renderElement(element, function (img) { + ExportImageService.prototype.exportPNG = function (element, filename) { + return renderElement(element, "blob").then(function (img) { saveAs(img, filename); - callback(); - }, "blob"); + }); }; return ExportImageService; From 989e4e1b75606e9383cd7f95c7f6419017e1b269 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sun, 4 Sep 2016 23:02:23 +0900 Subject: [PATCH 18/36] [Testing] Add jsPDF, html2canvas, and saveAs as injectible dependencies I would prefer this be passed in via the bundle, but it continues to fail saying "Unknown Provider". I have chosen to require them into the module the old-fashioned way, then allow an injectible dependency to override. --- .../plot/src/services/ExportImageService.js | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 611330c2d94..37300686a90 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -31,7 +31,7 @@ define( ], function ( html2canvas, - JsPdf, + jsPDF, saveAs ) { var self = this; @@ -39,13 +39,20 @@ define( /** * 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) { + function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injJsPDF, injSaveAs) { 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; } /** @@ -59,12 +66,13 @@ define( var defer = self.$q.defer(), renderTimeout; - renderTimeout = self.$timeout(function() { + renderTimeout = self.$timeout(function () { defer.reject("html2canvas timed out"); + self.$log.warn("html2canvas timed out"); }, self.EXPORT_IMAGE_TIMEOUT); try { - html2canvas(element, { + self.html2canvas(element, { onrendered: function (canvas) { switch (type.toLowerCase()) { case "blob": @@ -83,9 +91,9 @@ define( } } }); - } catch(e) { - self.$log.warn("html2canvas failed with error: " + e); + } catch (e) { defer.reject(e); + self.$log.warn("html2canvas failed with error: " + e); } defer.promise.finally(renderTimeout.cancel); @@ -95,7 +103,7 @@ define( ExportImageService.prototype.exportPDF = function (element, filename) { return renderElement(element, "jpeg").then(function (img) { - var pdf = new JsPdf("l", "px", [element.offsetHeight, element.offsetWidth]); + var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); }); @@ -103,13 +111,13 @@ define( ExportImageService.prototype.exportJPG = function (element, filename) { return renderElement(element, "blob").then(function (img) { - saveAs(img, filename); + self.saveAs(img, filename); }); }; ExportImageService.prototype.exportPNG = function (element, filename) { return renderElement(element, "blob").then(function (img) { - saveAs(img, filename); + self.saveAs(img, filename); }); }; From a49eebf9f5a46f8f1cffdb27b6673f154c347950 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sun, 4 Sep 2016 23:04:46 +0900 Subject: [PATCH 19/36] [Testing] Add test spec for ExportImageService --- .../test/services/ExportImageServiceSpec.js | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 platform/features/plot/test/services/ExportImageServiceSpec.js diff --git a/platform/features/plot/test/services/ExportImageServiceSpec.js b/platform/features/plot/test/services/ExportImageServiceSpec.js new file mode 100644 index 00000000000..e25572e3a1b --- /dev/null +++ b/platform/features/plot/test/services/ExportImageServiceSpec.js @@ -0,0 +1,122 @@ +/***************************************************************************** + * 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, + mockJsPDF, + mockJsPDFSave, + mockSaveAs, + mockExportTimeoutConstant, + 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"); + mockJsPDFSave = jasmine.createSpy("jsPDFSave"); + mockJsPDF = function () { + return { + "addImage": function () {}, + "save": mockJsPDFSave + }; + }; + mockSaveAs = jasmine.createSpy("saveAs"); + mockExportTimeoutConstant = 0; + + exportImageService = new ExportImageService( + mockQ, + mockTimeout, + mockLog, + mockExportTimeoutConstant, + mockHtml2Canvas, + mockJsPDF, + mockSaveAs + ); + }); + + it("runs html2canvas and tries to save a pdf", function () { + exportImageService.exportPDF("", "plot.pdf"); + + expect(mockHtml2Canvas).toHaveBeenCalled(); + expect(mockDeferred.reject).not.toHaveBeenCalled(); + expect(mockJsPDFSave).toHaveBeenCalled(); + expect(mockPromise.finally).toHaveBeenCalled(); + }); + + it("runs html2canvas and tries to save a png", function () { + exportImageService.exportPNG("", "plot.png"); + + expect(mockHtml2Canvas).toHaveBeenCalled(); + expect(mockDeferred.reject).not.toHaveBeenCalled(); + expect(mockSaveAs).toHaveBeenCalled(); + expect(mockPromise.finally).toHaveBeenCalled(); + }); + + it("runs html2canvas and tries to save a jpg", function () { + exportImageService.exportJPG("", "plot.png"); + + expect(mockHtml2Canvas).toHaveBeenCalled(); + expect(mockDeferred.reject).not.toHaveBeenCalled(); + expect(mockSaveAs).toHaveBeenCalled(); + expect(mockPromise.finally).toHaveBeenCalled(); + }); + }); + } +); From 7d13ce06261af94efa67386e30402c05961d6b34 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 5 Sep 2016 01:22:43 +0900 Subject: [PATCH 20/36] [Licenses] Add license information for html2canvas, jsPDF, saveAs --- platform/features/plot/bundle.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 874f8fa2405..b218fefe2ad 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -125,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" + } ] } }); From 35a331f3fd102e01f9e9ca931f914f6e1710a22a Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 5 Sep 2016 17:02:04 +0900 Subject: [PATCH 21/36] [Frontend] Fix plot image cutoff on export The images were being cutoff due to the "top" attribute of .gl-plot not changing when the buttons were hidden. The buttons are now hidden by CSS class rather than ng-show, which makes updating the .gl-plot top attribute easier. --- .../commonUI/general/res/sass/plots/_plots-main.scss | 10 ++++++++++ platform/features/plot/res/templates/plot.html | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index 12f148c0d9c..8fda639bea7 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -43,6 +43,16 @@ top: $btnExportH + $interiorMargin; } } + &.hide-l-btn-set { + .s-button.t-export { + @include trans-prop-nice(opacity, 0ms, 0ms); + opacity: 0; + } + .gl-plot { + @include trans-prop-nice(top, $dur: 0ms); + top: 0; + } + } } .gl-plot { diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index dc230bcfa9c..ac4fb0c3886 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -20,8 +20,9 @@ at runtime from the About dialog for additional information. --> - + class="abs holder holder-plot" + ng-class="{'hide-l-btn-set': plot.hideExportButtons}"> + From ceb3e8e3dd2825e7d716f6ef0fe707e92691a472 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 5 Sep 2016 19:40:22 +0900 Subject: [PATCH 22/36] [Enhancement] Add IE, Opera, Safari support for canvas.toBlob() This is currently being used for exporting plots to PNG/JPG. --- .../plot/src/services/ExportImageService.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 37300686a90..21b56e2bda1 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -101,6 +101,30 @@ define( 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"})); + } + }); + } + } + ExportImageService.prototype.exportPDF = function (element, filename) { return renderElement(element, "jpeg").then(function (img) { var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); @@ -121,6 +145,8 @@ define( }); }; + polyfillToBlob(); + return ExportImageService; } ); From 4517bd13563978ac1467fcd69c55e8c4aed7d8bc Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Thu, 1 Sep 2016 19:48:41 -0700 Subject: [PATCH 23/36] [Frontend] New markup for CSS from #1166 Fixes #967 Requires new styles implemented in #1166 (cherry picked from commit 0457f7bee80f228878912f7ea1fcbe191c2046dc) --- .../features/plot/res/templates/plot.html | 235 +++++++++--------- 1 file changed, 119 insertions(+), 116 deletions(-) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index ac4fb0c3886..411fd156295 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -20,138 +20,141 @@ at runtime from the About dialog for additional information. --> - - - JPG - - - PNG - - - PDF - - -
-
- + class="abs holder holder-plot has-control-bar"> + +
+
+
+ + class='plot-legend-item' + ng-repeat="telemetryObject in subplot.getTelemetryObjects()" + ng-class="plot.getLegendClass(telemetryObject)"> {{telemetryObject.getModel().name}} -
-
- {{subplot.getHoverCoordinates()}} -
-
-
- {{axes[1].active.name}}
-
- {{tick.label | reverse}} +
+ {{subplot.getHoverCoordinates()}}
-
-
- +
+
+ {{axes[1].active.name}} +
+
+ {{tick.label | reverse}} +
+
+
+ +
-
-
- - -
-
-
-
-
-
- - - -
- - - - -
-
-
-
- {{tick.label | reverse}} -
-
- {{axes[0].active.name}} -
-
-
- +
+
+ {{tick.label | reverse}} +
+
+ {{axes[0].active.name}} +
+
+
+ +
-
+
From 222b4421e511f3a26b3f43bd370c9c50a42777fb Mon Sep 17 00:00:00 2001 From: David Hudson Date: Thu, 8 Sep 2016 11:03:36 +0900 Subject: [PATCH 24/36] [Frontend] Remove styles that are no longer necessary Based on updates from 0457f7bee80f228878912f7ea1fcbe191c2046dc these styles are no longer necessary and have been removed. --- .../general/res/sass/plots/_plots-main.scss | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index 8fda639bea7..6ab9a373ec6 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -20,8 +20,6 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ .abs.holder-plot { - $btnExportH: $btnFrameH; - // Fend off the scrollbar when less than min-height; right: $interiorMargin; @@ -38,20 +36,6 @@ @include trans-prop-nice(opacity, 150ms, 100ms); opacity: 1; } - .gl-plot { - @include trans-prop-nice(top, $dur: 150ms); - top: $btnExportH + $interiorMargin; - } - } - &.hide-l-btn-set { - .s-button.t-export { - @include trans-prop-nice(opacity, 0ms, 0ms); - opacity: 0; - } - .gl-plot { - @include trans-prop-nice(top, $dur: 0ms); - top: 0; - } } } From 911ece761250a7ae52819035326cd17069d75df2 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Thu, 8 Sep 2016 11:03:36 +0900 Subject: [PATCH 25/36] [Frontend] Remove styles that are no longer necessary Issue #1164. Based on updates from 0457f7bee80f228878912f7ea1fcbe191c2046dc these styles are no longer necessary and have been removed. --- .../general/res/sass/plots/_plots-main.scss | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index 8fda639bea7..6ab9a373ec6 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -20,8 +20,6 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ .abs.holder-plot { - $btnExportH: $btnFrameH; - // Fend off the scrollbar when less than min-height; right: $interiorMargin; @@ -38,20 +36,6 @@ @include trans-prop-nice(opacity, 150ms, 100ms); opacity: 1; } - .gl-plot { - @include trans-prop-nice(top, $dur: 150ms); - top: $btnExportH + $interiorMargin; - } - } - &.hide-l-btn-set { - .s-button.t-export { - @include trans-prop-nice(opacity, 0ms, 0ms); - opacity: 0; - } - .gl-plot { - @include trans-prop-nice(top, $dur: 0ms); - top: 0; - } } } From c6caae8647273ff19572c5c604c65af113fb730b Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 9 Sep 2016 12:06:42 +0900 Subject: [PATCH 26/36] [Frontend] Remove unnecessary export button styles --- .../general/res/sass/plots/_plots-main.scss | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index 6ab9a373ec6..572b5429e3a 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -22,27 +22,12 @@ .abs.holder-plot { // Fend off the scrollbar when less than min-height; right: $interiorMargin; - - .s-button.t-export { - @include trans-prop-nice(opacity, $dur: 50ms); - opacity: 0; - } - .gl-plot { - @include trans-prop-nice(top, $dur: 150ms, $delay: 50ms); - top: 0; - } - &:hover { - .s-button.t-export { - @include trans-prop-nice(opacity, 150ms, 100ms); - opacity: 1; - } - } } .gl-plot { color: $colorPlotFg; font-size: 0.7rem; - position: absolute; + position: relative; width: 100%; height: 100%; min-height: $plotMinH; From 72ecbb0abc455ab5a0a1012ef5eda36def9b9168 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Fri, 9 Sep 2016 12:07:02 +0900 Subject: [PATCH 27/36] [Frontend] Fix typo --- platform/features/plot/res/templates/plot.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index 411fd156295..e25b582044d 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -26,7 +26,7 @@ - JPG + PDF - PDF + JPG
From a6386b16123d2589c892cb034d6b5b9dca73a5a2 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 10 Sep 2016 01:45:50 +0900 Subject: [PATCH 28/36] [Formatting] Rename export image service variables Issue #1164 --- platform/features/plot/bundle.js | 4 ++-- platform/features/plot/src/PlotController.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index b218fefe2ad..4564ec5d3c6 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -34,7 +34,7 @@ define([ PlotController, PlotViewPolicy, PlotOptionsController, - ExportImageService, + exportImageService, plotTemplate, plotOptionsBrowseTemplate, legacyRegistry @@ -91,7 +91,7 @@ define([ "services": [ { "key": "ExportImageService", - "implementation": ExportImageService, + "implementation": exportImageService, "depends": [ "$q", "$timeout", diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 97e848f66aa..3b23bce34ae 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -64,7 +64,7 @@ define( function PlotController( $scope, $element, - ExportImageService, + exportImageService, telemetryFormatter, telemetryHandler, throttle, @@ -249,7 +249,7 @@ define( self.pending = true; self.$element = $element; - self.ExportImageService = ExportImageService; + self.exportImageService = exportImageService; // Initialize axes; will get repopulated when telemetry // metadata becomes available. @@ -374,7 +374,7 @@ define( PlotController.prototype.exportPDF = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPDF(self.$element[0], "plot.pdf").finally(function () { + self.exportImageService.exportPDF(self.$element[0], "plot.pdf").finally(function () { self.hideExportButtons = false; }); }; @@ -385,7 +385,7 @@ define( PlotController.prototype.exportPNG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportPNG(self.$element[0], "plot.png").finally(function () { + self.exportImageService.exportPNG(self.$element[0], "plot.png").finally(function () { self.hideExportButtons = false; }); }; @@ -396,7 +396,7 @@ define( PlotController.prototype.exportJPG = function () { var self = this; self.hideExportButtons = true; - self.ExportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () { + self.exportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () { self.hideExportButtons = false; }); }; From b51ffcada6c92713e3ee7efebbd116b7b6561a7c Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 10 Sep 2016 01:59:07 +0900 Subject: [PATCH 29/36] [Formatting] Add inline JSDoc Issue #1164 --- .../plot/src/services/ExportImageService.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 21b56e2bda1..093360dccea 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -125,6 +125,12 @@ define( } } + /** + * 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) { var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); @@ -133,12 +139,24 @@ define( }); }; + /** + * 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, "blob").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, "blob").then(function (img) { self.saveAs(img, filename); From 98deac042ec8598c7b35015ba73304431ee3409e Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 10 Sep 2016 02:56:23 +0900 Subject: [PATCH 30/36] [Formatting] Reduce line length of gl declaration Issue #1164 --- platform/features/plot/src/GLChart.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js index 348a64f9230..0ca7776171d 100644 --- a/platform/features/plot/src/GLChart.js +++ b/platform/features/plot/src/GLChart.js @@ -54,7 +54,8 @@ define( * @throws {Error} an error is thrown if WebGL is unavailable. */ function GLChart(canvas) { - var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) || canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }), + var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) || + canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }), vertexShader, fragmentShader, program, From 8e39da672633c26b40834f21fd55a2b523453410 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 10 Sep 2016 03:12:00 +0900 Subject: [PATCH 31/36] [Formatting] Rename export image service key Issue #1164 --- platform/features/plot/bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 4564ec5d3c6..87b2723113c 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -90,7 +90,7 @@ define([ ], "services": [ { - "key": "ExportImageService", + "key": "exportImageService", "implementation": exportImageService, "depends": [ "$q", From d62989bc5d9f8a925057e1b22461bb6ef03d82d8 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Sat, 10 Sep 2016 03:18:46 +0900 Subject: [PATCH 32/36] [Formatting] Rename export image service key Issue #1164 --- platform/features/plot/bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 87b2723113c..691e3107918 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -73,7 +73,7 @@ define([ "depends": [ "$scope", "$element", - "ExportImageService", + "exportImageService", "telemetryFormatter", "telemetryHandler", "throttle", From 3093ab80673eef86a498845264adb92a10207c65 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 12 Sep 2016 16:58:21 +0900 Subject: [PATCH 33/36] [Testing] Convert blob to proper format. Inject FileReader. Issue #967 --- .../plot/src/services/ExportImageService.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 093360dccea..7fc2708b3eb 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -45,7 +45,7 @@ define( * @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) { + function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injJsPDF, injSaveAs, injFileReader) { self.$q = $q; self.$timeout = $timeout; self.$log = $log; @@ -53,6 +53,7 @@ define( self.html2canvas = injHtml2Canvas || html2canvas; self.jsPDF = injJsPDF || jsPDF; self.saveAs = injSaveAs || saveAs; + self.reader = injFileReader || new FileReader(); } /** @@ -64,8 +65,14 @@ define( */ 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"); @@ -75,18 +82,14 @@ define( self.html2canvas(element, { onrendered: function (canvas) { switch (type.toLowerCase()) { - case "blob": - canvas.toBlob(defer.resolve); - break; - case "png": - defer.resolve(canvas.toDataURL("image/png", 1.0)); + canvas.toBlob(defer.resolve, "image/png"); break; default: case "jpg": case "jpeg": - defer.resolve(canvas.toDataURL("image/jpeg", 1.0)); + canvas.toBlob(defer.resolve, "image/jpeg"); break; } } @@ -133,9 +136,12 @@ define( */ ExportImageService.prototype.exportPDF = function (element, filename) { return renderElement(element, "jpeg").then(function (img) { - var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); - pdf.addImage(img, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); - pdf.save(filename); + self.reader.readAsDataURL(img); + self.reader.onloadend = function() { + var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); + pdf.addImage(reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); + pdf.save(filename); + }; }); }; @@ -146,7 +152,7 @@ define( * @returns {promise} */ ExportImageService.prototype.exportJPG = function (element, filename) { - return renderElement(element, "blob").then(function (img) { + return renderElement(element, "jpeg").then(function (img) { self.saveAs(img, filename); }); }; @@ -158,7 +164,7 @@ define( * @returns {promise} */ ExportImageService.prototype.exportPNG = function (element, filename) { - return renderElement(element, "blob").then(function (img) { + return renderElement(element, "png").then(function (img) { self.saveAs(img, filename); }); }; From 487ec7907c02297efcadcf05f8bdf9559c0454a2 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 12 Sep 2016 16:59:39 +0900 Subject: [PATCH 34/36] [Formatting] Fix missing space Issue #967 --- platform/features/plot/src/services/ExportImageService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 7fc2708b3eb..666ddf55059 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -137,7 +137,7 @@ define( ExportImageService.prototype.exportPDF = function (element, filename) { return renderElement(element, "jpeg").then(function (img) { self.reader.readAsDataURL(img); - self.reader.onloadend = function() { + self.reader.onloadend = function () { var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); pdf.addImage(reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); From bb34528a863f891f64ac06a2a0092530ebf0786b Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 12 Sep 2016 17:00:28 +0900 Subject: [PATCH 35/36] [Testing] Add tests to ensure correct image type Issue #967 --- .../test/services/ExportImageServiceSpec.js | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/platform/features/plot/test/services/ExportImageServiceSpec.js b/platform/features/plot/test/services/ExportImageServiceSpec.js index e25572e3a1b..111a8d3432e 100644 --- a/platform/features/plot/test/services/ExportImageServiceSpec.js +++ b/platform/features/plot/test/services/ExportImageServiceSpec.js @@ -32,10 +32,13 @@ define( mockTimeout, mockLog, mockHtml2Canvas, + mockCanvas, mockJsPDF, mockJsPDFSave, mockSaveAs, + mockFileReader, mockExportTimeoutConstant, + testElement, exportImageService; describe("ExportImageService", function () { @@ -69,7 +72,13 @@ define( "$log", ["warn"] ); - mockHtml2Canvas = jasmine.createSpy("html2canvas"); + mockHtml2Canvas = jasmine.createSpy("html2canvas").andCallFake(function (element, opts) { + opts.onrendered(mockCanvas); + }); + mockCanvas = jasmine.createSpyObj( + "canvas", + ["toBlob"] + ); mockJsPDFSave = jasmine.createSpy("jsPDFSave"); mockJsPDF = function () { return { @@ -78,7 +87,12 @@ define( }; }; mockSaveAs = jasmine.createSpy("saveAs"); + mockFileReader = jasmine.createSpyObj( + "FileReader", + ["readAsDataURL", "onloadend"] + ); mockExportTimeoutConstant = 0; + testElement = {}; exportImageService = new ExportImageService( mockQ, @@ -87,32 +101,37 @@ define( mockExportTimeoutConstant, mockHtml2Canvas, mockJsPDF, - mockSaveAs + mockSaveAs, + mockFileReader ); }); it("runs html2canvas and tries to save a pdf", function () { - exportImageService.exportPDF("", "plot.pdf"); + exportImageService.exportPDF(testElement, "plot.pdf"); + mockFileReader.onloadend(); - expect(mockHtml2Canvas).toHaveBeenCalled(); + 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("", "plot.png"); + exportImageService.exportPNG(testElement, "plot.png"); - expect(mockHtml2Canvas).toHaveBeenCalled(); + 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("", "plot.png"); + exportImageService.exportJPG(testElement, "plot.png"); - expect(mockHtml2Canvas).toHaveBeenCalled(); + 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(); From bad901a162e4d777f29bac480162586a2b0d02b8 Mon Sep 17 00:00:00 2001 From: David Hudson Date: Mon, 12 Sep 2016 17:06:23 +0900 Subject: [PATCH 36/36] [Typo] Add reference to self Issue #967 --- platform/features/plot/src/services/ExportImageService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js index 666ddf55059..d49adc15eca 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/platform/features/plot/src/services/ExportImageService.js @@ -139,7 +139,7 @@ define( self.reader.readAsDataURL(img); self.reader.onloadend = function () { var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]); - pdf.addImage(reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); + pdf.addImage(self.reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight); pdf.save(filename); }; });