diff --git a/.gitignore b/.gitignore index aacf7f9728..b8eea4a5be 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.gzip *.tgz *.DS_Store +*.swp # Compiled CSS, unless directly added *.sass-cache diff --git a/README.md b/README.md index c19be6ee1d..b9486dad3c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Differences between the two APIs include a move away from a declarative system o ## Building and Running Open MCT Locally Building and running Open MCT in your local dev environment is very easy. Be sure you have [Git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/) installed, then follow the directions below. Need additional information? Check out the [Getting Started](https://nasa.github.io/openmct/getting-started/) page on our website. +(These instructions assume you are installing as a non-root user; developers have [reported issues](https://github.com/nasa/openmct/issues/1151) running these steps with root privileges.) 1. Clone the source code diff --git a/app.js b/app.js index 20429a3a5f..6b7b1bb635 100644 --- a/app.js +++ b/app.js @@ -67,14 +67,10 @@ }); app.use('/proxyUrl', function proxyRequest(req, res, next) { - var targetUrl = req.query.url; - var queryParameters = req.query; - console.log('Proxying request to: ', targetUrl); - delete queryParameters['url']; + console.log('Proxying request to: ', req.query.url); req.pipe(request({ - url: targetUrl, - strictSSL: false, - qs: queryParameters + url: req.query.url, + strictSSL: false }).on('error', next)).pipe(res); }); diff --git a/bower.json b/bower.json index 7c913754cf..922eb905f3 100644 --- a/bower.json +++ b/bower.json @@ -13,11 +13,13 @@ "moment-duration-format": "^1.3.0", "requirejs": "~2.1.22", "text": "requirejs-text#^2.0.14", - "es6-promise": "^3.0.2", + "es6-promise": "^3.3.0", "screenfull": "^3.0.0", "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/docs/src/guide/index.md b/docs/src/guide/index.md index 308ca6d2c1..1d8a422299 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -933,7 +933,7 @@ Note that `templateUrl` is not supported for `containers`. Controls provide options for the `mct-control` directive. -Six standard control types are included in the forms bundle: +Ten standard control types are included in the forms bundle: * `textfield`: An area to enter plain text. * `select`: A drop-down list of options. @@ -941,7 +941,13 @@ Six standard control types are included in the forms bundle: * `color`: A color picker. * `button`: A button. * `datetime`: An input for UTC date/time entry; gives result as a UNIX -timestamp, in milliseconds since start of 1970, UTC. +timestamp, in milliseconds since start of 1970, UTC. +* `composite`: A control parenting an array of other controls. +* `menu-button`: A drop-down list of items supporting custom behavior +on click. +* `dialog-button`: A button which opens a dialog allowing a single property +to be edited. +* `radio`: A radio button. New controls may be added as extensions of the controls category. Extensions of this category have two properties: diff --git a/gulpfile.js b/gulpfile.js index bf52224863..ff994c6a2c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -103,11 +103,6 @@ gulp.task('stylesheets', function () { .pipe(gulp.dest(__dirname)); }); -gulp.task('nsp', function (done) { - var nsp = require('gulp-nsp'); - nsp({package: __dirname + '/package.json'}, done); -}); - gulp.task('lint', function () { var nonspecs = paths.specs.map(function (glob) { return "!" + glob; @@ -157,6 +152,6 @@ gulp.task('develop', ['serve', 'stylesheets', 'watch']); gulp.task('install', [ 'static', 'scripts' ]); -gulp.task('verify', [ 'lint', 'test', 'checkstyle', 'nsp' ]); +gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]); gulp.task('build', [ 'verify', 'install' ]); diff --git a/index.html b/index.html index e47efe91b5..fcd37cfb0b 100644 --- a/index.html +++ b/index.html @@ -19,16 +19,15 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> - - - - + + + + - - - - - - + + + +
diff --git a/main.js b/main.js index 268c70060d..3fc2c4da36 100644 --- a/main.js +++ b/main.js @@ -27,7 +27,9 @@ requirejs.config({ "angular": "bower_components/angular/angular.min", "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", + "es6-promise": "bower_components/es6-promise/es6-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,12 @@ requirejs.config({ "angular-route": { "deps": ["angular"] }, + "html2canvas": { + "exports": "html2canvas" + }, + "jsPDF": { + "exports": "jsPDF" + }, "moment-duration-format": { "deps": ["moment"] }, diff --git a/package.json b/package.json index 491904fe4d..555375007e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "gulp-jscs": "^3.0.2", "gulp-jshint": "^2.0.0", "gulp-jshint-html-reporter": "^0.1.3", - "gulp-nsp": "^2.4.2", "gulp-rename": "^1.2.2", "gulp-replace-task": "^0.11.0", "gulp-requirejs-optimize": "^0.3.1", @@ -40,7 +39,7 @@ "mkdirp": "^0.5.1", "moment": "^2.11.1", "node-bourbon": "^4.2.3", - "phantomjs-prebuilt": "^2.1.0", + "phantomjs-prebuilt": "2.1.11 || >2.1.12 <3.0.0", "requirejs": "2.1.x", "split": "^1.0.0" }, diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 2c5a79bda3..33d92f913c 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -387,7 +387,7 @@ define([ "constants": [ { "key": "editModeBlacklist", - "value": ["copy", "follow", "window", "link", "locate"] + "value": ["copy", "follow", "link", "locate"] }, { "key": "nonEditContextBlacklist", diff --git a/platform/commonUI/general/res/sass/_constants.scss b/platform/commonUI/general/res/sass/_constants.scss index 5550a9019e..8fb1a39cb1 100644 --- a/platform/commonUI/general/res/sass/_constants.scss +++ b/platform/commonUI/general/res/sass/_constants.scss @@ -126,6 +126,7 @@ $menuLineH: 1.5rem; $menuLineHPx: 24px; $btnStdH: 25px; $btnToolbarH: $btnStdH; +$controlBarH: $btnStdH; $btnFrameH: 16px; /************************** PATHS */ diff --git a/platform/commonUI/general/res/sass/_views.scss b/platform/commonUI/general/res/sass/_views.scss index 96c78f1dd2..357f9f3c7c 100644 --- a/platform/commonUI/general/res/sass/_views.scss +++ b/platform/commonUI/general/res/sass/_views.scss @@ -1,8 +1,11 @@ /* Styles for sub-dividing views generically */ +.l-control-bar { + // Element that can be placed above l-view-section, holds controls, buttons, etc. + height: $controlBarH; +} .l-view-section { @include absPosDefault(0); - font-size: 0.8rem; h2 { color: #fff; margin-bottom: $interiorMargin; @@ -15,4 +18,36 @@ .inline-block { display: inline-block; } +} + +.has-control-bar { + .l-view-section { + top: $controlBarH + $interiorMargin; + } +} + + +.child-frame { + .has-control-bar { + $btnExportH: $btnFrameH; + .l-control-bar { + @include trans-prop-nice(opacity, $dur: 50ms); + opacity: 0; + } + .l-view-section { + @include trans-prop-nice(top, $dur: 150ms, $delay: 50ms); + top: 0; + } + &:hover { + .l-control-bar { + @include trans-prop-nice(opacity, 150ms, 100ms); + opacity: 1; + } + .l-view-section { + @include trans-prop-nice(top, $dur: 150ms); + top: $btnExportH + $interiorMargin; + } + } + } + } \ No newline at end of file diff --git a/platform/commonUI/general/res/sass/lists/_tabular.scss b/platform/commonUI/general/res/sass/lists/_tabular.scss index 5671fe178d..d68486547e 100644 --- a/platform/commonUI/general/res/sass/lists/_tabular.scss +++ b/platform/commonUI/general/res/sass/lists/_tabular.scss @@ -160,39 +160,3 @@ table { } } } - -/********************************************************** SPECIFIC TABULAR VIEWS */ -.tabular-holder { - &.t-exportable { - $btnExportH: 25px; - .l-view-section { - top: $btnExportH + $interiorMargin; - } - } -} - -.child-frame { - .tabular-holder { - &.t-exportable { - $btnExportH: $btnFrameH; - .s-button.t-export { - @include trans-prop-nice(opacity, $dur: 50ms); - opacity: 0; - } - .l-view-section { - @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; - } - .l-view-section { - @include trans-prop-nice(top, $dur: 150ms); - top: $btnExportH + $interiorMargin; - } - } - } - } -} \ No newline at end of file diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 3ce136abdf..691e310791 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 @@ -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" + } ] } }); diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index 6dc3cd71b9..e25b582044 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -20,120 +20,141 @@ at runtime from the About dialog for additional information. --> -
-
- + 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}} -
-
- {{axes[0].active.name}} -
-
-
- +
+ + +
+
+
+
+
+
+ + + +
+ + + + +
+
+
+ {{tick.label | reverse}} +
+
+ {{axes[0].active.name}} +
+
+
+ +
+
+
diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js index 56ca29bb3e..0ca7776171 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") || canvas.getContext("experimental-webgl"), + var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) || + canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }), vertexShader, fragmentShader, program, diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 88d82ea31b..3b23bce34a 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -63,6 +63,8 @@ define( */ function PlotController( $scope, + $element, + exportImageService, telemetryFormatter, telemetryHandler, throttle, @@ -246,6 +248,8 @@ define( }); self.pending = true; + self.$element = $element; + self.exportImageService = exportImageService; // Initialize axes; will get repopulated when telemetry // metadata becomes available. @@ -364,6 +368,39 @@ define( return this.pending; }; + /** + * 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; } ); diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js new file mode 100644 index 0000000000..d49adc15ec --- /dev/null +++ b/platform/features/plot/src/services/ExportImageService.js @@ -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; + } +); diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index a3de710bdf..73a01d716a 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 @@ -29,6 +31,8 @@ define( describe("The plot controller", function () { var mockScope, + mockElement, + mockExportImageService, mockFormatter, mockHandler, mockThrottle, @@ -65,6 +69,11 @@ define( "$scope", ["$watch", "$on", "$emit"] ); + mockElement = angular.element('
'); + mockExportImageService = jasmine.createSpyObj( + "ExportImageService", + ["exportJPG", "exportPNG", "exportPDF"] + ); mockFormatter = jasmine.createSpyObj( "formatter", ["formatDomainValue", "formatRangeValue"] @@ -107,6 +116,8 @@ define( controller = new PlotController( mockScope, + mockElement, + mockExportImageService, mockFormatter, mockHandler, mockThrottle diff --git a/platform/features/plot/test/services/ExportImageServiceSpec.js b/platform/features/plot/test/services/ExportImageServiceSpec.js new file mode 100644 index 0000000000..111a8d3432 --- /dev/null +++ b/platform/features/plot/test/services/ExportImageServiceSpec.js @@ -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(); + }); + }); + } +); diff --git a/platform/features/table/res/templates/historical-table.html b/platform/features/table/res/templates/historical-table.html index e2cd91b0db..d306601daf 100644 --- a/platform/features/table/res/templates/historical-table.html +++ b/platform/features/table/res/templates/historical-table.html @@ -4,6 +4,6 @@ rows="rows" enableFilter="true" enableSort="true" - class="tabular-holder t-exportable"> + class="tabular-holder has-control-bar">
\ No newline at end of file diff --git a/platform/features/table/res/templates/mct-table.html b/platform/features/table/res/templates/mct-table.html index 6d96b17afd..ac06f53e28 100644 --- a/platform/features/table/res/templates/mct-table.html +++ b/platform/features/table/res/templates/mct-table.html @@ -1,8 +1,10 @@ - - Export - +
diff --git a/platform/features/table/res/templates/rt-table.html b/platform/features/table/res/templates/rt-table.html index c6fb0d2422..573eeb89f6 100644 --- a/platform/features/table/res/templates/rt-table.html +++ b/platform/features/table/res/templates/rt-table.html @@ -4,7 +4,7 @@ rows="rows" enableFilter="true" enableSort="true" - class="tabular-holder t-exportable" + class="tabular-holder has-control-bar" auto-scroll="true"> \ No newline at end of file diff --git a/platform/forms/res/templates/controls/color.html b/platform/forms/res/templates/controls/color.html index e7e4913341..c9a4bbdd4a 100644 --- a/platform/forms/res/templates/controls/color.html +++ b/platform/forms/res/templates/controls/color.html @@ -36,18 +36,18 @@ ng-controller="ColorController as colors" ng-show="toggle.isActive()">
+ class="l-palette-row l-option-row" + ng-if="!structure.mandatory">
None
diff --git a/platform/forms/res/templates/controls/composite.html b/platform/forms/res/templates/controls/composite.html index 1c3559e2fc..e036617c3c 100644 --- a/platform/forms/res/templates/controls/composite.html +++ b/platform/forms/res/templates/controls/composite.html @@ -21,18 +21,18 @@ --> -
- - - - {{item.name}} - -
+
+ + + + {{item.name}} + +
diff --git a/platform/forms/res/templates/toolbar.html b/platform/forms/res/templates/toolbar.html index 58923e0314..f109b670e7 100644 --- a/platform/forms/res/templates/toolbar.html +++ b/platform/forms/res/templates/toolbar.html @@ -20,31 +20,31 @@ at runtime from the About dialog for additional information. -->
-
- - +
+ + - - - - - -
+ + + +
+
+
\ No newline at end of file diff --git a/test-main.js b/test-main.js index c329407f93..43142766b9 100644 --- a/test-main.js +++ b/test-main.js @@ -53,7 +53,9 @@ requirejs.config({ "angular": "bower_components/angular/angular.min", "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", + "es6-promise": "bower_components/es6-promise/es6-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",