Merge remote-tracking branch 'origin/master' into persist-on-mutation-825

This commit is contained in:
Victor Woeltjen
2016-10-07 11:27:58 -07:00
314 changed files with 6153 additions and 7464 deletions

View File

@@ -136,7 +136,7 @@ define([
],
"category": "contextual",
"name": "Start",
"glyph": ",
"cssclass": "icon-play",
"priority": "preferred"
},
{
@@ -147,7 +147,7 @@ define([
],
"category": "contextual",
"name": "Restart at 0",
"glyph": "r",
"cssclass": "icon-refresh",
"priority": "preferred"
}
],
@@ -155,7 +155,7 @@ define([
{
"key": "clock",
"name": "Clock",
"glyph": "\u0043",
"cssclass": "icon-clock",
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
"priority": 101,
"features": [
@@ -212,7 +212,7 @@ define([
{
"key": "timer",
"name": "Timer",
"glyph": "\u00f5",
"cssclass": "icon-timer",
"description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.",
"priority": 100,
"features": [

View File

@@ -21,20 +21,13 @@
-->
<div class="l-time-display l-digital l-timer s-timer" ng-controller="TimerController as timer">
<div class="l-elem-wrapper l-flex-row">
<a
ng-click="timer.clickButton()"
<a ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="flex-elem s-icon-btn control"
>
{{timer.buttonGlyph()}}
</a>
<span class="flex-elem l-value">
<span class="ui-symbol direction">{{timer.sign()}}</span>
<span
class="value"
ng-class="{ active:timer.text() }"
>{{timer.text() || "--:--:--"}}
</span>
class="flex-elem control s-icon-button {{timer.buttonCssClass()}}"></a>
<span class="flex-elem l-value {{timer.signClass()}}">
<span class="value"
ng-class="{ active:timer.text() }">{{timer.text() || "--:--:--"}}
</span>
</span>
<span ng-controller="RefreshingController"></span>
</div>

View File

@@ -50,10 +50,13 @@ define(
if (formatter && !isNaN(timeDelta)) {
self.textValue = formatter(timeDelta);
self.signValue = timeDelta < 0 ? "-" :
timeDelta >= 1000 ? "+" : "";
timeDelta >= 1000 ? "+" : "";
self.signCssClass = timeDelta < 0 ? "icon-minus" :
timeDelta >= 1000 ? "icon-plus" : "";
} else {
self.textValue = "";
self.signValue = "";
self.signCssClass = "";
}
}
@@ -126,12 +129,13 @@ define(
}
/**
* Get the glyph to display for the start/restart button.
* @returns {string} glyph to display
* Get the CSS class to display the right icon
* for the start/restart button.
* @returns {string} cssclass to display
*/
TimerController.prototype.buttonGlyph = function () {
TimerController.prototype.buttonCssClass = function () {
return this.relevantAction ?
this.relevantAction.getMetadata().glyph : "";
this.relevantAction.getMetadata().cssclass : "";
};
/**
@@ -164,6 +168,15 @@ define(
return this.signValue;
};
/**
* Get the sign (+ or -) of the current timer value, as
* a CSS class.
* @returns {string} sign of the current timer value
*/
TimerController.prototype.signClass = function () {
return this.signCssClass;
};
/**
* Get the text to display for the current timer value.
* @returns {string} current timer value

View File

@@ -44,12 +44,12 @@ define(
});
}
ClockIndicator.prototype.getGlyph = function () {
return "C";
ClockIndicator.prototype.getGlyphClass = function () {
return "no-collapse float-right subdued";
};
ClockIndicator.prototype.getGlyphClass = function () {
return "no-icon no-collapse float-right subtle";
ClockIndicator.prototype.getCssClass = function () {
return "icon-clock";
};
ClockIndicator.prototype.getText = function () {

View File

@@ -85,8 +85,8 @@ define(
'timer.restart': mockRestart
}[k]];
});
mockStart.getMetadata.andReturn({ glyph: "S", name: "Start" });
mockRestart.getMetadata.andReturn({ glyph: "R", name: "Restart" });
mockStart.getMetadata.andReturn({ cssclass: "icon-play", name: "Start" });
mockRestart.getMetadata.andReturn({ cssclass: "icon-refresh", name: "Restart" });
mockScope.domainObject = mockDomainObject;
testModel = {};
@@ -144,14 +144,14 @@ define(
expect(controller.text()).toEqual("0D 00:00:00");
});
it("shows glyph & name for the applicable start/restart action", function () {
it("shows cssclass & name for the applicable start/restart action", function () {
invokeWatch('domainObject', mockDomainObject);
expect(controller.buttonGlyph()).toEqual("S");
expect(controller.buttonCssClass()).toEqual("icon-play");
expect(controller.buttonText()).toEqual("Start");
testModel.timestamp = 12321;
invokeWatch('model.modified', 1);
expect(controller.buttonGlyph()).toEqual("R");
expect(controller.buttonCssClass()).toEqual("icon-refresh");
expect(controller.buttonText()).toEqual("Restart");
});

View File

@@ -48,8 +48,7 @@ define(
});
it("implements the Indicator interface", function () {
expect(indicator.getGlyph()).toEqual(jasmine.any(String));
expect(indicator.getGlyphClass()).toEqual(jasmine.any(String));
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});

View File

@@ -36,7 +36,7 @@ define([
{
"key": "fixed-display",
"name": "Fixed Position Display",
"glyph": "3",
"cssclass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": [
@@ -49,28 +49,28 @@ define([
"items": [
{
"method": "add",
"glyph": "\u002b",
"cssclass": "icon-plus",
"control": "menu-button",
"text": "Add",
"options": [
{
"name": "Box",
"glyph": "\u00e0",
"cssclass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"glyph": "\u00e2",
"cssclass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"glyph": "\u00e4",
"cssclass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"glyph": "\u00e3",
"cssclass": "icon-image",
"key": "fixed.image"
}
]
@@ -81,63 +81,74 @@ define([
"items": [
{
"method": "order",
"glyph": ",
"cssclass": "icon-layers",
"control": "menu-button",
"title": "Layering",
"description": "Move the selected object above or below other objects",
"options": [
{
"name": "Move to Top",
"glyph": "\u00eb",
"cssclass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"glyph": "\u005e",
"cssclass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"glyph": "\u0076",
"cssclass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"glyph": "\u00ee",
"cssclass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
"property": "fill",
"glyph": "",
"cssclass": "icon-paint-bucket",
"title": "Fill color",
"description": "Set fill color",
"control": "color"
},
{
"property": "stroke",
"glyph": ",
"cssclass": "icon-line-horz",
"title": "Border color",
"description": "Set border color",
"control": "color"
},
{
"property": "color",
"glyph": "ä",
"cssclass": "icon-T",
"title": "Text color",
"description": "Set text color",
"mandatory": true,
"control": "color"
},
{
"property": "url",
"glyph": ",
"cssclass": "icon-image",
"control": "dialog-button",
"title": "Image Properties",
"description": "Edit image properties",
"dialog": {
"control": "textfield",
"name": "Image URL",
"cssclass": "l-input-lg",
"required": true
}
},
{
"property": "text",
"glyph": "G",
"cssclass": "icon-gear",
"control": "dialog-button",
"title": "Text Properties",
"description": "Edit text properties",
"dialog": {
"control": "textfield",
"name": "Text",
@@ -146,15 +157,17 @@ define([
},
{
"method": "showTitle",
"glyph": ",
"cssclass": "icon-two-parts-both",
"control": "button",
"description": "Show telemetry element title."
"title": "Show title",
"description": "Show telemetry element title"
},
{
"method": "hideTitle",
"glyph": ",
"cssclass": "icon-two-parts-one-only",
"control": "button",
"description": "Hide telemetry element title."
"title": "Hide title",
"description": "Hide telemetry element title"
}
]
},
@@ -163,7 +176,7 @@ define([
{
"method": "remove",
"control": "button",
"glyph": "Z"
"cssclass": "icon-trash"
}
]
}
@@ -175,9 +188,11 @@ define([
{
"key": "telemetry.fixed",
"name": "Fixed Position Display",
"glyph": "3",
"description": "A panel for collecting telemetry" +
" elements in a fixed position display.",
"cssclass": "icon-box-with-dashed-lines",
"description": "Collect and display telemetry elements in " +
"alphanumeric format in a simple canvas workspace. " +
"Elements can be positioned and sized. " +
"Lines, boxes and images can be added as well.",
"priority": 899,
"delegates": [
"telemetry"
@@ -199,12 +214,12 @@ define([
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",

View File

@@ -41,7 +41,7 @@ define([
{
"name": "Imagery",
"key": "imagery",
"glyph": ",
"cssclass": "icon-image",
"template": imageryTemplate,
"priority": "preferred",
"needs": [

View File

@@ -5,18 +5,16 @@
<div
class="l-local-controls s-local-controls"
ng-show="false && showLocalControls">
<a class="s-btn"
<a class="s-button icon-arrow-left"
ng-click="plot.stepBackPanZoom()"
ng-show="1"
title="Restore previous pan/zoom">
<span class="ui-symbol icon">&lt;</span>
</a>
<a class="s-btn"
<a class="s-button icon-arrows-out"
ng-click="plot.unzoom()"
ng-show="1"
title="Reset pan/zoom">
<span class="ui-symbol icon">I</span>
</a>
</div>
@@ -27,24 +25,24 @@
<div class="l-image-main-controlbar flex-elem l-flex-row">
<div class="left flex-elem grows">
<a class="s-btn show-thumbs sm hidden"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"><span class="ui-symbol icon"></span></a>
<a class="s-button show-thumbs sm hidden icon-thumbs-strip"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"></a>
<span class="l-timezone">{{imagery.getZone()}}</span>
<span class="l-time">{{imagery.getTime()}}</span>
<span class="l-date">{{imagery.getDate()}}</span>
</div>
<div class="right flex-elem">
<a class="s-btn pause-play"
<a class="s-button pause-play"
ng-click="imagery.paused(!imagery.paused())"
ng-class="{ paused: imagery.paused() }"><span class="ui-symbol icon"></span></a>
ng-class="{ paused: imagery.paused() }"></a>
<a href="{{imagery.getImageUrl()}}"
ng-if="imagery.getImageUrl()"
target="_blank"
title="Open image in new tab."
class="s-btn">
<span class="ui-symbol icon">y</span></a>
class="s-button icon-new-window">
</a>
<a href=""
class="s-btn l-mag s-mag ui-symbol vsm"
class="s-button l-mag s-mag ui-symbol vsm icon-arrows-out"
ng-click="clipped = false"
ng-show="clipped === true"
title="Not all of image is visible; click to reset."></a>

View File

@@ -56,7 +56,7 @@ define([
{
"key": "layout",
"name": "Display Layout",
"glyph": "\u004c",
"cssclass": "icon-layout",
"type": "layout",
"template": layoutTemplate,
"editable": true,
@@ -65,7 +65,7 @@ define([
{
"key": "fixed",
"name": "Fixed Position",
"glyph": "3",
"cssclass": "icon-box-with-dashed-lines",
"type": "telemetry.panel",
"template": fixedTemplate,
"uses": [
@@ -77,28 +77,30 @@ define([
"items": [
{
"method": "add",
"glyph": "\u002b",
"cssclass": "icon-plus",
"control": "menu-button",
"text": "Add",
"title": "Add",
"description": "Add new items",
"options": [
{
"name": "Box",
"glyph": "\u00e0",
"cssclass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"glyph": "\u00e2",
"cssclass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"glyph": "\u00e4",
"cssclass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"glyph": "\u00e3",
"cssclass": "icon-image",
"key": "fixed.image"
}
]
@@ -109,63 +111,74 @@ define([
"items": [
{
"method": "order",
"glyph": ",
"cssclass": "icon-layers",
"control": "menu-button",
"title": "Layering",
"description": "Move the selected object above or below other objects",
"options": [
{
"name": "Move to Top",
"glyph": "\u00eb",
"cssclass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"glyph": "\u005e",
"cssclass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"glyph": "\u0076",
"cssclass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"glyph": "\u00ee",
"cssclass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
"property": "fill",
"glyph": "",
"cssclass": "icon-paint-bucket",
"title": "Fill color",
"description": "Set fill color",
"control": "color"
},
{
"property": "stroke",
"glyph": ",
"cssclass": "icon-line-horz",
"title": "Border color",
"description": "Set border color",
"control": "color"
},
{
"property": "color",
"glyph": "ä",
"cssclass": "icon-T",
"title": "Text color",
"description": "Set text color",
"mandatory": true,
"control": "color"
},
{
"property": "url",
"glyph": ",
"cssclass": "icon-image",
"control": "dialog-button",
"title": "Image Properties",
"description": "Edit image properties",
"dialog": {
"control": "textfield",
"name": "Image URL",
"cssclass": "l-input-lg",
"required": true
}
},
{
"property": "text",
"glyph": "G",
"cssclass": "icon-gear",
"control": "dialog-button",
"title": "Text Properties",
"description": "Edit text properties",
"dialog": {
"control": "textfield",
"name": "Text",
@@ -174,15 +187,17 @@ define([
},
{
"method": "showTitle",
"glyph": ",
"cssclass": "icon-two-parts-both",
"control": "button",
"description": "Show telemetry element title."
"title": "Show title",
"description": "Show telemetry element title"
},
{
"method": "hideTitle",
"glyph": ",
"cssclass": "icon-two-parts-one-only",
"control": "button",
"description": "Hide telemetry element title."
"title": "Hide title",
"description": "Hide telemetry element title"
}
]
},
@@ -191,7 +206,9 @@ define([
{
"method": "remove",
"control": "button",
"glyph": "Z"
"cssclass": "icon-trash",
"title": "Delete",
"description": "Delete the selected item"
}
]
}
@@ -258,7 +275,7 @@ define([
{
"key": "layout",
"name": "Display Layout",
"glyph": "\u004c",
"cssclass": "icon-layout",
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
"priority": 900,
"features": "creation",
@@ -290,7 +307,7 @@ define([
{
"key": "telemetry.panel",
"name": "Telemetry Panel",
"glyph": "t",
"cssclass": "icon-telemetry-panel",
"description": "A panel for collecting telemetry elements.",
"priority": 899,
"delegates": [
@@ -313,12 +330,12 @@ define([
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",

View File

@@ -44,7 +44,7 @@
<!-- Selection highlight, handles -->
<span ng-if="controller.selected()">
<div class="l-fixed-position-item s-selected"
<div class="l-fixed-position-item s-selectable s-selected s-moveable"
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.moveHandle().endDrag()"

View File

@@ -33,61 +33,28 @@
</div>
<!-- Drag handles -->
<span ng-show="domainObject.hasCapability('editor')">
<span
class="edit-handle edit-move"
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<!--
<span
class="edit-handle edit-resize-w"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,0])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-handle edit-resize-e"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,0])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-handle edit-resize-n"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [0,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-handle edit-resize-s"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [0,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
-->
<span
class="edit-corner edit-resize-nw"
<span class="edit-corner edit-resize-nw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-corner edit-resize-ne"
<span class="edit-corner edit-resize-ne"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-corner edit-resize-sw"
<span class="edit-corner edit-resize-sw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">
</span>
<span
class="edit-corner edit-resize-se"
<span class="edit-corner edit-resize-se"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()">

View File

@@ -94,7 +94,7 @@ define(
};
/**
* End a drag gesture. This should be callled when a drag
* End a drag gesture. This should be called when a drag
* concludes to trigger commit of changes.
*/
FixedDragHandle.prototype.endDrag = function () {

View File

@@ -152,7 +152,7 @@ define(
}
// Convert from { positions: ..., dimensions: ... } to an
// apropriate ng-style argument, to position frames.
// appropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) {
var gridSize = this.gridSize;
// Multiply position/dimensions by grid size

View File

@@ -55,6 +55,7 @@ define(
key: "url",
control: "textfield",
name: "Image URL",
"cssclass": "l-input-lg",
required: true
}
]

View File

@@ -164,7 +164,7 @@ define(
// Populate scope
mockScope.$watchCollection.mostRecentCall.args[1]();
// Verify precondtion
// Verify precondition
expect(testConfiguration.panels.b).not.toBeDefined();
// Do a drag

View File

@@ -36,7 +36,7 @@ define([
{
"key": "example.page",
"name": "Web Page",
"glyph": "\u00ea",
"cssclass": "icon-page",
"description": "Embed a web page or web-based image in a resizeable window component. Can be added to Display Layouts. Note that the URL being embedded must allow iframing.",
"priority": 50,
"features": [

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
@@ -45,7 +47,7 @@ define([
{
"name": "Plot",
"key": "plot",
"glyph": "6",
"cssclass": "icon-sine",
"template": plotTemplate,
"needs": [
"telemetry"
@@ -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,28 @@ 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"
}
]
}
});

View File

@@ -25,13 +25,13 @@
ng-model="configuration.plot.xAxis"
structure="xAxisForm"
name="xAxisFormState"
class="flex-elem l-flex-row no-validate no-margin">
class="flex-elem l-flex-row no-margin">
</mct-form>
<mct-form
ng-model="configuration.plot.yAxis"
structure="yAxisForm"
name="yAxisFormState"
class="flex-elem l-flex-row no-validate no-margin">
class="flex-elem l-flex-row no-margin">
</mct-form>
<div class="form">
<div class="section-header ng-binding ng-scope">
@@ -60,7 +60,7 @@
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row no-validate">
class="flex-elem l-flex-row">
</mct-form>
</span>
</li>

View File

@@ -20,126 +20,136 @@
at runtime from the About dialog for additional information.
-->
<span ng-controller="PlotController as plot"
class="abs holder holder-plot">
<div class="gl-plot"
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
ng-repeat="subplot in plot.getSubPlots()">
<div class="gl-plot-legend">
<!-- ng-class is temporarily hard-coded in next element -->
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 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()">
<div class="gl-plot-legend">
<!-- ng-class is temporarily hard-coded in next element -->
<span
class='plot-legend-item'
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
ng-class="plot.getLegendClass(telemetryObject)">
class='plot-legend-item'
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
ng-class="plot.getLegendClass(telemetryObject)">
<span class='plot-color-swatch'
ng-style="{ 'background-color': plot.getColor($index) }">
</span>
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
</span>
</div>
<div class="gl-plot-coords"
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
{{subplot.getHoverCoordinates()}}
</div>
<div class="gl-plot-axis-area gl-plot-y">
<div class="gl-plot-label gl-plot-y-label">
{{axes[1].active.name}}
</div>
<div ng-repeat="tick in subplot.getRangeTicks()"
class="gl-plot-tick gl-plot-y-tick-label"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
<div class="gl-plot-coords"
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
{{subplot.getHoverCoordinates()}}
</div>
<div class="gl-plot-y-options gl-plot-local-controls"
ng-if="axes[1].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[1].active"
ng-options="option.name for option in axes[1].options">
</select>
<div class="gl-plot-axis-area gl-plot-y">
<div class="gl-plot-label gl-plot-y-label">
{{axes[1].active.name}}
</div>
</div>
</div>
<div class="gl-plot-display-area"
ng-mouseenter="subplot.isHovering(true);"
ng-mouseleave="subplot.isHovering(false)"
ng-class="{ loading: plot.isRequestPending() }">
<!-- Out-of-bounds data indicators -->
<!-- ng-show is temporarily hard-coded in next element -->
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
<div class="gl-plot-hash hash-v"
ng-repeat="tick in subplot.getDomainTicks()"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
</div>
<div class="gl-plot-hash hash-h"
ng-repeat="tick in subplot.getRangeTicks()"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
</div>
<mct-chart draw="subplot.getDrawingObject()"
ng-if="subplot.getTelemetryObjects().length > 0"
ng-mousemove="subplot.hover($event)"
mct-drag="subplot.continueDrag($event)"
mct-drag-down="subplot.startDrag($event)"
mct-drag-up="subplot.endDrag($event); plot.update()">
</mct-chart>
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
ng-if="$first">
<a class="s-btn"
ng-click="plot.stepBackPanZoom()"
ng-show="plot.isZoomed()"
title="Restore previous pan/zoom">
<span class="ui-symbol icon">&lt;</span>
</a>
<a class="s-btn"
ng-click="plot.unzoom()"
ng-show="plot.isZoomed()"
title="Reset pan/zoom">
<span class="ui-symbol icon">I</span>
</a>
<div class="menu-element s-menu-btn menus-to-left"
ng-if="plot.getModeOptions().length > 1"
ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span class="ui-symbol icon type-icon">{{plot.getMode().glyph}}</span>
<span>{{plot.getMode().name}}</span>
<div class="menu" ng-show="toggle.isActive()">
<ul>
<li ng-repeat="option in plot.getModeOptions()">
<a href="" ng-click="plot.setMode(option); toggle.setState(false)">
<span class="ui-symbol type-icon icon">
{{option.glyph}}
</span>
{{option.name}}
</a>
</li>
</ul>
<div ng-repeat="tick in subplot.getRangeTicks()"
class="gl-plot-tick gl-plot-y-tick-label"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-y-options gl-plot-local-controls"
ng-if="axes[1].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[1].active"
ng-options="option.name for option in axes[1].options">
</select>
</div>
</div>
</div>
</div>
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
<div ng-repeat="tick in subplot.getDomainTicks()"
class="gl-plot-tick gl-plot-x-tick-label"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-label gl-plot-x-label">
{{axes[0].active.name}}
</div>
<div class="gl-plot-x-options gl-plot-local-controls"
ng-if="axes[0].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[0].active"
ng-options="option.name for option in axes[0].options">
</select>
<div class="gl-plot-display-area"
ng-mouseenter="subplot.isHovering(true);"
ng-mouseleave="subplot.isHovering(false)"
ng-class="{ loading: plot.isRequestPending() }">
<!-- Out-of-bounds data indicators -->
<!-- ng-show is temporarily hard-coded in next element -->
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
<div class="gl-plot-hash hash-v"
ng-repeat="tick in subplot.getDomainTicks()"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
</div>
<div class="gl-plot-hash hash-h"
ng-repeat="tick in subplot.getRangeTicks()"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
</div>
<mct-chart draw="subplot.getDrawingObject()"
ng-if="subplot.getTelemetryObjects().length > 0"
ng-mousemove="subplot.hover($event)"
mct-drag="subplot.continueDrag($event)"
mct-drag-down="subplot.startDrag($event)"
mct-drag-up="subplot.endDrag($event); plot.update()">
</mct-chart>
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
ng-if="$first">
<a class="s-button icon-arrow-left"
ng-click="plot.stepBackPanZoom()"
ng-show="plot.isZoomed()"
title="Restore previous pan/zoom">
</a>
<a class="s-button icon-arrows-out"
ng-click="plot.unzoom()"
ng-show="plot.isZoomed()"
title="Reset pan/zoom">
</a>
<div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssclass}}"
ng-if="plot.getModeOptions().length > 1"
ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span>{{plot.getMode().name}}</span>
<div class="menu" ng-show="toggle.isActive()">
<ul>
<li ng-repeat="option in plot.getModeOptions()"
ng-click="plot.setMode(option); toggle.setState(false)"
class="{{option.cssclass}}">
{{option.name}}
</li>
</ul>
</div>
</div>
</div>
</div>
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
<div ng-repeat="tick in subplot.getDomainTicks()"
class="gl-plot-tick gl-plot-x-tick-label"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-label gl-plot-x-label">
{{axes[0].active.name}}
</div>
<div class="gl-plot-x-options gl-plot-local-controls"
ng-if="axes[0].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[0].active"
ng-options="option.name for option in axes[0].options">
</select>
</div>
</div>
</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.
@@ -313,7 +317,7 @@ define(
/**
* Get the current mode that is applicable to this plot. This
* will include key, name, and glyph fields.
* will include key, name, and cssclass fields.
*/
PlotController.prototype.getMode = function () {
return this.modeOptions.getMode();
@@ -364,6 +368,28 @@ define(
return this.pending;
};
/**
* 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

@@ -27,13 +27,13 @@ define(
var STACKED = {
key: "stacked",
name: "Stacked",
glyph: "m",
cssclass: "icon-plot-stacked",
Constructor: PlotStackMode
},
OVERLAID = {
key: "overlaid",
name: "Overlaid",
glyph: "6",
cssclass: "icon-plot-overlay",
Constructor: PlotOverlayMode
};
@@ -115,7 +115,7 @@ define(
/**
* Get all mode options available for each plot. Each
* mode contains a `name` and `glyph` field suitable
* mode contains a `name` and `cssclass` field suitable
* for display in a template.
* @return {Array} the available modes
*/

View File

@@ -0,0 +1,156 @@
/*****************************************************************************
* 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",
"saveAs"
],
function (
html2canvas,
saveAs
) {
var self = this;
/**
* The export image service will export any HTML node to
* 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, injSaveAs, injFileReader) {
self.$q = $q;
self.$timeout = $timeout;
self.$log = $log;
self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT;
self.html2canvas = injHtml2Canvas || html2canvas;
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 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

@@ -59,7 +59,7 @@ define(
expect(mock2d.clearRect).toHaveBeenCalled();
});
it("doees not construct if 2D is unavailable", function () {
it("does not construct if 2D is unavailable", function () {
mockCanvas.getContext.andReturn(undefined);
expect(function () {
return new Canvas2DChart(mockCanvas);

View File

@@ -82,7 +82,7 @@ define(
expect(mockGL.clear).toHaveBeenCalled();
});
it("doees not construct if WebGL is unavailable", function () {
it("does not construct if WebGL is unavailable", function () {
mockCanvas.getContext.andReturn(undefined);
expect(function () {
return new GLChart(mockCanvas);

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"]
);
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,120 @@
/*****************************************************************************
* 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,
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"]
);
mockSaveAs = jasmine.createSpy("saveAs");
mockFileReader = jasmine.createSpyObj(
"FileReader",
["readAsDataURL", "onloadend"]
);
mockExportTimeoutConstant = 0;
testElement = {};
exportImageService = new ExportImageService(
mockQ,
mockTimeout,
mockLog,
mockExportTimeoutConstant,
mockHtml2Canvas,
mockSaveAs,
mockFileReader
);
});
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

@@ -36,7 +36,7 @@ define([
{
"key": "static.markup",
"name": "Static Markup",
"glyph": "p",
"cssclass": "icon-pencil",
"description": "Static markup sandbox",
"features": [
"creation"

View File

@@ -60,7 +60,7 @@ define([
{
"key": "table",
"name": "Historical Telemetry Table",
"glyph": "\ue604",
"cssclass": "icon-tabular",
"description": "A static table of all values over time for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. The number of rows is based on the range of your query. New incoming data must be manually re-queried for.",
"priority": 861,
"features": "creation",
@@ -83,7 +83,7 @@ define([
{
"key": "rttable",
"name": "Real-time Telemetry Table",
"glyph": "\ue620",
"cssclass": "icon-tabular-realtime",
"description": "A scrolling table of latest values for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. New incoming data is automatically added to the view.",
"priority": 860,
"features": "creation",
@@ -127,7 +127,7 @@ define([
{
"name": "Historical Table",
"key": "table",
"glyph": "\ue604",
"cssclass": "icon-tabular",
"templateUrl": "templates/historical-table.html",
"needs": [
"telemetry"
@@ -138,7 +138,7 @@ define([
{
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue620",
"cssclass": "icon-tabular-realtime",
"templateUrl": "templates/rt-table.html",
"needs": [
"telemetry"

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 @@
<a class="t-btn l-btn s-btn t-export"
ng-click="exportAsCSV()"
title="Export This View's Data">
Export
</a>
<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

@@ -25,6 +25,6 @@
ng-model="configuration.table.columns"
structure="columnsForm"
name="columnsFormState"
class="flex-elem l-flex-row no-validate no-margin">
class="flex-elem l-flex-row no-margin">
</mct-form>
</div>

View File

@@ -1,7 +1,7 @@
define(
[],
function () {
['zepto'],
function ($) {
/**
* A controller for the MCTTable directive. Populates scope with
@@ -16,13 +16,13 @@ define(
var self = this;
this.$scope = $scope;
this.element = element;
this.element = $(element[0]);
this.$timeout = $timeout;
this.maxDisplayRows = 50;
this.scrollable = element.find('div');
this.thead = element.find('thead');
this.tbody = element.find('tbody');
this.scrollable = this.element.find('.l-view-section.scrolling').first();
this.resultsHeader = this.element.find('.mct-table>thead').first();
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
this.$scope.sizingRow = {};
this.scrollable.on('scroll', this.onScroll.bind(this));
@@ -261,8 +261,8 @@ define(
* for individual rows.
*/
MCTTableController.prototype.setElementSizes = function () {
var thead = this.thead,
tbody = this.tbody,
var thead = this.resultsHeader,
tbody = this.sizingTableBody,
firstRow = tbody.find('tr'),
column = firstRow.find('td'),
headerHeight = thead.prop('offsetHeight'),

View File

@@ -22,9 +22,16 @@
define(
[
"zepto",
"../../src/controllers/MCTTableController"
],
function (MCTTableController) {
function ($, MCTTableController) {
var MOCK_ELEMENT_TEMPLATE =
'<div><div class="l-view-section scrolling">' +
'<table class="sizing-table"><tbody></tbody></table>' +
'<table class="mct-table"><thead></thead></table>' +
'</div></div>';
describe('The MCTTable Controller', function () {
@@ -55,19 +62,7 @@ define(
watches[event] = callback;
});
mockElement = jasmine.createSpyObj('element', [
'find',
'prop',
'on'
]);
mockElement.find.andReturn(mockElement);
mockElement.prop.andReturn(0);
mockElement[0] = {
scrollTop: 0,
scrollHeight: 500,
offsetHeight: 1000
};
mockElement = $(MOCK_ELEMENT_TEMPLATE);
mockExportService = jasmine.createSpyObj('exportService', [
'exportCSV'
]);

View File

@@ -151,7 +151,7 @@ define([
{
"key": "timeline",
"name": "Timeline",
"glyph": "\u0053",
"cssclass": "icon-timeline",
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
"priority": 502,
"features": [
@@ -182,6 +182,16 @@ define([
"capacity"
],
"pattern": "^-?\\d+(\\.\\d*)?$"
},
{
"name": "Battery starting SOC (%)",
"control": "textfield",
"required": false,
"conversion": "number",
"property": [
"startingSOC"
],
"pattern": "^([0-9](\\.\\d*)?|[1-9][0-9](\\.\\d*)?|100)%?$"
}
],
"model": {
@@ -194,7 +204,7 @@ define([
{
"key": "activity",
"name": "Activity",
"glyph": "\u0061",
"cssclass": "icon-activity",
"features": [
"creation"
],
@@ -240,7 +250,7 @@ define([
{
"key": "mode",
"name": "Activity Mode",
"glyph": "\u0041",
"cssclass": "icon-activity-mode",
"features": [
"creation"
],
@@ -280,7 +290,7 @@ define([
{
"key": "values",
"name": "Values",
"glyph": "\u0041",
"cssclass": "icon-activity-mode",
"template": valuesTemplate,
"type": "mode",
"uses": [
@@ -291,7 +301,7 @@ define([
{
"key": "timeline",
"name": "Timeline",
"glyph": "\u0053",
"cssclass": "icon-timeline",
"type": "timeline",
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
"template": timelineTemplate,
@@ -302,18 +312,17 @@ define([
"items": [
{
"method": "add",
"glyph": "\u002b",
"control": "menu-button",
"text": "Add",
"options": [
{
"name": "Timeline",
"glyph": "\u0053",
"cssclass": "icon-timeline",
"key": "timeline"
},
{
"name": "Activity",
"glyph": "\u0061",
"cssclass": "icon-activity",
"key": "activity"
}
]
@@ -323,13 +332,13 @@ define([
{
"items": [
{
"glyph": "\u00e9",
"cssclass": "icon-plot-resource",
"description": "Graph Resource Utilization",
"control": "button",
"method": "toggleGraph"
},
{
"glyph": "\u0041",
"cssclass": "icon-activity-mode",
"control": "dialog-button",
"description": "Apply Activity Modes...",
"title": "Apply Activity Modes",
@@ -342,7 +351,7 @@ define([
"property": "modes"
},
{
"glyph": "\u00e8",
"cssclass": "icon-chain-links",
"description": "Edit Activity Link",
"title": "Activity Link",
"control": "dialog-button",
@@ -355,7 +364,7 @@ define([
"property": "link"
},
{
"glyph": "\u0047",
"cssclass": "icon-gear",
"description": "Edit Properties...",
"control": "button",
"method": "properties"
@@ -368,7 +377,7 @@ define([
"method": "remove",
"description": "Remove Item",
"control": "button",
"glyph": "\u005a"
"cssclass": "icon-trash"
}
]
}

View File

@@ -52,8 +52,8 @@
.l-pane-l {
// Left pane of the tabular area
.l-cols {
.s-label .ui-symbol.icon {
color: $colorGanttBarTabularFgIcon;
.t-object-label .t-item-icon {
color: pullForward($colorGanttBarBg, 10%);
}
}
}
@@ -104,7 +104,7 @@
}
.s-ticks {
@include bgTicks($timelineColorAlt1);
@include bgTicks( $timelineColorAlt1);
}
.s-hover-btns-holder {
$bg: $timelineHeaderColorBg;
@@ -112,7 +112,7 @@
$l: 5%;
@include user-select(none);
@include background-image(linear-gradient(-90deg, rgba($bg, $bga), rgba($bg, $bga) 70%, rgba($bg, 0) 100%));
.s-btn {
.s-button {
height: 16px;
line-height: 16px;
.icon {

View File

@@ -241,8 +241,8 @@
@include trans-prop-nice-fade(500ms);
opacity: 0;
height: $timelineTopPaneHeaderH;
width: 100px; left: auto;
padding: $interiorMargin;
left: auto;
padding: $interiorMargin $interiorMargin $interiorMargin $interiorMargin * 10;
text-align: right;
z-index: 10;
}
@@ -266,9 +266,6 @@
width: $timelineColIconW;
text-align: center;
padding: 0;
.ui-symbol {
color: $colorKey;
}
}
&.l-plot-resource {

View File

@@ -29,9 +29,7 @@
} : {}">
<div class="bar">
<span class="s-activity-type ui-symbol">
{{type.getGlyph()}}
</span>
<span class="s-activity-type {{type.getCssClass()}}"></span>
<span class="s-title">
{{model.name}}
</span>

View File

@@ -31,20 +31,17 @@
<span class="l-col l-col-icon l-plot-resource"
ng-click="ngModel.toggleGraph(); parameters.commit()"
title="Click to enable or disable inclusion in Resource Graphing">
<span class="ui-symbol"
ng-show="ngModel.graph()"
>
&#x00e9;
<span class="icon-plot-resource s-toggle-icon"
ng-class="{ active: ngModel.graph() }">
</span>
</span>
<span class="l-col l-col-icon l-link">
<a class="ui-symbol"
<a class="icon-chain-links"
target="_blank"
ng-href="{{ngModel.link()}}"
ng-if="ngModel.link().length > 0"
title="{{ngModel.link()}}"
>
&#x00e8;
</a>
</span>
<span class="l-col l-title"

View File

@@ -19,7 +19,7 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="s-timeline l-timeline-holder split-layout vertical"
<div class="s-timeline l-timeline-holder split-layout vertical splitter-sm"
ng-controller="TimelineController as timelineController">
<mct-split-pane anchor="left" class="abs" position="pane.x">
@@ -27,7 +27,7 @@
<mct-split-pane anchor="bottom"
position="pane.y"
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v">
<!-- TOP PANE TABULAR AREA. ADD CLASS "hidden" FOR INTERIM NO-TABULAR DELIVERY -->
<!-- TOP PANE TABULAR AREA -->
<div class="split-pane-component s-timeline-tabular l-timeline-pane t-pane-h l-pane-top">
<!-- TABULAR LEFT FIXED AREA -->
<div class="t-pane-v l-pane-l l-tabular-l"
@@ -35,8 +35,8 @@
<div class="t-header l-header s-header">
<div class="l-cols">
<span title="Resource Graphing: click a row to toggle" class="l-col l-col-icon l-plot-resource ui-symbol">&#x00e9;</span>
<span title="Activity Links" class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</span>
<span title="Resource Graphing: click a row to toggle" class="l-col l-col-icon l-plot-resource icon-plot-resource"></span>
<span title="Activity Links" class="l-col l-col-icon l-col-link icon-chain-links"></span>
<span class="l-col l-title">Title</span>
</div>
</div>
@@ -102,26 +102,23 @@
<!-- TOP PANE GANTT BARS -->
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
<div class="l-hover-btns-holder s-hover-btns-holder t-btns-zoom">
<a class="t-btn l-btn s-btn"
<div class="l-hover-btns-holder s-hover-btns-holder">
<a class="s-button icon-arrows-out"
ng-click="zoomController.fit()"
ng-show="true"
title="Zoom to fit">
<span class="ui-symbol icon zoom-in">I</span>
</a>
<a class="t-btn l-btn s-btn"
<a class="s-button icon-magnify-in"
ng-click="zoomController.zoom(-1)"
ng-show="true"
title="Zoom in">
<span class="ui-symbol icon zoom-in">X</span>
</a>
<a class="t-btn l-btn s-btn"
<a class="s-button icon-magnify-out"
ng-click="zoomController.zoom(1)"
ng-show="true"
title="Zoom out">
<span class="ui-symbol icon zoom-out">Y</span>
</a>
</div>

View File

@@ -37,7 +37,8 @@ define(
// Build graphs for this group of utilizations
function buildGraphs(utilizations) {
var utilizationMap = {},
result = {};
result = {},
startingSOC;
// Bucket utilizations by type
utilizations.forEach(function (u) {
@@ -55,12 +56,14 @@ define(
if (domainObject.getModel().type === 'timeline' &&
result.power &&
domainObject.getModel().capacity > 0) {
startingSOC = isNaN(parseFloat(domainObject.getModel().startingSOC)) ?
100 : parseFloat(domainObject.getModel().startingSOC);
result.battery = new CumulativeGraph(
result.power,
0,
domainObject.getModel().capacity, // Watts
domainObject.getModel().capacity,
(startingSOC / 100) * domainObject.getModel().capacity,
1 / 3600000 // millis-to-hour (since units are watt-hours)
);
}

View File

@@ -26,7 +26,7 @@ define(
/**
* Control for Gantt bars in a timeline view.
* Primarily reesponsible for supporting the positioning of Gantt
* Primarily responsible for supporting the positioning of Gantt
* bars; particularly, this ensures that the left and right edges
* never go to far off screen, because in some environments this
* will effect rendering performance without visible results.

View File

@@ -28,7 +28,7 @@ define(
/**
* Provides labels for the tick mark area of a timeline view.
* Since the tick mark regin is potentially extremeley large,
* Since the tick mark region is potentially extremely large,
* only the subset of ticks which will actually be shown in
* view are provided.
* @constructor

View File

@@ -140,7 +140,7 @@ define(
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set the end (ensuring it doesn't preceed start)
// Set the end (ensuring it doesn't precede start)
timespan.setEnd(
Math.max(value, timespan.getStart())
);

View File

@@ -41,7 +41,7 @@ define(
ids,
candidates;
// Filter an id for inclustion
// Filter an id for inclusion
function include(id) {
return id !== exclude;
}

View File

@@ -101,6 +101,7 @@ define(
it("provides a battery graph for timelines with capacity", function () {
var mockCallback = jasmine.createSpy('callback');
testModel.capacity = 1000;
testModel.startingSOC = 100;
testModel.type = "timeline";
mockDomainObject.useCapability.andReturn(asPromise([
{ key: "power", start: 0, end: 15 }