Compare commits
34 Commits
testathon-
...
activity-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a94a9dd5 | ||
|
|
2b11b8d63c | ||
|
|
006e99fb07 | ||
|
|
63dc4b6253 | ||
|
|
85868f690e | ||
|
|
b1f34f7cd7 | ||
|
|
8785d9a9d7 | ||
|
|
3a6e1fd301 | ||
|
|
fcef4274e5 | ||
|
|
744a5340d3 | ||
|
|
d140051054 | ||
|
|
8161e4fc89 | ||
|
|
8da74f2665 | ||
|
|
abf7654027 | ||
|
|
8fba707321 | ||
|
|
8ad5cca936 | ||
|
|
754d484501 | ||
|
|
74717b59c3 | ||
|
|
ffdb19787b | ||
|
|
5dc0d8c7f8 | ||
|
|
2390278b97 | ||
|
|
8a66731271 | ||
|
|
0a9ea48355 | ||
|
|
01d93306f3 | ||
|
|
0588f9190a | ||
|
|
1378b57567 | ||
|
|
9e12886c66 | ||
|
|
2d352ac574 | ||
|
|
284dec4903 | ||
|
|
5a0656c700 | ||
|
|
425655bae0 | ||
|
|
50b4d5cb28 | ||
|
|
bc62d7d5ae | ||
|
|
c0dcf4495e |
@@ -43,6 +43,9 @@
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.ImportExport());
|
||||
openmct.install(openmct.plugins.AutoflowView({
|
||||
type: "telemetry.panel"
|
||||
}));
|
||||
openmct.install(openmct.plugins.Conductor({
|
||||
menuOptions: [
|
||||
{
|
||||
@@ -65,6 +68,7 @@
|
||||
]
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.install(openmct.plugins.ActivityModes());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
||||
@@ -36,6 +36,7 @@ module.exports = function(config) {
|
||||
files: [
|
||||
{pattern: 'bower_components/**/*.js', included: false},
|
||||
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
||||
{pattern: 'node_modules/vue/**/*.js', included: false},
|
||||
{pattern: 'src/**/*.js', included: false},
|
||||
{pattern: 'example/**/*.html', included: false},
|
||||
{pattern: 'example/**/*.js', included: false},
|
||||
|
||||
@@ -37,6 +37,7 @@ requirejs.config({
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
"vue": "node_modules/vue/dist/vue.min",
|
||||
"zepto": "bower_components/zepto/zepto.min",
|
||||
"lodash": "bower_components/lodash/lodash",
|
||||
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
||||
@@ -48,7 +49,8 @@ requirejs.config({
|
||||
"d3-format": "node_modules/d3-format/build/d3-format.min",
|
||||
"d3-interpolate": "node_modules/d3-interpolate/build/d3-interpolate.min",
|
||||
"d3-time": "node_modules/d3-time/build/d3-time.min",
|
||||
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min"
|
||||
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min",
|
||||
"d3-dsv": "node_modules/d3-dsv/build/d3-dsv.min"
|
||||
},
|
||||
"shim": {
|
||||
"angular": {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"d3-axis": "^1.0.4",
|
||||
"d3-collection": "^1.0.2",
|
||||
"d3-color": "^1.0.2",
|
||||
"d3-dsv": "^1.0.8",
|
||||
"d3-format": "^1.0.2",
|
||||
"d3-interpolate": "^1.1.3",
|
||||
"d3-scale": "^1.0.4",
|
||||
@@ -15,7 +16,8 @@
|
||||
"d3-time-format": "^2.0.3",
|
||||
"express": "^4.13.1",
|
||||
"minimist": "^1.1.1",
|
||||
"request": "^2.69.0"
|
||||
"request": "^2.69.0",
|
||||
"vue": "^2.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "^1.7.7",
|
||||
|
||||
@@ -57,7 +57,12 @@
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll">
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
mct-selectable="{
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
}"
|
||||
mct-init-select>
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,12 +19,21 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="InspectorController">
|
||||
<div ng-repeat="region in regions">
|
||||
<div ng-controller="InspectorController as controller">
|
||||
<mct-representation
|
||||
key="region.content.key"
|
||||
mct-object="domainObject"
|
||||
key="'object-properties'"
|
||||
mct-object="controller.selectedItem()"
|
||||
ng-model="ngModel">
|
||||
</mct-representation>
|
||||
</div>
|
||||
|
||||
<div ng-if="!controller.hasProviderView()">
|
||||
<mct-representation
|
||||
key="inspectorKey"
|
||||
mct-object="controller.selectedItem()"
|
||||
ng-model="ngModel">
|
||||
</mct-representation>
|
||||
</div>
|
||||
|
||||
<div class='inspector-provider-view'>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,8 +38,6 @@
|
||||
ng-class="{ last:($index + 1) === contextualParents.length }">
|
||||
<mct-representation key="'label'"
|
||||
mct-object="parent"
|
||||
ng-model="ngModel"
|
||||
ng-click="ngModel.selectedObject = parent"
|
||||
class="location-item">
|
||||
</mct-representation>
|
||||
</span>
|
||||
@@ -51,8 +49,6 @@
|
||||
ng-class="{ last:($index + 1) === primaryParents.length }">
|
||||
<mct-representation key="'label'"
|
||||
mct-object="parent"
|
||||
ng-model="ngModel"
|
||||
ng-click="ngModel.selectedObject = parent"
|
||||
class="location-item">
|
||||
</mct-representation>
|
||||
</span>
|
||||
|
||||
@@ -121,7 +121,8 @@ define([
|
||||
"key": "ElementsController",
|
||||
"implementation": ElementsController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
"$scope",
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -299,9 +300,6 @@ define([
|
||||
{
|
||||
"key": "edit-elements",
|
||||
"template": elementsTemplate,
|
||||
"uses": [
|
||||
"composition"
|
||||
],
|
||||
"gestures": [
|
||||
"drop"
|
||||
]
|
||||
@@ -385,7 +383,10 @@ define([
|
||||
]
|
||||
},
|
||||
{
|
||||
"implementation": EditToolbarRepresenter
|
||||
"implementation": EditToolbarRepresenter,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
|
||||
@@ -61,7 +61,12 @@
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
toolbar="toolbar"
|
||||
mct-selectable="{
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
}"
|
||||
mct-init-select>
|
||||
</mct-representation>
|
||||
</div><!--/ l-object-wrapper-inner -->
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
ng-model="filterBy">
|
||||
</mct-include>
|
||||
<div class="flex-elem grows vscroll">
|
||||
<ul class="tree">
|
||||
<ul class="tree" ng-if="composition.length > 0">
|
||||
<li ng-repeat="containedObject in composition | filter:searchElements">
|
||||
<span class="tree-item">
|
||||
<mct-representation
|
||||
@@ -36,5 +36,6 @@
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="composition.length === 0">No contained elements</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,11 @@ define(
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function ElementsController($scope) {
|
||||
function ElementsController($scope, openmct) {
|
||||
this.scope = $scope;
|
||||
this.scope.composition = [];
|
||||
var self = this;
|
||||
|
||||
function filterBy(text) {
|
||||
if (typeof text === 'undefined') {
|
||||
return $scope.searchText;
|
||||
@@ -47,10 +51,44 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
function setSelection(selection) {
|
||||
self.scope.selection = selection;
|
||||
self.refreshComposition(selection);
|
||||
}
|
||||
|
||||
$scope.filterBy = filterBy;
|
||||
$scope.searchElements = searchElements;
|
||||
|
||||
openmct.selection.on('change', setSelection);
|
||||
setSelection(openmct.selection.get());
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
openmct.selection.off("change", setSelection);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the composition for the selected object and populates the scope with it.
|
||||
*
|
||||
* @param selection the selection object
|
||||
* @private
|
||||
*/
|
||||
ElementsController.prototype.refreshComposition = function (selection) {
|
||||
if (!selection[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedObjectComposition = selection[0].context.oldItem.useCapability('composition');
|
||||
|
||||
if (selectedObjectComposition) {
|
||||
selectedObjectComposition.then(function (composition) {
|
||||
this.scope.composition = composition;
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.scope.composition = [];
|
||||
}
|
||||
};
|
||||
|
||||
return ElementsController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ define(
|
||||
* @constructor
|
||||
* @implements {Representer}
|
||||
*/
|
||||
function EditToolbarRepresenter(scope, element, attrs) {
|
||||
function EditToolbarRepresenter(openmct, scope, element, attrs) {
|
||||
var self = this;
|
||||
|
||||
// Mark changes as ready to persist
|
||||
@@ -109,6 +109,7 @@ define(
|
||||
this.updateSelection = updateSelection;
|
||||
this.toolbar = undefined;
|
||||
this.toolbarObject = {};
|
||||
this.openmct = openmct;
|
||||
|
||||
// If this representation exposes a toolbar, set up watches
|
||||
// to synchronize with it.
|
||||
@@ -146,7 +147,7 @@ define(
|
||||
// Expose the toolbar object to the parent scope
|
||||
initialize(definition);
|
||||
// Create a selection scope
|
||||
this.setSelection(new EditToolbarSelection());
|
||||
this.setSelection(new EditToolbarSelection(this.openmct));
|
||||
// Initialize toolbar to an empty selection
|
||||
this.updateSelection([]);
|
||||
};
|
||||
|
||||
@@ -38,10 +38,18 @@ define(
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbarSelection() {
|
||||
function EditToolbarSelection(openmct) {
|
||||
this.selection = [{}];
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
|
||||
openmct.selection.on('change', function (selection) {
|
||||
if (selection[0] && selection[0].context.toolbar) {
|
||||
this.select(selection[0].context.toolbar);
|
||||
} else {
|
||||
this.deselect();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,11 +27,23 @@ define(
|
||||
|
||||
describe("The Elements Pane controller", function () {
|
||||
var mockScope,
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpy("$scope");
|
||||
controller = new ElementsController(mockScope);
|
||||
mockScope = jasmine.createSpyObj("$scope", ['$on']);
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
|
||||
controller = new ElementsController(mockScope, mockOpenMCT);
|
||||
});
|
||||
|
||||
function getModel(model) {
|
||||
|
||||
@@ -29,7 +29,9 @@ define(
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockUnwatch,
|
||||
representer;
|
||||
representer,
|
||||
mockOpenMCT,
|
||||
mockSelection;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
@@ -46,7 +48,18 @@ define(
|
||||
|
||||
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
|
||||
representer = new EditToolbarRepresenter(
|
||||
mockOpenMCT,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs
|
||||
|
||||
@@ -28,13 +28,25 @@ define(
|
||||
var testProxy,
|
||||
testElement,
|
||||
otherElement,
|
||||
selection;
|
||||
selection,
|
||||
mockSelection,
|
||||
mockOpenMCT;
|
||||
|
||||
beforeEach(function () {
|
||||
testProxy = { someKey: "some value" };
|
||||
testElement = { someOtherKey: "some other value" };
|
||||
otherElement = { yetAnotherKey: 42 };
|
||||
selection = new EditToolbarSelection();
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
// 'select',
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
selection = new EditToolbarSelection(mockOpenMCT);
|
||||
selection.proxy(testProxy);
|
||||
});
|
||||
|
||||
|
||||
@@ -121,6 +121,9 @@ define([
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
if (typeof text === 'number') {
|
||||
return text;
|
||||
}
|
||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ define([
|
||||
"./src/controllers/BannerController",
|
||||
"./src/directives/MCTContainer",
|
||||
"./src/directives/MCTDrag",
|
||||
"./src/directives/MCTSelectable",
|
||||
"./src/directives/MCTClickElsewhere",
|
||||
"./src/directives/MCTResize",
|
||||
"./src/directives/MCTPopup",
|
||||
@@ -90,6 +91,7 @@ define([
|
||||
BannerController,
|
||||
MCTContainer,
|
||||
MCTDrag,
|
||||
MCTSelectable,
|
||||
MCTClickElsewhere,
|
||||
MCTResize,
|
||||
MCTPopup,
|
||||
@@ -328,6 +330,13 @@ define([
|
||||
"$document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctSelectable",
|
||||
"implementation": MCTSelectable,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctClickElsewhere",
|
||||
"implementation": MCTClickElsewhere,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
}
|
||||
|
||||
.l-fixed-position-item {
|
||||
border-width: 1px;
|
||||
position: absolute;
|
||||
&.s-not-selected {
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -80,23 +80,32 @@
|
||||
|
||||
// Editing Grids
|
||||
.l-grid-holder {
|
||||
display: block;
|
||||
.l-grid {
|
||||
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
||||
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent nested frames from showing their grids
|
||||
.t-frame-outer .l-grid-holder { display: none !important; }
|
||||
|
||||
// Prevent nested elements from showing s-hover-border
|
||||
.t-frame-outer .s-hover-border {
|
||||
border: none !important;
|
||||
// Display grid when selected or selection parent.
|
||||
.s-selected .l-grid-holder,
|
||||
.s-selected-parent .l-grid-holder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Prevent nested frames from being selectable until we have proper sub-object editing
|
||||
.t-frame-outer .t-frame-outer {
|
||||
pointer-events: none;
|
||||
// Display in nested frames...
|
||||
.t-frame-outer {
|
||||
// ...when drilled in or selection parent...
|
||||
&.s-drilled-in, &.s-selected-parent {
|
||||
.l-grid-holder {
|
||||
display: block;
|
||||
}
|
||||
.t-frame-outer:not(.s-drilled-in) .l-grid-holder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
// ...but hide otherwise.
|
||||
.l-grid-holder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,14 @@
|
||||
$ohH: $btnFrameH;
|
||||
$bc: $colorInteriorBorder;
|
||||
&.child-frame.panel {
|
||||
border: 1px solid transparent;
|
||||
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: 1px solid $bc;
|
||||
&:hover {
|
||||
border-color: lighten($bc, 10%);
|
||||
}
|
||||
border-color: $bc;
|
||||
}
|
||||
}
|
||||
|
||||
.object-browse-bar {
|
||||
font-size: 0.75em;
|
||||
height: $ohH;
|
||||
@@ -92,9 +91,9 @@
|
||||
|
||||
&.no-frame {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
border: none;
|
||||
.object-browse-bar .right {
|
||||
$m: 0; // $interiorMarginSm;
|
||||
$m: 0;
|
||||
background: rgba(black, 0.3);
|
||||
border-radius: $basicCr;
|
||||
padding: $interiorMarginSm;
|
||||
@@ -104,7 +103,7 @@
|
||||
}
|
||||
&.t-frame-outer > .t-rep-frame {
|
||||
&.contents {
|
||||
$m: 2px;
|
||||
$m: 0px;
|
||||
top: $m;
|
||||
right: $m;
|
||||
bottom: $m;
|
||||
@@ -115,6 +114,7 @@
|
||||
display: none;
|
||||
}
|
||||
> .object-holder.abs {
|
||||
overflow: hidden;
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,35 +20,51 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.s-hover-border {
|
||||
border: 1px dotted transparent;
|
||||
&:hover {
|
||||
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-editing {
|
||||
// Limit to editing mode until we have sub-object selection
|
||||
// Limit to editing mode
|
||||
$o: 0.5;
|
||||
$oHover: 0.8;
|
||||
$bc: $colorSelectableSelectedPrimary;
|
||||
.s-hover-border {
|
||||
// Show a border by default so user can see object bounds and empty objects
|
||||
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
|
||||
border-color: rgba($bc, $o) !important;
|
||||
border-style: dotted !important;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
|
||||
border-color: rgba($bc, $oHover) !important;
|
||||
}
|
||||
|
||||
&.t-object-type-layout {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
}
|
||||
|
||||
.s-selected > .s-hover-border,
|
||||
.s-selected.s-hover-border {
|
||||
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
||||
border-color: $colorSelectableSelectedPrimary !important;
|
||||
@include boxShdwLarge();
|
||||
// Show edit-corners if you got 'em
|
||||
.edit-corner {
|
||||
display: block;
|
||||
&:hover {
|
||||
background-color: rgba($colorKey, 1);
|
||||
.s-selected {
|
||||
&.s-moveable {
|
||||
&:not(.s-drilled-in) {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.s-selected > .s-moveable,
|
||||
.s-selected.s-moveable {
|
||||
cursor: move;
|
||||
.s-selected > .s-hover-border,
|
||||
.s-selected.s-hover-border {
|
||||
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
||||
border-color: $colorSelectableSelectedPrimary !important;
|
||||
@include boxShdwLarge();
|
||||
// Show edit-corners if you got 'em
|
||||
.edit-corner {
|
||||
display: block;
|
||||
&:hover {
|
||||
background-color: rgba($colorKey, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="DateTimeFieldController">
|
||||
<input type="text"
|
||||
<input type="text" autocorrect="off" spellcheck="false"
|
||||
ng-model="textValue"
|
||||
ng-blur="restoreTextValue(); ngBlur()"
|
||||
ng-mouseup="ngMouseup()"
|
||||
|
||||
@@ -40,7 +40,7 @@ define(
|
||||
|
||||
// Gets an array of the contextual parents/ancestors of the selected object
|
||||
function getContextualPath() {
|
||||
var currentObj = $scope.ngModel.selectedObject,
|
||||
var currentObj = $scope.domainObject,
|
||||
currentParent,
|
||||
parents = [];
|
||||
|
||||
@@ -68,7 +68,7 @@ define(
|
||||
|
||||
// If this the the initial call of this recursive function
|
||||
if (!current) {
|
||||
current = $scope.ngModel.selectedObject;
|
||||
current = $scope.domainObject;
|
||||
$scope.primaryParents = [];
|
||||
}
|
||||
|
||||
@@ -87,16 +87,16 @@ define(
|
||||
|
||||
// Gets the metadata for the selected object
|
||||
function getMetadata() {
|
||||
$scope.metadata = $scope.ngModel.selectedObject &&
|
||||
$scope.ngModel.selectedObject.hasCapability('metadata') &&
|
||||
$scope.ngModel.selectedObject.useCapability('metadata');
|
||||
$scope.metadata = $scope.domainObject &&
|
||||
$scope.domainObject.hasCapability('metadata') &&
|
||||
$scope.domainObject.useCapability('metadata');
|
||||
}
|
||||
|
||||
// Set scope variables when the selected object changes
|
||||
$scope.$watch('ngModel.selectedObject', function () {
|
||||
$scope.isLink = $scope.ngModel.selectedObject &&
|
||||
$scope.ngModel.selectedObject.hasCapability('location') &&
|
||||
$scope.ngModel.selectedObject.getCapability('location').isLink();
|
||||
$scope.$watch('domainObject', function () {
|
||||
$scope.isLink = $scope.domainObject &&
|
||||
$scope.domainObject.hasCapability('location') &&
|
||||
$scope.domainObject.getCapability('location').isLink();
|
||||
|
||||
if ($scope.isLink) {
|
||||
getPrimaryPath();
|
||||
@@ -109,7 +109,7 @@ define(
|
||||
getMetadata();
|
||||
});
|
||||
|
||||
var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
|
||||
var mutation = $scope.domainObject.getCapability('mutation');
|
||||
var unlisten = mutation.listen(getMetadata);
|
||||
$scope.$on('$destroy', unlisten);
|
||||
}
|
||||
|
||||
60
platform/commonUI/general/src/directives/MCTSelectable.js
Normal file
60
platform/commonUI/general/src/directives/MCTSelectable.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The mct-selectable directive allows selection functionality
|
||||
* (click) to be attached to specific elements.
|
||||
*
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
function MCTSelectable(openmct) {
|
||||
|
||||
// Link; install event handlers.
|
||||
function link(scope, element, attrs) {
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||
);
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
removeSelectable();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// mct-selectable only makes sense as an attribute
|
||||
restrict: "A",
|
||||
// Link function, to install event handlers
|
||||
link: link
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return MCTSelectable;
|
||||
}
|
||||
);
|
||||
@@ -41,16 +41,6 @@ define(
|
||||
"$scope",
|
||||
["$watch", "$on"]
|
||||
);
|
||||
mockScope.ngModel = {};
|
||||
mockScope.ngModel.selectedObject = {
|
||||
getCapability: function () {
|
||||
return {
|
||||
listen: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
mockObjectService = jasmine.createSpyObj(
|
||||
"objectService",
|
||||
@@ -77,22 +67,27 @@ define(
|
||||
"location capability",
|
||||
["isLink"]
|
||||
);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (param) {
|
||||
if (param === 'location') {
|
||||
return mockLocationCapability;
|
||||
} else if (param === 'context') {
|
||||
return mockContextCapability;
|
||||
} else if (param === 'mutation') {
|
||||
return {
|
||||
listen: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
controller = new ObjectInspectorController(mockScope, mockObjectService);
|
||||
|
||||
// Change the selected object to trigger the watch call
|
||||
mockScope.ngModel.selectedObject = mockDomainObject;
|
||||
});
|
||||
|
||||
it("watches for changes to the selected object", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("looks for contextual parent objects", function () {
|
||||
|
||||
@@ -38,7 +38,8 @@ define([
|
||||
"implementation": InspectorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"policyService"
|
||||
"openmct",
|
||||
"$document"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -21,44 +21,69 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../browse/src/InspectorRegion'],
|
||||
function (InspectorRegion) {
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The InspectorController adds region data for a domain object's type
|
||||
* to the scope.
|
||||
* The InspectorController listens for the selection changes and adds the selection
|
||||
* object to the scope.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function InspectorController($scope, policyService) {
|
||||
var domainObject = $scope.domainObject,
|
||||
typeCapability = domainObject.getCapability('type'),
|
||||
statusListener;
|
||||
function InspectorController($scope, openmct, $document) {
|
||||
var self = this;
|
||||
self.$scope = $scope;
|
||||
|
||||
/**
|
||||
* Filters region parts to only those allowed by region policies
|
||||
* @param regions
|
||||
* @returns {{}}
|
||||
* Callback handler for the selection change event.
|
||||
* Adds the selection object to the scope. If the selected item has an inspector view,
|
||||
* it puts the key in the scope. If provider view exists, it shows the view.
|
||||
*/
|
||||
function filterRegions(inspector) {
|
||||
//Dupe so we're not modifying the type definition.
|
||||
return inspector.regions && inspector.regions.filter(function (region) {
|
||||
return policyService.allow('region', region, domainObject);
|
||||
});
|
||||
function setSelection(selection) {
|
||||
if (selection[0]) {
|
||||
var view = openmct.inspectorViews.get(selection);
|
||||
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
|
||||
container.innerHTML = "";
|
||||
|
||||
if (view) {
|
||||
self.providerView = true;
|
||||
view.show(container);
|
||||
} else {
|
||||
self.providerView = false;
|
||||
$scope.inspectorKey = selection[0].context.oldItem.getCapability("type").typeDef.inspector;
|
||||
}
|
||||
}
|
||||
|
||||
self.$scope.selection = selection;
|
||||
}
|
||||
|
||||
function setRegions() {
|
||||
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
|
||||
}
|
||||
openmct.selection.on("change", setSelection);
|
||||
|
||||
setSelection(openmct.selection.get());
|
||||
|
||||
statusListener = domainObject.getCapability("status").listen(setRegions);
|
||||
$scope.$on("$destroy", function () {
|
||||
statusListener();
|
||||
openmct.selection.off("change", setSelection);
|
||||
});
|
||||
|
||||
setRegions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected item.
|
||||
*
|
||||
* @returns a domain object
|
||||
*/
|
||||
InspectorController.prototype.selectedItem = function () {
|
||||
return this.$scope.selection[0].context.oldItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a provider view exists.
|
||||
*
|
||||
* @returns 'true' if provider view exists, 'false' otherwise
|
||||
*/
|
||||
InspectorController.prototype.hasProviderView = function () {
|
||||
return this.providerView;
|
||||
};
|
||||
|
||||
return InspectorController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -27,82 +27,93 @@ define(
|
||||
describe("The inspector controller ", function () {
|
||||
var mockScope,
|
||||
mockDomainObject,
|
||||
mockTypeCapability,
|
||||
mockTypeDefinition,
|
||||
mockPolicyService,
|
||||
mockStatusCapability,
|
||||
capabilities = {},
|
||||
controller;
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockInspectorViews,
|
||||
mockTypeDef,
|
||||
controller,
|
||||
container,
|
||||
$document = [],
|
||||
selectable = [];
|
||||
|
||||
beforeEach(function () {
|
||||
mockTypeDefinition = {
|
||||
inspector:
|
||||
{
|
||||
'regions': [
|
||||
{'name': 'Part One'},
|
||||
{'name': 'Part Two'}
|
||||
]
|
||||
}
|
||||
mockTypeDef = {
|
||||
typeDef: {
|
||||
inspector: "some-key"
|
||||
}
|
||||
};
|
||||
|
||||
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
|
||||
'getDefinition'
|
||||
]);
|
||||
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
|
||||
capabilities.type = mockTypeCapability;
|
||||
|
||||
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
|
||||
'listen'
|
||||
]);
|
||||
capabilities.status = mockStatusCapability;
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability'
|
||||
]);
|
||||
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||
return capabilities[name];
|
||||
});
|
||||
|
||||
mockPolicyService = jasmine.createSpyObj('policyService', [
|
||||
'allow'
|
||||
]);
|
||||
mockDomainObject.getCapability.andReturn(mockTypeDef);
|
||||
|
||||
mockScope = jasmine.createSpyObj('$scope',
|
||||
['$on']
|
||||
['$on', 'selection']
|
||||
);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
selectable[0] = {
|
||||
context: {
|
||||
oldItem: mockDomainObject
|
||||
}
|
||||
};
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn(selectable);
|
||||
|
||||
mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection,
|
||||
inspectorViews: mockInspectorViews
|
||||
};
|
||||
|
||||
container = jasmine.createSpy('container', ['innerHTML']);
|
||||
$document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
|
||||
$document[0].querySelectorAll.andReturn([container]);
|
||||
|
||||
controller = new InspectorController(mockScope, mockOpenMCT, $document);
|
||||
});
|
||||
|
||||
it("filters out regions disallowed by region policy", function () {
|
||||
mockPolicyService.allow.andReturn(false);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(0);
|
||||
it("listens for selection change event", function () {
|
||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
||||
|
||||
var mockItem = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability'
|
||||
]);
|
||||
mockItem.getCapability.andReturn(mockTypeDef);
|
||||
selectable[0].context.oldItem = mockItem;
|
||||
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
expect(controller.selectedItem()).toEqual(mockItem);
|
||||
});
|
||||
|
||||
it("does not filter out regions allowed by region policy", function () {
|
||||
mockPolicyService.allow.andReturn(true);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(2);
|
||||
it("cleans up on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockScope.$on.calls[0].args[1]();
|
||||
|
||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("Responds to status changes", function () {
|
||||
mockPolicyService.allow.andReturn(true);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(2);
|
||||
expect(mockStatusCapability.listen).toHaveBeenCalled();
|
||||
mockPolicyService.allow.andReturn(false);
|
||||
mockStatusCapability.listen.mostRecentCall.args[0]();
|
||||
expect(mockScope.regions.length).toBe(0);
|
||||
});
|
||||
|
||||
it("Unregisters status listener", function () {
|
||||
var mockListener = jasmine.createSpy('listener');
|
||||
mockStatusCapability.listen.andReturn(mockListener);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
it("adds selection object to scope", function () {
|
||||
expect(mockScope.selection).toEqual(selectable);
|
||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
define([
|
||||
'text!./res/templates/autoflow-tabular.html',
|
||||
'./src/AutoflowTabularController',
|
||||
'./src/MCTAutoflowTable'
|
||||
], function (
|
||||
autoflowTabularTemplate,
|
||||
AutoflowTabularController,
|
||||
MCTAutoflowTable
|
||||
) {
|
||||
return function (options) {
|
||||
return function (openmct) {
|
||||
openmct.legacyRegistry.register("platform/features/autoflow", {
|
||||
"name": "WARP Telemetry Adapter",
|
||||
"description": "Retrieves telemetry from the WARP Server and provides related types and views.",
|
||||
"resources": "res",
|
||||
"extensions": {
|
||||
"views": [
|
||||
{
|
||||
"key": "autoflow",
|
||||
"name": "Autoflow Tabular",
|
||||
"cssClass": "icon-packet",
|
||||
"description": "A tabular view of packet contents.",
|
||||
"template": autoflowTabularTemplate,
|
||||
"type": options && options.type,
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "AutoflowTabularController",
|
||||
"implementation": AutoflowTabularController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"telemetrySubscriber"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctAutoflowTable",
|
||||
"implementation": MCTAutoflowTable
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
openmct.legacyRegistry.enable("platform/features/autoflow");
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="items-holder abs contents autoflow obj-value-format"
|
||||
ng-controller="AutoflowTabularController as autoflow">
|
||||
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
||||
<mct-include key="'input-filter'"
|
||||
ng-model="autoflow.filter"
|
||||
class="flex-elem">
|
||||
</mct-include>
|
||||
<div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
|
||||
<a title="Change column width"
|
||||
class="s-button flex-elem icon-arrows-right-left change-column-width"
|
||||
ng-click="autoflow.increaseColumnWidth()"></a>
|
||||
</div>
|
||||
<div class="abs t-autoflow-items l-autoflow-items"
|
||||
mct-resize="autoflow.setBounds(bounds)"
|
||||
mct-resize-interval="50">
|
||||
<mct-autoflow-table values="autoflow.rangeValues()"
|
||||
objects="autoflow.getTelemetryObjects()"
|
||||
rows="autoflow.getRows()"
|
||||
classes="autoflow.classes()"
|
||||
updated="autoflow.updated()"
|
||||
column-width="autoflow.columnWidth()"
|
||||
counter="autoflow.counter()"
|
||||
>
|
||||
</mct-autoflow-table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,169 +0,0 @@
|
||||
/*global angular*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The link step for the `mct-autoflow-table` directive;
|
||||
* watches scope and updates the DOM appropriately.
|
||||
* See documentation in `MCTAutoflowTable.js` for the rationale
|
||||
* for including this directive, as well as for an explanation
|
||||
* of which values are placed in scope.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Scope} scope the scope for this usage of the directive
|
||||
* @param element the jqLite-wrapped element which used this directive
|
||||
*/
|
||||
function AutoflowTableLinker(scope, element) {
|
||||
var objects, // Domain objects at last structure refresh
|
||||
rows, // Number of rows from last structure refresh
|
||||
priorClasses = {},
|
||||
valueSpans = {}; // Span elements to put data values in
|
||||
|
||||
// Create a new name-value pair in the specified column
|
||||
function createListItem(domainObject, ul) {
|
||||
// Create a new li, and spans to go in it.
|
||||
var li = angular.element('<li>'),
|
||||
titleSpan = angular.element('<span>'),
|
||||
valueSpan = angular.element('<span>');
|
||||
|
||||
// Place spans in the li, and li into the column.
|
||||
// valueSpan must precede titleSpan in the DOM due to new CSS float approach
|
||||
li.append(valueSpan).append(titleSpan);
|
||||
ul.append(li);
|
||||
|
||||
// Style appropriately
|
||||
li.addClass('l-autoflow-row');
|
||||
titleSpan.addClass('l-autoflow-item l');
|
||||
valueSpan.addClass('l-autoflow-item r l-obj-val-format');
|
||||
|
||||
// Set text/tooltip for the name-value row
|
||||
titleSpan.text(domainObject.getModel().name);
|
||||
titleSpan.attr("title", domainObject.getModel().name);
|
||||
|
||||
// Keep a reference to the span which will hold the
|
||||
// data value, to populate in the next refreshValues call
|
||||
valueSpans[domainObject.getId()] = valueSpan;
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
// Create a new column of name-value pairs in this table.
|
||||
function createColumn(el) {
|
||||
// Create a ul
|
||||
var ul = angular.element('<ul>');
|
||||
|
||||
// Add it into the mct-autoflow-table
|
||||
el.append(ul);
|
||||
|
||||
// Style appropriately
|
||||
ul.addClass('l-autoflow-col');
|
||||
|
||||
// Get the current col width and apply at time of column creation
|
||||
// Important to do this here, as new columns could be created after
|
||||
// the user has changed the width.
|
||||
ul.css('width', scope.columnWidth + 'px');
|
||||
|
||||
// Return it, so some li elements can be added
|
||||
return ul;
|
||||
}
|
||||
|
||||
// Change the width of the columns when user clicks the resize button.
|
||||
function resizeColumn() {
|
||||
element.find('ul').css('width', scope.columnWidth + 'px');
|
||||
}
|
||||
|
||||
// Rebuild the DOM associated with this table.
|
||||
function rebuild(domainObjects, rowCount) {
|
||||
var activeColumn;
|
||||
|
||||
// Empty out our cached span elements
|
||||
valueSpans = {};
|
||||
|
||||
// Start with an empty DOM beneath this directive
|
||||
element.html("");
|
||||
|
||||
// Add DOM elements for each domain object being displayed
|
||||
// in this table.
|
||||
domainObjects.forEach(function (object, index) {
|
||||
// Start a new column if we'd run out of room
|
||||
if (index % rowCount === 0) {
|
||||
activeColumn = createColumn(element);
|
||||
}
|
||||
// Add the DOM elements for that object to whichever
|
||||
// column (a `ul` element) is current.
|
||||
createListItem(object, activeColumn);
|
||||
});
|
||||
}
|
||||
|
||||
// Update spans with values, as made available via the
|
||||
// `values` attribute of this directive.
|
||||
function refreshValues() {
|
||||
// Get the available values
|
||||
var values = scope.values || {},
|
||||
classes = scope.classes || {};
|
||||
|
||||
// Populate all spans with those values (or clear
|
||||
// those spans if no value is available)
|
||||
(objects || []).forEach(function (object) {
|
||||
var id = object.getId(),
|
||||
span = valueSpans[id],
|
||||
value;
|
||||
|
||||
if (span) {
|
||||
// Look up the value...
|
||||
value = values[id];
|
||||
// ...and convert to empty string if it's undefined
|
||||
value = value === undefined ? "" : value;
|
||||
span.attr("data-value", value);
|
||||
|
||||
// Update the span
|
||||
span.text(value);
|
||||
span.attr("title", value);
|
||||
span.removeClass(priorClasses[id]);
|
||||
span.addClass(classes[id]);
|
||||
priorClasses[id] = classes[id];
|
||||
}
|
||||
// Also need stale/alert/ok class
|
||||
// on span
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh the DOM for this table, if necessary
|
||||
function refreshStructure() {
|
||||
// Only rebuild if number of rows or set of objects
|
||||
// has changed; otherwise, our structure is still valid.
|
||||
if (scope.objects !== objects ||
|
||||
scope.rows !== rows) {
|
||||
|
||||
// Track those values to support future refresh checks
|
||||
objects = scope.objects;
|
||||
rows = scope.rows;
|
||||
|
||||
// Rebuild the DOM
|
||||
rebuild(objects || [], rows || 1);
|
||||
|
||||
// Refresh all data values shown
|
||||
refreshValues();
|
||||
}
|
||||
}
|
||||
|
||||
// Changing the domain objects in use or the number
|
||||
// of rows should trigger a structure change (DOM rebuild)
|
||||
scope.$watch("objects", refreshStructure);
|
||||
scope.$watch("rows", refreshStructure);
|
||||
|
||||
// When the current column width has been changed, resize the column
|
||||
scope.$watch('columnWidth', resizeColumn);
|
||||
|
||||
// When the last-updated time ticks,
|
||||
scope.$watch("updated", refreshValues);
|
||||
|
||||
// Update displayed values when the counter changes.
|
||||
scope.$watch("counter", refreshValues);
|
||||
|
||||
}
|
||||
|
||||
return AutoflowTableLinker;
|
||||
}
|
||||
);
|
||||
@@ -1,324 +0,0 @@
|
||||
|
||||
define(
|
||||
['moment'],
|
||||
function (moment) {
|
||||
|
||||
var ROW_HEIGHT = 16,
|
||||
SLIDER_HEIGHT = 10,
|
||||
INITIAL_COLUMN_WIDTH = 225,
|
||||
MAX_COLUMN_WIDTH = 525,
|
||||
COLUMN_WIDTH_STEP = 25,
|
||||
DEBOUNCE_INTERVAL = 100,
|
||||
DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
|
||||
NOT_UPDATED = "No updates",
|
||||
EMPTY_ARRAY = [];
|
||||
|
||||
/**
|
||||
* Responsible for supporting the autoflow tabular view.
|
||||
* Implements the all-over logic which drives that view,
|
||||
* mediating between template-provided areas, the included
|
||||
* `mct-autoflow-table` directive, and the underlying
|
||||
* domain object model.
|
||||
* @constructor
|
||||
*/
|
||||
function AutflowTabularController(
|
||||
$scope,
|
||||
$timeout,
|
||||
telemetrySubscriber
|
||||
) {
|
||||
var filterValue = "",
|
||||
filterValueLowercase = "",
|
||||
subscription,
|
||||
filteredObjects = [],
|
||||
lastUpdated = {},
|
||||
updateText = NOT_UPDATED,
|
||||
rangeValues = {},
|
||||
classes = {},
|
||||
limits = {},
|
||||
updatePending = false,
|
||||
lastBounce = Number.NEGATIVE_INFINITY,
|
||||
columnWidth = INITIAL_COLUMN_WIDTH,
|
||||
rows = 1,
|
||||
counter = 0;
|
||||
|
||||
// Trigger an update of the displayed table by incrementing
|
||||
// the counter that it watches.
|
||||
function triggerDisplayUpdate() {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
// Check whether or not an object's name matches the
|
||||
// user-entered filter value.
|
||||
function filterObject(domainObject) {
|
||||
return (domainObject.getModel().name || "")
|
||||
.toLowerCase()
|
||||
.indexOf(filterValueLowercase) !== -1;
|
||||
}
|
||||
|
||||
// Comparator for sorting points back into packet order
|
||||
function compareObject(objectA, objectB) {
|
||||
var indexA = objectA.getModel().index || 0,
|
||||
indexB = objectB.getModel().index || 0;
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
// Update the list of currently-displayed objects; these
|
||||
// will be the subset of currently subscribed-to objects
|
||||
// which match a user-entered filter.
|
||||
function doUpdateFilteredObjects() {
|
||||
// Generate the list
|
||||
filteredObjects = (
|
||||
subscription ?
|
||||
subscription.getTelemetryObjects() :
|
||||
[]
|
||||
).filter(filterObject).sort(compareObject);
|
||||
|
||||
// Clear the pending flag
|
||||
updatePending = false;
|
||||
|
||||
// Track when this occurred, so that we can wait
|
||||
// a whole before updating again.
|
||||
lastBounce = Date.now();
|
||||
|
||||
triggerDisplayUpdate();
|
||||
}
|
||||
|
||||
// Request an update to the list of current objects; this may
|
||||
// run on a timeout to avoid excessive calls, e.g. while the user
|
||||
// is typing a filter.
|
||||
function updateFilteredObjects() {
|
||||
// Don't do anything if an update is already scheduled
|
||||
if (!updatePending) {
|
||||
if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
|
||||
// Update immediately if it's been long enough
|
||||
doUpdateFilteredObjects();
|
||||
} else {
|
||||
// Otherwise, update later, and track that we have
|
||||
// an update pending so that subsequent calls can
|
||||
// be ignored.
|
||||
updatePending = true;
|
||||
$timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track the latest data values for this domain object
|
||||
function recordData(telemetryObject) {
|
||||
// Get latest domain/range values for this object.
|
||||
var id = telemetryObject.getId(),
|
||||
domainValue = subscription.getDomainValue(telemetryObject),
|
||||
rangeValue = subscription.getRangeValue(telemetryObject);
|
||||
|
||||
// Track the most recent timestamp change observed...
|
||||
if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
|
||||
lastUpdated[id] = domainValue;
|
||||
// ... and update the displayable text for that timestamp
|
||||
updateText = isNaN(domainValue) ? "" :
|
||||
moment.utc(domainValue).format(DATE_FORMAT);
|
||||
}
|
||||
|
||||
// Store data values into the rangeValues structure, which
|
||||
// will be used to populate the table itself.
|
||||
// Note that we want full precision here.
|
||||
rangeValues[id] = rangeValue;
|
||||
|
||||
// Update limit states as well
|
||||
classes[id] = limits[id] && (limits[id].evaluate({
|
||||
// This relies on external knowledge that the
|
||||
// range value of a telemetry point is encoded
|
||||
// in its datum as "value."
|
||||
value: rangeValue
|
||||
}) || {}).cssClass;
|
||||
}
|
||||
|
||||
|
||||
// Look at telemetry objects from the subscription; this is watched
|
||||
// to detect changes from the subscription.
|
||||
function subscribedTelemetry() {
|
||||
return subscription ?
|
||||
subscription.getTelemetryObjects() : EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
// Update the data values which will be used to populate the table
|
||||
function updateValues() {
|
||||
subscribedTelemetry().forEach(recordData);
|
||||
triggerDisplayUpdate();
|
||||
}
|
||||
|
||||
// Getter-setter function for user-entered filter text.
|
||||
function filter(value) {
|
||||
// If value was specified, we're a setter
|
||||
if (value !== undefined) {
|
||||
// Store the new value
|
||||
filterValue = value;
|
||||
filterValueLowercase = value.toLowerCase();
|
||||
// Change which objects appear in the table
|
||||
updateFilteredObjects();
|
||||
}
|
||||
|
||||
// Always act as a getter
|
||||
return filterValue;
|
||||
}
|
||||
|
||||
// Update the bounds (width and height) of this view;
|
||||
// called from the mct-resize directive. Recalculates how
|
||||
// many rows should appear in the contained table.
|
||||
function setBounds(bounds) {
|
||||
var availableSpace = bounds.height - SLIDER_HEIGHT;
|
||||
rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
|
||||
}
|
||||
|
||||
// Increment the current column width, up to the defined maximum.
|
||||
// When the max is hit, roll back to the default.
|
||||
function increaseColumnWidth() {
|
||||
columnWidth += COLUMN_WIDTH_STEP;
|
||||
// Cycle down to the initial width instead of exceeding max
|
||||
columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
|
||||
INITIAL_COLUMN_WIDTH : columnWidth;
|
||||
}
|
||||
|
||||
// Get displayable text for last-updated value
|
||||
function updated() {
|
||||
return updateText;
|
||||
}
|
||||
|
||||
// Unsubscribe, if a subscription is active.
|
||||
function releaseSubscription() {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Update set of telemetry objects managed by this view
|
||||
function updateTelemetryObjects(telemetryObjects) {
|
||||
updateFilteredObjects();
|
||||
limits = {};
|
||||
telemetryObjects.forEach(function (telemetryObject) {
|
||||
var id = telemetryObject.getId();
|
||||
limits[id] = telemetryObject.getCapability('limit');
|
||||
});
|
||||
}
|
||||
|
||||
// Create a subscription for the represented domain object.
|
||||
// This will resolve capability delegation as necessary.
|
||||
function makeSubscription(domainObject) {
|
||||
// Unsubscribe, if there is an existing subscription
|
||||
releaseSubscription();
|
||||
|
||||
// Clear updated timestamp
|
||||
lastUpdated = {};
|
||||
updateText = NOT_UPDATED;
|
||||
|
||||
// Create a new subscription; telemetrySubscriber gets
|
||||
// to do the meaningful work here.
|
||||
subscription = domainObject && telemetrySubscriber.subscribe(
|
||||
domainObject,
|
||||
updateValues
|
||||
);
|
||||
|
||||
// Our set of in-view telemetry objects may have changed,
|
||||
// so update the set that is being passed down to the table.
|
||||
updateFilteredObjects();
|
||||
}
|
||||
|
||||
// Watch for changes to the set of objects which have telemetry
|
||||
$scope.$watch(subscribedTelemetry, updateTelemetryObjects);
|
||||
|
||||
// Watch for the represented domainObject (this field will
|
||||
// be populated by mct-representation)
|
||||
$scope.$watch("domainObject", makeSubscription);
|
||||
|
||||
// Make sure we unsubscribe when this view is destroyed.
|
||||
$scope.$on("$destroy", releaseSubscription);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the number of rows which should be shown in this table.
|
||||
* @return {number} the number of rows to show
|
||||
*/
|
||||
getRows: function () {
|
||||
return rows;
|
||||
},
|
||||
/**
|
||||
* Get the objects which should currently be displayed in
|
||||
* this table. This will be watched, so the return value
|
||||
* should be stable when this list is unchanging. Only
|
||||
* objects which match the user-entered filter value should
|
||||
* be returned here.
|
||||
* @return {DomainObject[]} the domain objects to include in
|
||||
* this table.
|
||||
*/
|
||||
getTelemetryObjects: function () {
|
||||
return filteredObjects;
|
||||
},
|
||||
/**
|
||||
* Set the bounds (width/height) of this autoflow tabular view.
|
||||
* The template must ensure that these bounds are tracked on
|
||||
* the table area only.
|
||||
* @param bounds the bounds; and object with `width` and
|
||||
* `height` properties, both as numbers, in pixels.
|
||||
*/
|
||||
setBounds: setBounds,
|
||||
/**
|
||||
* Increments the width of the autoflow column.
|
||||
* Setting does not yet persist.
|
||||
*/
|
||||
increaseColumnWidth: increaseColumnWidth,
|
||||
/**
|
||||
* Get-or-set the user-supplied filter value.
|
||||
* @param {string} [value] the new filter value; omit to use
|
||||
* as a getter
|
||||
* @returns {string} the user-supplied filter value
|
||||
*/
|
||||
filter: filter,
|
||||
/**
|
||||
* Get all range values for use in this table. These will be
|
||||
* returned as an object of key-value pairs, where keys are
|
||||
* domain object IDs, and values are the most recently observed
|
||||
* data values associated with those objects, formatted for
|
||||
* display.
|
||||
* @returns {object.<string,string>} most recent values
|
||||
*/
|
||||
rangeValues: function () {
|
||||
return rangeValues;
|
||||
},
|
||||
/**
|
||||
* Get CSS classes to apply to specific rows, representing limit
|
||||
* states and/or stale states. These are returned as key-value
|
||||
* pairs where keys are domain object IDs, and values are CSS
|
||||
* classes to display for domain objects with those IDs.
|
||||
* @returns {object.<string,string>} CSS classes
|
||||
*/
|
||||
classes: function () {
|
||||
return classes;
|
||||
},
|
||||
/**
|
||||
* Get the "last updated" text for this view; this will be
|
||||
* the most recent timestamp observed for any telemetry-
|
||||
* providing object, formatted for display.
|
||||
* @returns {string} the time of the most recent update
|
||||
*/
|
||||
updated: updated,
|
||||
/**
|
||||
* Get the current column width, in pixels.
|
||||
* @returns {number} column width
|
||||
*/
|
||||
columnWidth: function () {
|
||||
return columnWidth;
|
||||
},
|
||||
/**
|
||||
* Keep a counter and increment this whenever the display
|
||||
* should be updated; this will be watched by the
|
||||
* `mct-autoflow-table`.
|
||||
* @returns {number} a counter value
|
||||
*/
|
||||
counter: function () {
|
||||
return counter;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return AutflowTabularController;
|
||||
}
|
||||
);
|
||||
@@ -1,60 +0,0 @@
|
||||
|
||||
define(
|
||||
["./AutoflowTableLinker"],
|
||||
function (AutoflowTableLinker) {
|
||||
|
||||
/**
|
||||
* The `mct-autoflow-table` directive specifically supports
|
||||
* autoflow tabular views; it is not intended for use outside
|
||||
* of that view.
|
||||
*
|
||||
* This directive is responsible for creating the structure
|
||||
* of the table in this view, and for updating its values.
|
||||
* While this is achievable using a regular Angular template,
|
||||
* this is undesirable from the perspective of performance
|
||||
* due to the number of watches that can be involved for large
|
||||
* tables. Instead, this directive will maintain a small number
|
||||
* of watches, rebuilding table structure only when necessary,
|
||||
* and updating displayed values in the more common case of
|
||||
* new data arriving.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MCTAutoflowTable() {
|
||||
return {
|
||||
// Only applicable at the element level
|
||||
restrict: "E",
|
||||
|
||||
// The link function; handles DOM update/manipulation
|
||||
link: AutoflowTableLinker,
|
||||
|
||||
// Parameters to pass from attributes into scope
|
||||
scope: {
|
||||
// Set of domain objects to show in the table
|
||||
objects: "=",
|
||||
|
||||
// Values for those objects, by ID
|
||||
values: "=",
|
||||
|
||||
// CSS classes to show for objects, by ID
|
||||
classes: "=",
|
||||
|
||||
// Number of rows to show before autoflowing
|
||||
rows: "=",
|
||||
|
||||
// Time of last update; watched to refresh values
|
||||
updated: "=",
|
||||
|
||||
// Current width of the autoflow column
|
||||
columnWidth: "=",
|
||||
|
||||
// A counter used to trigger display updates
|
||||
counter: "="
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTAutoflowTable;
|
||||
|
||||
}
|
||||
);
|
||||
@@ -1,178 +0,0 @@
|
||||
|
||||
define(
|
||||
["../src/AutoflowTableLinker"],
|
||||
function (AutoflowTableLinker) {
|
||||
|
||||
describe("The mct-autoflow-table linker", function () {
|
||||
var cachedAngular,
|
||||
mockAngular,
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockElements,
|
||||
linker;
|
||||
|
||||
// Utility function to generate more mock elements
|
||||
function createMockElement(html) {
|
||||
var mockEl = jasmine.createSpyObj(
|
||||
"element-" + html,
|
||||
[
|
||||
"append",
|
||||
"addClass",
|
||||
"removeClass",
|
||||
"text",
|
||||
"attr",
|
||||
"html",
|
||||
"css",
|
||||
"find"
|
||||
]
|
||||
);
|
||||
mockEl.testHtml = html;
|
||||
mockEl.append.andReturn(mockEl);
|
||||
mockElements.push(mockEl);
|
||||
return mockEl;
|
||||
}
|
||||
|
||||
function createMockDomainObject(id) {
|
||||
var mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject-" + id,
|
||||
["getId", "getModel"]
|
||||
);
|
||||
mockDomainObject.getId.andReturn(id);
|
||||
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
|
||||
return mockDomainObject;
|
||||
}
|
||||
|
||||
function fireWatch(watchExpression, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === watchExpression) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// AutoflowTableLinker accesses Angular in the global
|
||||
// scope, since it is not injectable; we simulate that
|
||||
// here by adding/removing it to/from the window object.
|
||||
beforeEach(function () {
|
||||
mockElements = [];
|
||||
|
||||
mockAngular = jasmine.createSpyObj("angular", ["element"]);
|
||||
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
|
||||
mockElement = createMockElement('<div>');
|
||||
|
||||
mockAngular.element.andCallFake(createMockElement);
|
||||
|
||||
if (window.angular !== undefined) {
|
||||
cachedAngular = window.angular;
|
||||
}
|
||||
window.angular = mockAngular;
|
||||
|
||||
linker = new AutoflowTableLinker(mockScope, mockElement);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (cachedAngular !== undefined) {
|
||||
window.angular = cachedAngular;
|
||||
} else {
|
||||
delete window.angular;
|
||||
}
|
||||
});
|
||||
|
||||
it("watches for changes in inputs", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"objects",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"rows",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"counter",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes structure when domain objects change", function () {
|
||||
// Set up scope
|
||||
mockScope.rows = 4;
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
|
||||
// Fire an update to the set of objects
|
||||
fireWatch("objects");
|
||||
|
||||
// Should have rebuilt with two columns of
|
||||
// four and two rows each; first, by clearing...
|
||||
expect(mockElement.html).toHaveBeenCalledWith("");
|
||||
|
||||
// Should have appended two columns...
|
||||
expect(mockElement.append.calls.length).toEqual(2);
|
||||
|
||||
// ...which should have received two and four rows each
|
||||
expect(mockElement.append.calls[0].args[0].append.calls.length)
|
||||
.toEqual(4);
|
||||
expect(mockElement.append.calls[1].args[0].append.calls.length)
|
||||
.toEqual(2);
|
||||
});
|
||||
|
||||
it("updates values", function () {
|
||||
var mockSpans;
|
||||
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
mockScope.values = { a: 0 };
|
||||
|
||||
// Fire an update to the set of values
|
||||
fireWatch("objects");
|
||||
fireWatch("updated");
|
||||
|
||||
// Get all created spans
|
||||
mockSpans = mockElements.filter(function (mockElem) {
|
||||
return mockElem.testHtml === '<span>';
|
||||
});
|
||||
|
||||
// First span should be a, should have gotten this value.
|
||||
// This test detects, in particular, WTD-749
|
||||
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
|
||||
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("listens for changes in column width", function () {
|
||||
var mockUL = createMockElement("<ul>");
|
||||
mockElement.find.andReturn(mockUL);
|
||||
mockScope.columnWidth = 200;
|
||||
fireWatch("columnWidth", mockScope.columnWidth);
|
||||
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
|
||||
});
|
||||
|
||||
it("updates CSS classes", function () {
|
||||
var mockSpans;
|
||||
|
||||
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
.map(createMockDomainObject);
|
||||
mockScope.values = { a: "a value to find" };
|
||||
mockScope.classes = { a: 'class-a' };
|
||||
|
||||
// Fire an update to the set of values
|
||||
fireWatch("objects");
|
||||
fireWatch("updated");
|
||||
|
||||
// Figure out which span holds the relevant value...
|
||||
mockSpans = mockElements.filter(function (mockElem) {
|
||||
return mockElem.testHtml === '<span>';
|
||||
}).filter(function (mockSpan) {
|
||||
var attrCalls = mockSpan.attr.calls;
|
||||
return attrCalls.some(function (call) {
|
||||
return call.args[0] === 'title' &&
|
||||
call.args[1] === mockScope.values.a;
|
||||
});
|
||||
});
|
||||
|
||||
// ...and make sure it also has had its class applied
|
||||
expect(mockSpans[0].addClass)
|
||||
.toHaveBeenCalledWith(mockScope.classes.a);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,341 +0,0 @@
|
||||
|
||||
define(
|
||||
["../src/AutoflowTabularController"],
|
||||
function (AutoflowTabularController) {
|
||||
|
||||
describe("The autoflow tabular controller", function () {
|
||||
var mockScope,
|
||||
mockTimeout,
|
||||
mockSubscriber,
|
||||
mockDomainObject,
|
||||
mockSubscription,
|
||||
controller;
|
||||
|
||||
// Fire watches that are registered as functions.
|
||||
function fireFnWatches() {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (typeof call.args[0] === 'function') {
|
||||
call.args[1](call.args[0]());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$on", "$watch"]
|
||||
);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockSubscriber = jasmine.createSpyObj(
|
||||
"telemetrySubscriber",
|
||||
["subscribe"]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel", "getCapability"]
|
||||
);
|
||||
mockSubscription = jasmine.createSpyObj(
|
||||
"subscription",
|
||||
[
|
||||
"unsubscribe",
|
||||
"getTelemetryObjects",
|
||||
"getDomainValue",
|
||||
"getRangeValue"
|
||||
]
|
||||
);
|
||||
|
||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||
mockDomainObject.getModel.andReturn({name: "something"});
|
||||
|
||||
controller = new AutoflowTabularController(
|
||||
mockScope,
|
||||
mockTimeout,
|
||||
mockSubscriber
|
||||
);
|
||||
});
|
||||
|
||||
it("listens for the represented domain object", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"domainObject",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("provides a getter-setter function for filtering", function () {
|
||||
expect(controller.filter()).toEqual("");
|
||||
controller.filter("something");
|
||||
expect(controller.filter()).toEqual("something");
|
||||
});
|
||||
|
||||
it("tracks bounds and adjust number of rows accordingly", function () {
|
||||
// Rows are 15px high, and need room for an 10px slider
|
||||
controller.setBounds({ width: 700, height: 120 });
|
||||
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
|
||||
controller.setBounds({ width: 700, height: 240 });
|
||||
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
|
||||
});
|
||||
|
||||
it("subscribes to a represented object's telemetry", function () {
|
||||
// Set up subscription, scope
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Invoke the watcher with represented domain object
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Should have subscribed to it
|
||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Should report objects as reported from subscription
|
||||
expect(controller.getTelemetryObjects())
|
||||
.toEqual([mockDomainObject]);
|
||||
});
|
||||
|
||||
it("releases subscriptions on destroy", function () {
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Verify precondition
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
|
||||
// Make sure we're listening for $destroy
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Fire a destroy event
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
|
||||
// Should have unsubscribed
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("presents latest values and latest update state", function () {
|
||||
// Make sure values are available
|
||||
mockSubscription.getDomainValue.andReturn(402654321123);
|
||||
mockSubscription.getRangeValue.andReturn(789);
|
||||
mockDomainObject.getId.andReturn('testId');
|
||||
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Fire subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
// ...and exposed the results for template to consume
|
||||
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
|
||||
expect(controller.rangeValues().testId).toEqual(789);
|
||||
});
|
||||
|
||||
it("sorts domain objects by index", function () {
|
||||
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
|
||||
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
|
||||
var mockDomainObj = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel"]
|
||||
);
|
||||
|
||||
mockDomainObj.getId.andReturn(id);
|
||||
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
|
||||
|
||||
return mockDomainObj;
|
||||
});
|
||||
|
||||
// Expose those domain objects...
|
||||
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
// Fire subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
// Controller should expose same objects, but sorted by index from model
|
||||
expect(controller.getTelemetryObjects()).toEqual([
|
||||
mockDomainObjects[3], // d, index=0
|
||||
mockDomainObjects[1], // b, index=1
|
||||
mockDomainObjects[0], // a, index=2
|
||||
mockDomainObjects[2] // c, index=3
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses a timeout to throttle update", function () {
|
||||
// Set up subscription...
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
// Set the object in view; should not need a timeout
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(0);
|
||||
|
||||
// Next call should schedule an update on a timeout
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
|
||||
// ...but this last one should not, since existing
|
||||
// timeout will cover it
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("allows changing column width", function () {
|
||||
var initialWidth = controller.columnWidth();
|
||||
controller.increaseColumnWidth();
|
||||
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
|
||||
});
|
||||
|
||||
describe("filter", function () {
|
||||
var doFilter,
|
||||
filteredObjects,
|
||||
filteredObjectNames;
|
||||
|
||||
beforeEach(function () {
|
||||
var telemetryObjects,
|
||||
updateFilteredObjects;
|
||||
|
||||
telemetryObjects = [
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
].map(function (objectName, index) {
|
||||
var mockTelemetryObject = jasmine.createSpyObj(
|
||||
objectName,
|
||||
["getId", "getModel"]
|
||||
);
|
||||
|
||||
mockTelemetryObject.getId.andReturn(objectName);
|
||||
mockTelemetryObject.getModel.andReturn({
|
||||
name: objectName,
|
||||
index: index
|
||||
});
|
||||
|
||||
return mockTelemetryObject;
|
||||
});
|
||||
|
||||
mockSubscription
|
||||
.getTelemetryObjects
|
||||
.andReturn(telemetryObjects);
|
||||
|
||||
// Trigger domainObject change to create subscription.
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
|
||||
updateFilteredObjects = function () {
|
||||
filteredObjects = controller.getTelemetryObjects();
|
||||
filteredObjectNames = filteredObjects.map(function (o) {
|
||||
return o.getModel().name;
|
||||
});
|
||||
};
|
||||
|
||||
doFilter = function (term) {
|
||||
controller.filter(term);
|
||||
// Filter is debounced so we have to force it to occur.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
updateFilteredObjects();
|
||||
};
|
||||
|
||||
updateFilteredObjects();
|
||||
});
|
||||
|
||||
it("initially shows all objects", function () {
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
]);
|
||||
});
|
||||
|
||||
it("by blank string matches all objects", function () {
|
||||
doFilter('');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'abc789',
|
||||
'456abc',
|
||||
'4ab3cdef',
|
||||
'hjs[12].*(){}^\\'
|
||||
]);
|
||||
});
|
||||
|
||||
it("exactly matches an object name", function () {
|
||||
doFilter('4ab3cdef');
|
||||
expect(filteredObjectNames).toEqual(['4ab3cdef']);
|
||||
});
|
||||
|
||||
it("partially matches object names", function () {
|
||||
doFilter('abc');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'abc789',
|
||||
'456abc'
|
||||
]);
|
||||
});
|
||||
|
||||
it("matches case insensitive names", function () {
|
||||
doFilter('def');
|
||||
expect(filteredObjectNames).toEqual([
|
||||
'DEF123',
|
||||
'4ab3cdef'
|
||||
]);
|
||||
});
|
||||
|
||||
it("works as expected with special characters", function () {
|
||||
doFilter('[12]');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*()');
|
||||
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||
doFilter('.*?');
|
||||
expect(filteredObjectNames).toEqual([]);
|
||||
doFilter('.+');
|
||||
expect(filteredObjectNames).toEqual([]);
|
||||
});
|
||||
|
||||
it("exposes CSS classes from limits", function () {
|
||||
var id = mockDomainObject.getId(),
|
||||
testClass = "some-css-class",
|
||||
mockLimitCapability =
|
||||
jasmine.createSpyObj('limit', ['evaluate']);
|
||||
|
||||
mockDomainObject.getCapability.andCallFake(function (key) {
|
||||
return key === 'limit' && mockLimitCapability;
|
||||
});
|
||||
mockLimitCapability.evaluate
|
||||
.andReturn({ cssClass: testClass });
|
||||
|
||||
mockSubscription.getTelemetryObjects
|
||||
.andReturn([mockDomainObject]);
|
||||
|
||||
fireFnWatches();
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
|
||||
expect(controller.classes()[id]).toEqual(testClass);
|
||||
});
|
||||
|
||||
it("exposes a counter that changes with each update", function () {
|
||||
var i, prior;
|
||||
|
||||
for (i = 0; i < 10; i += 1) {
|
||||
prior = controller.counter();
|
||||
expect(controller.counter()).toEqual(prior);
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
expect(controller.counter()).not.toEqual(prior);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
define(
|
||||
["../src/MCTAutoflowTable"],
|
||||
function (MCTAutoflowTable) {
|
||||
|
||||
describe("The mct-autoflow-table directive", function () {
|
||||
var mctAutoflowTable;
|
||||
|
||||
beforeEach(function () {
|
||||
mctAutoflowTable = new MCTAutoflowTable();
|
||||
});
|
||||
|
||||
// Real functionality is contained/tested in the linker,
|
||||
// so just check to make sure we're exposing the directive
|
||||
// appropriately.
|
||||
it("is applicable at the element level", function () {
|
||||
expect(mctAutoflowTable.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
it("two-ways binds needed scope variables", function () {
|
||||
expect(mctAutoflowTable.scope).toEqual({
|
||||
objects: "=",
|
||||
values: "=",
|
||||
rows: "=",
|
||||
updated: "=",
|
||||
classes: "=",
|
||||
columnWidth: "=",
|
||||
counter: "="
|
||||
});
|
||||
});
|
||||
|
||||
it("provides a link function", function () {
|
||||
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -45,7 +45,7 @@ define(
|
||||
|
||||
FollowIndicator.prototype.getText = function () {
|
||||
var timer = this.timerService.getTimer();
|
||||
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
|
||||
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
|
||||
};
|
||||
|
||||
FollowIndicator.prototype.getDescription = function () {
|
||||
|
||||
@@ -42,18 +42,15 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
|
||||
});
|
||||
|
||||
describe("when a timer is set", function () {
|
||||
var testModel;
|
||||
var mockDomainObject;
|
||||
var testObject;
|
||||
|
||||
beforeEach(function () {
|
||||
testModel = { name: "some timer!" };
|
||||
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
|
||||
mockDomainObject.getModel.andReturn(testModel);
|
||||
mockTimerService.getTimer.andReturn(mockDomainObject);
|
||||
testObject = { name: "some timer!" };
|
||||
mockTimerService.getTimer.andReturn(testObject);
|
||||
});
|
||||
|
||||
it("displays the timer's name", function () {
|
||||
expect(indicator.getText().indexOf(testModel.name))
|
||||
expect(indicator.getText().indexOf(testObject.name))
|
||||
.not.toEqual(-1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -260,7 +260,9 @@ define([
|
||||
"key": "LayoutController",
|
||||
"implementation": LayoutController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
"$scope",
|
||||
"$element",
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
's-selected': controller.selected(element)
|
||||
}"
|
||||
ng-style="element.style"
|
||||
ng-click="controller.select(element)">
|
||||
ng-click="controller.select(element, $event)">
|
||||
<mct-include key="element.template"
|
||||
parameters="{ gridSize: controller.getGridSize() }"
|
||||
ng-model="element">
|
||||
@@ -53,14 +53,16 @@
|
||||
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
|
||||
mct-drag="controller.moveHandle().continueDrag(delta)"
|
||||
mct-drag-up="controller.moveHandle().endDrag()"
|
||||
ng-style="controller.selected().style">
|
||||
ng-style="controller.selected().style"
|
||||
ng-click="$event.stopPropagation()">
|
||||
</div>
|
||||
<div ng-repeat="handle in controller.handles()"
|
||||
class="l-fixed-position-item-handle edit-corner"
|
||||
ng-style="handle.style()"
|
||||
mct-drag-down="handle.startDrag()"
|
||||
mct-drag="handle.continueDrag(delta)"
|
||||
mct-drag-up="handle.endDrag()">
|
||||
mct-drag-up="handle.endDrag()"
|
||||
ng-click="$event.stopPropagation()">
|
||||
</div>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
<div class="abs l-layout"
|
||||
ng-controller="LayoutController as controller"
|
||||
ng-click="controller.clearSelection()">
|
||||
ng-click="controller.bypassSelection($event)">
|
||||
|
||||
<!-- Background grid -->
|
||||
<div class="l-grid-holder" ng-click="controller.clearSelection()">
|
||||
<div class="l-grid-holder"
|
||||
ng-show="!controller.drilledIn"
|
||||
ng-click="controller.bypassSelection($event)">
|
||||
<div class="l-grid l-grid-x"
|
||||
ng-if="!controller.getGridSize()[0] < 3"
|
||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||
@@ -34,10 +36,12 @@
|
||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
||||
</div>
|
||||
|
||||
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
|
||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
|
||||
<div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border {{childObject.getId() + '-' + $id}} t-object-type-{{ childObject.getModel().type }}"
|
||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
||||
ng-repeat="childObject in composition"
|
||||
ng-click="controller.select($event, childObject.getId())"
|
||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
||||
mct-selectable="controller.getContext(childObject, true)"
|
||||
ng-dblclick="controller.drill($event, childObject)"
|
||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||
|
||||
<mct-representation key="'frame'"
|
||||
@@ -45,7 +49,7 @@
|
||||
mct-object="childObject">
|
||||
</mct-representation>
|
||||
<!-- Drag handles -->
|
||||
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
|
||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
||||
<span class="edit-handle edit-move"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
@@ -73,7 +77,6 @@
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -506,7 +506,11 @@ define(
|
||||
* Set the active user selection in this view.
|
||||
* @param element the element to select
|
||||
*/
|
||||
FixedController.prototype.select = function select(element) {
|
||||
FixedController.prototype.select = function select(element, event) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (this.selection) {
|
||||
// Update selection...
|
||||
this.selection.select(element);
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'zepto',
|
||||
'./LayoutDrag'
|
||||
],
|
||||
function (
|
||||
$,
|
||||
LayoutDrag
|
||||
) {
|
||||
|
||||
@@ -50,10 +52,12 @@ define(
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function LayoutController($scope) {
|
||||
function LayoutController($scope, $element, openmct) {
|
||||
var self = this,
|
||||
callbackCount = 0;
|
||||
|
||||
this.$element = $element;
|
||||
|
||||
// Update grid size when it changed
|
||||
function updateGridSize(layoutGrid) {
|
||||
var oldSize = self.gridSize;
|
||||
@@ -123,12 +127,11 @@ define(
|
||||
self.layoutPanels(ids);
|
||||
self.setFrames(ids);
|
||||
|
||||
// If there is a newly-dropped object, select it.
|
||||
if (self.droppedIdToSelectAfterRefresh) {
|
||||
self.select(null, self.droppedIdToSelectAfterRefresh);
|
||||
delete self.droppedIdToSelectAfterRefresh;
|
||||
} else if (composition.indexOf(self.selectedId) === -1) {
|
||||
self.clearSelection();
|
||||
if (self.selectedId &&
|
||||
self.selectedId !== $scope.domainObject.getId() &&
|
||||
composition.indexOf(self.selectedId) === -1) {
|
||||
// Click triggers selection of layout parent.
|
||||
self.$element[0].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -160,22 +163,39 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
// Sets the selectable object in response to the selection change event.
|
||||
function setSelection(selectable) {
|
||||
var selection = selectable[0];
|
||||
|
||||
if (!selection) {
|
||||
delete self.selectedId;
|
||||
return;
|
||||
}
|
||||
|
||||
self.selectedId = selection.context.oldItem.getId();
|
||||
self.drilledIn = undefined;
|
||||
self.selectable = selectable;
|
||||
}
|
||||
|
||||
this.positions = {};
|
||||
this.rawPositions = {};
|
||||
this.gridSize = DEFAULT_GRID_SIZE;
|
||||
this.$scope = $scope;
|
||||
this.drilledIn = undefined;
|
||||
this.openmct = openmct;
|
||||
|
||||
// Watch for changes to the grid size in the model
|
||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||
|
||||
$scope.$watch("selection", function (selection) {
|
||||
this.selection = selection;
|
||||
}.bind(this));
|
||||
|
||||
// Update composed objects on screen, and position panes
|
||||
$scope.$watchCollection("model.composition", refreshComposition);
|
||||
|
||||
// Position panes where they are dropped
|
||||
openmct.selection.on('change', setSelection);
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
openmct.selection.off("change", setSelection);
|
||||
});
|
||||
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
}
|
||||
|
||||
@@ -357,37 +377,14 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the object is currently selected.
|
||||
* Checks if the object is currently selected.
|
||||
*
|
||||
* @param {string} obj the object to check for selection
|
||||
* @returns {boolean} true if selected, otherwise false
|
||||
*/
|
||||
LayoutController.prototype.selected = function (obj) {
|
||||
return !!this.selectedId && this.selectedId === obj.getId();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the active user selection in this view.
|
||||
*
|
||||
* @param event the mouse event
|
||||
* @param {string} id the object id
|
||||
*/
|
||||
LayoutController.prototype.select = function (event, id) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
if (this.selection) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedId = id;
|
||||
|
||||
var selectedObj = {};
|
||||
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
|
||||
|
||||
if (this.selection) {
|
||||
this.selection.select(selectedObj);
|
||||
}
|
||||
var sobj = this.openmct.selection.get()[0];
|
||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -396,7 +393,7 @@ define(
|
||||
* @param {string} id the object id
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.toggleFrame = function (id) {
|
||||
LayoutController.prototype.toggleFrame = function (id, domainObject) {
|
||||
var configuration = this.$scope.configuration;
|
||||
|
||||
if (!configuration.panels[id]) {
|
||||
@@ -404,21 +401,75 @@ define(
|
||||
}
|
||||
|
||||
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
||||
this.select(undefined, id); // reselect so toolbar updates
|
||||
|
||||
var selection = this.openmct.selection.get();
|
||||
selection[0].context.toolbar = this.getToolbar(id, domainObject);
|
||||
this.openmct.selection.select(selection); // reselect so toolbar updates
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the current user selection.
|
||||
* Gets the toolbar object for the given domain object.
|
||||
*
|
||||
* @param id the domain object id
|
||||
* @param domainObject the domain object
|
||||
* @returns {object}
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.clearSelection = function () {
|
||||
LayoutController.prototype.getToolbar = function (id, domainObject) {
|
||||
var toolbarObj = {};
|
||||
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
|
||||
return toolbarObj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bypasses selection if drag is in progress.
|
||||
*
|
||||
* @param event the angular event object
|
||||
*/
|
||||
LayoutController.prototype.bypassSelection = function (event) {
|
||||
if (this.dragInProgress) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the domain object is drilled in.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @return true if the object is drilled in, false otherwise
|
||||
*/
|
||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
||||
return this.drilledIn === domainObject.getId();
|
||||
};
|
||||
|
||||
/**
|
||||
* Puts the given object in the drilled-in mode.
|
||||
*
|
||||
* @param event the angular event object
|
||||
* @param domainObject the domain object
|
||||
*/
|
||||
LayoutController.prototype.drill = function (event, domainObject) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selection) {
|
||||
this.selection.deselect();
|
||||
delete this.selectedId;
|
||||
if (!domainObject.hasCapability('composition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable since fixed position doesn't use the selection API yet
|
||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.drilledIn = domainObject.getId();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -440,6 +491,36 @@ define(
|
||||
return this.gridSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the selection context.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @returns {object} the context object which includes
|
||||
* item, oldItem and toolbar
|
||||
*/
|
||||
LayoutController.prototype.getContext = function (domainObject, toolbar) {
|
||||
return {
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject,
|
||||
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects a newly-dropped object.
|
||||
*
|
||||
* @param classSelector the css class selector
|
||||
* @param domainObject the domain object
|
||||
*/
|
||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
||||
setTimeout(function () {
|
||||
$('.' + selector)[0].click();
|
||||
delete this.droppedIdToSelectAfterRefresh;
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
return LayoutController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -21,8 +21,14 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../src/LayoutController"],
|
||||
function (LayoutController) {
|
||||
[
|
||||
"../src/LayoutController",
|
||||
"zepto"
|
||||
],
|
||||
function (
|
||||
LayoutController,
|
||||
$
|
||||
) {
|
||||
|
||||
describe("The Layout controller", function () {
|
||||
var mockScope,
|
||||
@@ -32,7 +38,12 @@ define(
|
||||
controller,
|
||||
mockCompositionCapability,
|
||||
mockComposition,
|
||||
mockCompositionObjects;
|
||||
mockCompositionObjects,
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockDomainObjectCapability,
|
||||
$element = [],
|
||||
selectable = [];
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
@@ -58,21 +69,18 @@ define(
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getCapability: function () {
|
||||
return mockDomainObjectCapability;
|
||||
},
|
||||
hasCapability: function (param) {
|
||||
if (param === 'composition') {
|
||||
return id !== 'b';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Utility function to find a watch for a given expression
|
||||
function findWatch(expr) {
|
||||
var watch;
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
watch = call.args[1];
|
||||
}
|
||||
});
|
||||
return watch;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
@@ -88,7 +96,6 @@ define(
|
||||
mockComposition = ["a", "b", "c"];
|
||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
||||
|
||||
|
||||
testConfiguration = {
|
||||
panels: {
|
||||
a: {
|
||||
@@ -97,27 +104,70 @@ define(
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
||||
['inEditContext']
|
||||
);
|
||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||
|
||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
||||
mockScope.model = testModel;
|
||||
mockScope.configuration = testConfiguration;
|
||||
mockScope.selection = jasmine.createSpyObj(
|
||||
'selection',
|
||||
['select', 'get', 'selected', 'deselect']
|
||||
);
|
||||
|
||||
selectable[0] = {
|
||||
context: {
|
||||
oldItem: mockScope.domainObject
|
||||
}
|
||||
};
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'select',
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn(selectable);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
|
||||
$element = $('<div></div>');
|
||||
$(document).find('body').append($element);
|
||||
spyOn($element[0], 'click');
|
||||
|
||||
spyOn(mockScope.domainObject, "useCapability").andCallThrough();
|
||||
|
||||
controller = new LayoutController(mockScope);
|
||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
||||
spyOn(controller, "layoutPanels").andCallThrough();
|
||||
|
||||
findWatch("selection")(mockScope.selection);
|
||||
|
||||
jasmine.Clock.useMock();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
$element.remove();
|
||||
});
|
||||
|
||||
|
||||
it("listens for selection change events", function () {
|
||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("cleans up on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockScope.$on.calls[0].args[1]();
|
||||
|
||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
// Model changes will indicate that panel positions
|
||||
// may have changed, for instance.
|
||||
it("watches for changes to composition", function () {
|
||||
@@ -320,67 +370,35 @@ define(
|
||||
.not.toEqual(oldStyle);
|
||||
});
|
||||
|
||||
it("allows panels to be selected", function () {
|
||||
it("allows objects to be selected", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
expect(controller.selected(childObj)).toBe(true);
|
||||
});
|
||||
|
||||
it("prevents event bubbling while drag is in progress", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
|
||||
controller.select(mockEvent, childObj.getId());
|
||||
// Do a drag
|
||||
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
// Because mouse position could cause the parent object to be selected, this should be ignored.
|
||||
controller.bypassSelection(mockEvent);
|
||||
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
||||
|
||||
expect(controller.selected(childObj)).toBe(true);
|
||||
});
|
||||
|
||||
it("allows selection to be cleared", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
|
||||
controller.select(null, childObj.getId());
|
||||
controller.clearSelection();
|
||||
|
||||
expect(controller.selected(childObj)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("prevents clearing selection while drag is in progress", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
var id = childObj.getId();
|
||||
|
||||
controller.select(mockEvent, id);
|
||||
|
||||
// Do a drag
|
||||
controller.startDrag(id, [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
// Because mouse position could cause clearSelection to be called, this should be ignored.
|
||||
controller.clearSelection();
|
||||
|
||||
expect(controller.selected(childObj)).toBe(true);
|
||||
|
||||
// Shoud be able to clear the selection after dragging is done.
|
||||
// Shoud be able to select another object when dragging is done.
|
||||
jasmine.Clock.tick(0);
|
||||
controller.clearSelection();
|
||||
mockEvent.stopPropagation.reset();
|
||||
controller.bypassSelection(mockEvent);
|
||||
|
||||
expect(controller.selected(childObj)).toBe(false);
|
||||
});
|
||||
|
||||
it("clears selection after moving/resizing", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
var id = childObj.getId();
|
||||
|
||||
controller.select(mockEvent, id);
|
||||
|
||||
// Do a drag
|
||||
controller.startDrag(id, [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
jasmine.Clock.tick(0);
|
||||
controller.clearSelection();
|
||||
|
||||
expect(controller.selected(childObj)).toBe(false);
|
||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows frames by default", function () {
|
||||
@@ -398,43 +416,74 @@ define(
|
||||
it("hides frame when selected object has frame ", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
controller.select(mockEvent, childObj.getId());
|
||||
|
||||
expect(mockScope.selection.select).toHaveBeenCalled();
|
||||
|
||||
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
||||
|
||||
expect(controller.hasFrame(childObj)).toBe(true);
|
||||
expect(selectedObj.hideFrame).toBeDefined();
|
||||
expect(selectedObj.hideFrame).toEqual(jasmine.any(Function));
|
||||
expect(toolbarObj.hideFrame).toBeDefined();
|
||||
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("shows frame when selected object has no frame", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
|
||||
var childObj = mockCompositionObjects[1];
|
||||
controller.select(mockEvent, childObj.getId());
|
||||
|
||||
expect(mockScope.selection.select).toHaveBeenCalled();
|
||||
|
||||
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
||||
|
||||
expect(controller.hasFrame(childObj)).toBe(false);
|
||||
expect(selectedObj.showFrame).toBeDefined();
|
||||
expect(selectedObj.showFrame).toEqual(jasmine.any(Function));
|
||||
expect(toolbarObj.showFrame).toBeDefined();
|
||||
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("deselects the object that is no longer in the composition", function () {
|
||||
it("selects the parent object when selected object is removed", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
controller.select(mockEvent, childObj.getId());
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
var composition = ["b", "c"];
|
||||
mockScope.$watchCollection.mostRecentCall.args[1](composition);
|
||||
|
||||
expect(controller.selected(childObj)).toBe(false);
|
||||
expect($element[0].click).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows objects to be drilled-in only when editing", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
childObj.getCapability().inEditContext.andReturn(false);
|
||||
controller.drill(mockEvent, childObj);
|
||||
|
||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
||||
});
|
||||
|
||||
it("allows objects to be drilled-in only if it has sub objects", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[1];
|
||||
childObj.getCapability().inEditContext.andReturn(true);
|
||||
controller.drill(mockEvent, childObj);
|
||||
|
||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
||||
});
|
||||
|
||||
it("selects a newly-dropped object", function () {
|
||||
mockScope.$on.mostRecentCall.args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
|
||||
var childObj = mockDomainObject("d");
|
||||
var testElement = $("<div class='some-class'></div>");
|
||||
$element.append(testElement);
|
||||
spyOn(testElement[0], 'click');
|
||||
|
||||
controller.selectIfNew('some-class', childObj);
|
||||
jasmine.Clock.tick(0);
|
||||
|
||||
expect(testElement[0].click).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -69,7 +69,7 @@ define([
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
],
|
||||
"inspector": tableInspector,
|
||||
"inspector": "table-options-edit",
|
||||
"contains": [
|
||||
{
|
||||
"has": "telemetry"
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="TableOptionsController" class="l-controls-first flex-elem grows l-inspector-part">
|
||||
|
||||
<div ng-if="domainObject.getCapability('editor').inEditContext()"
|
||||
ng-controller="TableOptionsController"
|
||||
class="l-controls-first flex-elem grows l-inspector-part">
|
||||
<em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
|
||||
<mct-form
|
||||
ng-model="configuration.table.columns"
|
||||
|
||||
@@ -32,6 +32,7 @@ define(
|
||||
*/
|
||||
function TelemetryCollection() {
|
||||
EventEmitter.call(this, arguments);
|
||||
this.dupeCheck = false;
|
||||
this.telemetry = [];
|
||||
this.highBuffer = [];
|
||||
this.sortField = undefined;
|
||||
@@ -161,7 +162,7 @@ define(
|
||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||
var endIx;
|
||||
|
||||
if (startIx !== array.length) {
|
||||
if (this.dupeCheck && startIx !== array.length) {
|
||||
endIx = _.sortedLastIndex(array, item, this.sortField);
|
||||
|
||||
// Create an array of potential dupes, based on having the
|
||||
@@ -189,6 +190,7 @@ define(
|
||||
TelemetryCollection.prototype.add = function (items) {
|
||||
var added = items.filter(this.addOne);
|
||||
this.emit('added', added);
|
||||
this.dupeCheck = true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -436,9 +436,31 @@ define(
|
||||
* @param {Object} searchElement Object to find the insertion point for
|
||||
*/
|
||||
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
||||
//First, use a binary search to find the correct insertion point
|
||||
var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1);
|
||||
var testIndex = index;
|
||||
var index;
|
||||
var testIndex;
|
||||
var first = searchArray[0];
|
||||
var last = searchArray[searchArray.length - 1];
|
||||
|
||||
if (first) {
|
||||
first = first[this.$scope.sortColumn].text;
|
||||
}
|
||||
if (last) {
|
||||
last = last[this.$scope.sortColumn].text;
|
||||
}
|
||||
// Shortcut check for append/prepend
|
||||
if (first && this.sortComparator(first, searchElement) >= 0) {
|
||||
index = testIndex = 0;
|
||||
} else if (last && this.sortComparator(last, searchElement) <= 0) {
|
||||
index = testIndex = searchArray.length;
|
||||
} else {
|
||||
// use a binary search to find the correct insertion point
|
||||
index = testIndex = this.binarySearch(
|
||||
searchArray,
|
||||
searchElement,
|
||||
0,
|
||||
searchArray.length - 1
|
||||
);
|
||||
}
|
||||
|
||||
//It's possible that the insertion point is a duplicate of the element to be inserted
|
||||
var isDupe = function () {
|
||||
|
||||
@@ -32,6 +32,7 @@ define([
|
||||
"./src/controllers/TimelineTOIController",
|
||||
"./src/controllers/ActivityModeValuesController",
|
||||
"./src/capabilities/ActivityTimespanCapability",
|
||||
"./src/capabilities/ActivityValueCapability",
|
||||
"./src/capabilities/TimelineTimespanCapability",
|
||||
"./src/capabilities/UtilizationCapability",
|
||||
"./src/capabilities/GraphCapability",
|
||||
@@ -42,6 +43,7 @@ define([
|
||||
"./src/services/ObjectLoader",
|
||||
"./src/chart/MCTTimelineChart",
|
||||
"text!./res/templates/values.html",
|
||||
"text!./res/templates/activity-view.html",
|
||||
"text!./res/templates/timeline.html",
|
||||
"text!./res/templates/activity-gantt.html",
|
||||
"text!./res/templates/tabular-swimlane-cols-tree.html",
|
||||
@@ -64,6 +66,7 @@ define([
|
||||
TimelineTOIController,
|
||||
ActivityModeValuesController,
|
||||
ActivityTimespanCapability,
|
||||
ActivityValueCapability,
|
||||
TimelineTimespanCapability,
|
||||
UtilizationCapability,
|
||||
GraphCapability,
|
||||
@@ -74,6 +77,7 @@ define([
|
||||
ObjectLoader,
|
||||
MCTTimelineChart,
|
||||
valuesTemplate,
|
||||
activityTemplate,
|
||||
timelineTemplate,
|
||||
activityGanttTemplate,
|
||||
tabularSwimlaneColsTreeTemplate,
|
||||
@@ -204,7 +208,9 @@ define([
|
||||
"composition": [],
|
||||
"start": {
|
||||
"timestamp": 0
|
||||
}
|
||||
},
|
||||
"activityStart": {},
|
||||
"activityDuration": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -304,6 +310,17 @@ define([
|
||||
],
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"key": "activityValues",
|
||||
"name": "Activity Values",
|
||||
"cssClass": "icon-activity",
|
||||
"template": activityTemplate,
|
||||
"type": "activity",
|
||||
"uses": [
|
||||
"activityValue"
|
||||
],
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"key": "timeline",
|
||||
"name": "Timeline",
|
||||
@@ -555,6 +572,10 @@ define([
|
||||
{
|
||||
"key": "cost",
|
||||
"implementation": CostCapability
|
||||
},
|
||||
{
|
||||
"key": "activityValue",
|
||||
"implementation": ActivityValueCapability
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
|
||||
27
platform/features/timeline/res/templates/activity-view.html
Normal file
27
platform/features/timeline/res/templates/activity-view.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2009-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.
|
||||
-->
|
||||
<ul ng-controller="ActivityModeValuesController as controller" class="cols cols-2-ff properties">
|
||||
<li ng-repeat="(key, value) in activityValue" class="l-row s-row">
|
||||
<span class="col col-100px s-title"></span>
|
||||
<span class="col s-value">{{value}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -20,6 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="s-timeline l-timeline-holder split-layout vertical splitter-sm"
|
||||
ng-click="$event.stopPropagation()"
|
||||
ng-controller="TimelineController as timelineController">
|
||||
|
||||
<mct-split-pane anchor="left" class="abs" position="pane.x">
|
||||
|
||||
@@ -21,27 +21,48 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
['EventEmitter'],
|
||||
function (EventEmitter) {
|
||||
/**
|
||||
* Describes the time span of an activity object.
|
||||
* @param model the activity's object model
|
||||
*/
|
||||
function ActivityTimespan(model, mutation) {
|
||||
function ActivityTimespan(model, mutation, parentTimeline) {
|
||||
var parentTimelineModel = parentTimeline.getModel(),
|
||||
parentMutation = parentTimeline.getCapability('mutation');
|
||||
|
||||
function getTimelineActivityStart (domainObjectModel) {
|
||||
if (domainObjectModel.activityStart && domainObjectModel.activityStart[model.id]) {
|
||||
return domainObjectModel.activityStart[model.id];
|
||||
} else {
|
||||
return model.start.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimelineActivityDuration (domainObjectModel) {
|
||||
if (domainObjectModel.activityDuration && domainObjectModel.activityDuration[model.id]) {
|
||||
return domainObjectModel.activityDuration[model.id];
|
||||
} else {
|
||||
return model.duration.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the start time for this timeline
|
||||
function getStart() {
|
||||
return model.start.timestamp;
|
||||
return getTimelineActivityStart(parentTimelineModel);
|
||||
}
|
||||
|
||||
// Get the end time for this timeline
|
||||
function getEnd() {
|
||||
return model.start.timestamp + model.duration.timestamp;
|
||||
var start = getTimelineActivityStart(parentTimelineModel),
|
||||
duration = getTimelineActivityDuration(parentTimelineModel);
|
||||
|
||||
return start + duration;
|
||||
}
|
||||
|
||||
// Get the duration of this timeline
|
||||
function getDuration() {
|
||||
return model.duration.timestamp;
|
||||
return getTimelineActivityDuration(parentTimelineModel);
|
||||
}
|
||||
|
||||
// Get the epoch used by this timeline
|
||||
@@ -52,26 +73,41 @@ define(
|
||||
// Set the start time associated with this object
|
||||
function setStart(value) {
|
||||
var end = getEnd();
|
||||
mutation.mutate(function (m) {
|
||||
m.start.timestamp = Math.max(value, 0);
|
||||
// Update duration to keep end time
|
||||
m.duration.timestamp = Math.max(end - value, 0);
|
||||
}, model.modified);
|
||||
|
||||
parentMutation.mutate(function (m) {
|
||||
m.activityStart[model.id] = Math.max(value,0);
|
||||
m.activityDuration[model.id] = Math.max(end - value, 0);
|
||||
});
|
||||
|
||||
// mutation.mutate(function (m) {
|
||||
// m.start.timestamp = Math.max(value, 0);
|
||||
// // Update duration to keep end time
|
||||
// m.duration.timestamp = Math.max(end - value, 0);
|
||||
// }, model.modified);
|
||||
}
|
||||
|
||||
// Set the duration associated with this object
|
||||
function setDuration(value) {
|
||||
mutation.mutate(function (m) {
|
||||
m.duration.timestamp = Math.max(value, 0);
|
||||
}, model.modified);
|
||||
parentMutation.mutate(function (m) {
|
||||
m.activityDuration[model.id] = Math.max(value, 0);
|
||||
});
|
||||
|
||||
// mutation.mutate(function (m) {
|
||||
// m.duration.timestamp = Math.max(value, 0);
|
||||
// }, model.modified);
|
||||
}
|
||||
|
||||
// Set the end time associated with this object
|
||||
function setEnd(value) {
|
||||
var start = getStart();
|
||||
mutation.mutate(function (m) {
|
||||
m.duration.timestamp = Math.max(value - start, 0);
|
||||
}, model.modified);
|
||||
|
||||
parentMutation.mutate(function (m) {
|
||||
m.activityDuration[model.id] = Math.max(value - start, 0);
|
||||
});
|
||||
|
||||
// mutation.mutate(function (m) {
|
||||
// m.duration.timestamp = Math.max(value - start, 0);
|
||||
// }, model.modified);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -32,11 +32,26 @@ define(
|
||||
* @param {DomainObject} domainObject the Activity
|
||||
*/
|
||||
function ActivityTimespanCapability($q, domainObject) {
|
||||
|
||||
function findTimeline (object) {
|
||||
var parent = domainObject.getCapability('context').parentObject;
|
||||
|
||||
while (parent.getModel().type !== 'timeline') {
|
||||
parent = parent.getCapability('context').parentObject;
|
||||
findTimeline(parent);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
var parent = findTimeline(domainObject);
|
||||
|
||||
// Promise time span
|
||||
function promiseTimeSpan() {
|
||||
return $q.when(new ActivityTimespan(
|
||||
domainObject.getModel(),
|
||||
domainObject.getCapability('mutation')
|
||||
domainObject.getCapability('mutation'),
|
||||
parent
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Exposes costs associated with a subsystem mode.
|
||||
* @constructor
|
||||
*/
|
||||
function ActivityValueCapability(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of resource types which have associated
|
||||
* costs for this object. Returned values are machine-readable
|
||||
* keys, and should be paired with external metadata for
|
||||
* presentation (see category of extension `resources`).
|
||||
* @returns {string[]} resource types
|
||||
*/
|
||||
resources: function () {
|
||||
return Object.keys(model.resources || {}).sort();
|
||||
},
|
||||
/**
|
||||
* Get the cost associated with a resource of an identified
|
||||
* type (typically, one of the types reported from a
|
||||
* `resources` call.)
|
||||
* @param {string} key the resource type
|
||||
* @returns {number} the associated cost
|
||||
*/
|
||||
cost: function (key) {
|
||||
return (model.resources || {})[key] || 0;
|
||||
},
|
||||
/**
|
||||
* Get an object containing key-value pairs describing
|
||||
* resource utilization as described by this object.
|
||||
* Keys are resource types; values are levels of associated
|
||||
* resource utilization.
|
||||
* @returns {object} resource utilizations
|
||||
*/
|
||||
invoke: function () {
|
||||
return {key: 'deep is the best'};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Only applies to subsystem modes.
|
||||
ActivityValueCapability.appliesTo = function (model) {
|
||||
return (model || {}).type === 'activity';
|
||||
};
|
||||
|
||||
return ActivityValueCapability;
|
||||
}
|
||||
);
|
||||
@@ -30,7 +30,7 @@ define(
|
||||
*/
|
||||
function CostCapability(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of resource types which have associated
|
||||
|
||||
17
src/MCT.js
17
src/MCT.js
@@ -28,7 +28,8 @@ define([
|
||||
'./selection/Selection',
|
||||
'./api/objects/object-utils',
|
||||
'./plugins/plugins',
|
||||
'./ui/ViewRegistry'
|
||||
'./ui/ViewRegistry',
|
||||
'./ui/InspectorViewRegistry'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
@@ -37,7 +38,8 @@ define([
|
||||
Selection,
|
||||
objectUtils,
|
||||
plugins,
|
||||
ViewRegistry
|
||||
ViewRegistry,
|
||||
InspectorViewRegistry
|
||||
) {
|
||||
/**
|
||||
* Open MCT is an extensible web application for building mission
|
||||
@@ -112,15 +114,13 @@ define([
|
||||
|
||||
/**
|
||||
* Registry for views which should appear in the Inspector area.
|
||||
* These views will be chosen based on selection state, so
|
||||
* providers should be prepared to test arbitrary objects for
|
||||
* viewability.
|
||||
* These views will be chosen based on the selection state.
|
||||
*
|
||||
* @type {module:openmct.ViewRegistry}
|
||||
* @type {module:openmct.InspectorViewRegistry}
|
||||
* @memberof module:openmct.MCT#
|
||||
* @name inspectors
|
||||
* @name inspectorViews
|
||||
*/
|
||||
this.inspectors = new ViewRegistry();
|
||||
this.inspectorViews = new InspectorViewRegistry();
|
||||
|
||||
/**
|
||||
* Registry for views which should appear in Edit Properties
|
||||
@@ -196,7 +196,6 @@ define([
|
||||
|
||||
this.Dialog = api.Dialog;
|
||||
|
||||
this.on('navigation', this.selection.clear.bind(this.selection));
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
@@ -48,6 +48,7 @@ define([
|
||||
this.unlisteners.forEach(function (unlisten) {
|
||||
unlisten();
|
||||
});
|
||||
this.unlisteners = [];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,8 +41,6 @@ define([
|
||||
};
|
||||
|
||||
this.valueMetadata = valueMetadata;
|
||||
this.parseCache = new WeakMap();
|
||||
this.formatCache = new WeakMap();
|
||||
try {
|
||||
this.formatter = formatService
|
||||
.getFormat(valueMetadata.format, valueMetadata);
|
||||
@@ -72,26 +70,14 @@ define([
|
||||
|
||||
TelemetryValueFormatter.prototype.parse = function (datum) {
|
||||
if (_.isObject(datum)) {
|
||||
if (!this.parseCache.has(datum)) {
|
||||
this.parseCache.set(
|
||||
datum,
|
||||
this.formatter.parse(datum[this.valueMetadata.source])
|
||||
);
|
||||
}
|
||||
return this.parseCache.get(datum);
|
||||
return this.formatter.parse(datum[this.valueMetadata.source]);
|
||||
}
|
||||
return this.formatter.parse(datum);
|
||||
};
|
||||
|
||||
TelemetryValueFormatter.prototype.format = function (datum) {
|
||||
if (_.isObject(datum)) {
|
||||
if (!this.formatCache.has(datum)) {
|
||||
this.formatCache.set(
|
||||
datum,
|
||||
this.formatter.format(datum[this.valueMetadata.source])
|
||||
);
|
||||
}
|
||||
return this.formatCache.get(datum);
|
||||
return this.formatter.format(datum[this.valueMetadata.source]);
|
||||
}
|
||||
return this.formatter.format(datum);
|
||||
};
|
||||
|
||||
47
src/plugins/activityModes/plugin.js
Normal file
47
src/plugins/activityModes/plugin.js
Normal file
@@ -0,0 +1,47 @@
|
||||
define(['./src/actions/activityModesImportAction'], function (ActivityModes) {
|
||||
function plugin() {
|
||||
|
||||
return function install(openmct) {
|
||||
|
||||
openmct.legacyRegistry.register("src/plugins/activityModes", {
|
||||
"name": "Activity Import",
|
||||
"description": "Defines a root named My Items",
|
||||
"extensions": {
|
||||
"roots": [
|
||||
{
|
||||
"id": "activity-import"
|
||||
}
|
||||
],
|
||||
"models": [
|
||||
{
|
||||
"id": "activity-import",
|
||||
"model": {
|
||||
"name": "Activity Import",
|
||||
"type": "folder",
|
||||
"composition": [],
|
||||
"location": "ROOT"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
openmct.legacyRegistry.enable("src/plugins/activityModes");
|
||||
|
||||
openmct.legacyExtension('actions', {
|
||||
key: "import-csv",
|
||||
category: ["contextual"],
|
||||
implementation: ActivityModes,
|
||||
cssClass: "major icon-import",
|
||||
name: "Import Activity Definitions from CSV",
|
||||
description: "Import activities from a CSV file",
|
||||
depends: [
|
||||
"dialogService",
|
||||
"openmct"
|
||||
]
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return plugin;
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
define(['d3-dsv'], function (d3Dsv) {
|
||||
|
||||
function ActivityModesImportAction(dialogService, openmct, context) {
|
||||
this.dialogService = dialogService;
|
||||
this.openmct = openmct;
|
||||
this.context = context;
|
||||
this.parent = this.context.domainObject;
|
||||
this.instantiate = this.openmct.$injector.get("instantiate");
|
||||
|
||||
this.instantiateActivities = this.instantiateActivities.bind(this);
|
||||
}
|
||||
|
||||
ActivityModesImportAction.prototype.perform = function () {
|
||||
this.dialogService.getUserInput(this.getFormModel(), function () {})
|
||||
.then(function (form) {
|
||||
if(form.selectFile.name.slice(-3) !== 'csv'){
|
||||
this.displayError();
|
||||
}
|
||||
|
||||
this.csvParse(form.selectFile.body).then(this.instantiateActivities);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ActivityModesImportAction.prototype.csvParse = function (csvString) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var parsedObject = d3Dsv.csvParse(csvString);
|
||||
|
||||
return parsedObject ? resolve(parsedObject) : reject('Could not parse provided file');
|
||||
});
|
||||
};
|
||||
|
||||
ActivityModesImportAction.prototype.instantiateActivities = function (csvObjects) {
|
||||
var parent = this.context.domainObject,
|
||||
parentId = parent.getId(),
|
||||
parentComposition = parent.getCapability("composition"),
|
||||
activitiesObjects = [],
|
||||
activityModesObjects = [];
|
||||
|
||||
csvObjects.forEach(function (activity) {
|
||||
var newActivity = {},
|
||||
newActivityMode = {};
|
||||
|
||||
newActivity.name = activity.name;
|
||||
newActivity.start = {timestamp: 0, epoch: "SET"};
|
||||
newActivity.duration = {timestamp: Number(activity.duration), epoch: "SET"};
|
||||
newActivity.type = "activity";
|
||||
newActivity.relationships = {modes: []};
|
||||
|
||||
activitiesObjects.push(newActivity);
|
||||
|
||||
newActivityMode.name = activity.name + ' Resources';
|
||||
newActivityMode.resources = {comms: Number(activity.comms), power: Number(activity.power)};
|
||||
newActivityMode.type = 'mode';
|
||||
|
||||
activityModesObjects.push(newActivityMode);
|
||||
});
|
||||
|
||||
var folderComposition = this.createActivityModesFolder().getCapability('composition');
|
||||
|
||||
activityModesObjects.forEach(function (activityMode, index) {
|
||||
var newActivityModeInstance = this.instantiate(activityMode, 'activity-mode-' + index);
|
||||
|
||||
newActivityModeInstance.getCapability('location').setPrimaryLocation('activity-modes-folder');
|
||||
folderComposition.add(newActivityModeInstance);
|
||||
}.bind(this));
|
||||
|
||||
activitiesObjects.forEach(function (activity, index) {
|
||||
activity.relationships.modes.push('activity-mode-' + index);
|
||||
activity.id = 'activity-' + index;
|
||||
|
||||
var newActivityInstance = this.instantiate(activity, 'activity-' + index);
|
||||
|
||||
newActivityInstance.getCapability('location').setPrimaryLocation(parentId);
|
||||
parentComposition.add(newActivityInstance);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ActivityModesImportAction.prototype.createActivityModesFolder = function () {
|
||||
var folderInstance = this.instantiate({name: 'Activity-Modes', type: 'folder', composition: []}, 'activity-modes-folder');
|
||||
folderInstance.getCapability('location').setPrimaryLocation(this.parent.getId());
|
||||
this.parent.getCapability('composition').add(folderInstance);
|
||||
|
||||
return folderInstance;
|
||||
};
|
||||
|
||||
ActivityModesImportAction.prototype.displayError = function () {
|
||||
var dialog,
|
||||
perform = this.perform.bind(this),
|
||||
model = {
|
||||
title: "Invalid File",
|
||||
actionText: "The selected file was not a valid CSV file",
|
||||
severity: "error",
|
||||
options: [
|
||||
{
|
||||
label: "Ok",
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
perform();
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
dialog = this.dialogService.showBlockingMessage(model);
|
||||
};
|
||||
|
||||
ActivityModesImportAction.prototype.getFormModel = function () {
|
||||
return {
|
||||
name: 'Import activities from CSV',
|
||||
sections: [
|
||||
{
|
||||
name: 'Import A File',
|
||||
rows: [
|
||||
{
|
||||
name: 'Select File',
|
||||
key: 'selectFile',
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
return ActivityModesImportAction;
|
||||
});
|
||||
34
src/plugins/autoflow/AutoflowTabularConstants.js
Normal file
34
src/plugins/autoflow/AutoflowTabularConstants.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* Constant values used by the Autoflow Tabular View.
|
||||
*/
|
||||
return {
|
||||
ROW_HEIGHT: 16,
|
||||
SLIDER_HEIGHT: 10,
|
||||
INITIAL_COLUMN_WIDTH: 225,
|
||||
MAX_COLUMN_WIDTH: 525,
|
||||
COLUMN_WIDTH_STEP: 25
|
||||
};
|
||||
});
|
||||
121
src/plugins/autoflow/AutoflowTabularController.js
Normal file
121
src/plugins/autoflow/AutoflowTabularController.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./AutoflowTabularRowController'
|
||||
], function (AutoflowTabularRowController) {
|
||||
/**
|
||||
* Controller for an Autoflow Tabular View. Subscribes to telemetry
|
||||
* associated with children of the domain object and passes that
|
||||
* information on to the view.
|
||||
*
|
||||
* @param {DomainObject} domainObject the object being viewed
|
||||
* @param {*} data the view data
|
||||
* @param openmct a reference to the openmct application
|
||||
*/
|
||||
function AutoflowTabularController(domainObject, data, openmct) {
|
||||
this.composition = openmct.composition.get(domainObject);
|
||||
this.data = data;
|
||||
this.openmct = openmct;
|
||||
|
||||
this.rows = {};
|
||||
this.controllers = {};
|
||||
|
||||
this.addRow = this.addRow.bind(this);
|
||||
this.removeRow = this.removeRow.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "Last Updated" value to be displayed.
|
||||
* @param {String} value the value to display
|
||||
* @private
|
||||
*/
|
||||
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
|
||||
this.data.updated = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to an `add` event from composition by adding a new row.
|
||||
* @private
|
||||
*/
|
||||
AutoflowTabularController.prototype.addRow = function (childObject) {
|
||||
var identifier = childObject.identifier;
|
||||
var id = [identifier.namespace, identifier.key].join(":");
|
||||
|
||||
if (!this.rows[id]) {
|
||||
this.rows[id] = {
|
||||
classes: "",
|
||||
name: childObject.name,
|
||||
value: undefined
|
||||
};
|
||||
this.controllers[id] = new AutoflowTabularRowController(
|
||||
childObject,
|
||||
this.rows[id],
|
||||
this.openmct,
|
||||
this.trackLastUpdated.bind(this)
|
||||
);
|
||||
this.controllers[id].activate();
|
||||
this.data.items.push(this.rows[id]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to an `remove` event from composition by removing any
|
||||
* related row.
|
||||
* @private
|
||||
*/
|
||||
AutoflowTabularController.prototype.removeRow = function (identifier) {
|
||||
var id = [identifier.namespace, identifier.key].join(":");
|
||||
|
||||
if (this.rows[id]) {
|
||||
this.data.items = this.data.items.filter(function (item) {
|
||||
return item !== this.rows[id];
|
||||
}.bind(this));
|
||||
this.controllers[id].destroy();
|
||||
delete this.controllers[id];
|
||||
delete this.rows[id];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate this controller; begin listening for changes.
|
||||
*/
|
||||
AutoflowTabularController.prototype.activate = function () {
|
||||
this.composition.on('add', this.addRow);
|
||||
this.composition.on('remove', this.removeRow);
|
||||
this.composition.load();
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy this controller; detach any associated resources.
|
||||
*/
|
||||
AutoflowTabularController.prototype.destroy = function () {
|
||||
Object.keys(this.controllers).forEach(function (id) {
|
||||
this.controllers[id].destroy();
|
||||
}.bind(this));
|
||||
this.controllers = {};
|
||||
this.composition.off('add', this.addRow);
|
||||
this.composition.off('remove', this.removeRow);
|
||||
};
|
||||
|
||||
return AutoflowTabularController;
|
||||
});
|
||||
60
src/plugins/autoflow/AutoflowTabularPlugin.js
Normal file
60
src/plugins/autoflow/AutoflowTabularPlugin.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./AutoflowTabularView'
|
||||
], function (
|
||||
AutoflowTabularView
|
||||
) {
|
||||
/**
|
||||
* This plugin provides an Autoflow Tabular View for domain objects
|
||||
* in Open MCT.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} [options.type] the domain object type for which
|
||||
* this view should be available; if omitted, this view will
|
||||
* be available for all objects
|
||||
*/
|
||||
return function (options) {
|
||||
return function (openmct) {
|
||||
var views = (openmct.mainViews || openmct.objectViews);
|
||||
|
||||
views.addProvider({
|
||||
name: "Autoflow Tabular",
|
||||
key: "autoflow",
|
||||
cssClass: "icon-packet",
|
||||
description: "A tabular view of packet contents.",
|
||||
canView: function (d) {
|
||||
return !options || (options.type === d.type);
|
||||
},
|
||||
view: function (domainObject) {
|
||||
return new AutoflowTabularView(
|
||||
domainObject,
|
||||
openmct,
|
||||
document
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
319
src/plugins/autoflow/AutoflowTabularPluginSpec.js
Normal file
319
src/plugins/autoflow/AutoflowTabularPluginSpec.js
Normal file
@@ -0,0 +1,319 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./AutoflowTabularPlugin',
|
||||
'./AutoflowTabularConstants',
|
||||
'../../MCT',
|
||||
'zepto'
|
||||
], function (AutoflowTabularPlugin, AutoflowTabularConstants, MCT, $) {
|
||||
describe("AutoflowTabularPlugin", function () {
|
||||
var testType;
|
||||
var testObject;
|
||||
var mockmct;
|
||||
|
||||
beforeEach(function () {
|
||||
testType = "some-type";
|
||||
testObject = { type: testType };
|
||||
mockmct = new MCT();
|
||||
spyOn(mockmct.composition, 'get');
|
||||
spyOn(mockmct.objectViews, 'addProvider');
|
||||
spyOn(mockmct.telemetry, 'getMetadata');
|
||||
spyOn(mockmct.telemetry, 'getValueFormatter');
|
||||
spyOn(mockmct.telemetry, 'limitEvaluator');
|
||||
spyOn(mockmct.telemetry, 'request');
|
||||
spyOn(mockmct.telemetry, 'subscribe');
|
||||
|
||||
var plugin = new AutoflowTabularPlugin({ type: testType });
|
||||
plugin(mockmct);
|
||||
});
|
||||
|
||||
it("installs a view provider", function () {
|
||||
expect(mockmct.objectViews.addProvider).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("installs a view provider which", function () {
|
||||
var provider;
|
||||
|
||||
beforeEach(function () {
|
||||
provider =
|
||||
mockmct.objectViews.addProvider.mostRecentCall.args[0];
|
||||
});
|
||||
|
||||
it("applies its view to the type from options", function () {
|
||||
expect(provider.canView(testObject)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not apply to other types", function () {
|
||||
expect(provider.canView({ type: 'foo' })).toBe(false);
|
||||
});
|
||||
|
||||
describe("provides a view which", function () {
|
||||
var testKeys;
|
||||
var testChildren;
|
||||
var testContainer;
|
||||
var testHistories;
|
||||
var mockComposition;
|
||||
var mockMetadata;
|
||||
var mockEvaluator;
|
||||
var mockUnsubscribes;
|
||||
var callbacks;
|
||||
var view;
|
||||
|
||||
function waitsForChange() {
|
||||
var callback = jasmine.createSpy('callback');
|
||||
window.requestAnimationFrame(callback);
|
||||
waitsFor(function () {
|
||||
return callback.calls.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
function emitEvent(mockEmitter, type, event) {
|
||||
mockEmitter.on.calls.forEach(function (call) {
|
||||
if (call.args[0] === type) {
|
||||
call.args[1](event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
callbacks = {};
|
||||
|
||||
testObject = { type: 'some-type' };
|
||||
testKeys = ['abc', 'def', 'xyz'];
|
||||
testChildren = testKeys.map(function (key) {
|
||||
return {
|
||||
identifier: { namespace: "test", key: key },
|
||||
name: "Object " + key
|
||||
};
|
||||
});
|
||||
testContainer = $('<div>')[0];
|
||||
testHistories = testKeys.reduce(function (histories, key, index) {
|
||||
histories[key] = { key: key, range: index + 10, domain: key + index };
|
||||
return histories;
|
||||
}, {});
|
||||
|
||||
mockComposition =
|
||||
jasmine.createSpyObj('composition', ['load', 'on', 'off']);
|
||||
mockMetadata =
|
||||
jasmine.createSpyObj('metadata', ['valuesForHints']);
|
||||
|
||||
mockEvaluator = jasmine.createSpyObj('evaluator', ['evaluate']);
|
||||
mockUnsubscribes = testKeys.reduce(function (map, key) {
|
||||
map[key] = jasmine.createSpy('unsubscribe-' + key);
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
mockmct.composition.get.andReturn(mockComposition);
|
||||
mockComposition.load.andCallFake(function () {
|
||||
testChildren.forEach(emitEvent.bind(null, mockComposition, 'add'));
|
||||
return Promise.resolve(testChildren);
|
||||
});
|
||||
|
||||
mockmct.telemetry.getMetadata.andReturn(mockMetadata);
|
||||
mockmct.telemetry.getValueFormatter.andCallFake(function (metadatum) {
|
||||
var mockFormatter = jasmine.createSpyObj('formatter', ['format']);
|
||||
mockFormatter.format.andCallFake(function (datum) {
|
||||
return datum[metadatum.hint];
|
||||
});
|
||||
return mockFormatter;
|
||||
});
|
||||
mockmct.telemetry.limitEvaluator.andReturn(mockEvaluator);
|
||||
mockmct.telemetry.subscribe.andCallFake(function (obj, callback) {
|
||||
var key = obj.identifier.key;
|
||||
callbacks[key] = callback;
|
||||
return mockUnsubscribes[key];
|
||||
});
|
||||
mockmct.telemetry.request.andCallFake(function (obj, request) {
|
||||
var key = obj.identifier.key;
|
||||
return Promise.resolve([testHistories[key]]);
|
||||
});
|
||||
mockMetadata.valuesForHints.andCallFake(function (hints) {
|
||||
return [{ hint: hints[0] }];
|
||||
});
|
||||
|
||||
view = provider.view(testObject);
|
||||
view.show(testContainer);
|
||||
|
||||
waitsForChange();
|
||||
});
|
||||
|
||||
it("populates its container", function () {
|
||||
expect(testContainer.children.length > 0).toBe(true);
|
||||
});
|
||||
|
||||
describe("when rows have been populated", function () {
|
||||
function rowsMatch() {
|
||||
var rows = $(testContainer).find(".l-autoflow-row").length;
|
||||
return rows === testChildren.length;
|
||||
}
|
||||
|
||||
it("shows one row per child object", function () {
|
||||
waitsFor(rowsMatch);
|
||||
});
|
||||
|
||||
it("adds rows on composition change", function () {
|
||||
var child = {
|
||||
identifier: { namespace: "test", key: "123" },
|
||||
name: "Object 123"
|
||||
};
|
||||
testChildren.push(child);
|
||||
emitEvent(mockComposition, 'add', child);
|
||||
waitsFor(rowsMatch);
|
||||
});
|
||||
|
||||
it("removes rows on composition change", function () {
|
||||
var child = testChildren.pop();
|
||||
emitEvent(mockComposition, 'remove', child.identifier);
|
||||
waitsFor(rowsMatch);
|
||||
});
|
||||
});
|
||||
|
||||
it("removes subscriptions when destroyed", function () {
|
||||
testKeys.forEach(function (key) {
|
||||
expect(mockUnsubscribes[key]).not.toHaveBeenCalled();
|
||||
});
|
||||
view.destroy();
|
||||
testKeys.forEach(function (key) {
|
||||
expect(mockUnsubscribes[key]).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("provides a button to change column width", function () {
|
||||
var initialWidth = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
|
||||
var nextWidth =
|
||||
initialWidth + AutoflowTabularConstants.COLUMN_WIDTH_STEP;
|
||||
|
||||
expect($(testContainer).find('.l-autoflow-col').css('width'))
|
||||
.toEqual(initialWidth + 'px');
|
||||
|
||||
$(testContainer).find('.change-column-width').click();
|
||||
|
||||
waitsFor(function () {
|
||||
var width = $(testContainer).find('.l-autoflow-col').css('width');
|
||||
return width !== initialWidth + 'px';
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
expect($(testContainer).find('.l-autoflow-col').css('width'))
|
||||
.toEqual(nextWidth + 'px');
|
||||
});
|
||||
});
|
||||
|
||||
it("subscribes to all child objects", function () {
|
||||
testKeys.forEach(function (key) {
|
||||
expect(callbacks[key]).toEqual(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
it("displays historical telemetry", function () {
|
||||
waitsFor(function () {
|
||||
return $(testContainer).find(".l-autoflow-item").filter(".r").text() !== "";
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
testKeys.forEach(function (key, index) {
|
||||
var datum = testHistories[key];
|
||||
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||
expect($cell.text()).toEqual(String(datum.range));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("displays incoming telemetry", function () {
|
||||
var testData = testKeys.map(function (key, index) {
|
||||
return { key: key, range: index * 100, domain: key + index };
|
||||
});
|
||||
|
||||
testData.forEach(function (datum) {
|
||||
callbacks[datum.key](datum);
|
||||
});
|
||||
|
||||
waitsForChange();
|
||||
|
||||
runs(function () {
|
||||
testData.forEach(function (datum, index) {
|
||||
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||
expect($cell.text()).toEqual(String(datum.range));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("updates classes for limit violations", function () {
|
||||
var testClass = "some-limit-violation";
|
||||
mockEvaluator.evaluate.andReturn({ cssClass: testClass });
|
||||
testKeys.forEach(function (key) {
|
||||
callbacks[key]({ range: 'foo', domain: 'bar' });
|
||||
});
|
||||
|
||||
waitsForChange();
|
||||
|
||||
runs(function () {
|
||||
testKeys.forEach(function (datum, index) {
|
||||
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
|
||||
expect($cell.hasClass(testClass)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("automatically flows to new columns", function () {
|
||||
var rowHeight = AutoflowTabularConstants.ROW_HEIGHT;
|
||||
var sliderHeight = AutoflowTabularConstants.SLIDER_HEIGHT;
|
||||
var count = testKeys.length;
|
||||
var $container = $(testContainer);
|
||||
|
||||
function columnsHaveAutoflowed() {
|
||||
var itemsHeight = $container.find('.l-autoflow-items').height();
|
||||
var availableHeight = itemsHeight - sliderHeight;
|
||||
var availableRows = Math.max(Math.floor(availableHeight / rowHeight), 1);
|
||||
var columns = Math.ceil(count / availableRows);
|
||||
return $container.find('.l-autoflow-col').length === columns;
|
||||
}
|
||||
|
||||
$container.find('.abs').css({
|
||||
position: 'absolute',
|
||||
left: '0px',
|
||||
right: '0px',
|
||||
top: '0px',
|
||||
bottom: '0px'
|
||||
});
|
||||
$container.css({ position: 'absolute' });
|
||||
|
||||
runs($container.appendTo.bind($container, document.body));
|
||||
for (var height = 0; height < rowHeight * count * 2; height += rowHeight / 2) {
|
||||
runs($container.css.bind($container, 'height', height + 'px'));
|
||||
waitsFor(columnsHaveAutoflowed);
|
||||
}
|
||||
runs($container.remove.bind($container));
|
||||
});
|
||||
|
||||
it("loads composition exactly once", function () {
|
||||
var testObj = testChildren.pop();
|
||||
emitEvent(mockComposition, 'remove', testObj.identifier);
|
||||
testChildren.push(testObj);
|
||||
emitEvent(mockComposition, 'add', testObj);
|
||||
expect(mockComposition.load.calls.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
94
src/plugins/autoflow/AutoflowTabularRowController.js
Normal file
94
src/plugins/autoflow/AutoflowTabularRowController.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* Controller for individual rows of an Autoflow Tabular View.
|
||||
* Subscribes to telemetry and updates row data.
|
||||
*
|
||||
* @param {DomainObject} domainObject the object being viewed
|
||||
* @param {*} data the view data
|
||||
* @param openmct a reference to the openmct application
|
||||
* @param {Function} callback a callback to invoke with "last updated" timestamps
|
||||
*/
|
||||
function AutoflowTabularRowController(domainObject, data, openmct, callback) {
|
||||
this.domainObject = domainObject;
|
||||
this.data = data;
|
||||
this.openmct = openmct;
|
||||
this.callback = callback;
|
||||
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.ranges = this.metadata.valuesForHints(['range']);
|
||||
this.domains = this.metadata.valuesForHints(['domain']);
|
||||
this.rangeFormatter =
|
||||
this.openmct.telemetry.getValueFormatter(this.ranges[0]);
|
||||
this.domainFormatter =
|
||||
this.openmct.telemetry.getValueFormatter(this.domains[0]);
|
||||
this.evaluator =
|
||||
this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update row to reflect incoming telemetry data.
|
||||
* @private
|
||||
*/
|
||||
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
|
||||
var violations = this.evaluator.evaluate(datum, this.ranges[0]);
|
||||
|
||||
this.initialized = true;
|
||||
this.data.classes = violations ? violations.cssClass : "";
|
||||
this.data.value = this.rangeFormatter.format(datum);
|
||||
this.callback(this.domainFormatter.format(datum));
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate this controller; begin listening for changes.
|
||||
*/
|
||||
AutoflowTabularRowController.prototype.activate = function () {
|
||||
this.unsubscribe = this.openmct.telemetry.subscribe(
|
||||
this.domainObject,
|
||||
this.updateRowData.bind(this)
|
||||
);
|
||||
|
||||
this.openmct.telemetry.request(
|
||||
this.domainObject,
|
||||
{ size: 1 }
|
||||
).then(function (history) {
|
||||
if (!this.initialized && history.length > 0) {
|
||||
this.updateRowData(history[history.length - 1]);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy this controller; detach any associated resources.
|
||||
*/
|
||||
AutoflowTabularRowController.prototype.destroy = function () {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
};
|
||||
|
||||
return AutoflowTabularRowController;
|
||||
});
|
||||
125
src/plugins/autoflow/AutoflowTabularView.js
Normal file
125
src/plugins/autoflow/AutoflowTabularView.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./AutoflowTabularController',
|
||||
'./AutoflowTabularConstants',
|
||||
'../../ui/VueView',
|
||||
'text!./autoflow-tabular.html'
|
||||
], function (
|
||||
AutoflowTabularController,
|
||||
AutoflowTabularConstants,
|
||||
VueView,
|
||||
autoflowTemplate
|
||||
) {
|
||||
var ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
|
||||
var SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
|
||||
var INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
|
||||
var MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
|
||||
var COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
|
||||
|
||||
/**
|
||||
* Implements the Autoflow Tabular view of a domain object.
|
||||
*/
|
||||
function AutoflowTabularView(domainObject, openmct) {
|
||||
var data = {
|
||||
items: [],
|
||||
columns: [],
|
||||
width: INITIAL_COLUMN_WIDTH,
|
||||
filter: "",
|
||||
updated: "No updates",
|
||||
rowCount: 1
|
||||
};
|
||||
var controller =
|
||||
new AutoflowTabularController(domainObject, data, openmct);
|
||||
var interval;
|
||||
|
||||
VueView.call(this, {
|
||||
data: data,
|
||||
methods: {
|
||||
increaseColumnWidth: function () {
|
||||
data.width += COLUMN_WIDTH_STEP;
|
||||
data.width = data.width > MAX_COLUMN_WIDTH ?
|
||||
INITIAL_COLUMN_WIDTH : data.width;
|
||||
},
|
||||
reflow: function () {
|
||||
var column = [];
|
||||
var index = 0;
|
||||
var filteredItems =
|
||||
data.items.filter(function (item) {
|
||||
return item.name.toLowerCase()
|
||||
.indexOf(data.filter.toLowerCase()) !== -1;
|
||||
});
|
||||
|
||||
data.columns = [];
|
||||
|
||||
while (index < filteredItems.length) {
|
||||
if (column.length >= data.rowCount) {
|
||||
data.columns.push(column);
|
||||
column = [];
|
||||
}
|
||||
|
||||
column.push(filteredItems[index]);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if (column.length > 0) {
|
||||
data.columns.push(column);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filter: 'reflow',
|
||||
items: 'reflow',
|
||||
rowCount: 'reflow'
|
||||
},
|
||||
template: autoflowTemplate,
|
||||
destroyed: function () {
|
||||
controller.destroy();
|
||||
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
controller.activate();
|
||||
|
||||
var updateRowHeight = function () {
|
||||
var tabularArea = this.$refs.autoflowItems;
|
||||
var height = tabularArea ? tabularArea.clientHeight : 0;
|
||||
var available = height - SLIDER_HEIGHT;
|
||||
var rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
|
||||
data.rowCount = rows;
|
||||
}.bind(this);
|
||||
|
||||
interval = setInterval(updateRowHeight, 50);
|
||||
this.$nextTick(updateRowHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AutoflowTabularView.prototype = Object.create(VueView.prototype);
|
||||
|
||||
return AutoflowTabularView;
|
||||
});
|
||||
|
||||
42
src/plugins/autoflow/autoflow-tabular.html
Normal file
42
src/plugins/autoflow/autoflow-tabular.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2017, 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.
|
||||
-->
|
||||
<div class="items-holder abs contents autoflow obj-value-format">
|
||||
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
||||
<span class="t-filter l-filter">
|
||||
<input type="search" class="t-filter-input" v-model="filter"/>
|
||||
<a v-if="filter !== ''" v-on:click="filter = ''" class="clear-icon icon-x-in-circle"></a>
|
||||
</span>
|
||||
|
||||
<div class="flex-elem grows t-last-update" title="Last Update">{{updated}}</div>
|
||||
<a title="Change column width"
|
||||
v-on:click="increaseColumnWidth()"
|
||||
class="s-button flex-elem icon-arrows-right-left change-column-width"></a>
|
||||
</div>
|
||||
<div class="abs t-autoflow-items l-autoflow-items" ref="autoflowItems">
|
||||
<ul v-for="column in columns" class="l-autoflow-col" :style="{ width: width + 'px' }">
|
||||
<li v-for="row in column" class="l-autoflow-row" >
|
||||
<span :title="row.value" :data-value="row.value" :class="'l-autoflow-item r l-obj-val-format ' + row.classes">{{row.value}}</span>
|
||||
<span :title="row.name" class="l-autoflow-item l">{{row.name}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,12 +24,14 @@ define([
|
||||
'lodash',
|
||||
'./utcTimeSystem/plugin',
|
||||
'../../example/generator/plugin',
|
||||
'../../platform/features/autoflow/plugin',
|
||||
'./autoflow/AutoflowTabularPlugin',
|
||||
'./timeConductor/plugin',
|
||||
'../../example/imagery/plugin',
|
||||
'../../platform/import-export/bundle',
|
||||
'./summaryWidget/plugin',
|
||||
'./URLIndicatorPlugin/URLIndicatorPlugin'
|
||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||
'./activityModes/plugin',
|
||||
'./telemetryMean/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@@ -39,7 +41,9 @@ define([
|
||||
ExampleImagery,
|
||||
ImportExport,
|
||||
SummaryWidget,
|
||||
URLIndicatorPlugin
|
||||
URLIndicatorPlugin,
|
||||
ActivityModes,
|
||||
TelemetryMean
|
||||
) {
|
||||
var bundleMap = {
|
||||
CouchDB: 'platform/persistence/couch',
|
||||
@@ -124,9 +128,10 @@ define([
|
||||
};
|
||||
|
||||
plugins.ExampleImagery = ExampleImagery;
|
||||
|
||||
plugins.SummaryWidget = SummaryWidget;
|
||||
plugins.TelemetryMean = TelemetryMean;
|
||||
plugins.URLIndicatorPlugin = URLIndicatorPlugin;
|
||||
plugins.ActivityModes = ActivityModes;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ define([
|
||||
'./input/ObjectSelect',
|
||||
'./input/KeySelect',
|
||||
'./input/OperationSelect',
|
||||
'./eventHelpers',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
@@ -10,10 +11,10 @@ define([
|
||||
ObjectSelect,
|
||||
KeySelect,
|
||||
OperationSelect,
|
||||
eventHelpers,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents an individual condition for a summary widget rule. Manages the
|
||||
* associated inputs and view.
|
||||
@@ -25,6 +26,7 @@ define([
|
||||
* selects with configuration data
|
||||
*/
|
||||
function Condition(conditionConfig, index, conditionManager) {
|
||||
eventHelpers.extend(this);
|
||||
this.config = conditionConfig;
|
||||
this.index = index;
|
||||
this.conditionManager = conditionManager;
|
||||
@@ -71,15 +73,17 @@ define([
|
||||
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
|
||||
inputIndex = self.valueInputs.indexOf(elem);
|
||||
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'values[' + inputIndex + ']',
|
||||
index: self.index
|
||||
});
|
||||
if (elem.tagName.toUpperCase() === 'INPUT') {
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'values[' + inputIndex + ']',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
this.listenTo(this.deleteButton, 'click', this.remove, this);
|
||||
this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
|
||||
|
||||
this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
|
||||
['any', 'any telemetry'],
|
||||
@@ -105,7 +109,7 @@ define([
|
||||
$('.t-configuration', self.domElement).append(select.getDOM());
|
||||
});
|
||||
|
||||
$(this.domElement).on('input', 'input', onValueInput);
|
||||
this.listenTo($(this.domElement), 'input', onValueInput);
|
||||
}
|
||||
|
||||
Condition.prototype.getDOM = function (container) {
|
||||
@@ -139,6 +143,14 @@ define([
|
||||
*/
|
||||
Condition.prototype.remove = function () {
|
||||
this.eventEmitter.emit('remove', this.index);
|
||||
this.destroy();
|
||||
};
|
||||
|
||||
Condition.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
select.destroy();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ define([
|
||||
'./Condition',
|
||||
'./input/ColorPalette',
|
||||
'./input/IconPalette',
|
||||
'./eventHelpers',
|
||||
'EventEmitter',
|
||||
'lodash',
|
||||
'zepto'
|
||||
@@ -11,11 +12,11 @@ define([
|
||||
Condition,
|
||||
ColorPalette,
|
||||
IconPalette,
|
||||
eventHelpers,
|
||||
EventEmitter,
|
||||
_,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* An object representing a summary widget rule. Maintains a set of text
|
||||
* and css properties for output, and a set of conditions for configuring
|
||||
@@ -29,6 +30,7 @@ define([
|
||||
* @param {element} container The DOM element which cotains this summary widget
|
||||
*/
|
||||
function Rule(ruleConfig, domainObject, openmct, conditionManager, widgetDnD, container) {
|
||||
eventHelpers.extend(this);
|
||||
var self = this;
|
||||
|
||||
this.config = ruleConfig;
|
||||
@@ -196,24 +198,24 @@ define([
|
||||
|
||||
Object.keys(this.textInputs).forEach(function (inputKey) {
|
||||
self.textInputs[inputKey].prop('value', self.config[inputKey] || '');
|
||||
self.textInputs[inputKey].on('input', function () {
|
||||
self.listenTo(self.textInputs[inputKey], 'input', function () {
|
||||
onTextInput(this, inputKey);
|
||||
});
|
||||
});
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
this.addConditionButton.on('click', function () {
|
||||
this.listenTo(this.deleteButton, 'click', this.remove);
|
||||
this.listenTo(this.duplicateButton, 'click', this.duplicate);
|
||||
this.listenTo(this.addConditionButton, 'click', function () {
|
||||
self.initCondition();
|
||||
});
|
||||
this.toggleConfigButton.on('click', toggleConfig);
|
||||
this.trigger.on('change', onTriggerInput);
|
||||
this.listenTo(this.toggleConfigButton, 'click', toggleConfig);
|
||||
this.listenTo(this.trigger, 'change', onTriggerInput);
|
||||
|
||||
this.title.html(self.config.name);
|
||||
this.description.html(self.config.description);
|
||||
this.trigger.prop('value', self.config.trigger);
|
||||
|
||||
this.grippy.on('mousedown', onDragStart);
|
||||
this.listenTo(this.grippy, 'mousedown', onDragStart);
|
||||
this.widgetDnD.on('drop', function () {
|
||||
this.domElement.show();
|
||||
$('.t-drag-indicator').hide();
|
||||
@@ -258,6 +260,10 @@ define([
|
||||
palette.destroy();
|
||||
});
|
||||
this.iconInput.destroy();
|
||||
this.stopListening();
|
||||
this.conditions.forEach(function (condition) {
|
||||
condition.destroy();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ define([
|
||||
'./ConditionManager',
|
||||
'./TestDataManager',
|
||||
'./WidgetDnD',
|
||||
'./eventHelpers',
|
||||
'lodash',
|
||||
'zepto'
|
||||
], function (
|
||||
@@ -12,6 +13,7 @@ define([
|
||||
ConditionManager,
|
||||
TestDataManager,
|
||||
WidgetDnD,
|
||||
eventHelpers,
|
||||
_,
|
||||
$
|
||||
) {
|
||||
@@ -32,6 +34,8 @@ define([
|
||||
* @param {MCT} openmct An MCT instance
|
||||
*/
|
||||
function SummaryWidget(domainObject, openmct) {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
|
||||
@@ -86,7 +90,7 @@ define([
|
||||
self.outerWrapper.toggleClass('expanded-widget-test-data');
|
||||
self.toggleTestDataControl.toggleClass('expanded');
|
||||
}
|
||||
this.toggleTestDataControl.on('click', toggleTestData);
|
||||
this.listenTo(this.toggleTestDataControl, 'click', toggleTestData);
|
||||
|
||||
/**
|
||||
* Toggles the configuration area for rules in the view
|
||||
@@ -96,7 +100,7 @@ define([
|
||||
self.outerWrapper.toggleClass('expanded-widget-rules');
|
||||
self.toggleRulesControl.toggleClass('expanded');
|
||||
}
|
||||
this.toggleRulesControl.on('click', toggleRules);
|
||||
this.listenTo(this.toggleRulesControl, 'click', toggleRules);
|
||||
|
||||
openmct.$injector.get('objectService')
|
||||
.getObjects([id])
|
||||
@@ -160,13 +164,15 @@ define([
|
||||
this.widgetDnD = new WidgetDnD(this.domElement, this.domainObject.configuration.ruleOrder, this.rulesById);
|
||||
this.initRule('default', 'Default');
|
||||
this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
|
||||
self.initRule(ruleId);
|
||||
if (ruleId !== 'default') {
|
||||
self.initRule(ruleId);
|
||||
}
|
||||
});
|
||||
this.refreshRules();
|
||||
this.updateWidget();
|
||||
this.updateView();
|
||||
|
||||
this.addRuleButton.on('click', this.addRule);
|
||||
this.listenTo(this.addRuleButton, 'click', this.addRule);
|
||||
this.conditionManager.on('receiveTelemetry', this.executeRules, this);
|
||||
this.widgetDnD.on('drop', this.reorder, this);
|
||||
};
|
||||
@@ -178,11 +184,14 @@ define([
|
||||
SummaryWidget.prototype.destroy = function (container) {
|
||||
this.editListenerUnsubscribe();
|
||||
this.conditionManager.destroy();
|
||||
this.testDataManager.destroy();
|
||||
this.widgetDnD.destroy();
|
||||
this.watchForChangesUnsubscribe();
|
||||
Object.values(this.rulesById).forEach(function (rule) {
|
||||
rule.destroy();
|
||||
});
|
||||
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,14 @@ define([
|
||||
'text!../res/testDataItemTemplate.html',
|
||||
'./input/ObjectSelect',
|
||||
'./input/KeySelect',
|
||||
'./eventHelpers',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
itemTemplate,
|
||||
ObjectSelect,
|
||||
KeySelect,
|
||||
eventHelpers,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
@@ -24,6 +26,7 @@ define([
|
||||
* @constructor
|
||||
*/
|
||||
function TestDataItem(itemConfig, index, conditionManager) {
|
||||
eventHelpers.extend(this);
|
||||
this.config = itemConfig;
|
||||
this.index = index;
|
||||
this.conditionManager = conditionManager;
|
||||
@@ -70,16 +73,17 @@ define([
|
||||
function onValueInput(event) {
|
||||
var elem = event.target,
|
||||
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber);
|
||||
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'value',
|
||||
index: self.index
|
||||
});
|
||||
if (elem.tagName.toUpperCase() === 'INPUT') {
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'value',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
this.listenTo(this.deleteButton, 'click', this.remove);
|
||||
this.listenTo(this.duplicateButton, 'click', this.duplicate);
|
||||
|
||||
this.selects.object = new ObjectSelect(this.config, this.conditionManager);
|
||||
this.selects.key = new KeySelect(
|
||||
@@ -97,8 +101,7 @@ define([
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
$('.t-configuration', self.domElement).append(select.getDOM());
|
||||
});
|
||||
|
||||
$(this.domElement).on('input', 'input', onValueInput);
|
||||
this.listenTo(this.domElement, 'input', onValueInput);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +140,11 @@ define([
|
||||
TestDataItem.prototype.remove = function () {
|
||||
var self = this;
|
||||
this.eventEmitter.emit('remove', self.index);
|
||||
this.stopListening();
|
||||
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
select.destroy();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
define([
|
||||
'./eventHelpers',
|
||||
'text!../res/testDataTemplate.html',
|
||||
'./TestDataItem',
|
||||
'zepto',
|
||||
'lodash'
|
||||
], function (
|
||||
eventHelpers,
|
||||
testDataTemplate,
|
||||
TestDataItem,
|
||||
$,
|
||||
@@ -18,6 +20,7 @@ define([
|
||||
* @param {MCT} openmct and MCT instance
|
||||
*/
|
||||
function TestDataManager(domainObject, conditionManager, openmct) {
|
||||
eventHelpers.extend(this);
|
||||
var self = this;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
@@ -45,10 +48,10 @@ define([
|
||||
self.updateTestCache();
|
||||
}
|
||||
|
||||
this.addItemButton.on('click', function () {
|
||||
this.listenTo(this.addItemButton, 'click', function () {
|
||||
self.initItem();
|
||||
});
|
||||
this.testDataInput.on('change', toggleTestData);
|
||||
this.listenTo(this.testDataInput, 'change', toggleTestData);
|
||||
|
||||
this.evaluator.setTestDataCache(this.testCache);
|
||||
this.evaluator.useTestData(false);
|
||||
@@ -186,5 +189,12 @@ define([
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
|
||||
};
|
||||
|
||||
TestDataManager.prototype.destroy = function () {
|
||||
this.items.forEach(function (item) {
|
||||
item.remove();
|
||||
});
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return TestDataManager;
|
||||
});
|
||||
|
||||
74
src/plugins/summaryWidget/src/eventHelpers.js
Normal file
74
src/plugins/summaryWidget/src/eventHelpers.js
Normal file
@@ -0,0 +1,74 @@
|
||||
var listenersCount = 0;
|
||||
/*global define*/
|
||||
// jscs:disable disallowDanglingUnderscores
|
||||
define([], function () {
|
||||
var helperFunctions = {
|
||||
listenTo: function (object, event, callback, context) {
|
||||
if (!this._listeningTo) {
|
||||
this._listeningTo = [];
|
||||
}
|
||||
var listener = {
|
||||
object: object,
|
||||
event: event,
|
||||
callback: callback,
|
||||
context: context,
|
||||
_cb: !!context ? callback.bind(context) : callback
|
||||
};
|
||||
if (object.$watch && event.indexOf('change:') === 0) {
|
||||
var scopePath = event.replace('change:', '');
|
||||
listener.unlisten = object.$watch(scopePath, listener._cb, true);
|
||||
} else if (object.$on) {
|
||||
listener.unlisten = object.$on(event, listener._cb);
|
||||
} else if (object.addEventListener) {
|
||||
object.addEventListener(event, listener._cb);
|
||||
} else {
|
||||
object.on(event, listener._cb);
|
||||
}
|
||||
this._listeningTo.push(listener);
|
||||
listenersCount++;
|
||||
},
|
||||
|
||||
stopListening: function (object, event, callback, context) {
|
||||
if (!this._listeningTo) {
|
||||
this._listeningTo = [];
|
||||
}
|
||||
|
||||
this._listeningTo.filter(function (listener) {
|
||||
if (object && object !== listener.object) {
|
||||
return false;
|
||||
}
|
||||
if (event && event !== listener.event) {
|
||||
return false;
|
||||
}
|
||||
if (callback && callback !== listener.callback) {
|
||||
return false;
|
||||
}
|
||||
if (context && context !== listener.context) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(function (listener) {
|
||||
if (listener.unlisten) {
|
||||
listener.unlisten();
|
||||
} else if (listener.object.removeEventListener) {
|
||||
listener.object.removeEventListener(listener.event, listener._cb);
|
||||
} else {
|
||||
listener.object.off(listener.event, listener._cb);
|
||||
}
|
||||
listenersCount--;
|
||||
return listener;
|
||||
})
|
||||
.forEach(function (listener) {
|
||||
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
|
||||
}, this);
|
||||
},
|
||||
|
||||
extend: function (object) {
|
||||
object.listenTo = helperFunctions.listenTo;
|
||||
object.stopListening = helperFunctions.stopListening;
|
||||
}
|
||||
};
|
||||
|
||||
return helperFunctions;
|
||||
});
|
||||
@@ -1,4 +1,8 @@
|
||||
define(['./Select'], function (Select) {
|
||||
define([
|
||||
'./Select'
|
||||
], function (
|
||||
Select
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a {Select} element whose composition is dynamically updated with
|
||||
@@ -62,7 +66,7 @@ define(['./Select'], function (Select) {
|
||||
onMetadataLoad();
|
||||
}
|
||||
|
||||
this.objectSelect.on('change', onObjectChange);
|
||||
this.objectSelect.on('change', onObjectChange, this);
|
||||
this.manager.on('metadata', onMetadataLoad);
|
||||
|
||||
return this.select;
|
||||
@@ -85,6 +89,10 @@ define(['./Select'], function (Select) {
|
||||
}
|
||||
};
|
||||
|
||||
KeySelect.prototype.destroy = function () {
|
||||
this.objectSelect.destroy();
|
||||
};
|
||||
|
||||
return KeySelect;
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
define(['./Select'], function (Select) {
|
||||
define([
|
||||
'./Select',
|
||||
'../eventHelpers'
|
||||
], function (
|
||||
Select,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a {Select} element whose composition is dynamically updated with
|
||||
@@ -17,6 +23,7 @@ define(['./Select'], function (Select) {
|
||||
var NULLVALUE = '- Select Comparison -';
|
||||
|
||||
function OperationSelect(config, keySelect, manager, changeCallback) {
|
||||
eventHelpers.extend(this);
|
||||
var self = this;
|
||||
|
||||
this.config = config;
|
||||
@@ -31,7 +38,7 @@ define(['./Select'], function (Select) {
|
||||
this.select.hide();
|
||||
this.select.addOption('', NULLVALUE);
|
||||
if (changeCallback) {
|
||||
this.select.on('change', changeCallback);
|
||||
this.listenTo(this.select, 'change', changeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +70,6 @@ define(['./Select'], function (Select) {
|
||||
}
|
||||
self.select.setSelected(self.config.operation);
|
||||
}
|
||||
|
||||
this.keySelect.on('change', onKeyChange);
|
||||
this.manager.on('metadata', onMetadataLoad);
|
||||
|
||||
@@ -109,6 +115,10 @@ define(['./Select'], function (Select) {
|
||||
});
|
||||
};
|
||||
|
||||
OperationSelect.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return OperationSelect;
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
define([
|
||||
'../eventHelpers',
|
||||
'text!../../res/input/paletteTemplate.html',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
eventHelpers,
|
||||
paletteTemplate,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* Instantiates a new Open MCT Color Palette input
|
||||
* @constructor
|
||||
@@ -19,6 +20,8 @@ define([
|
||||
* up to the descendent class
|
||||
*/
|
||||
function Palette(cssClass, container, items) {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this.cssClass = cssClass;
|
||||
@@ -49,8 +52,8 @@ define([
|
||||
|
||||
$('.menu', self.domElement).hide();
|
||||
|
||||
$(document).on('click', this.hideMenu);
|
||||
$('.l-click-area', self.domElement).on('click', function (event) {
|
||||
this.listenTo($(document), 'click', this.hideMenu);
|
||||
this.listenTo($('.l-click-area', self.domElement), 'click', function (event) {
|
||||
event.stopPropagation();
|
||||
$('.menu', self.container).hide();
|
||||
$('.menu', self.domElement).show();
|
||||
@@ -69,7 +72,7 @@ define([
|
||||
$('.menu', self.domElement).hide();
|
||||
}
|
||||
|
||||
$('.s-palette-item', self.domElement).on('click', handleItemClick);
|
||||
this.listenTo($('.s-palette-item', self.domElement), 'click', handleItemClick);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +86,7 @@ define([
|
||||
* Clean up any event listeners registered to DOM elements external to the widget
|
||||
*/
|
||||
Palette.prototype.destroy = function () {
|
||||
$(document).off('click', this.hideMenu);
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
Palette.prototype.hideMenu = function () {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
define([
|
||||
'../eventHelpers',
|
||||
'text!../../res/input/selectTemplate.html',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
eventHelpers,
|
||||
selectTemplate,
|
||||
EventEmitter,
|
||||
$
|
||||
@@ -14,6 +16,8 @@ define([
|
||||
* @constructor
|
||||
*/
|
||||
function Select() {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this.domElement = $(selectTemplate);
|
||||
@@ -36,7 +40,7 @@ define([
|
||||
self.eventEmitter.emit('change', value[0]);
|
||||
}
|
||||
|
||||
$('select', this.domElement).on('change', onChange);
|
||||
this.listenTo($('select', this.domElement), 'change', onChange, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,5 +144,9 @@ define([
|
||||
$('.equal-to').removeClass('hidden');
|
||||
};
|
||||
|
||||
Select.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return Select;
|
||||
});
|
||||
|
||||
76
src/plugins/telemetryMean/plugin.js
Executable file
76
src/plugins/telemetryMean/plugin.js
Executable file
@@ -0,0 +1,76 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./src/MeanTelemetryProvider'], function (MeanTelemetryProvider) {
|
||||
var DEFAULT_SAMPLES = 10;
|
||||
|
||||
function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('telemetry-mean', {
|
||||
name: 'Telemetry Filter',
|
||||
description: 'Provides telemetry values that represent the mean of the last N values of a telemetry stream',
|
||||
creatable: true,
|
||||
cssClass: 'icon-telemetry',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.samples = DEFAULT_SAMPLES;
|
||||
domainObject.telemetry = {};
|
||||
domainObject.telemetry.values =
|
||||
openmct.time.getAllTimeSystems().map(function (timeSystem, index) {
|
||||
return {
|
||||
key: timeSystem.key,
|
||||
name: timeSystem.name,
|
||||
hints: {
|
||||
domain: index + 1
|
||||
}
|
||||
};
|
||||
});
|
||||
domainObject.telemetry.values.push({
|
||||
key: "value",
|
||||
name: "Value",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
});
|
||||
},
|
||||
form: [
|
||||
{
|
||||
"key": "telemetryPoint",
|
||||
"name": "Telemetry Point",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
{
|
||||
"key": "samples",
|
||||
"name": "Samples to Average",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-sm"
|
||||
}
|
||||
]
|
||||
});
|
||||
openmct.telemetry.addProvider(new MeanTelemetryProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
return plugin;
|
||||
});
|
||||
116
src/plugins/telemetryMean/src/MeanTelemetryProvider.js
Normal file
116
src/plugins/telemetryMean/src/MeanTelemetryProvider.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
/*jshint latedef: nofunc */
|
||||
/*global console */
|
||||
define([
|
||||
'../../../api/objects/object-utils',
|
||||
'./TelemetryAverager'
|
||||
], function (objectUtils, TelemetryAverager) {
|
||||
|
||||
function MeanTelemetryProvider(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.telemetryAPI = openmct.telemetry;
|
||||
this.timeAPI = openmct.time;
|
||||
this.objectAPI = openmct.objects;
|
||||
this.perObjectProviders = {};
|
||||
}
|
||||
|
||||
MeanTelemetryProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'telemetry-mean';
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.supportsRequest =
|
||||
MeanTelemetryProvider.prototype.supportsSubscribe =
|
||||
MeanTelemetryProvider.prototype.canProvideTelemetry;
|
||||
|
||||
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var wrappedUnsubscribe;
|
||||
var unsubscribeCalled = false;
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
var samples = domainObject.samples;
|
||||
|
||||
this.objectAPI.get(objectId)
|
||||
.then(function (linkedDomainObject) {
|
||||
if (!unsubscribeCalled) {
|
||||
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(logError);
|
||||
|
||||
return function unsubscribe() {
|
||||
unsubscribeCalled = true;
|
||||
if (wrappedUnsubscribe !== undefined) {
|
||||
wrappedUnsubscribe();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, samples, callback) {
|
||||
var telemetryAverager = new TelemetryAverager(this.telemetryAPI, this.timeAPI, domainObject, samples, callback);
|
||||
var createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
|
||||
|
||||
return this.telemetryAPI.subscribe(domainObject, createAverageDatum);
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
var samples = domainObject.samples;
|
||||
|
||||
return this.objectAPI.get(objectId).then(function (linkedDomainObject) {
|
||||
return this.requestAverageTelemetry(linkedDomainObject, request, samples);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.requestAverageTelemetry = function (domainObject, request, samples) {
|
||||
var averageData = [];
|
||||
var addToAverageData = averageData.push.bind(averageData);
|
||||
var telemetryAverager = new TelemetryAverager(this.telemetryAPI, this.timeAPI, domainObject, samples, addToAverageData);
|
||||
var createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
|
||||
|
||||
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData) {
|
||||
telemetryData.forEach(createAverageDatum);
|
||||
|
||||
return averageData;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
|
||||
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
return this.objectAPI.get(objectId);
|
||||
};
|
||||
|
||||
function logError(error) {
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return MeanTelemetryProvider;
|
||||
});
|
||||
460
src/plugins/telemetryMean/src/MeanTelemetryProviderSpec.js
Normal file
460
src/plugins/telemetryMean/src/MeanTelemetryProviderSpec.js
Normal file
@@ -0,0 +1,460 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*jshint latedef: nofunc */
|
||||
define([
|
||||
"./MeanTelemetryProvider",
|
||||
"./MockTelemetryApi"
|
||||
], function (
|
||||
MeanTelemetryProvider,
|
||||
MockTelemetryApi
|
||||
) {
|
||||
var RANGE_KEY = 'value';
|
||||
|
||||
describe("The Mean Telemetry Provider", function () {
|
||||
var mockApi;
|
||||
var meanTelemetryProvider;
|
||||
var outstandingPromises = 0;
|
||||
var mockDomainObject;
|
||||
var associatedObject;
|
||||
|
||||
beforeEach(function () {
|
||||
createMockApi();
|
||||
setTimeSystemTo('utc');
|
||||
createMockObjects();
|
||||
meanTelemetryProvider = new MeanTelemetryProvider(mockApi);
|
||||
});
|
||||
|
||||
it("supports telemetry-mean objects only", function () {
|
||||
var mockTelemetryMeanObject = mockObjectWithType('telemetry-mean');
|
||||
var mockOtherObject = mockObjectWithType('other');
|
||||
|
||||
expect(meanTelemetryProvider.canProvideTelemetry(mockTelemetryMeanObject)).toBe(true);
|
||||
expect(meanTelemetryProvider.canProvideTelemetry(mockOtherObject)).toBe(false);
|
||||
});
|
||||
|
||||
describe("the subscribe function", function () {
|
||||
var subscriptionCallback;
|
||||
|
||||
beforeEach(function () {
|
||||
subscriptionCallback = jasmine.createSpy('subscriptionCallback');
|
||||
});
|
||||
|
||||
it("subscribes to telemetry for the associated object", function () {
|
||||
meanTelemetryProvider.subscribe(mockDomainObject);
|
||||
|
||||
expectObjectWasSubscribedTo(associatedObject);
|
||||
});
|
||||
|
||||
it("returns a function that unsubscribes from the associated object", function () {
|
||||
var unsubscribe = meanTelemetryProvider.subscribe(mockDomainObject);
|
||||
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(unsubscribe);
|
||||
|
||||
expectUnsubscribeFrom(associatedObject);
|
||||
});
|
||||
|
||||
it("returns an average only when the sample size is reached", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313}
|
||||
];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectNoAverageForTelemetry(inputTelemetry);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of five values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231}
|
||||
];
|
||||
var expectedAverages = [{
|
||||
'utc': 5, 'value': 222.44888
|
||||
}];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of ten values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var expectedAverages = [{
|
||||
'utc': 10, 'value': 451.07815
|
||||
}];
|
||||
|
||||
setSampleSize(10);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
|
||||
it("only averages values within its sample window", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var expectedAverages = [
|
||||
{'utc': 5, 'value': 222.44888},
|
||||
{'utc': 6, 'value': 662.4482599999999},
|
||||
{'utc': 7, 'value': 704.6078},
|
||||
{'utc': 8, 'value': 773.02748},
|
||||
{'utc': 9, 'value': 679.8234399999999},
|
||||
{'utc': 10, 'value': 679.70742}
|
||||
];
|
||||
|
||||
setSampleSize(5);
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
|
||||
});
|
||||
describe("given telemetry input with range values", function () {
|
||||
var inputTelemetry;
|
||||
|
||||
beforeEach(function () {
|
||||
inputTelemetry = [{
|
||||
'utc': 1,
|
||||
'rangeKey': 5678,
|
||||
'otherKey': 9999
|
||||
}];
|
||||
setSampleSize(1);
|
||||
});
|
||||
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForRangeKey = [{
|
||||
'utc': 1,
|
||||
'value': 5678
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('rangeKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
|
||||
});
|
||||
|
||||
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForOtherKey = [{
|
||||
'utc': 1,
|
||||
'value': 9999
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('otherKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
|
||||
|
||||
});
|
||||
});
|
||||
describe("given telemetry input with range values", function () {
|
||||
var inputTelemetry;
|
||||
|
||||
beforeEach(function () {
|
||||
inputTelemetry = [{
|
||||
'utc': 1,
|
||||
'rangeKey': 5678,
|
||||
'otherKey': 9999
|
||||
}];
|
||||
setSampleSize(1);
|
||||
});
|
||||
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForRangeKey = [{
|
||||
'utc': 1,
|
||||
'value': 5678
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('rangeKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
|
||||
});
|
||||
|
||||
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
|
||||
var averageTelemetryForOtherKey = [{
|
||||
'utc': 1,
|
||||
'value': 9999
|
||||
}];
|
||||
|
||||
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
|
||||
mockApi.telemetry.setDefaultRangeTo('otherKey');
|
||||
feedInputTelemetry(inputTelemetry);
|
||||
|
||||
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
|
||||
});
|
||||
});
|
||||
|
||||
function feedInputTelemetry(inputTelemetry) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
inputTelemetry.forEach(mockApi.telemetry.mockReceiveTelemetry);
|
||||
});
|
||||
}
|
||||
|
||||
function expectNoAverageForTelemetry(inputTelemetry) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(subscriptionCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
function expectAveragesForTelemetry(expectedAverages) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expectedAverages.forEach(function (averageDatum) {
|
||||
expect(subscriptionCallback).toHaveBeenCalledWith(averageDatum);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function expectObjectWasSubscribedTo(object) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.subscribe).toHaveBeenCalledWith(object, jasmine.any(Function));
|
||||
});
|
||||
}
|
||||
|
||||
function expectUnsubscribeFrom() {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
describe("the request function", function () {
|
||||
|
||||
it("requests telemetry for the associated object", function () {
|
||||
meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectTelemetryToBeRequestedFor(associatedObject);
|
||||
});
|
||||
|
||||
it("returns an average only when the sample size is reached", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectEmptyResponse(promiseForAverage);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of five values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(222.44888, promiseForAverage);
|
||||
});
|
||||
|
||||
it("correctly averages a sample of ten values", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(10);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(451.07815, promiseForAverage);
|
||||
});
|
||||
|
||||
it("only averages values within its sample window", function () {
|
||||
var inputTelemetry = [
|
||||
{'utc': 1, 'defaultRange': 123.1231},
|
||||
{'utc': 2, 'defaultRange': 321.3223},
|
||||
{'utc': 3, 'defaultRange': 111.4446},
|
||||
{'utc': 4, 'defaultRange': 555.2313},
|
||||
{'utc': 5, 'defaultRange': 1.1231},
|
||||
{'utc': 6, 'defaultRange': 2323.12},
|
||||
{'utc': 7, 'defaultRange': 532.12},
|
||||
{'utc': 8, 'defaultRange': 453.543},
|
||||
{'utc': 9, 'defaultRange': 89.2111},
|
||||
{'utc': 10, 'defaultRange': 0.543}
|
||||
];
|
||||
var promiseForAverage;
|
||||
|
||||
setSampleSize(5);
|
||||
whenTelemetryRequestedReturn(inputTelemetry);
|
||||
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
|
||||
|
||||
expectAverageToBe(679.70742, promiseForAverage);
|
||||
});
|
||||
|
||||
function expectAverageToBe(expectedValue, promiseForAverage) {
|
||||
var averageData;
|
||||
|
||||
promiseForAverage.then(function (data) {
|
||||
averageData = data;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return averageData !== undefined;
|
||||
}, 'data to return from request', 1);
|
||||
runs(function () {
|
||||
var averageDatum = averageData[averageData.length - 1];
|
||||
expect(averageDatum[RANGE_KEY]).toBe(expectedValue);
|
||||
});
|
||||
}
|
||||
|
||||
function expectEmptyResponse(promiseForAverage) {
|
||||
var averageData;
|
||||
|
||||
promiseForAverage.then(function (data) {
|
||||
averageData = data;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return averageData !== undefined;
|
||||
}, 'data to return from request', 1);
|
||||
runs(function () {
|
||||
expect(averageData.length).toBe(0);
|
||||
});
|
||||
}
|
||||
|
||||
function whenTelemetryRequestedReturn(telemetry) {
|
||||
mockApi.telemetry.request.andReturn(resolvePromiseWith(telemetry));
|
||||
}
|
||||
|
||||
function expectTelemetryToBeRequestedFor(object) {
|
||||
waitsFor(allPromisesToBeResolved);
|
||||
runs(function () {
|
||||
expect(mockApi.telemetry.request).toHaveBeenCalledWith(object, undefined);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function createMockObjects() {
|
||||
mockDomainObject = {
|
||||
telemetryPoint: 'someTelemetryPoint'
|
||||
};
|
||||
associatedObject = {};
|
||||
mockApi.objects.get.andReturn(resolvePromiseWith(associatedObject));
|
||||
}
|
||||
|
||||
function setSampleSize(sampleSize) {
|
||||
mockDomainObject.samples = sampleSize;
|
||||
}
|
||||
|
||||
function createMockApi() {
|
||||
mockApi = {
|
||||
telemetry: new MockTelemetryApi(),
|
||||
objects: createMockObjectApi(),
|
||||
time: createMockTimeApi()
|
||||
};
|
||||
}
|
||||
|
||||
function createMockObjectApi() {
|
||||
return jasmine.createSpyObj('ObjectAPI', [
|
||||
'get'
|
||||
]);
|
||||
}
|
||||
|
||||
function mockObjectWithType(type) {
|
||||
return {
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePromiseWith(value) {
|
||||
outstandingPromises++;
|
||||
return Promise.resolve(value).then(function () {
|
||||
outstandingPromises--;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function allPromisesToBeResolved() {
|
||||
return outstandingPromises === 0;
|
||||
}
|
||||
|
||||
function createMockTimeApi() {
|
||||
return jasmine.createSpyObj("timeApi", ['timeSystem']);
|
||||
}
|
||||
|
||||
function setTimeSystemTo(timeSystemKey) {
|
||||
mockApi.time.timeSystem.andReturn({
|
||||
key: timeSystemKey
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
106
src/plugins/telemetryMean/src/MockTelemetryApi.js
Normal file
106
src/plugins/telemetryMean/src/MockTelemetryApi.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
/*global jasmine, spyOn */
|
||||
define([], function () {
|
||||
|
||||
function MockTelemetryApi() {
|
||||
this.createSpy('subscribe');
|
||||
this.createSpy('getMetadata');
|
||||
|
||||
this.metadata = this.createMockMetadata();
|
||||
this.setDefaultRangeTo('defaultRange');
|
||||
this.unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
this.mockReceiveTelemetry = this.mockReceiveTelemetry.bind(this);
|
||||
}
|
||||
|
||||
MockTelemetryApi.prototype.subscribe = function () {
|
||||
return this.unsubscribe;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.getMetadata = function (object) {
|
||||
return this.metadata;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.request = jasmine.createSpy('request');
|
||||
|
||||
MockTelemetryApi.prototype.getValueFormatter = function (valueMetadata) {
|
||||
var mockValueFormatter = jasmine.createSpyObj("valueFormatter", [
|
||||
"parse"
|
||||
]);
|
||||
|
||||
mockValueFormatter.parse.andCallFake(function (value) {
|
||||
return value[valueMetadata.key];
|
||||
});
|
||||
|
||||
return mockValueFormatter;
|
||||
};
|
||||
|
||||
MockTelemetryApi.prototype.mockReceiveTelemetry = function (newTelemetryDatum) {
|
||||
var subscriptionCallback = this.subscribe.mostRecentCall.args[1];
|
||||
subscriptionCallback(newTelemetryDatum);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.onRequestReturn = function (telemetryData) {
|
||||
this.requestTelemetry = telemetryData;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.setDefaultRangeTo = function (rangeKey) {
|
||||
var mockMetadataValue = {
|
||||
key: rangeKey
|
||||
};
|
||||
this.metadata.valuesForHints.andReturn([mockMetadataValue]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.createMockMetadata = function () {
|
||||
var mockMetadata = jasmine.createSpyObj("metadata", [
|
||||
'value',
|
||||
'valuesForHints'
|
||||
]);
|
||||
|
||||
mockMetadata.value.andCallFake(function (key) {
|
||||
return {
|
||||
key: key
|
||||
};
|
||||
});
|
||||
return mockMetadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MockTelemetryApi.prototype.createSpy = function (functionName) {
|
||||
this[functionName] = this[functionName].bind(this);
|
||||
spyOn(this, functionName);
|
||||
this[functionName].andCallThrough();
|
||||
};
|
||||
|
||||
return MockTelemetryApi;
|
||||
});
|
||||
120
src/plugins/telemetryMean/src/TelemetryAverager.js
Normal file
120
src/plugins/telemetryMean/src/TelemetryAverager.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
function TelemetryAverager(telemetryAPI, timeAPI, domainObject, samples, averageDatumCallback) {
|
||||
this.telemetryAPI = telemetryAPI;
|
||||
this.timeAPI = timeAPI;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.samples = samples;
|
||||
this.averagingWindow = [];
|
||||
|
||||
this.rangeKey = undefined;
|
||||
this.rangeFormatter = undefined;
|
||||
this.setRangeKeyAndFormatter();
|
||||
|
||||
// Defined dynamically based on current time system
|
||||
this.domainKey = undefined;
|
||||
this.domainFormatter = undefined;
|
||||
|
||||
this.averageDatumCallback = averageDatumCallback;
|
||||
}
|
||||
|
||||
TelemetryAverager.prototype.createAverageDatum = function (telemetryDatum) {
|
||||
this.setDomainKeyAndFormatter();
|
||||
|
||||
var timeValue = this.domainFormatter.parse(telemetryDatum);
|
||||
var rangeValue = this.rangeFormatter.parse(telemetryDatum);
|
||||
|
||||
this.averagingWindow.push(rangeValue);
|
||||
|
||||
if (this.averagingWindow.length < this.samples) {
|
||||
// We do not have enough data to produce an average
|
||||
return;
|
||||
} else if (this.averagingWindow.length > this.samples) {
|
||||
//Do not let averaging window grow beyond defined sample size
|
||||
this.averagingWindow.shift();
|
||||
}
|
||||
|
||||
var averageValue = this.calculateMean();
|
||||
|
||||
var meanDatum = {};
|
||||
meanDatum[this.domainKey] = timeValue;
|
||||
meanDatum.value = averageValue;
|
||||
|
||||
this.averageDatumCallback(meanDatum);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.calculateMean = function () {
|
||||
var sum = 0;
|
||||
var i = 0;
|
||||
|
||||
for (; i < this.averagingWindow.length; i++) {
|
||||
sum += this.averagingWindow[i];
|
||||
}
|
||||
|
||||
return sum / this.averagingWindow.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* The mean telemetry filter produces domain values in whatever time
|
||||
* system is currently selected from the conductor. Because this can
|
||||
* change dynamically, the averager needs to be updated regularly with
|
||||
* the current domain.
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
|
||||
var domainKey = this.timeAPI.timeSystem().key;
|
||||
if (domainKey !== this.domainKey) {
|
||||
this.domainKey = domainKey;
|
||||
this.domainFormatter = this.getFormatter(domainKey);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.setRangeKeyAndFormatter = function () {
|
||||
var metadatas = this.telemetryAPI.getMetadata(this.domainObject);
|
||||
var rangeValues = metadatas.valuesForHints(['range']);
|
||||
|
||||
this.rangeKey = rangeValues[0].key;
|
||||
this.rangeFormatter = this.getFormatter(this.rangeKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.getFormatter = function (key) {
|
||||
var objectMetadata = this.telemetryAPI.getMetadata(this.domainObject);
|
||||
var valueMetadata = objectMetadata.value(key);
|
||||
|
||||
return this.telemetryAPI.getValueFormatter(valueMetadata);
|
||||
};
|
||||
|
||||
return TelemetryAverager;
|
||||
});
|
||||
@@ -33,37 +33,96 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
|
||||
Selection.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
Selection.prototype.add = function (context) {
|
||||
this.clear(); // Only allow single select as initial simplification
|
||||
this.selected.push(context);
|
||||
this.emit('change');
|
||||
};
|
||||
|
||||
Selection.prototype.remove = function (path) {
|
||||
this.selected = this.selected.filter(function (otherPath) {
|
||||
return !path.matches(otherPath);
|
||||
});
|
||||
this.emit('change');
|
||||
};
|
||||
|
||||
Selection.prototype.contains = function (path) {
|
||||
return this.selected.some(function (otherPath) {
|
||||
return path.matches(otherPath);
|
||||
});
|
||||
};
|
||||
|
||||
Selection.prototype.clear = function () {
|
||||
this.selected = [];
|
||||
this.emit('change');
|
||||
};
|
||||
|
||||
Selection.prototype.primary = function () {
|
||||
return this.selected[this.selected.length - 1];
|
||||
};
|
||||
|
||||
Selection.prototype.all = function () {
|
||||
/**
|
||||
* Gets the selected object.
|
||||
* @public
|
||||
*/
|
||||
Selection.prototype.get = function () {
|
||||
return this.selected;
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the selectable object and emits the 'change' event.
|
||||
*
|
||||
* @param {object} selectable an object with element and context properties
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.select = function (selectable) {
|
||||
if (!Array.isArray(selectable)) {
|
||||
selectable = [selectable];
|
||||
}
|
||||
|
||||
if (this.selected[0] && this.selected[0].element) {
|
||||
this.selected[0].element.classList.remove('s-selected');
|
||||
}
|
||||
|
||||
if (this.selected[1]) {
|
||||
this.selected[1].element.classList.remove('s-selected-parent');
|
||||
}
|
||||
|
||||
if (selectable[0] && selectable[0].element) {
|
||||
selectable[0].element.classList.add('s-selected');
|
||||
}
|
||||
|
||||
if (selectable[1]) {
|
||||
selectable[1].element.classList.add('s-selected-parent');
|
||||
}
|
||||
|
||||
this.selected = selectable;
|
||||
this.emit('change', this.selected);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.capture = function (selectable) {
|
||||
if (!this.capturing) {
|
||||
this.capturing = [];
|
||||
}
|
||||
|
||||
this.capturing.push(selectable);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.selectCapture = function (selectable) {
|
||||
if (!this.capturing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.select(this.capturing.reverse());
|
||||
delete this.capturing;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the click handlers to the element.
|
||||
*
|
||||
* @param element an html element
|
||||
* @param context object with oldItem, item and toolbar properties
|
||||
* @param select a flag to select the element if true
|
||||
* @returns a function that removes the click handlers from the element
|
||||
* @public
|
||||
*/
|
||||
Selection.prototype.selectable = function (element, context, select) {
|
||||
var selectable = {
|
||||
context: context,
|
||||
element: element
|
||||
};
|
||||
var capture = this.capture.bind(this, selectable);
|
||||
var selectCapture = this.selectCapture.bind(this, selectable);
|
||||
element.addEventListener('click', capture, true);
|
||||
element.addEventListener('click', selectCapture);
|
||||
|
||||
if (select) {
|
||||
this.select(selectable);
|
||||
}
|
||||
|
||||
return function () {
|
||||
element.removeEventListener('click', capture);
|
||||
element.removeEventListener('click', selectCapture);
|
||||
};
|
||||
};
|
||||
|
||||
return Selection;
|
||||
});
|
||||
|
||||
154
src/ui/InspectorViewRegistry.js
Normal file
154
src/ui/InspectorViewRegistry.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
/*global console */
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* A InspectorViewRegistry maintains the definitions for views
|
||||
* that may occur in the inspector.
|
||||
*
|
||||
* @interface InspectorViewRegistry
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function InspectorViewRegistry() {
|
||||
this.providers = {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} selection the object to be viewed
|
||||
* @returns {module:openmct.InspectorViewRegistry[]} any providers
|
||||
* which can provide views of this object
|
||||
* @private for platform-internal use
|
||||
*/
|
||||
InspectorViewRegistry.prototype.get = function (selection) {
|
||||
var providers = this.getAllProviders().filter(function (provider) {
|
||||
return provider.canView(selection);
|
||||
});
|
||||
|
||||
if (providers && providers.length > 0) {
|
||||
return providers[0].view(selection);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
InspectorViewRegistry.prototype.getAllProviders = function () {
|
||||
return Object.values(this.providers);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new type of view.
|
||||
*
|
||||
* @param {module:openmct.InspectorViewRegistry} provider the provider for this view
|
||||
* @method addProvider
|
||||
* @memberof module:openmct.InspectorViewRegistry#
|
||||
*/
|
||||
InspectorViewRegistry.prototype.addProvider = function (provider) {
|
||||
var key = provider.key;
|
||||
|
||||
if (key === undefined) {
|
||||
throw "View providers must have a unique 'key' property defined";
|
||||
}
|
||||
|
||||
if (this.providers[key] !== undefined) {
|
||||
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
|
||||
}
|
||||
|
||||
this.providers[key] = provider;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
InspectorViewRegistry.prototype.getByProviderKey = function (key) {
|
||||
return this.providers[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* A View is used to provide displayable content, and to react to
|
||||
* associated life cycle events.
|
||||
*
|
||||
* @name View
|
||||
* @interface
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
|
||||
/**
|
||||
* Populate the supplied DOM element with the contents of this view.
|
||||
*
|
||||
* View implementations should use this method to attach any
|
||||
* listeners or acquire other resources that are necessary to keep
|
||||
* the contents of this view up-to-date.
|
||||
*
|
||||
* @param {HTMLElement} container the DOM element to populate
|
||||
* @method show
|
||||
* @memberof module:openmct.View#
|
||||
*/
|
||||
|
||||
/**
|
||||
* Release any resources associated with this view.
|
||||
*
|
||||
* View implementations should use this method to detach any
|
||||
* listeners or release other resources that are no longer necessary
|
||||
* once a view is no longer used.
|
||||
*
|
||||
* @method destroy
|
||||
* @memberof module:openmct.View#
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exposes types of views in inspector.
|
||||
*
|
||||
* @interface InspectorViewProvider
|
||||
* @property {string} key a unique identifier for this view
|
||||
* @property {string} name the human-readable name of this view
|
||||
* @property {string} [description] a longer-form description (typically
|
||||
* a single sentence or short paragraph) of this kind of view
|
||||
* @property {string} [cssClass] the CSS class to apply to labels for this
|
||||
* view (to add icons, for instance)
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if this provider can supply views for a selection.
|
||||
*
|
||||
* @method canView
|
||||
* @memberof module:openmct.InspectorViewProvider#
|
||||
* @param {module:openmct.selection} selection
|
||||
* @returns {boolean} 'true' if the view applies to the provided selection,
|
||||
* otherwise 'false'.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a view of the selection object in the inspector.
|
||||
*
|
||||
* @method view
|
||||
* @memberof module:openmct.InspectorViewProvider#
|
||||
* @param {module:openmct.selection} selection the selection object
|
||||
* @returns {module:openmct.View} a view of this selection
|
||||
*/
|
||||
|
||||
return InspectorViewRegistry;
|
||||
});
|
||||
33
src/ui/VueView.js
Normal file
33
src/ui/VueView.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['vue'], function (Vue) {
|
||||
function VueView(options) {
|
||||
var vm = new Vue(options);
|
||||
this.show = function (container) {
|
||||
container.appendChild(vm.$mount().$el);
|
||||
};
|
||||
this.destroy = vm.$destroy.bind(vm);
|
||||
}
|
||||
|
||||
return VueView;
|
||||
});
|
||||
@@ -63,6 +63,7 @@ requirejs.config({
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
"vue": "node_modules/vue/dist/vue.min",
|
||||
"zepto": "bower_components/zepto/zepto.min",
|
||||
"lodash": "bower_components/lodash/lodash",
|
||||
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
|
||||
|
||||
Reference in New Issue
Block a user