Compare commits

..

9 Commits

Author SHA1 Message Date
Henry
7e3ade81e3 WIP 2017-12-11 12:31:47 -08:00
Henry
05ed6597e8 [Examples] Return fibonacci number as text 2017-12-11 12:31:03 -08:00
Henry
638651a0ad [Examples] Return example user on demand instead of on service construction 2017-12-11 12:31:03 -08:00
Henry
dbae627ad9 WIP 2017-12-11 12:31:03 -08:00
Henry
a6e93f7ad0 [API] Some refactoring of the Indicator API 2017-12-11 12:31:03 -08:00
Henry
0dc75b2ace [Indicators] Support legacy indicators with new API 2017-12-11 12:31:03 -08:00
Henry
96e11c4f1b [Indicators] Initial integration of new Indicator API with existing indicators 2017-12-11 12:31:03 -08:00
Henry
6cc7f23786 [API] Initial implementation of Indicator API 2017-12-11 12:31:03 -08:00
Henry
ce11d48d25 WIP 2017-12-11 12:31:03 -08:00
60 changed files with 688 additions and 1127 deletions

View File

@@ -21,6 +21,5 @@
"shadow": "outer",
"strict": "implied",
"undef": true,
"unused": "vars",
"latedef": "nofunc"
"unused": "vars"
}

View File

@@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
To build Open MCT for deployment:
`npm run prepare`
`npm run prepublish`
This will compile and minify JavaScript sources, as well as copy over assets.
The contents of the `dist` folder will contain a runnable Open MCT

View File

@@ -1,11 +1,3 @@
machine:
node:
version: 4.7.0
dependencies:
pre:
- npm install -g npm@latest
deployment:
production:
branch: master
@@ -24,4 +16,4 @@ test:
general:
branches:
ignore:
- gh-pages
- gh-pages

View File

@@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
To build:
`npm run prepare`
`npm run prepublish`
This will compile and minify JavaScript sources, as well as copy over assets.
The contents of the `dist` folder will contain a runnable Open MCT

View File

@@ -1,121 +0,0 @@
# Security Guide
Open MCT is a rich client with plugin support that executes as a single page
web application in a browser environment. Security concerns and
vulnerabilities associated with the web as a platform should be considered
before deploying Open MCT (or any other web application) for mission or
production usage.
This document describes several important points to consider when developing
for or deploying Open MCT securely. Other resources such as
[Open Web Application Security Project (OWASP)](https://www.owasp.org)
provide a deeper and more general overview of security for web applications.
## Security Model
Open MCT has been architected assuming the following deployment pattern:
* A tagged, tested Open MCT version will be used.
* Externally authored plugins will be installed.
* A server will provide persistent storage, telemetry, and other shared data.
* Authorization, authentication, and auditing will be handled by a server.
## Security Procedures
The Open MCT team secures our code base using a combination of code review,
dependency review, and periodic security reviews. Static analysis performed
during automated verification additionally safeguards against common
coding errors which may result in vulnerabilities.
### Code Review
All contributions are reviewed by internal team members. External
contributors receive increased scrutiny for security and quality,
and must sign a licensing agreement.
### Dependency Review
Before integrating third-party dependencies, they are reviewed for security
and quality, with consideration given to authors and users of these
dependencies, as well as review of open source code.
### Periodic Security Reviews
Open MCT's code, design, and architecture are periodically reviewed
(approximately annually) for common security issues, such as the
[OWASP Top Ten](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project).
## Security Concerns
Certain security concerns deserve special attention when deploying Open MCT,
or when authoring plugins.
### Identity Spoofing
Open MCT issues calls to web services with the privileges of a logged in user.
Compromised sources (either for Open MCT itself or a plugin) could
therefore allow malicious code to execute with those privileges.
To avoid this:
* Serve Open MCT and other scripts over SSL (https rather than http)
to prevent man-in-the-middle attacks.
* Exercise precautions such as security reviews for any plugins or
applications built for or with Open MCT to reject malicious changes.
### Information Disclosure
If Open MCT is used to handle or display sensitive data, any components
(such as adapter plugins) must take care to avoid leaking or disclosing
this information. For example, avoid sending sensitive data to third-party
servers or insecure APIs.
### Data Tampering
The web application architecture leaves open the possibility that direct
calls will be made to back-end services, circumventing Open MCT entirely.
As such, Open MCT assumes that server components will perform any necessary
data validation during calls issues to the server.
Additionally, plugins which serialize and write data to the server must
escape that data to avoid database injection attacks, and similar.
### Repudiation
Open MCT assumes that servers log any relevant interactions and associates
these with a user identity; the specific user actions taken within the
application are assumed not to be of concern for auditing.
In the absence of server-side logging, users may disclaim (maliciously,
mistakenly, or otherwise) actions taken within the system without any
way to prove otherwise.
If keeping client-level interactions is important, this will need to be
implemented via a plugin.
### Denial-of-service
Open MCT assumes that server-side components will be insulated against
denial-of-service attacks. Services should only permit resource-intensive
tasks to be initiated by known or trusted users.
### Elevation of Privilege
Corollary to the assumption that servers guide against identity spoofing,
Open MCT assumes that services do not allow a user to act with
inappropriately escalated privileges. Open MCT cannot protect against
such escalation; in the clearest case, a malicious actor could interact
with web services directly to exploit such a vulnerability.
## Additional Reading
The following resources have been used as a basis for identifying potential
security threats to Open MCT deployments in preparation of this document:
* [STRIDE model](https://www.owasp.org/index.php/Threat_Risk_Modeling#STRIDE)
* [Attack Surface Analysis Cheat Sheet](https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet)
* [XSS Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)

View File

@@ -38,7 +38,8 @@ define([
"provides": "identityService",
"type": "provider",
"depends": [
"dialogService"
"dialogService",
"$q"
]
}
]

View File

@@ -55,21 +55,37 @@ define(
* @implements {IdentityService}
* @memberof platform/identity
*/
function ExampleIdentityProvider(dialogService) {
// Handle rejected dialog messages by treating the
// current user as undefined.
function echo(v) { return v; }
function giveUndefined() { return undefined; }
function ExampleIdentityProvider(dialogService, $q) {
this.dialogService = dialogService;
this.$q = $q;
this.userPromise =
dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY)
.then(echo, giveUndefined);
this.returnUser = this.returnUser.bind(this);
this.returnUndefined = this.returnUndefined.bind(this);
}
ExampleIdentityProvider.prototype.getUser = function () {
return this.userPromise;
if (this.user) {
return this.$q.when(this.user);
} else {
return this.dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY)
.then(this.returnUser, this.returnUndefined);
}
};
/**
* @private
*/
ExampleIdentityProvider.prototype.returnUser = function (user) {
return this.user = user;
}
/**
* @private
*/
ExampleIdentityProvider.prototype.returnUndefined = function () {
return undefined;
}
return ExampleIdentityProvider;
}
);

View File

