Merge branch 'master' into mct588comm

This commit is contained in:
Alex M 2016-09-14 19:50:19 +03:00
commit d8dc3c8445
24 changed files with 667 additions and 206 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.gzip
*.tgz
*.DS_Store
*.swp
# Compiled CSS, unless directly added
*.sass-cache

View File

@ -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"
}
}

View File

@ -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.
@ -942,6 +942,12 @@ Six standard control types are included in the forms bundle:
* `button`: A button.
* `datetime`: An input for UTC date/time entry; gives result as a UNIX
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:

View File

@ -19,16 +19,15 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title></title>
<script type="text/javascript"
src="bower_components/requirejs/require.js">
<script src="bower_components/requirejs/require.js">
</script>
<script type="text/javascript">
<script>
require(['main'], function (mct) {
require([
'./example/imagery/bundle',

10
main.js
View File

@ -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"]
},

View File

@ -40,7 +40,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"
},

View File

@ -387,7 +387,7 @@ define([
"constants": [
{
"key": "editModeBlacklist",
"value": ["copy", "follow", "window", "link", "locate"]
"value": ["copy", "follow", "link", "locate"]
},
{
"key": "nonEditContextBlacklist",

View File

@ -126,6 +126,7 @@ $menuLineH: 1.5rem;
$menuLineHPx: 24px;
$btnStdH: 25px;
$btnToolbarH: $btnStdH;
$controlBarH: $btnStdH;
$btnFrameH: 16px;
/************************** PATHS */

View File

@ -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;
@ -16,3 +19,35 @@
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;
}
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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"
}
]
}
});

View File

@ -20,7 +20,27 @@
at runtime from the About dialog for additional information.
-->
<span ng-controller="PlotController as plot"
class="abs holder holder-plot">
class="abs holder holder-plot has-control-bar">
<div class="l-control-bar" ng-show="!plot.hideExportButtons">
<span class="l-btn-set">
<a class="s-button t-export icon-download labeled first"
ng-click="plot.exportPDF()"
title="Export This View's Data as PDF">
PDF
</a>
<a class="s-button t-export labeled"
ng-click="plot.exportPNG()"
title="Export This View's Data as PNG">
PNG
</a>
<a class="s-button t-export labeled last"
ng-click="plot.exportJPG()"
title="Export This View's Data as JPG">
JPG
</a>
</span>
</div>
<div class="l-view-section">
<div class="gl-plot"
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
ng-repeat="subplot in plot.getSubPlots()">
@ -136,4 +156,5 @@
</div>
</div>
</div>
</span>

View File

@ -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,

View File

@ -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;
}
);

View File