@@ -54,7 +54,7 @@ define(
return "icon-object-unknown";
},
getText: function () {
return latest;
return "" + latest;
},
getDescription: function () {
return "";

View File

@@ -22,8 +22,6 @@
/*global require,__dirname*/
require("v8-compile-cache");
var gulp = require('gulp'),
sourcemaps = require('gulp-sourcemaps'),
path = require('path'),
@@ -179,4 +177,4 @@ gulp.task('install', [ 'assets', 'scripts' ]);
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
gulp.task('build', [ 'verify', 'install' ]);
gulp.task('build', [ 'verify', 'install' ]);

View File

@@ -40,7 +40,7 @@ requirejs.config({
"vue": "node_modules/vue/dist/vue.min",
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",

View File

@@ -50,8 +50,7 @@
"moment": "^2.11.1",
"node-bourbon": "^4.2.3",
"requirejs": "2.1.x",
"split": "^1.0.0",
"v8-compile-cache": "^1.1.0"
"split": "^1.0.0"
},
"scripts": {
"start": "node app.js",
@@ -61,7 +60,7 @@
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"docs": "npm run jsdoc ; npm run otherdoc",
"prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
},
"repository": {
"type": "git",

View File

@@ -28,6 +28,16 @@ define(
[],
function () {
function isDirty(domainObject) {
var navigatedObject = domainObject,
editorCapability = navigatedObject &&
navigatedObject.getCapability("editor");
return editorCapability &&
editorCapability.isEditContextRoot() &&
editorCapability.dirty();
}
function cancelEditing(domainObject) {
var navigatedObject = domainObject,
editorCapability = navigatedObject &&
@@ -49,7 +59,10 @@ define(
var removeCheck = navigationService
.checkBeforeNavigation(function () {
return "Continuing will cause the loss of any unsaved changes.";
if (isDirty(domainObject)) {
return "Continuing will cause the loss of any unsaved changes.";
}
return false;
});
$scope.$on('$destroy', function () {

View File

@@ -52,22 +52,8 @@ define(
}
function setSelection(selection) {
if (!selection[0]) {
return;
}
if (self.mutationListener) {
self.mutationListener();
delete self.mutationListener;
}
var domainObject = selection[0].context.oldItem;
self.refreshComposition(domainObject);
if (domainObject) {
self.mutationListener = domainObject.getCapability('mutation')
.listen(self.refreshComposition.bind(self, domainObject));
}
self.scope.selection = selection;
self.refreshComposition(selection);
}
$scope.filterBy = filterBy;
@@ -84,11 +70,15 @@ define(
/**
* Gets the composition for the selected object and populates the scope with it.
*
* @param domainObject the selected object
* @param selection the selection object
* @private
*/
ElementsController.prototype.refreshComposition = function (domainObject) {
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
ElementsController.prototype.refreshComposition = function (selection) {
if (!selection[0]) {
return;
}
var selectedObjectComposition = selection[0].context.oldItem.useCapability('composition');
if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) {

View File

@@ -44,17 +44,11 @@ define(
this.selectedObj = undefined;
openmct.selection.on('change', function (selection) {
var selected = selection[0];
if (selected && selected.context.toolbar) {
this.select(selected.context.toolbar);
if (selection[0] && selection[0].context.toolbar) {
this.select(selection[0].context.toolbar);
} else {
this.deselect();
}
if (selected && selected.context.viewProxy) {
this.proxy(selected.context.viewProxy);
}
}.bind(this));
}

View File

@@ -104,10 +104,10 @@ define(
mockEditorCapability.isEditContextRoot.andReturn(false);
mockEditorCapability.dirty.andReturn(false);
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
expect(checkFn()).toBe(false);
mockEditorCapability.isEditContextRoot.andReturn(true);
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
expect(checkFn()).toBe(false);
mockEditorCapability.dirty.andReturn(true);
expect(checkFn())

View File

@@ -29,25 +29,9 @@ define(
var mockScope,
mockOpenMCT,
mockSelection,
mockDomainObject,
mockMutationCapability,
mockUnlisten,
selectable = [],
controller;
beforeEach(function () {
mockUnlisten = jasmine.createSpy('unlisten');
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
"listen"
]);
mockMutationCapability.listen.andReturn(mockUnlisten);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability",
"useCapability"
]);
mockDomainObject.useCapability.andCallThrough();
mockDomainObject.getCapability.andReturn(mockMutationCapability);
mockScope = jasmine.createSpyObj("$scope", ['$on']);
mockSelection = jasmine.createSpyObj("selection", [
'on',
@@ -59,14 +43,6 @@ define(
selection: mockSelection
};
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
spyOn(ElementsController.prototype, 'refreshComposition');
controller = new ElementsController(mockScope, mockOpenMCT);
});
@@ -99,44 +75,6 @@ define(
expect(objects.filter(mockScope.searchElements).length).toBe(4);
});
it("refreshes composition on selection", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
});
it("listens on mutation and refreshes composition", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
expect(mockMutationCapability.listen).toHaveBeenCalled();
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(1);
mockMutationCapability.listen.mostRecentCall.args[0](mockDomainObject);
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(2);
});
it("cleans up mutation listener when selection changes", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockMutationCapability.listen).toHaveBeenCalled();
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockUnlisten).toHaveBeenCalled();
});
it("does not listen on mutation for element proxy selectable", function () {
selectable[0] = {
context: {
elementProxy: {}
}
};
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
});
});
}
);

View File

@@ -34,7 +34,6 @@ define([
"./src/controllers/ContextMenuController",
"./src/controllers/ClickAwayController",
"./src/controllers/ViewSwitcherController",
"./src/controllers/BottomBarController",
"./src/controllers/GetterSetterController",
"./src/controllers/SelectorController",
"./src/controllers/ObjectInspectorController",
@@ -49,6 +48,7 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/directives/MCTIndicators",
"./src/filters/ReverseFilter",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
@@ -84,7 +84,6 @@ define([
ContextMenuController,
ClickAwayController,
ViewSwitcherController,
BottomBarController,
GetterSetterController,
SelectorController,
ObjectInspectorController,
@@ -99,6 +98,7 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
MCTIndicators,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
@@ -275,13 +275,6 @@ define([
"$timeout"
]
},
{
"key": "BottomBarController",
"implementation": BottomBarController,
"depends": [
"indicators[]"
]
},
{
"key": "GetterSetterController",
"implementation": GetterSetterController,
@@ -395,6 +388,11 @@ define([
"key": "mctTree",
"implementation": MCTTree,
"depends": ['gestureService']
},
{
"key": "mctIndicators",
"implementation": MCTIndicators,
"depends": ['openmct']
}
],
"constants": [

View File

@@ -23,6 +23,7 @@
$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;
@@ -90,7 +91,7 @@
&.no-frame {
background: transparent !important;
border-color: transparent;
border: none;
.object-browse-bar .right {
$m: 0;
background: rgba(black, 0.3);

View File

@@ -20,7 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.s-hover-border {
border: 1px solid transparent;
&:hover {
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
}

View File

@@ -19,14 +19,9 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class='abs bottom-bar ue-bottom-bar mobile-disable-select' ng-controller="BottomBarController as bar">
<div class='abs bottom-bar ue-bottom-bar mobile-disable-select'>
<div id='status' class='status-holder'>
<mct-include ng-repeat="indicator in bar.getIndicators()"
ng-model="indicator.ngModel"
key="indicator.template"
class="status-block-holder"
ng-class='indicator.ngModel.getGlyphClass()'>
</mct-include>
<mct-indicators></mct-indicators>
</div>
<mct-include key="'message-banner'"></mct-include>
<mct-include key="'about-logo'"></mct-include>

View File

@@ -21,13 +21,11 @@
-->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class='status block'
title="{{ngModel.getDescription()}}"
ng-click='ngModel.configure()'
ng-show="ngModel.getText().length > 0">
<span class="status-indicator {{ngModel.getCssClass()}}"></span><span class="label"
ng-class='ngModel.getTextClass()'>
{{ngModel.getText()}}
<a class="s-button icon-gear" ng-if="ngModel.configure"></a>
title="{{ngModel.description()}}"
ng-show="ngModel.text().length > 0">
<span class="status-indicator {{ngModel.cssClass()}}"></span><span class="label"
ng-class='ngModel.textClass()'>
{{ngModel.text()}}
</span><span class="count">
</span>
</div>

View File

@@ -23,37 +23,21 @@
define(
[],
function () {
/**
* Controller for the bottombar template. Exposes
* available indicators (of extension category "indicators")
* @memberof platform/commonUI/general
* @constructor
*/
function BottomBarController(indicators) {
// Utility function used to make indicators presentable
// for display.
function present(Indicator) {
return {
template: Indicator.template || "indicator",
ngModel: typeof Indicator === 'function' ?
new Indicator() : Indicator
};
}
this.indicators = indicators.map(present);
function MCTIndicators(openmct) {
return {
restrict: "E",
link: function link(scope, element, attrs) {
openmct.indicators.displayFunctions().then(function (displayFunctions){
displayFunctions.forEach(function (displayFunction){
var displayElement = displayFunction();
element.append(displayElement);
});
})
}
};
}
/**
* Get all indicators to display.
* @returns {Indicator[]} all indicators
* to display in the bottom bar.
* @memberof platform/commonUI/general.BottomBarController#
*/
BottomBarController.prototype.getIndicators = function () {
return this.indicators;
};
return MCTIndicators;
return BottomBarController;
}
);

View File

@@ -1,76 +0,0 @@
/*****************************************************************************
* 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/controllers/BottomBarController"],
function (BottomBarController) {
describe("The bottom bar controller", function () {
var testIndicators,
testIndicatorA,
testIndicatorB,
testIndicatorC,
mockIndicator,
controller;
beforeEach(function () {
mockIndicator = jasmine.createSpyObj(
"indicator",
["getGlyph", "getCssClass", "getText"]
);
testIndicatorA = {};
testIndicatorB = function () {
return mockIndicator;
};
testIndicatorC = { template: "someTemplate" };
testIndicators = [
testIndicatorA,
testIndicatorB,
testIndicatorC
];
controller = new BottomBarController(testIndicators);
});
it("exposes one indicator description per extension", function () {
expect(controller.getIndicators().length)
.toEqual(testIndicators.length);
});
it("uses template field provided, or its own default", function () {
// "indicator" is the default;
// only testIndicatorC overrides this.
var indicators = controller.getIndicators();
expect(indicators[0].template).toEqual("indicator");
expect(indicators[1].template).toEqual("indicator");
expect(indicators[2].template).toEqual("someTemplate");
});
it("instantiates indicators given as constructors", function () {
// testIndicatorB constructs to mockIndicator
expect(controller.getIndicators()[1].ngModel).toBe(mockIndicator);
});
});
}
);

View File

@@ -65,10 +65,6 @@ define(
options = Object.create(OPTIONS);
options.marginX = -bubbleSpaceLR;
// prevent bubble from appearing right under pointer,
// which causes hover callback to be called multiple times
options.offsetX = 1;
// On a phone, bubble takes up more screen real estate,
// so position it differently (toward the bottom)
if (this.agentService.isPhone()) {

View File

@@ -50,11 +50,7 @@ define(
view.show(container);
} else {
self.providerView = false;
var selectedItem = selection[0].context.oldItem;
if (selectedItem) {
$scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
}
$scope.inspectorKey = selection[0].context.oldItem.getCapability("type").typeDef.inspector;
}
}

View File

@@ -55,7 +55,6 @@ define([
timerTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/clock", {
"name": "Clocks/Timers",
"descriptions": "Domain objects for displaying current & relative times.",
@@ -86,11 +85,6 @@ define([
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
},
{
"implementation": FollowIndicator,
"depends": ["timerService"],
"priority": "fallback"
}
],
"services": [
@@ -305,6 +299,10 @@ define([
}
}
],
"runs": [{
"implementation": FollowIndicator,
"depends": ["openmct", "timerService"]
}],
"licenses": [
{
"name": "moment-duration-format",

View File

@@ -20,38 +20,29 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['moment'],
function (moment) {
var NO_TIMER = "No timer being followed";
define([],function () {
/**
* Indicator that displays the active timer, as well as its
* current state.
* @implements {Indicator}
* @memberof platform/features/clock
*/
function FollowIndicator(timerService) {
this.timerService = timerService;
function setIndicatorStatus(indicator, timer) {
if (timer !== undefined) {
indicator.cssClass('icon-timer s-status-ok');
indicator.text('Following timer ' + timer.name);
} else {
indicator.cssClass('icon-timer');
indicator.text('No timer being followed.');
}
FollowIndicator.prototype.getGlyphClass = function () {
return "";
};
FollowIndicator.prototype.getCssClass = function () {
return (this.timerService.getTimer()) ? "icon-timer s-status-ok" : "icon-timer";
};
FollowIndicator.prototype.getText = function () {
var timer = this.timerService.getTimer();
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
};
FollowIndicator.prototype.getDescription = function () {
return "";
};
return FollowIndicator;
}
);
/**
* Indicator that displays the active timer, as well as its
* current state.
* @memberof platform/features/clock
*/
return function installFollowIndicator(openmct, timerService) {
var indicator = openmct.indicators.create();
var timer = timerService.getTimer();
var setIndicatorStatusFromTimer = setIndicatorStatus.bind(this, indicator);
setIndicatorStatusFromTimer(timer);
timerService.on('change', setIndicatorStatusFromTimer);
};
});

View File

@@ -44,7 +44,7 @@ define(['EventEmitter'], function (EventEmitter) {
*/
TimerService.prototype.setTimer = function (timer) {
this.timer = timer;
this.emit('change');
this.emit('change', timer);
if (this.stopObserving) {
this.stopObserving();

View File

@@ -42,15 +42,18 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
});
describe("when a timer is set", function () {
var testObject;
var testModel;
var mockDomainObject;
beforeEach(function () {
testObject = { name: "some timer!" };
mockTimerService.getTimer.andReturn(testObject);
testModel = { name: "some timer!" };
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
mockDomainObject.getModel.andReturn(testModel);
mockTimerService.getTimer.andReturn(mockDomainObject);
});
it("displays the timer's name", function () {
expect(indicator.getText().indexOf(testObject.name))
expect(indicator.getText().indexOf(testModel.name))
.not.toEqual(-1);
});
});

View File

@@ -272,8 +272,7 @@ define([
"$scope",
"$q",
"dialogService",
"openmct",
"$element"
"openmct"
]
}
],

View File

@@ -23,7 +23,7 @@
ng-controller="FixedController as controller">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@@ -35,28 +35,35 @@
<!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
ng-class="{
's-not-selected': controller.selected() && !controller.selected(element),
's-selected': controller.selected(element)
}"
ng-style="element.style"
mct-selectable="controller.getContext(element)"
mct-init-select="controller.shouldSelect(element)">
ng-click="controller.select(element, $event)">
<mct-include key="element.template"
parameters="{ gridSize: controller.getGridSize() }"
ng-model="element">
</mct-include>
</mct-include>
</div>
<!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
<span class="s-selected s-moveable" ng-if="controller.selected()">
<div class="l-fixed-position-item t-edit-handle-holder"
mct-drag-down="controller.moveHandle().startDrag()"
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.endDrag()"
ng-style="controller.getSelectedElementStyle()">
mct-drag-up="controller.moveHandle().endDrag()"
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="controller.endDrag(handle)">
mct-drag-up="handle.endDrag()"
ng-click="$event.stopPropagation()">
</div>
</span>
</div>

View File

@@ -36,8 +36,7 @@
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 t-object-type-{{ childObject.getModel().type }}"
data-layout-id="{{childObject.getId() + '-' + $id}}"
<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-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"

View File

@@ -47,7 +47,7 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function FixedController($scope, $q, dialogService, openmct, $element) {
function FixedController($scope, $q, dialogService, openmct) {
this.names = {}; // Cache names by ID
this.values = {}; // Cache values by ID
this.elementProxiesById = {};
@@ -55,11 +55,9 @@ define(
this.telemetryObjects = [];
this.subscriptions = [];
this.openmct = openmct;
this.$element = $element;
this.$scope = $scope;
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
this.fixedViewSelectable = false;
var self = this;
[
@@ -89,8 +87,9 @@ define(
// Update the style for a selected element
function updateSelectionStyle() {
if (self.selectedElementProxy) {
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
var element = self.selection && self.selection.get();
if (element) {
element.style = convertPosition(element);
}
}
@@ -137,19 +136,25 @@ define(
// Decorate elements in the current configuration
function refreshElements() {
var elements = (($scope.configuration || {}).elements || []);
// Cache selection; we are instantiating new proxies
// so we may want to restore this.
var selected = self.selection && self.selection.get(),
elements = (($scope.configuration || {}).elements || []),
index = -1; // Start with a 'not-found' value
// Find the selection in the new array
if (selected !== undefined) {
index = elements.indexOf(selected.element);
}
// Create the new proxies...
self.elementProxies = elements.map(makeProxyElement);
// If selection is not in array, select parent.
// Otherwise, set the element to select after refresh.
if (self.selectedElementProxy) {
var index = elements.indexOf(self.selectedElementProxy.element);
if (index === -1) {
self.$element[0].click();
} else if (!self.elementToSelectAfterRefresh) {
self.elementToSelectAfterRefresh = self.elementProxies[index].element;
// Clear old selection, and restore if appropriate
if (self.selection) {
self.selection.deselect();
if (index > -1) {
self.select(self.elementProxies[index]);
}
}
@@ -219,12 +224,12 @@ define(
$scope.configuration.elements || [];
// Store the position of this element.
$scope.configuration.elements.push(element);
self.elementToSelectAfterRefresh = element;
// Refresh displayed elements
refreshElements();
// Select the newly-added element
self.select(
self.elementProxies[self.elementProxies.length - 1]
);
// Mark change as persistable
if ($scope.commit) {
$scope.commit("Dropped an element.");
@@ -258,36 +263,21 @@ define(
self.getTelemetry($scope.domainObject);
}
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
return;
}
if (selection.context.elementProxy) {
self.selectedElementProxy = selection.context.elementProxy;
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
} else {
// Make fixed view selectable if it's not already.
if (!self.fixedViewSelectable && selectable.length === 1) {
self.fixedViewSelectable = true;
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
self.openmct.selection.select(selection);
}
self.resizeHandles = [];
self.mvHandle = undefined;
self.selectedElementProxy = undefined;
}
}
this.elementProxies = [];
this.generateDragHandle = generateDragHandle;
this.generateDragHandles = generateDragHandles;
this.updateSelectionStyle = updateSelectionStyle;
// Track current selection state
$scope.$watch("selection", function (selection) {
this.selection = selection;
// Expose the view's selection proxy
if (this.selection) {
this.selection.proxy(
new FixedProxy(addElement, $q, dialogService)
);
}
}.bind(this));
// Detect changes to grid size
$scope.$watch("model.layoutGrid", updateElementPositions);
@@ -308,13 +298,10 @@ define(
$scope.$on("$destroy", function () {
self.unsubscribe();
self.openmct.time.off("bounds", updateDisplayBounds);
self.openmct.selection.off("change", setSelection);
});
// Respond to external bounds changes
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
}
/**
@@ -505,56 +492,42 @@ define(
};
/**
* Checks if the element should be selected or not.
*
* @param elementProxy the element to check
* @returns {boolean} true if the element should be selected.
* Check if the element is currently selected, or (if no
* argument is supplied) get the currently selected element.
* @returns {boolean} true if selected
*/
FixedController.prototype.shouldSelect = function (elementProxy) {
if (elementProxy.element === this.elementToSelectAfterRefresh) {
delete this.elementToSelectAfterRefresh;
return true;
} else {
return false;
FixedController.prototype.selected = function (element) {
var selection = this.selection;
return selection && ((arguments.length > 0) ?
selection.selected(element) : selection.get());
};
/**
* Set the active user selection in this view.
* @param element the element to select
*/
FixedController.prototype.select = function select(element, event) {
if (event) {
event.stopPropagation();
}
if (this.selection) {
// Update selection...
this.selection.select(element);
// ...as well as move, resize handles
this.mvHandle = this.generateDragHandle(element);
this.resizeHandles = this.generateDragHandles(element);
}
};
/**
* Checks if an element is currently selected.
*
* @returns {boolean} true if an element is selected.
* Clear the current user selection.
*/
FixedController.prototype.isElementSelected = function () {
return (this.selectedElementProxy) ? true : false;
};
/**
* Gets the style for the selected element.
*
* @returns {string} element style
*/
FixedController.prototype.getSelectedElementStyle = function () {
return (this.selectedElementProxy) ? this.selectedElementProxy.style : undefined;
};
/**
* Gets the selected element.
*
* @returns the selected element
*/
FixedController.prototype.getSelectedElement = function () {
return this.selectedElementProxy;
};
/**
* Prevents the event from bubbling up if drag is in progress.
*/
FixedController.prototype.bypassSelection = function ($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopPropagation();
}
return;
FixedController.prototype.clearSelection = function () {
if (this.selection) {
this.selection.deselect();
this.resizeHandles = [];
this.mvHandle = undefined;
}
};
@@ -575,38 +548,6 @@ define(
return this.mvHandle;
};
/**
* Gets the selection context.
*
* @param elementProxy the element proxy
* @returns {object} the context object which includes elementProxy and toolbar
*/
FixedController.prototype.getContext = function (elementProxy) {
return {
elementProxy: elementProxy,
toolbar: elementProxy
};
};
/**
* End drag.
*
* @param handle the resize handle
*/
FixedController.prototype.endDrag = function (handle) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
if (handle) {
handle.endDrag();
} else {
this.moveHandle().endDrag();
}
};
return FixedController;
}
);

View File

@@ -65,7 +65,7 @@ define(
* Start a drag gesture. This should be called when a drag
* begins to track initial state.
*/
FixedDragHandle.prototype.startDrag = function () {
FixedDragHandle.prototype.startDrag = function startDrag() {
// Cache initial x/y positions
this.dragging = {
x: this.elementHandle.x(),

View File

@@ -515,7 +515,7 @@ define(
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
setTimeout(function () {
$('[data-layout-id="' + selector + '"]')[0].click();
$('.' + selector)[0].click();
delete this.droppedIdToSelectAfterRefresh;
}.bind(this), 0);
}

View File

@@ -55,8 +55,8 @@ define(
* @param element the fixed position element, as stored in its
* configuration
* @param index the element's index within its array
* @param {Array} elements the full array of elements
* @param {number[]} gridSize the current layout grid size in [x,y] from
* @param {Array} elements the full array of elements
*/
function ElementProxy(element, index, elements, gridSize) {
/**

View File

@@ -21,14 +21,8 @@
*****************************************************************************/
define(
[
"../src/FixedController",
"zepto"
],
function (
FixedController,
$
) {
["../src/FixedController"],
function (FixedController) {
describe("The Fixed Position controller", function () {
var mockScope,
@@ -52,9 +46,6 @@ define(
mockMetadata,
mockTimeSystem,
mockLimitEvaluator,
mockSelection,
$element = [],
selectable = [],
controller;
// Utility function; find a watch for a given expression
@@ -189,30 +180,17 @@ define(
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.andCallThrough();
mockScope.selection = jasmine.createSpyObj(
'selection',
['select', 'get', 'selected', 'deselect', 'proxy']
);
mockOpenMCT = {
time: mockConductor,
telemetry: mockTelemetryAPI,
composition: mockCompositionAPI,
selection: mockSelection
composition: mockCompositionAPI
};
$element = $('<div></div>');
spyOn($element[0], 'click');
mockMetadata = jasmine.createSpyObj('mockMetadata', [
'valuesForHints',
'value',
@@ -248,11 +226,11 @@ define(
mockScope,
mockQ,
mockDialogService,
mockOpenMCT,
$element
mockOpenMCT
);
findWatch("model.layoutGrid")(testModel.layoutGrid);
findWatch("selection")(mockScope.selection);
});
it("subscribes when a domain object is available", function () {
@@ -328,41 +306,41 @@ define(
});
it("allows elements to be selected", function () {
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.isElementSelected()).toBe(true);
});
it("allows selection retrieval", function () {
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.getSelectedElement()).toEqual(elements[1]);
controller.select(elements[1]);
expect(mockScope.selection.select)
.toHaveBeenCalledWith(elements[1]);
});
it("selects the parent view when selected element is removed", function () {
it("allows selection retrieval", function () {
// selected with no arguments should give the current
// selection
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
var elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
elements = controller.getElements();
controller.select(elements[1]);
mockScope.selection.get.andReturn(elements[1]);
expect(controller.selected()).toEqual(elements[1]);
});
elements[1].remove();
testModel.modified = 2;
it("allows selections to be cleared", function () {
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
expect($element[0].click).toHaveBeenCalled();
elements = controller.getElements();
controller.select(elements[1]);
controller.clearSelection();
expect(controller.selected(elements[1])).toBeFalsy();
});
it("retains selections during refresh", function () {
@@ -374,21 +352,23 @@ define(
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
controller.select(elements[1]);
expect(controller.getSelectedElement()).toEqual(elements[1]);
// Verify precondition
expect(mockScope.selection.select.calls.length).toEqual(1);
// Mimic selection behavior
mockScope.selection.get.andReturn(elements[1]);
elements[2].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
// Verify removal, as test assumes this
expect(elements.length).toEqual(2);
expect(controller.shouldSelect(elements[1])).toBe(true);
expect(mockScope.selection.select.calls.length).toEqual(2);
});
it("Displays received values for telemetry elements", function () {
@@ -525,25 +505,21 @@ define(
});
it("exposes a view-level selection proxy", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
expect(selection.context.viewProxy).toBeDefined();
expect(mockScope.selection.proxy).toHaveBeenCalledWith(
jasmine.any(Object)
);
});
it("exposes drag handles", function () {
var handles;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
controller.select(controller.getElements()[1]);
// Should have a non-empty array of handles
handles = controller.handles();
expect(handles).toEqual(jasmine.any(Array));
expect(handles.length).not.toEqual(0);
@@ -556,14 +532,15 @@ define(
});
it("exposes a move handle", function () {
var handle;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
controller.select(controller.getElements()[1]);
// Should have a move handle
var handle = controller.moveHandle();
handle = controller.moveHandle();
// And it should have start/continue/end drag methods
expect(handle.startDrag).toEqual(jasmine.any(Function));
@@ -574,40 +551,26 @@ define(
it("updates selection style during drag", function () {
var oldStyle;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
controller.select(controller.getElements()[1]);
mockScope.selection.get.andReturn(controller.getElements()[1]);
// Get style
oldStyle = controller.getSelectedElementStyle();
oldStyle = controller.selected().style;
// Start a drag gesture
controller.moveHandle().startDrag();
// Haven't moved yet; style shouldn't have updated yet
expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
expect(controller.selected().style).toEqual(oldStyle);
// Drag a little
controller.moveHandle().continueDrag([1000, 100]);
// Style should have been updated
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
});
it("cleans up slection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.mostRecentCall.args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
expect(controller.selected().style).not.toEqual(oldStyle);
});
describe("on display bounds changes", function () {
@@ -739,14 +702,6 @@ define(
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
});
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
});
});
}

View File

@@ -475,11 +475,11 @@ define(
);
var childObj = mockDomainObject("d");
var testElement = $("<div data-layout-id='some-id'></div>");
var testElement = $("<div class='some-class'></div>");
$element.append(testElement);
spyOn(testElement[0], 'click');
controller.selectIfNew('some-id', childObj);
controller.selectIfNew('some-class', childObj);
jasmine.Clock.tick(0);
expect(testElement[0].click).toHaveBeenCalled();

View File

@@ -415,7 +415,7 @@ define(
PlotController.prototype.exportPNG = function () {
var self = this;
self.hideExportButtons = true;
self.exportImageService.exportPNG(self.$element[0], "plot.png", 'white').finally(function () {
self.exportImageService.exportPNG(self.$element[0], "plot.png").finally(function () {
self.hideExportButtons = false;
});
};
@@ -426,7 +426,7 @@ define(
PlotController.prototype.exportJPG = function () {
var self = this;
self.hideExportButtons = true;
self.exportImageService.exportJPG(self.$element[0], "plot.jpg", 'white').finally(function () {
self.exportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () {
self.hideExportButtons = false;
});
};

View File

@@ -43,7 +43,7 @@ define(
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
* @constructor
*/
function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injSaveAs, injFileReader, injChangeBackgroundColor) {
function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injSaveAs, injFileReader) {
self.$q = $q;
self.$timeout = $timeout;
self.$log = $log;
@@ -51,7 +51,6 @@ define(
self.html2canvas = injHtml2Canvas || html2canvas;
self.saveAs = injSaveAs || saveAs;
self.reader = injFileReader || new FileReader();
self.changeBackgroundColor = injChangeBackgroundColor || self.changeBackgroundColor;
}
/**
@@ -61,25 +60,16 @@ define(
* @param {string} type of image to convert the element to
* @returns {promise}
*/
function renderElement(element, type, color) {
function renderElement(element, type) {
var defer = self.$q.defer(),
validTypes = ["png", "jpg", "jpeg"],
renderTimeout,
originalColor;
renderTimeout;
if (validTypes.indexOf(type) === -1) {
self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
return;
}
if (color) {
// Save color to be restored later
originalColor = element.style.backgroundColor || '';
// Defaulting to white so we can see the chart when printed
self.changeBackgroundColor(element, color);
}
renderTimeout = self.$timeout(function () {
defer.reject("html2canvas timed out");
self.$log.warn("html2canvas timed out");
@@ -88,15 +78,13 @@ define(
try {
self.html2canvas(element, {
onrendered: function (canvas) {
if (color) {
self.changeBackgroundColor(element, originalColor);
}
switch (type.toLowerCase()) {
case "png":
canvas.toBlob(defer.resolve, "image/png");
break;
default:
case "jpg":
case "jpeg":
canvas.toBlob(defer.resolve, "image/jpeg");
break;
@@ -108,13 +96,7 @@ define(
self.$log.warn("html2canvas failed with error: " + e);
}
defer.promise.finally(function () {
renderTimeout.cancel();
if (color) {
self.changeBackgroundColor(element, originalColor);
}
});
defer.promise.finally(renderTimeout.cancel);
return defer.promise;
}
@@ -143,21 +125,14 @@ define(
}
}
/**
* @private
*/
self.changeBackgroundColor = function (element, color) {
element.style.backgroundColor = color;
};
/**
* Takes a screenshot of a DOM node and exports to JPG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportJPG = function (element, filename, color) {
return renderElement(element, "jpeg", color).then(function (img) {
ExportImageService.prototype.exportJPG = function (element, filename) {
return renderElement(element, "jpeg").then(function (img) {
self.saveAs(img, filename);
});
};
@@ -168,8 +143,8 @@ define(
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportPNG = function (element, filename, color) {
return renderElement(element, "png", color).then(function (img) {
ExportImageService.prototype.exportPNG = function (element, filename) {
return renderElement(element, "png").then(function (img) {
self.saveAs(img, filename);
});
};

View File

@@ -37,8 +37,7 @@ define(
mockFileReader,
mockExportTimeoutConstant,
testElement,
exportImageService,
mockChangeBackgroundColor;
exportImageService;
describe("ExportImageService", function () {
beforeEach(function () {
@@ -84,9 +83,7 @@ define(
["readAsDataURL", "onloadend"]
);
mockExportTimeoutConstant = 0;
testElement = {style: {backgroundColor: 'black'}};
mockChangeBackgroundColor = jasmine.createSpy('changeBackgroundColor');
testElement = {};
exportImageService = new ExportImageService(
mockQ,
@@ -95,8 +92,7 @@ define(
mockExportTimeoutConstant,
mockHtml2Canvas,
mockSaveAs,
mockFileReader,
mockChangeBackgroundColor
mockFileReader
);
});
@@ -119,28 +115,6 @@ define(
expect(mockSaveAs).toHaveBeenCalled();
expect(mockPromise.finally).toHaveBeenCalled();
});
it("changes background color to white and returns color back to original after snapshot, for better visibility of plot lines on print", function () {
exportImageService.exportPNG(testElement, "plot.png", 'white');
expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'white');
expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'black');
exportImageService.exportJPG(testElement, "plot.jpg", 'white');
expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'white');
expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'black');
});
it("does not change background color when color is not specified in parameters", function () {
exportImageService.exportPNG(testElement, "plot.png");
expect(mockChangeBackgroundColor).not.toHaveBeenCalled();
exportImageService.exportJPG(testElement, "plot.jpg");
expect(mockChangeBackgroundColor).not.toHaveBeenCalled();
});
});
}
);

View File

@@ -20,7 +20,6 @@
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">

View File

@@ -193,6 +193,15 @@ define([
* @name telemetry
*/
this.telemetry = new api.TelemetryAPI(this);
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
this.indicators = new api.IndicatorAPI(this);
this.Dialog = api.Dialog;

View File

@@ -27,7 +27,8 @@ define([
'./types/TypeRegistry',
'./ui/Dialog',
'./ui/GestureAPI',
'./telemetry/TelemetryAPI'
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI'
], function (
TimeAPI,
ObjectAPI,
@@ -35,7 +36,8 @@ define([
TypeRegistry,
Dialog,
GestureAPI,
TelemetryAPI
TelemetryAPI,
IndicatorAPI
) {
return {
TimeAPI: TimeAPI,
@@ -44,6 +46,7 @@ define([
Dialog: Dialog,
TypeRegistry: TypeRegistry,
GestureAPI: GestureAPI,
TelemetryAPI: TelemetryAPI
TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI
};
});

View File

@@ -1,269 +0,0 @@
define([
'./CompositionAPI',
'./CompositionCollection'
], function (
CompositionAPI,
CompositionCollection
) {
describe('The Composition API', function () {
var publicAPI;
var compositionAPI;
var topicService;
var mutationTopic;
beforeEach(function () {
mutationTopic = jasmine.createSpyObj('mutationTopic', [
'listen'
]);
topicService = jasmine.createSpy('topicService');
topicService.andReturn(mutationTopic);
publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get'
]);
publicAPI.objects.get.andCallFake(function (identifier) {
return Promise.resolve({identifier: identifier});
});
publicAPI.$injector = jasmine.createSpyObj('$injector', [
'get'
]);
publicAPI.$injector.get.andReturn(topicService);
compositionAPI = new CompositionAPI(publicAPI);
});
it('returns falsy if an object does not support composition', function () {
expect(compositionAPI.get({})).toBeFalsy();
});
describe('default composition', function () {
var domainObject;
var composition;
beforeEach(function () {
domainObject = {
name: 'test folder',
identifier: {
namespace: 'test',
key: '1'
},
composition: [
{
namespace: 'test',
key: 'a'
}
]
};
composition = compositionAPI.get(domainObject);
});
it('returns composition collection', function () {
expect(composition).toBeDefined();
expect(composition).toEqual(jasmine.any(CompositionCollection));
});
it('loads composition from domain object', function () {
var listener = jasmine.createSpy('addListener');
var loaded = false;
composition.on('add', listener);
composition.load()
.then(function () {
loaded = true;
});
waitsFor(function () {
return loaded;
});
runs(function () {
expect(listener.calls.length).toBe(1);
expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'test', key: 'a'}
});
});
});
// TODO: Implement add/removal in new default provider.
xit('synchronizes changes between instances', function () {
var otherComposition = compositionAPI.get(domainObject);
var addListener = jasmine.createSpy('addListener');
var removeListener = jasmine.createSpy('removeListener');
var otherAddListener = jasmine.createSpy('otherAddListener');
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
composition.on('add', addListener);
composition.on('remove', removeListener);
otherComposition.on('add', otherAddListener);
otherComposition.on('remove', otherRemoveListener);
var loaded = false;
Promise.all([composition.load(), otherComposition.load()])
.then(function () {
loaded = true;
});
waitsFor(function () {
return loaded;
});
runs(function () {
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
expect(removeListener).not.toHaveBeenCalled();
expect(otherRemoveListener).not.toHaveBeenCalled();
var object = addListener.mostRecentCall.args[0];
composition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
composition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
removeListener.reset();
otherRemoveListener.reset();
otherComposition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
otherComposition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
});
});
});
describe('static custom composition', function () {
var customProvider;
var domainObject;
var composition;
beforeEach(function () {
// A simple custom provider, returns the same composition for
// all objects of a given type.
customProvider = {
appliesTo: function (object) {
return object.type === 'custom-object-type';
},
load: function (object) {
return Promise.resolve([
{
namespace: 'custom',
key: 'thing'
}
]);
}
};
domainObject = {
identifier: {
namespace: 'test',
key: '1'
},
type: 'custom-object-type'
};
compositionAPI.addProvider(customProvider);
composition = compositionAPI.get(domainObject);
});
it('supports listening and loading', function () {
var listener = jasmine.createSpy('addListener');
var loaded = false;
composition.on('add', listener);
composition.load()
.then(function () {
loaded = true;
});
waitsFor(function () {
return loaded;
});
runs(function () {
expect(listener.calls.length).toBe(1);
expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'custom', key: 'thing'}
});
});
});
});
describe('dynamic custom composition', function () {
var customProvider;
var domainObject;
var composition;
beforeEach(function () {
// A dynamic provider, loads an empty composition and exposes
// listener functions.
customProvider = jasmine.createSpyObj('dynamicProvider', [
'appliesTo',
'load',
'on',
'off'
]);
customProvider.appliesTo.andReturn('true');
customProvider.load.andReturn(Promise.resolve([]));
domainObject = {
identifier: {
namespace: 'test',
key: '1'
},
type: 'custom-object-type'
};
compositionAPI.addProvider(customProvider);
composition = compositionAPI.get(domainObject);
});
it('supports listening and loading', function () {
var addListener = jasmine.createSpy('addListener');
var removeListener = jasmine.createSpy('removeListener');
var loaded = false;
composition.on('add', addListener);
composition.on('remove', removeListener);
expect(customProvider.on).toHaveBeenCalledWith(
domainObject,
'add',
jasmine.any(Function),
jasmine.any(CompositionCollection)
);
expect(customProvider.on).toHaveBeenCalledWith(
domainObject,
'remove',
jasmine.any(Function),
jasmine.any(CompositionCollection)
);
var add = customProvider.on.calls[0].args[2];
var remove = customProvider.on.calls[1].args[2];
composition.load()
.then(function () {
loaded = true;
});
waitsFor(function () {
return loaded;
});
runs(function () {
expect(addListener).not.toHaveBeenCalled();
expect(removeListener).not.toHaveBeenCalled();
add({namespace: 'custom', key: 'thing'});
});
waitsFor(function () {
return addListener.calls.length > 0;
});
runs(function () {
expect(addListener).toHaveBeenCalledWith({
identifier: {namespace: 'custom', key: 'thing'}
});
remove(addListener.mostRecentCall.args[0]);
});
waitsFor(function () {
return removeListener.calls.length > 0;
});
runs(function () {
expect(removeListener).toHaveBeenCalledWith({
identifier: {namespace: 'custom', key: 'thing'}
});
});
});
});
});
});

View File

@@ -76,22 +76,20 @@ define([
throw new Error('Event not supported by composition: ' + event);
}
if (this.provider.on && this.provider.off) {
if (event === 'add') {
this.provider.on(
this.domainObject,
'add',
this.onProviderAdd,
this
);
} if (event === 'remove') {
this.provider.on(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
if (event === 'add') {
this.provider.on(
this.domainObject,
'add',
this.onProviderAdd,
this
);
} if (event === 'remove') {
this.provider.on(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
this.listeners[event].push({
@@ -126,22 +124,20 @@ define([
if (this.listeners[event].length === 0) {
// Remove provider listener if this is the last callback to
// be removed.
if (this.provider.off && this.provider.on) {
if (event === 'add') {
this.provider.off(
this.domainObject,
'add',
this.onProviderAdd,
this
);
} else if (event === 'remove') {
this.provider.off(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
if (event === 'add') {
this.provider.off(
this.domainObject,
'add',
this.onProviderAdd,
this
);
} else if (event === 'remove') {
this.provider.off(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
}
};

View File

@@ -0,0 +1,111 @@
/*****************************************************************************
* 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([
'text!./res/indicator-template.html'
], function (
indicatorTemplate
) {
function Indicator(openmct) {
this.openmct = openmct;
this.textValue = '';
this.descriptionValue = '';
this.cssClassValue = '';
this.iconClassValue = '';
this.textClassValue = '';
this.glyphClassValue = '';
this.node = undefined;
}
Indicator.prototype.text = function (text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
Indicator.defaultDisplayFunction.call(this);
}
return this.textValue;
}
Indicator.prototype.description = function (description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
Indicator.defaultDisplayFunction.call(this);
}
return this.descriptionValue;
}
Indicator.prototype.iconClass = function (iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
this.iconClassValue = iconClass;
Indicator.defaultDisplayFunction.call(this);
}
return this.iconClassValue;
}
Indicator.prototype.cssClass = function (cssClass) {
if (cssClass !== undefined && cssClass !== this.cssClassValue) {
this.cssClassValue = cssClass;
Indicator.defaultDisplayFunction.call(this);
}
return this.cssClassValue;
}
Indicator.prototype.glyphClass = function (glyphClass) {
if (glyphClass !== undefined && glyphClass !== this.glyphClassValue) {
this.glyphClassValue = glyphClass;
Indicator.defaultDisplayFunction.call(this);
}
return this.glyphClassValue;
}
Indicator.prototype.textClass = function (textClass) {
if (textClass !== undefined && textClass !== this.textClassValue) {
this.textClassValue = textClass;
Indicator.defaultDisplayFunction.call(this);
}
return this.textClassValue;
}
Indicator.defaultDisplayFunction = function () {
var html = indicatorTemplate.replace('{{indicator.text}}', this.text())
.replace('{{indicator.textClass}}', this.textClass())
.replace('{{indicator.cssClass}}', this.cssClass())
.replace('{{indicator.glyphClass}}', this.glyphClass())
.replace('{{indicator.description}}', this.description());
if (!this.node){
this.node = document.createElement('div');
this.node.className = 'status-block-holder';
}
this.node.innerHTML = html;
return this.node;
}
return Indicator;
});

View File

@@ -0,0 +1,85 @@
/*****************************************************************************
* 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([
'./Indicator',
'./LegacyIndicator'
], function (
Indicator,
LegacyIndicator
) {
function IndicatorAPI(openmct) {
this.openmct = openmct;
this.indicators = [];
this.legacyIndicatorsPromise = new Promise(function (resolve) {
this.resolveWithIndicators = resolve;
onStartWrapLegacyIndicators.call(this);
}.bind(this));
}
IndicatorAPI.prototype.create = function (displayFunction) {
if (displayFunction !== undefined) {
this.indicators.push(displayFunction);
} else {
var indicator = new Indicator(this.openmct);
this.indicators.push(Indicator.defaultDisplayFunction.bind(indicator));
return indicator;
}
}
/**
* @private
*/
IndicatorAPI.prototype.displayFunctions = function () {
return this.legacyIndicatorsPromise.then(function (legacyIndicators) {
var wrappedLegacyIndicators = wrapAllLegacyIndicators.call(this, legacyIndicators);
return this.indicators.concat(wrappedLegacyIndicators);
}.bind(this));
}
function onStartWrapLegacyIndicators() {
this.openmct.legacyExtension('runs', {
depends: ['indicators[]'],
implementation: this.resolveWithIndicators
});
}
function wrapAllLegacyIndicators(legacyIndicators) {
return legacyIndicators.map(convertToNewIndicator.bind(this));
}
function convertToNewIndicator(legacyIndicatorDef) {
var legacyIndicator;
if (typeof legacyIndicatorDef === 'function') {
legacyIndicator = new legacyIndicatorDef();
} else {
legacyIndicator = legacyIndicatorDef;
}
var newStyleIndicator = new LegacyIndicator(this.openmct, legacyIndicator, legacyIndicatorDef.template);
return LegacyIndicator.displayFunction.bind(newStyleIndicator);
}
return IndicatorAPI;
});

View File

@@ -0,0 +1,81 @@
/*****************************************************************************
* 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([
'zepto',
'./Indicator'
], function ($, Indicator) {
var TEMPLATE =
'<mct-include ' +
' ng-model="indicator" ' +
' key="template" ' +
' class="status-block-holder" ' +
' ng-class="indicator.glyphClass()"> ' +
' </mct-include>';
function LegacyIndicator(openmct, legacyIndicator, template) {
this.openmct = openmct;
this.legacyIndicator = legacyIndicator;
this.template = template;
}
LegacyIndicator.prototype.display = function () {
return legacyDisplayFunction.bind(this);
}
LegacyIndicator.prototype.text = function () {
return this.legacyIndicator.getText &&
this.legacyIndicator.getText();
}
LegacyIndicator.prototype.description = function () {
return this.legacyIndicator.getDescription &&
this.legacyIndicator.getDescription();
}
LegacyIndicator.prototype.cssClass = function () {
return this.legacyIndicator.getCssClass &&
this.legacyIndicator.getCssClass();
}
LegacyIndicator.prototype.glyphClass = function () {
return this.legacyIndicator.getGlyphClass &&
this.legacyIndicator.getGlyphClass();
}
LegacyIndicator.prototype.textClass = function () {
return this.legacyIndicator.getTextClass &&
this.legacyIndicator.getTextClass();
}
LegacyIndicator.displayFunction = function () {
var $compile = this.openmct.$injector.get('$compile');
var $rootScope = this.openmct.$injector.get('$rootScope');
var scope = $rootScope.$new(true);
scope.indicator = this;
scope.template = this.template || 'indicator';
return $compile(TEMPLATE)(scope)[0];
}
return LegacyIndicator;
});

View File

@@ -0,0 +1,7 @@
<div class="{{indicator.glyphClass}}">
<div class="status block" title="{{indicator.description}}" ng-show="ngModel.text().length > 0">
<span class="status-indicator {{indicator.cssClass}}"></span><span class="label" class='{{indicator.textClass}}'>
{{indicator.text}}
</span><span class="count"></span>
</div>
</div>

View File

@@ -21,8 +21,8 @@
*****************************************************************************/
define(
[],
function () {
['zepto'],
function ($) {
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
@@ -33,65 +33,81 @@ define(
glyphClass: "ok"
},
PENDING = {
glyphClass: 'caution'
glyphClass: "caution"
},
DISCONNECTED = {
glyphClass: "err"
};
function URLIndicator($http, $interval) {
var self = this;
this.cssClass = this.options.cssClass ? this.options.cssClass : "icon-database";
this.URLpath = this.options.url;
this.label = this.options.label ? this.options.label : this.options.url;
this.interval = this.options.interval || 10000;
this.state = PENDING;
function URLIndicator(options, openmct) {
this.bindMethods();
function handleError(e) {
self.state = DISCONNECTED;
}
function handleResponse() {
self.state = CONNECTED;
}
function updateIndicator() {
$http.get(self.URLpath).then(handleResponse, handleError);
}
updateIndicator();
$interval(updateIndicator, self.interval, 0, false);
this.indicator = openmct.indicators.create();
this.setDefaultsFromOptions(options);
this.setIndicatorToState(PENDING);
this.fetchUrl();
setInterval(this.fetchUrl, this.interval);
}
URLIndicator.prototype.getCssClass = function () {
return this.cssClass;
};
URLIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass;
};
URLIndicator.prototype.getText = function () {
switch (this.state) {
URLIndicator.prototype.setIndicatorToState = function (state) {
switch (state) {
case CONNECTED: {
return this.label + " is connected";
this.indicator.text(this.label + " is connected");
this.indicator.description(this.label + " is online, checking status every " + this.interval + " milliseconds.");
break;
}
case PENDING: {
return "Checking status of " + this.label + " please stand by...";
this.indicator.text("Checking status of " + this.label + " please stand by...");
this.indicator.description("Checking status of " + this.label + " please stand by...");
break;
}
case DISCONNECTED: {
return this.label + " is offline";
this.indicator.text(this.label + " is offline");
this.indicator.description(this.label + " is offline, checking status every " + this.interval + " milliseconds");
break;
}
}
};
this.indicator.glyphClass(state.glyphClass);
};
URLIndicator.prototype.getDescription = function () {
switch (this.state) {
case CONNECTED: {
return this.label + " is online, checking status every " +
this.interval + " milliseconds.";
}
case PENDING: {
return "Checking status of " + this.label + " please stand by...";
}
case DISCONNECTED: {
return this.label + " is offline, checking status every " +
this.interval + " milliseconds";
}
}
URLIndicator.prototype.fetchUrl = function () {
this.get(this.URLpath)
.then(this.handleSuccess)
.catch(this.handleError);
};
URLIndicator.prototype.get = function (url) {
return new Promise(function(resolve, reject){
$.ajax({
type: 'GET',
url: url,
success: resolve,
error: reject
});
}.bind(this));
};
URLIndicator.prototype.handleError = function (e) {
this.setIndicatorToState(DISCONNECTED);
};
URLIndicator.prototype.handleSuccess = function () {
this.setIndicatorToState(CONNECTED);
}
URLIndicator.prototype.setDefaultsFromOptions = function (options) {
this.URLpath = options.url;
this.label = options.label || options.url;
this.interval = options.inverval || 10000;
this.indicator.cssClass(options.cssClass || 'icon-database');
}
URLIndicator.prototype.bindMethods = function () {
this.fetchUrl = this.fetchUrl.bind(this);
this.handleSuccess = this.handleSuccess.bind(this);
this.handleError = this.handleError.bind(this);
this.setIndicatorToState = this.setIndicatorToState.bind(this);
}
return URLIndicator;
});

View File

@@ -3,18 +3,10 @@ define(
'./URLIndicator'
],
function URLIndicatorPlugin(URLIndicator) {
return function (opts) {
// Wrap the plugin in a function so we can apply the arguments.
function URLIndicatorWrapper() {
this.options = opts;
URLIndicator.apply(this, arguments);
}
URLIndicatorWrapper.prototype = Object.create(URLIndicator.prototype);
return function install(openmct) {
openmct.legacyExtension('indicators', {
"implementation": URLIndicatorWrapper,
"depends": ["$http", "$interval"]
});
return new URLIndicator(opts, openmct);
};
};
});

View File

@@ -35,6 +35,10 @@ define([
key: 'local-format',
implementation: LocalTimeFormat
});
var indicator = openmct.indicators.create();
indicator.cssClass('something');
indicator.show(function showIndicator(element) {});
};
};
});

View File

@@ -128,7 +128,7 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicatorPlugin = URLIndicatorPlugin;
plugins.URLIndicator = URLIndicatorPlugin;
return plugins;
});

View File

@@ -206,17 +206,6 @@ define([], function () {
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
}
};
}
@@ -315,12 +304,9 @@ define([], function () {
op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) {
if (this.operations[operation].appliesTo.length === 2) {
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
} else {
return validator(input) && op(input);
}
return validator(input) && op(input);
} else {
throw new Error('Malformed condition');
}

View File

@@ -5,7 +5,6 @@ define([
'./TestDataManager',
'./WidgetDnD',
'./eventHelpers',
'../../../api/objects/object-utils',
'lodash',
'zepto'
], function (
@@ -15,7 +14,6 @@ define([
TestDataManager,
WidgetDnD,
eventHelpers,
objectUtils,
_,
$
) {
@@ -79,7 +77,7 @@ define([
this.addHyperlink(domainObject.url, domainObject.openNewTab);
this.watchForChanges(openmct, domainObject);
var id = objectUtils.makeKeyString(this.domainObject.identifier),
var id = this.domainObject.identifier.key,
self = this,
oldDomainObject,
statusCapability;

View File

@@ -325,10 +325,6 @@ define(['../src/ConditionEvaluator'], function (ConditionEvaluator) {
testOperation = testEvaluator.operations.isUndefined.operation;
expect(testOperation([1])).toEqual(false);
expect(testOperation([])).toEqual(true);
//isDefined
testOperation = testEvaluator.operations.isDefined.operation;
expect(testOperation([1])).toEqual(true);
expect(testOperation([])).toEqual(false);
});
it('can produce a description for all supported operations', function () {

View File

@@ -14,8 +14,7 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) {
beforeEach(function () {
mockDomainObject = {
identifier: {
key: 'testKey',
namespace: 'testNamespace'
key: 'testKey'
},
name: 'testName',
composition: [],
@@ -50,7 +49,7 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) {
mockObjectService.getObjects = jasmine.createSpy('objectService');
mockObjectService.getObjects.andReturn(new Promise(function (resolve, reject) {
resolve({
'testNamespace:testKey': mockOldDomainObject
testKey: mockOldDomainObject
});
}));
mockOpenMCT = jasmine.createSpyObj('openmct', [
@@ -74,10 +73,6 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) {
summaryWidget.show(mockContainer);
});
it('queries with legacyId', function () {
expect(mockObjectService.getObjects).toHaveBeenCalledWith(['testNamespace:testKey']);
});
it('adds its DOM element to the view', function () {
expect(mockContainer.getElementsByClassName('w-summary-widget').length).toBeGreaterThan(0);
});

View File

@@ -115,7 +115,7 @@ define(['EventEmitter'], function (EventEmitter) {
element.addEventListener('click', selectCapture);
if (select) {
element.click();
this.select(selectable);
}
return function () {

View File

@@ -66,7 +66,7 @@ requirejs.config({
"vue": "node_modules/vue/dist/vue.min",
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",