@ -0,0 +1,176 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
*/
define(
[
"html2canvas",
"jsPDF",
"saveAs"
],
function (
html2canvas,
jsPDF,
saveAs
) {
var self = this;
/**
* The export image service will export any HTML node to
* PDF, JPG, or PNG.
* @param {object} $q
* @param {object} $timeout
* @param {object} $log
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
* @constructor
*/
function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injJsPDF, injSaveAs, injFileReader) {
self.$q = $q;
self.$timeout = $timeout;
self.$log = $log;
self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT;
self.html2canvas = injHtml2Canvas || html2canvas;
self.jsPDF = injJsPDF || jsPDF;
self.saveAs = injSaveAs || saveAs;
self.reader = injFileReader || new FileReader();
}
/**
* Renders an HTML element into a base64 encoded image
* as a BLOB, PNG, or JPG.
* @param {node} element that will be converted to an image
* @param {string} type of image to convert the element to
* @returns {promise}
*/
function renderElement(element, type) {
var defer = self.$q.defer(),
validTypes = ["png", "jpg", "jpeg"],
renderTimeout;
if (validTypes.indexOf(type) === -1) {
self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
return;
}
renderTimeout = self.$timeout(function () {
defer.reject("html2canvas timed out");
self.$log.warn("html2canvas timed out");
}, self.EXPORT_IMAGE_TIMEOUT);
try {
self.html2canvas(element, {
onrendered: function (canvas) {
switch (type.toLowerCase()) {
case "png":
canvas.toBlob(defer.resolve, "image/png");
break;
default:
case "jpg":
case "jpeg":
canvas.toBlob(defer.resolve, "image/jpeg");
break;
}
}
});
} catch (e) {
defer.reject(e);
self.$log.warn("html2canvas failed with error: " + e);
}
defer.promise.finally(renderTimeout.cancel);
return defer.promise;
}
/**
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
* implements the method in browsers that would not otherwise support it.
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
*/
function polyfillToBlob() {
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
value: function (callback, type, quality) {
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], {type: type || "image/png"}));
}
});
}
}
/**
* Takes a screenshot of a DOM node and exports to PDF.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportPDF = function (element, filename) {
return renderElement(element, "jpeg").then(function (img) {
self.reader.readAsDataURL(img);
self.reader.onloadend = function () {
var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]);
pdf.addImage(self.reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight);
pdf.save(filename);
};
});
};
/**
* Takes a screenshot of a DOM node and exports to JPG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportJPG = function (element, filename) {
return renderElement(element, "jpeg").then(function (img) {
self.saveAs(img, filename);
});
};
/**
* Takes a screenshot of a DOM node and exports to PNG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportPNG = function (element, filename) {
return renderElement(element, "png").then(function (img) {
self.saveAs(img, filename);
});
};
polyfillToBlob();
return ExportImageService;
}
);

View File

@ -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('<div />');
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

View File

@ -0,0 +1,141 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* ExportImageServiceSpec. Created by hudsonfoo on 09/03/16.
*/
define(
["../../src/services/ExportImageService"],
function (ExportImageService) {
var mockQ,
mockDeferred,
mockPromise,
mockTimeout,
mockLog,
mockHtml2Canvas,
mockCanvas,
mockJsPDF,
mockJsPDFSave,
mockSaveAs,
mockFileReader,
mockExportTimeoutConstant,
testElement,
exportImageService;
describe("ExportImageService", function () {
beforeEach(function () {
mockDeferred = jasmine.createSpyObj(
"deferred",
["reject", "resolve"]
);
mockPromise = jasmine.createSpyObj(
"promise",
["then", "finally"]
);
mockPromise.then = function (callback) {
callback();
};
mockQ = {
"defer": function () {
return {
"resolve": mockDeferred.resolve,
"reject": mockDeferred.reject,
"promise": mockPromise
};
}
};
mockTimeout = function (fn, time) {
return {
"cancel": function () {}
};
};
mockLog = jasmine.createSpyObj(
"$log",
["warn"]
);
mockHtml2Canvas = jasmine.createSpy("html2canvas").andCallFake(function (element, opts) {
opts.onrendered(mockCanvas);
});
mockCanvas = jasmine.createSpyObj(
"canvas",
["toBlob"]
);
mockJsPDFSave = jasmine.createSpy("jsPDFSave");
mockJsPDF = function () {
return {
"addImage": function () {},
"save": mockJsPDFSave
};
};
mockSaveAs = jasmine.createSpy("saveAs");
mockFileReader = jasmine.createSpyObj(
"FileReader",
["readAsDataURL", "onloadend"]
);
mockExportTimeoutConstant = 0;
testElement = {};
exportImageService = new ExportImageService(
mockQ,
mockTimeout,
mockLog,
mockExportTimeoutConstant,
mockHtml2Canvas,
mockJsPDF,
mockSaveAs,
mockFileReader
);
});
it("runs html2canvas and tries to save a pdf", function () {
exportImageService.exportPDF(testElement, "plot.pdf");
mockFileReader.onloadend();
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
expect(mockDeferred.reject).not.toHaveBeenCalled();
expect(mockJsPDFSave).toHaveBeenCalled();
expect(mockPromise.finally).toHaveBeenCalled();
});
it("runs html2canvas and tries to save a png", function () {
exportImageService.exportPNG(testElement, "plot.png");
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/png");
expect(mockDeferred.reject).not.toHaveBeenCalled();
expect(mockSaveAs).toHaveBeenCalled();
expect(mockPromise.finally).toHaveBeenCalled();
});
it("runs html2canvas and tries to save a jpg", function () {
exportImageService.exportJPG(testElement, "plot.png");
expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
expect(mockDeferred.reject).not.toHaveBeenCalled();
expect(mockSaveAs).toHaveBeenCalled();
expect(mockPromise.finally).toHaveBeenCalled();
});
});
}
);

View File

@ -4,6 +4,6 @@
rows="rows"
enableFilter="true"
enableSort="true"
class="tabular-holder t-exportable">
class="tabular-holder has-control-bar">
</mct-table>
</div>

View File

@ -1,8 +1,10 @@
<div class="l-control-bar">
<a class="s-button t-export icon-download labeled"
ng-click="exportAsCSV()"
title="Export This View's Data">
Export
</a>
</div>
<div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
<table class="sizing-table">
<tbody>

View File

@ -4,7 +4,7 @@
rows="rows"
enableFilter="true"
enableSort="true"
class="tabular-holder t-exportable"
class="tabular-holder has-control-bar"
auto-scroll="true">
</mct-table>
</div>

View File

@ -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",