mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 09:52:04 +00:00
Merge remote-tracking branch 'github/master' into open115b
Merge in latest from master into topic branch for nasa/openmctweb#115 Conflicts: platform/features/conductor/src/ConductorRepresenter.js platform/features/conductor/src/ConductorTelemetrySeries.js platform/features/conductor/src/TimeConductor.js platform/features/conductor/test/ConductorRepresenterSpec.js platform/features/conductor/test/ConductorTelemetrySeriesSpec.js
This commit is contained in:
commit
d8276c532b
@ -238,6 +238,9 @@ Commit messages should:
|
|||||||
* Contain a reference to a relevant issue number in the body of the commit.
|
* Contain a reference to a relevant issue number in the body of the commit.
|
||||||
* This is important for traceability; while branch names also provide this,
|
* This is important for traceability; while branch names also provide this,
|
||||||
you cannot tell from looking at a commit what branch it was authored on.
|
you cannot tell from looking at a commit what branch it was authored on.
|
||||||
|
* This may be omitted if the relevant issue is otherwise obvious from the
|
||||||
|
commit history (that is, if using `git log` from the relevant commit
|
||||||
|
directly leads to a similar issue reference) to minimize clutter.
|
||||||
* Describe the change that was made, and any useful rationale therefore.
|
* Describe the change that was made, and any useful rationale therefore.
|
||||||
* Comments in code should explain what things do, commit messages describe
|
* Comments in code should explain what things do, commit messages describe
|
||||||
how they came to be done that way.
|
how they came to be done that way.
|
||||||
|
15
README.md
15
README.md
@ -103,6 +103,21 @@ This build will:
|
|||||||
|
|
||||||
Run as `mvn clean install`.
|
Run as `mvn clean install`.
|
||||||
|
|
||||||
|
### Building Documentation
|
||||||
|
|
||||||
|
Open MCT Web's documentation is generated by an
|
||||||
|
[npm](https://www.npmjs.com/)-based build:
|
||||||
|
|
||||||
|
* `npm install` _(only needs to run once)_
|
||||||
|
* `npm run docs`
|
||||||
|
|
||||||
|
Documentation will be generated in `target/docs`. Note that diagram
|
||||||
|
generation is dependent on having [Cairo](http://cairographics.org/download/)
|
||||||
|
installed; see
|
||||||
|
[node-canvas](https://github.com/Automattic/node-canvas#installation)'s
|
||||||
|
documentation for help with installation.
|
||||||
|
|
||||||
|
|
||||||
# Glossary
|
# Glossary
|
||||||
|
|
||||||
Certain terms are used throughout Open MCT Web with consistent meanings
|
Certain terms are used throughout Open MCT Web with consistent meanings
|
||||||
|
@ -28,7 +28,9 @@
|
|||||||
<mct-split-pane class='contents abs' anchor='left'>
|
<mct-split-pane class='contents abs' anchor='left'>
|
||||||
<div class='split-pane-component treeview pane left'>
|
<div class='split-pane-component treeview pane left'>
|
||||||
<div class="holder abs l-mobile">
|
<div class="holder abs l-mobile">
|
||||||
<mct-representation key="'create-button'" mct-object="navigatedObject">
|
<mct-representation key="'create-button'"
|
||||||
|
mct-object="navigatedObject"
|
||||||
|
mct-device="desktop">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
<div class='holder search-holder abs'
|
<div class='holder search-holder abs'
|
||||||
ng-class="{active: treeModel.search}">
|
ng-class="{active: treeModel.search}">
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
"key": "urlService",
|
"key": "urlService",
|
||||||
"implementation": "services/UrlService.js",
|
"implementation": "services/UrlService.js",
|
||||||
"depends": [ "$location" ]
|
"depends": [ "$location" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "popupService",
|
||||||
|
"implementation": "services/PopupService.js",
|
||||||
|
"depends": [ "$document", "$window" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [
|
"runs": [
|
||||||
@ -128,7 +133,7 @@
|
|||||||
{
|
{
|
||||||
"key": "mctPopup",
|
"key": "mctPopup",
|
||||||
"implementation": "directives/MCTPopup.js",
|
"implementation": "directives/MCTPopup.js",
|
||||||
"depends": [ "$window", "$document", "$compile", "$interval" ]
|
"depends": [ "$compile", "popupService" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "mctScrollX",
|
"key": "mctScrollX",
|
||||||
|
@ -27,33 +27,36 @@ define(
|
|||||||
|
|
||||||
var TEMPLATE = "<div></div>";
|
var TEMPLATE = "<div></div>";
|
||||||
|
|
||||||
function MCTPopup($window, $document, $compile) {
|
/**
|
||||||
|
* The `mct-popup` directive may be used to display elements
|
||||||
|
* which "pop up" over other parts of the page. Typically, this is
|
||||||
|
* done in conjunction with an `ng-if` to control the visibility
|
||||||
|
* of the popup.
|
||||||
|
*
|
||||||
|
* Example of usage:
|
||||||
|
*
|
||||||
|
* <mct-popup ng-if="someExpr">
|
||||||
|
* <span>These are the contents of the popup!</span>
|
||||||
|
* </mct-popup>
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @param $compile Angular's $compile service
|
||||||
|
* @param {platform/commonUI/general.PopupService} popupService
|
||||||
|
*/
|
||||||
|
function MCTPopup($compile, popupService) {
|
||||||
function link(scope, element, attrs, ctrl, transclude) {
|
function link(scope, element, attrs, ctrl, transclude) {
|
||||||
var body = $document.find('body'),
|
var div = $compile(TEMPLATE)(scope),
|
||||||
popup = $compile(TEMPLATE)(scope),
|
|
||||||
winDim = [$window.innerWidth, $window.innerHeight],
|
|
||||||
rect = element.parent()[0].getBoundingClientRect(),
|
rect = element.parent()[0].getBoundingClientRect(),
|
||||||
position = [ rect.left, rect.top ],
|
position = [ rect.left, rect.top ],
|
||||||
isLeft = position[0] <= (winDim[0] / 2),
|
popup = popupService.display(div, position);
|
||||||
isTop = position[1] <= (winDim[1] / 2);
|
|
||||||
|
|
||||||
popup.css('position', 'absolute');
|
|
||||||
popup.css(
|
|
||||||
isLeft ? 'left' : 'right',
|
|
||||||
(isLeft ? position[0] : (winDim[0] - position[0])) + 'px'
|
|
||||||
);
|
|
||||||
popup.css(
|
|
||||||
isTop ? 'top' : 'bottom',
|
|
||||||
(isTop ? position[1] : (winDim[1] - position[1])) + 'px'
|
|
||||||
);
|
|
||||||
body.append(popup);
|
|
||||||
|
|
||||||
transclude(function (clone) {
|
transclude(function (clone) {
|
||||||
popup.append(clone);
|
div.append(clone);
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.$on('$destroy', function () {
|
scope.$on('$destroy', function () {
|
||||||
popup.remove();
|
popup.dismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
89
platform/commonUI/general/src/services/Popup.js
Normal file
89
platform/commonUI/general/src/services/Popup.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A popup is an element that has been displayed at a particular
|
||||||
|
* location within the page.
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @param element the jqLite-wrapped element
|
||||||
|
* @param {object} styles an object containing key-value pairs
|
||||||
|
* of styles used to position the element.
|
||||||
|
*/
|
||||||
|
function Popup(element, styles) {
|
||||||
|
this.styles = styles;
|
||||||
|
this.element = element;
|
||||||
|
|
||||||
|
element.css(styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop showing this popup.
|
||||||
|
*/
|
||||||
|
Popup.prototype.dismiss = function () {
|
||||||
|
this.element.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this popup is positioned such that it appears to the
|
||||||
|
* left of its original location.
|
||||||
|
* @returns {boolean} true if the popup goes left
|
||||||
|
*/
|
||||||
|
Popup.prototype.goesLeft = function () {
|
||||||
|
return !this.styles.left;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this popup is positioned such that it appears to the
|
||||||
|
* right of its original location.
|
||||||
|
* @returns {boolean} true if the popup goes right
|
||||||
|
*/
|
||||||
|
Popup.prototype.goesRight = function () {
|
||||||
|
return !this.styles.right;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this popup is positioned such that it appears above
|
||||||
|
* its original location.
|
||||||
|
* @returns {boolean} true if the popup goes up
|
||||||
|
*/
|
||||||
|
Popup.prototype.goesUp = function () {
|
||||||
|
return !this.styles.top;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this popup is positioned such that it appears below
|
||||||
|
* its original location.
|
||||||
|
* @returns {boolean} true if the popup goes down
|
||||||
|
*/
|
||||||
|
Popup.prototype.goesDown = function () {
|
||||||
|
return !this.styles.bottom;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Popup;
|
||||||
|
}
|
||||||
|
);
|
127
platform/commonUI/general/src/services/PopupService.js
Normal file
127
platform/commonUI/general/src/services/PopupService.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./Popup'],
|
||||||
|
function (Popup) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays popup elements at specific positions within the document.
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function PopupService($document, $window) {
|
||||||
|
this.$document = $document;
|
||||||
|
this.$window = $window;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options controlling how the popup is displaed.
|
||||||
|
*
|
||||||
|
* @typedef PopupOptions
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @property {number} [offsetX] the horizontal distance, in pixels,
|
||||||
|
* to offset the element in whichever direction it is
|
||||||
|
* displayed. Defaults to 0.
|
||||||
|
* @property {number} [offsetY] the vertical distance, in pixels,
|
||||||
|
* to offset the element in whichever direction it is
|
||||||
|
* displayed. Defaults to 0.
|
||||||
|
* @property {number} [marginX] the horizontal position, in pixels,
|
||||||
|
* after which to prefer to display the element to the left.
|
||||||
|
* If negative, this is relative to the right edge of the
|
||||||
|
* page. Defaults to half the window's width.
|
||||||
|
* @property {number} [marginY] the vertical position, in pixels,
|
||||||
|
* after which to prefer to display the element upward.
|
||||||
|
* If negative, this is relative to the right edge of the
|
||||||
|
* page. Defaults to half the window's height.
|
||||||
|
* @property {string} [leftClass] class to apply when shifting to the left
|
||||||
|
* @property {string} [rightClass] class to apply when shifting to the right
|
||||||
|
* @property {string} [upClass] class to apply when shifting upward
|
||||||
|
* @property {string} [downClass] class to apply when shifting downward
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a popup at a particular location. The location chosen will
|
||||||
|
* be the corner of the element; the element will be positioned either
|
||||||
|
* to the left or the right of this point depending on available
|
||||||
|
* horizontal space, and will similarly be shifted upward or downward
|
||||||
|
* depending on available vertical space.
|
||||||
|
*
|
||||||
|
* @param element the jqLite-wrapped DOM element to pop up
|
||||||
|
* @param {number[]} position x,y position of the element, in
|
||||||
|
* pixel coordinates. Negative values are interpreted as
|
||||||
|
* relative to the right or bottom of the window.
|
||||||
|
* @param {PopupOptions} [options] additional options to control
|
||||||
|
* positioning of the popup
|
||||||
|
* @returns {platform/commonUI/general.Popup} the popup
|
||||||
|
*/
|
||||||
|
PopupService.prototype.display = function (element, position, options) {
|
||||||
|
var $document = this.$document,
|
||||||
|
$window = this.$window,
|
||||||
|
body = $document.find('body'),
|
||||||
|
winDim = [ $window.innerWidth, $window.innerHeight ],
|
||||||
|
styles = { position: 'absolute' },
|
||||||
|
margin,
|
||||||
|
offset,
|
||||||
|
bubble;
|
||||||
|
|
||||||
|
function adjustNegatives(value, index) {
|
||||||
|
return value < 0 ? (value + winDim[index]) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
options = options || {};
|
||||||
|
offset = [
|
||||||
|
options.offsetX !== undefined ? options.offsetX : 0,
|
||||||
|
options.offsetY !== undefined ? options.offsetY : 0
|
||||||
|
];
|
||||||
|
margin = [ options.marginX, options.marginY ].map(function (m, i) {
|
||||||
|
return m === undefined ? (winDim[i] / 2) : m;
|
||||||
|
}).map(adjustNegatives);
|
||||||
|
|
||||||
|
position = position.map(adjustNegatives);
|
||||||
|
|
||||||
|
if (position[0] > margin[0]) {
|
||||||
|
styles.right = (winDim[0] - position[0] + offset[0]) + 'px';
|
||||||
|
} else {
|
||||||
|
styles.left = (position[0] + offset[0]) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position[1] > margin[1]) {
|
||||||
|
styles.bottom = (winDim[1] - position[1] + offset[1]) + 'px';
|
||||||
|
} else {
|
||||||
|
styles.top = (position[1] + offset[1]) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the menu to the body
|
||||||
|
body.append(element);
|
||||||
|
|
||||||
|
// Return a function to dismiss the bubble
|
||||||
|
return new Popup(element, styles);
|
||||||
|
};
|
||||||
|
|
||||||
|
return PopupService;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -29,15 +29,16 @@ define(
|
|||||||
var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
|
var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
|
||||||
|
|
||||||
describe("The mct-popup directive", function () {
|
describe("The mct-popup directive", function () {
|
||||||
var testWindow,
|
var mockCompile,
|
||||||
mockDocument,
|
mockPopupService,
|
||||||
mockCompile,
|
mockPopup,
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
testAttrs,
|
testAttrs,
|
||||||
mockBody,
|
mockBody,
|
||||||
mockTransclude,
|
mockTransclude,
|
||||||
mockParentEl,
|
mockParentEl,
|
||||||
|
mockNewElement,
|
||||||
testRect,
|
testRect,
|
||||||
mctPopup;
|
mctPopup;
|
||||||
|
|
||||||
@ -50,12 +51,12 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testWindow =
|
|
||||||
{ innerWidth: 600, innerHeight: 300 };
|
|
||||||
mockDocument =
|
|
||||||
jasmine.createSpyObj("$document", JQLITE_METHODS);
|
|
||||||
mockCompile =
|
mockCompile =
|
||||||
jasmine.createSpy("$compile");
|
jasmine.createSpy("$compile");
|
||||||
|
mockPopupService =
|
||||||
|
jasmine.createSpyObj("popupService", ["display"]);
|
||||||
|
mockPopup =
|
||||||
|
jasmine.createSpyObj("popup", ["dismiss"]);
|
||||||
mockScope =
|
mockScope =
|
||||||
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
|
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
|
||||||
mockElement =
|
mockElement =
|
||||||
@ -66,6 +67,8 @@ define(
|
|||||||
jasmine.createSpy("transclude");
|
jasmine.createSpy("transclude");
|
||||||
mockParentEl =
|
mockParentEl =
|
||||||
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
||||||
|
mockNewElement =
|
||||||
|
jasmine.createSpyObj("newElement", JQLITE_METHODS);
|
||||||
|
|
||||||
testAttrs = {
|
testAttrs = {
|
||||||
mctClickElsewhere: "some Angular expression"
|
mctClickElsewhere: "some Angular expression"
|
||||||
@ -77,15 +80,17 @@ define(
|
|||||||
height: 75
|
height: 75
|
||||||
};
|
};
|
||||||
|
|
||||||
mockDocument.find.andReturn(mockBody);
|
mockCompile.andCallFake(function () {
|
||||||
mockCompile.andReturn(jasmine.createSpy());
|
var mockFn = jasmine.createSpy();
|
||||||
mockCompile().andCallFake(function () {
|
mockFn.andReturn(mockNewElement);
|
||||||
return jasmine.createSpyObj("newElement", JQLITE_METHODS);
|
return mockFn;
|
||||||
});
|
});
|
||||||
mockElement.parent.andReturn([mockParentEl]);
|
mockElement.parent.andReturn([mockParentEl]);
|
||||||
mockParentEl.getBoundingClientRect.andReturn(testRect);
|
mockParentEl.getBoundingClientRect.andReturn(testRect);
|
||||||
|
mockPopupService.display.andReturn(mockPopup);
|
||||||
|
|
||||||
|
mctPopup = new MCTPopup(mockCompile, mockPopupService);
|
||||||
|
|
||||||
mctPopup = new MCTPopup(testWindow, mockDocument, mockCompile);
|
|
||||||
mctPopup.link(
|
mctPopup.link(
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
@ -99,6 +104,32 @@ define(
|
|||||||
expect(mctPopup.restrict).toEqual("E");
|
expect(mctPopup.restrict).toEqual("E");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("creates an element which", function () {
|
||||||
|
it("displays as a popup", function () {
|
||||||
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
|
mockNewElement,
|
||||||
|
[ testRect.left, testRect.top ]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays transcluded content", function () {
|
||||||
|
var mockClone =
|
||||||
|
jasmine.createSpyObj('clone', JQLITE_METHODS);
|
||||||
|
mockTransclude.mostRecentCall.args[0](mockClone);
|
||||||
|
expect(mockNewElement.append)
|
||||||
|
.toHaveBeenCalledWith(mockClone);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is removed when its containing scope is destroyed", function () {
|
||||||
|
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||||
|
mockScope.$on.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === '$destroy') {
|
||||||
|
call.args[1]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
98
platform/commonUI/general/test/services/PopupServiceSpec.js
Normal file
98
platform/commonUI/general/test/services/PopupServiceSpec.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/services/PopupService"],
|
||||||
|
function (PopupService) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("PopupService", function () {
|
||||||
|
var mockDocument,
|
||||||
|
testWindow,
|
||||||
|
mockBody,
|
||||||
|
mockElement,
|
||||||
|
popupService;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDocument = jasmine.createSpyObj('$document', [ 'find' ]);
|
||||||
|
testWindow = { innerWidth: 1000, innerHeight: 800 };
|
||||||
|
mockBody = jasmine.createSpyObj('body', [ 'append' ]);
|
||||||
|
mockElement = jasmine.createSpyObj('element', [
|
||||||
|
'css',
|
||||||
|
'remove'
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockDocument.find.andCallFake(function (query) {
|
||||||
|
return query === 'body' && mockBody;
|
||||||
|
});
|
||||||
|
|
||||||
|
popupService = new PopupService(mockDocument, testWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds elements to the body of the document", function () {
|
||||||
|
popupService.display(mockElement, [ 0, 0 ]);
|
||||||
|
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when positioned in appropriate quadrants", function () {
|
||||||
|
it("orients elements relative to the top-left", function () {
|
||||||
|
popupService.display(mockElement, [ 25, 50 ]);
|
||||||
|
expect(mockElement.css).toHaveBeenCalledWith({
|
||||||
|
position: 'absolute',
|
||||||
|
left: '25px',
|
||||||
|
top: '50px'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("orients elements relative to the top-right", function () {
|
||||||
|
popupService.display(mockElement, [ 800, 50 ]);
|
||||||
|
expect(mockElement.css).toHaveBeenCalledWith({
|
||||||
|
position: 'absolute',
|
||||||
|
right: '200px',
|
||||||
|
top: '50px'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("orients elements relative to the bottom-right", function () {
|
||||||
|
popupService.display(mockElement, [ 800, 650 ]);
|
||||||
|
expect(mockElement.css).toHaveBeenCalledWith({
|
||||||
|
position: 'absolute',
|
||||||
|
right: '200px',
|
||||||
|
bottom: '150px'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("orients elements relative to the bottom-left", function () {
|
||||||
|
popupService.display(mockElement, [ 120, 650 ]);
|
||||||
|
expect(mockElement.css).toHaveBeenCalledWith({
|
||||||
|
position: 'absolute',
|
||||||
|
left: '120px',
|
||||||
|
bottom: '150px'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
74
platform/commonUI/general/test/services/PopupSpec.js
Normal file
74
platform/commonUI/general/test/services/PopupSpec.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/services/Popup"],
|
||||||
|
function (Popup) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("Popup", function () {
|
||||||
|
var mockElement,
|
||||||
|
testStyles,
|
||||||
|
popup;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockElement =
|
||||||
|
jasmine.createSpyObj('element', [ 'css', 'remove' ]);
|
||||||
|
testStyles = { left: '12px', top: '14px' };
|
||||||
|
popup = new Popup(mockElement, testStyles);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies CSS styles when instantiated", function () {
|
||||||
|
expect(mockElement.css)
|
||||||
|
.toHaveBeenCalledWith(testStyles);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports the orientation of the popup", function () {
|
||||||
|
var otherStyles = {
|
||||||
|
right: '12px',
|
||||||
|
bottom: '14px'
|
||||||
|
},
|
||||||
|
otherPopup = new Popup(mockElement, otherStyles);
|
||||||
|
|
||||||
|
expect(popup.goesLeft()).toBeFalsy();
|
||||||
|
expect(popup.goesRight()).toBeTruthy();
|
||||||
|
expect(popup.goesUp()).toBeFalsy();
|
||||||
|
expect(popup.goesDown()).toBeTruthy();
|
||||||
|
|
||||||
|
expect(otherPopup.goesLeft()).toBeTruthy();
|
||||||
|
expect(otherPopup.goesRight()).toBeFalsy();
|
||||||
|
expect(otherPopup.goesUp()).toBeTruthy();
|
||||||
|
expect(otherPopup.goesDown()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes elements when dismissed", function () {
|
||||||
|
expect(mockElement.remove).not.toHaveBeenCalled();
|
||||||
|
popup.dismiss();
|
||||||
|
expect(mockElement.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
@ -17,6 +17,8 @@
|
|||||||
"directives/MCTPopup",
|
"directives/MCTPopup",
|
||||||
"directives/MCTResize",
|
"directives/MCTResize",
|
||||||
"directives/MCTScroll",
|
"directives/MCTScroll",
|
||||||
|
"services/Popup",
|
||||||
|
"services/PopupService",
|
||||||
"services/UrlService",
|
"services/UrlService",
|
||||||
"StyleSheetLoader"
|
"StyleSheetLoader"
|
||||||
]
|
]
|
||||||
|
@ -45,13 +45,12 @@
|
|||||||
"implementation": "services/InfoService.js",
|
"implementation": "services/InfoService.js",
|
||||||
"depends": [
|
"depends": [
|
||||||
"$compile",
|
"$compile",
|
||||||
"$document",
|
|
||||||
"$window",
|
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
"popupService",
|
||||||
"agentService"
|
"agentService"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
{
|
{
|
||||||
"key": "INFO_HOVER_DELAY",
|
"key": "INFO_HOVER_DELAY",
|
||||||
@ -66,4 +65,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,13 +31,19 @@ define({
|
|||||||
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
|
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
|
||||||
"bubble-title=\"{{bubbleTitle}}\" " +
|
"bubble-title=\"{{bubbleTitle}}\" " +
|
||||||
"bubble-layout=\"{{bubbleLayout}}\" " +
|
"bubble-layout=\"{{bubbleLayout}}\" " +
|
||||||
"class=\"bubble-container\">" +
|
"class=\"bubble-container\">" +
|
||||||
"<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
|
"<mct-include key=\"bubbleTemplate\" " +
|
||||||
|
"ng-model=\"bubbleModel\">" +
|
||||||
"</mct-include>" +
|
"</mct-include>" +
|
||||||
"</mct-container>",
|
"</mct-container>",
|
||||||
// Pixel offset for bubble, to align arrow position
|
// Options and classes for bubble
|
||||||
BUBBLE_OFFSET: [ 0, -26 ],
|
BUBBLE_OPTIONS: {
|
||||||
// Max width and margins allowed for bubbles; defined in /platform/commonUI/general/res/sass/_constants.scss
|
offsetX: 0,
|
||||||
BUBBLE_MARGIN_LR: 10,
|
offsetY: -26
|
||||||
BUBBLE_MAX_WIDTH: 300
|
},
|
||||||
|
BUBBLE_MOBILE_POSITION: [ 0, -25 ],
|
||||||
|
// Max width and margins allowed for bubbles;
|
||||||
|
// defined in /platform/commonUI/general/res/sass/_constants.scss
|
||||||
|
BUBBLE_MARGIN_LR: 10,
|
||||||
|
BUBBLE_MAX_WIDTH: 300
|
||||||
});
|
});
|
||||||
|
@ -27,18 +27,18 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
|
var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
|
||||||
OFFSET = InfoConstants.BUBBLE_OFFSET;
|
MOBILE_POSITION = InfoConstants.BUBBLE_MOBILE_POSITION,
|
||||||
|
OPTIONS = InfoConstants.BUBBLE_OPTIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays informative content ("info bubbles") for the user.
|
* Displays informative content ("info bubbles") for the user.
|
||||||
* @memberof platform/commonUI/inspect
|
* @memberof platform/commonUI/inspect
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function InfoService($compile, $document, $window, $rootScope, agentService) {
|
function InfoService($compile, $rootScope, popupService, agentService) {
|
||||||
this.$compile = $compile;
|
this.$compile = $compile;
|
||||||
this.$document = $document;
|
|
||||||
this.$window = $window;
|
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
|
this.popupService = popupService;
|
||||||
this.agentService = agentService;
|
this.agentService = agentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,53 +55,47 @@ define(
|
|||||||
*/
|
*/
|
||||||
InfoService.prototype.display = function (templateKey, title, content, position) {
|
InfoService.prototype.display = function (templateKey, title, content, position) {
|
||||||
var $compile = this.$compile,
|
var $compile = this.$compile,
|
||||||
$document = this.$document,
|
|
||||||
$window = this.$window,
|
|
||||||
$rootScope = this.$rootScope,
|
$rootScope = this.$rootScope,
|
||||||
body = $document.find('body'),
|
|
||||||
scope = $rootScope.$new(),
|
scope = $rootScope.$new(),
|
||||||
winDim = [$window.innerWidth, $window.innerHeight],
|
span = $compile('<span></span>')(scope),
|
||||||
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR + InfoConstants.BUBBLE_MAX_WIDTH,
|
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR +
|
||||||
goLeft = position[0] > (winDim[0] - bubbleSpaceLR),
|
InfoConstants.BUBBLE_MAX_WIDTH,
|
||||||
goUp = position[1] > (winDim[1] / 2),
|
options,
|
||||||
|
popup,
|
||||||
bubble;
|
bubble;
|
||||||
|
|
||||||
|
options = Object.create(OPTIONS);
|
||||||
|
options.marginX = -bubbleSpaceLR;
|
||||||
|
|
||||||
|
// On a phone, bubble takes up more screen real estate,
|
||||||
|
// so position it differently (toward the bottom)
|
||||||
|
if (this.agentService.isPhone(navigator.userAgent)) {
|
||||||
|
position = MOBILE_POSITION;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
popup = this.popupService.display(span, position, options);
|
||||||
|
|
||||||
// Pass model & container parameters into the scope
|
// Pass model & container parameters into the scope
|
||||||
scope.bubbleModel = content;
|
scope.bubbleModel = content;
|
||||||
scope.bubbleTemplate = templateKey;
|
scope.bubbleTemplate = templateKey;
|
||||||
scope.bubbleLayout = (goUp ? 'arw-btm' : 'arw-top') + ' ' +
|
|
||||||
(goLeft ? 'arw-right' : 'arw-left');
|
|
||||||
scope.bubbleTitle = title;
|
scope.bubbleTitle = title;
|
||||||
|
// Style the bubble according to how it was positioned
|
||||||
|
scope.bubbleLayout = [
|
||||||
|
popup.goesUp() ? 'arw-btm' : 'arw-top',
|
||||||
|
popup.goesLeft() ? 'arw-right' : 'arw-left'
|
||||||
|
].join(' ');
|
||||||
|
scope.bubbleLayout = 'arw-top arw-left';
|
||||||
|
|
||||||
// Create the context menu
|
// Create the info bubble, now that we know how to
|
||||||
|
// point the arrow...
|
||||||
bubble = $compile(BUBBLE_TEMPLATE)(scope);
|
bubble = $compile(BUBBLE_TEMPLATE)(scope);
|
||||||
|
span.append(bubble);
|
||||||
|
|
||||||
// Position the bubble
|
// Return a function to dismiss the info bubble
|
||||||
bubble.css('position', 'absolute');
|
return function dismiss() {
|
||||||
if (this.agentService.isPhone(navigator.userAgent)) {
|
popup.dismiss();
|
||||||
bubble.css('right', '0px');
|
scope.$destroy();
|
||||||
bubble.css('left', '0px');
|
|
||||||
bubble.css('top', 'auto');
|
|
||||||
bubble.css('bottom', '25px');
|
|
||||||
} else {
|
|
||||||
if (goLeft) {
|
|
||||||
bubble.css('right', (winDim[0] - position[0] + OFFSET[0]) + 'px');
|
|
||||||
} else {
|
|
||||||
bubble.css('left', position[0] + OFFSET[0] + 'px');
|
|
||||||
}
|
|
||||||
if (goUp) {
|
|
||||||
bubble.css('bottom', (winDim[1] - position[1] + OFFSET[1]) + 'px');
|
|
||||||
} else {
|
|
||||||
bubble.css('top', position[1] + OFFSET[1] + 'px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the menu to the body
|
|
||||||
body.append(bubble);
|
|
||||||
|
|
||||||
// Return a function to dismiss the bubble
|
|
||||||
return function () {
|
|
||||||
bubble.remove();
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,117 +28,85 @@ define(
|
|||||||
|
|
||||||
describe("The info service", function () {
|
describe("The info service", function () {
|
||||||
var mockCompile,
|
var mockCompile,
|
||||||
mockDocument,
|
|
||||||
testWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockCompiledTemplate,
|
mockScope,
|
||||||
testScope,
|
mockElements,
|
||||||
mockBody,
|
mockPopup,
|
||||||
mockElement,
|
|
||||||
service;
|
service;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockCompile = jasmine.createSpy('$compile');
|
mockCompile = jasmine.createSpy('$compile');
|
||||||
mockDocument = jasmine.createSpyObj('$document', ['find']);
|
|
||||||
testWindow = { innerWidth: 1000, innerHeight: 100 };
|
|
||||||
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
|
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
|
||||||
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
|
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
|
||||||
mockCompiledTemplate = jasmine.createSpy('template');
|
mockPopupService = jasmine.createSpyObj(
|
||||||
testScope = {};
|
'popupService',
|
||||||
mockBody = jasmine.createSpyObj('body', ['append']);
|
['display']
|
||||||
mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
|
);
|
||||||
|
mockPopup = jasmine.createSpyObj('popup', [
|
||||||
|
'dismiss',
|
||||||
|
'goesLeft',
|
||||||
|
'goesRight',
|
||||||
|
'goesUp',
|
||||||
|
'goesDown'
|
||||||
|
]);
|
||||||
|
|
||||||
mockDocument.find.andCallFake(function (tag) {
|
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||||
return tag === 'body' ? mockBody : undefined;
|
mockElements = [];
|
||||||
|
|
||||||
|
mockPopupService.display.andReturn(mockPopup);
|
||||||
|
mockCompile.andCallFake(function () {
|
||||||
|
var mockCompiledTemplate = jasmine.createSpy('template'),
|
||||||
|
mockElement = jasmine.createSpyObj('element', [
|
||||||
|
'css',
|
||||||
|
'remove',
|
||||||
|
'append'
|
||||||
|
]);
|
||||||
|
mockCompiledTemplate.andReturn(mockElement);
|
||||||
|
mockElements.push(mockElement);
|
||||||
|
return mockCompiledTemplate;
|
||||||
});
|
});
|
||||||
mockCompile.andReturn(mockCompiledTemplate);
|
mockRootScope.$new.andReturn(mockScope);
|
||||||
mockCompiledTemplate.andReturn(mockElement);
|
|
||||||
mockRootScope.$new.andReturn(testScope);
|
|
||||||
|
|
||||||
service = new InfoService(
|
service = new InfoService(
|
||||||
mockCompile,
|
mockCompile,
|
||||||
mockDocument,
|
|
||||||
testWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService
|
mockAgentService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates elements and appends them to the body to display", function () {
|
it("creates elements and displays them as popups", function () {
|
||||||
service.display('', '', {}, [0, 0]);
|
service.display('', '', {}, [123, 456]);
|
||||||
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
|
mockElements[0],
|
||||||
|
[ 123, 456 ],
|
||||||
|
jasmine.any(Object)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides a function to remove displayed info bubbles", function () {
|
it("provides a function to remove displayed info bubbles", function () {
|
||||||
var fn = service.display('', '', {}, [0, 0]);
|
var fn = service.display('', '', {}, [0, 0]);
|
||||||
expect(mockElement.remove).not.toHaveBeenCalled();
|
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||||
fn();
|
fn();
|
||||||
expect(mockElement.remove).toHaveBeenCalled();
|
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("depending on mouse position", function () {
|
it("when on phone device, positions at bottom", function () {
|
||||||
// Positioning should vary based on quadrant in window,
|
mockAgentService.isPhone.andReturn(true);
|
||||||
// which is 1000 x 100 in this test case.
|
service = new InfoService(
|
||||||
it("displays from the top-left in the top-left quadrant", function () {
|
mockCompile,
|
||||||
service.display('', '', {}, [250, 25]);
|
mockRootScope,
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
mockPopupService,
|
||||||
'left',
|
mockAgentService
|
||||||
(250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
|
);
|
||||||
);
|
service.display('', '', {}, [123, 456]);
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
'top',
|
mockElements[0],
|
||||||
(25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
|
[ 0, -25 ],
|
||||||
);
|
jasmine.any(Object)
|
||||||
});
|
);
|
||||||
|
|
||||||
it("displays from the top-right in the top-right quadrant", function () {
|
|
||||||
service.display('', '', {}, [700, 25]);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'right',
|
|
||||||
(300 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
|
|
||||||
);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'top',
|
|
||||||
(25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays from the bottom-left in the bottom-left quadrant", function () {
|
|
||||||
service.display('', '', {}, [250, 70]);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'left',
|
|
||||||
(250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
|
|
||||||
);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'bottom',
|
|
||||||
(30 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays from the bottom-right in the bottom-right quadrant", function () {
|
|
||||||
service.display('', '', {}, [800, 60]);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'right',
|
|
||||||
(200 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
|
|
||||||
);
|
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
|
||||||
'bottom',
|
|
||||||
(40 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when on phone device, positioning is always on bottom", function () {
|
|
||||||
mockAgentService.isPhone.andReturn(true);
|
|
||||||
service = new InfoService(
|
|
||||||
mockCompile,
|
|
||||||
mockDocument,
|
|
||||||
testWindow,
|
|
||||||
mockRootScope,
|
|
||||||
mockAgentService
|
|
||||||
);
|
|
||||||
service.display('', '', {}, [0, 0]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"depends": [
|
"depends": [
|
||||||
"persistenceService",
|
"persistenceService",
|
||||||
"$q",
|
"$q",
|
||||||
|
"now",
|
||||||
"PERSISTENCE_SPACE",
|
"PERSISTENCE_SPACE",
|
||||||
"ADDITIONAL_PERSISTENCE_SPACES"
|
"ADDITIONAL_PERSISTENCE_SPACES"
|
||||||
]
|
]
|
||||||
|
@ -39,14 +39,16 @@ define(
|
|||||||
* @param {PersistenceService} persistenceService the service in which
|
* @param {PersistenceService} persistenceService the service in which
|
||||||
* domain object models are persisted.
|
* domain object models are persisted.
|
||||||
* @param $q Angular's $q service, for working with promises
|
* @param $q Angular's $q service, for working with promises
|
||||||
|
* @param {function} now a function which provides the current time
|
||||||
* @param {string} space the name of the persistence space(s)
|
* @param {string} space the name of the persistence space(s)
|
||||||
* from which models should be retrieved.
|
* from which models should be retrieved.
|
||||||
* @param {string} spaces additional persistence spaces to use
|
* @param {string} spaces additional persistence spaces to use
|
||||||
*/
|
*/
|
||||||
function PersistedModelProvider(persistenceService, $q, space, spaces) {
|
function PersistedModelProvider(persistenceService, $q, now, space, spaces) {
|
||||||
this.persistenceService = persistenceService;
|
this.persistenceService = persistenceService;
|
||||||
this.$q = $q;
|
this.$q = $q;
|
||||||
this.spaces = [space].concat(spaces || []);
|
this.spaces = [space].concat(spaces || []);
|
||||||
|
this.now = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the most recently modified model, for cases where
|
// Take the most recently modified model, for cases where
|
||||||
@ -61,7 +63,9 @@ define(
|
|||||||
PersistedModelProvider.prototype.getModels = function (ids) {
|
PersistedModelProvider.prototype.getModels = function (ids) {
|
||||||
var persistenceService = this.persistenceService,
|
var persistenceService = this.persistenceService,
|
||||||
$q = this.$q,
|
$q = this.$q,
|
||||||
spaces = this.spaces;
|
spaces = this.spaces,
|
||||||
|
space = this.space,
|
||||||
|
now = this.now;
|
||||||
|
|
||||||
// Load a single object model from any persistence spaces
|
// Load a single object model from any persistence spaces
|
||||||
function loadModel(id) {
|
function loadModel(id) {
|
||||||
@ -72,11 +76,24 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that models read from persistence have some
|
||||||
|
// sensible timestamp indicating they've been persisted.
|
||||||
|
function addPersistedTimestamp(model) {
|
||||||
|
if (model && (model.persisted === undefined)) {
|
||||||
|
model.persisted = model.modified !== undefined ?
|
||||||
|
model.modified : now();
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
// Package the result as id->model
|
// Package the result as id->model
|
||||||
function packageResult(models) {
|
function packageResult(models) {
|
||||||
var result = {};
|
var result = {};
|
||||||
ids.forEach(function (id, index) {
|
ids.forEach(function (id, index) {
|
||||||
result[id] = models[index];
|
if (models[index]) {
|
||||||
|
result[id] = addPersistedTimestamp(models[index]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ define(
|
|||||||
SPACE = "space0",
|
SPACE = "space0",
|
||||||
spaces = [ "space1" ],
|
spaces = [ "space1" ],
|
||||||
modTimes,
|
modTimes,
|
||||||
|
mockNow,
|
||||||
provider;
|
provider;
|
||||||
|
|
||||||
function mockPromise(value) {
|
function mockPromise(value) {
|
||||||
@ -55,19 +56,33 @@ define(
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
modTimes = {};
|
modTimes = {};
|
||||||
mockQ = { when: mockPromise, all: mockAll };
|
mockQ = { when: mockPromise, all: mockAll };
|
||||||
mockPersistenceService = {
|
mockPersistenceService = jasmine.createSpyObj(
|
||||||
readObject: function (space, id) {
|
'persistenceService',
|
||||||
|
[
|
||||||
|
'createObject',
|
||||||
|
'readObject',
|
||||||
|
'updateObject',
|
||||||
|
'deleteObject',
|
||||||
|
'listSpaces',
|
||||||
|
'listObjects'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockNow = jasmine.createSpy("now");
|
||||||
|
|
||||||
|
mockPersistenceService.readObject
|
||||||
|
.andCallFake(function (space, id) {
|
||||||
return mockPromise({
|
return mockPromise({
|
||||||
space: space,
|
space: space,
|
||||||
id: id,
|
id: id,
|
||||||
modified: (modTimes[space] || {})[id]
|
modified: (modTimes[space] || {})[id],
|
||||||
|
persisted: 0
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
provider = new PersistedModelProvider(
|
provider = new PersistedModelProvider(
|
||||||
mockPersistenceService,
|
mockPersistenceService,
|
||||||
mockQ,
|
mockQ,
|
||||||
|
mockNow,
|
||||||
SPACE,
|
SPACE,
|
||||||
spaces
|
spaces
|
||||||
);
|
);
|
||||||
@ -81,12 +96,13 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(models).toEqual({
|
expect(models).toEqual({
|
||||||
a: { space: SPACE, id: "a" },
|
a: { space: SPACE, id: "a", persisted: 0 },
|
||||||
x: { space: SPACE, id: "x" },
|
x: { space: SPACE, id: "x", persisted: 0 },
|
||||||
zz: { space: SPACE, id: "zz" }
|
zz: { space: SPACE, id: "zz", persisted: 0 }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("reads object models from multiple spaces", function () {
|
it("reads object models from multiple spaces", function () {
|
||||||
var models;
|
var models;
|
||||||
|
|
||||||
@ -99,9 +115,36 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(models).toEqual({
|
expect(models).toEqual({
|
||||||
a: { space: SPACE, id: "a" },
|
a: { space: SPACE, id: "a", persisted: 0 },
|
||||||
x: { space: 'space1', id: "x", modified: 12321 },
|
x: { space: 'space1', id: "x", modified: 12321, persisted: 0 },
|
||||||
zz: { space: SPACE, id: "zz" }
|
zz: { space: SPACE, id: "zz", persisted: 0 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("ensures that persisted timestamps are present", function () {
|
||||||
|
var mockCallback = jasmine.createSpy("callback"),
|
||||||
|
testModels = {
|
||||||
|
a: { modified: 123, persisted: 1984, name: "A" },
|
||||||
|
b: { persisted: 1977, name: "B" },
|
||||||
|
c: { modified: 42, name: "C" },
|
||||||
|
d: { name: "D" }
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPersistenceService.readObject.andCallFake(
|
||||||
|
function (space, id) {
|
||||||
|
return mockPromise(testModels[id]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
mockNow.andReturn(12321);
|
||||||
|
|
||||||
|
provider.getModels(Object.keys(testModels)).then(mockCallback);
|
||||||
|
|
||||||
|
expect(mockCallback).toHaveBeenCalledWith({
|
||||||
|
a: { modified: 123, persisted: 1984, name: "A" },
|
||||||
|
b: { persisted: 1977, name: "B" },
|
||||||
|
c: { modified: 42, persisted: 42, name: "C" },
|
||||||
|
d: { persisted: 12321, name: "D" }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
"representers": [
|
"representers": [
|
||||||
{
|
{
|
||||||
"implementation": "ConductorRepresenter.js",
|
"implementation": "ConductorRepresenter.js",
|
||||||
"depends": [ "conductorService", "$compile", "views[]" ]
|
"depends": [
|
||||||
|
"throttle",
|
||||||
|
"conductorService",
|
||||||
|
"$compile",
|
||||||
|
"views[]"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -36,6 +36,7 @@ define(
|
|||||||
"</mct-include>",
|
"</mct-include>",
|
||||||
'</div>'
|
'</div>'
|
||||||
].join(''),
|
].join(''),
|
||||||
|
THROTTLE_MS = 200,
|
||||||
GLOBAL_SHOWING = false;
|
GLOBAL_SHOWING = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +46,8 @@ define(
|
|||||||
* @implements {Representer}
|
* @implements {Representer}
|
||||||
* @constructor
|
* @constructor
|
||||||
* @memberof platform/features/conductor
|
* @memberof platform/features/conductor
|
||||||
|
* @param {Function} throttle a function used to reduce the frequency
|
||||||
|
* of function invocations
|
||||||
* @param {platform/features/conductor.ConductorService} conductorService
|
* @param {platform/features/conductor.ConductorService} conductorService
|
||||||
* service which provides the active time conductor
|
* service which provides the active time conductor
|
||||||
* @param $compile Angular's $compile
|
* @param $compile Angular's $compile
|
||||||
@ -52,7 +55,15 @@ define(
|
|||||||
* @param {Scope} the scope of the representation
|
* @param {Scope} the scope of the representation
|
||||||
* @param element the jqLite-wrapped representation element
|
* @param element the jqLite-wrapped representation element
|
||||||
*/
|
*/
|
||||||
function ConductorRepresenter(conductorService, $compile, views, scope, element) {
|
function ConductorRepresenter(
|
||||||
|
throttle,
|
||||||
|
conductorService,
|
||||||
|
$compile,
|
||||||
|
views,
|
||||||
|
scope,
|
||||||
|
element
|
||||||
|
) {
|
||||||
|
this.throttle = throttle;
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.conductorService = conductorService;
|
this.conductorService = conductorService;
|
||||||
this.element = element;
|
this.element = element;
|
||||||
@ -60,31 +71,34 @@ define(
|
|||||||
this.$compile = $compile;
|
this.$compile = $compile;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine start/end times & domain into a single object
|
|
||||||
function bounds(start, end, domain) {
|
|
||||||
return { start: start, end: end, domain: domain };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the time conductor from the scope
|
// Update the time conductor from the scope
|
||||||
function wireScope(conductor, conductorScope, repScope) {
|
ConductorRepresenter.prototype.wireScope = function () {
|
||||||
function updateConductorOuter() {
|
var conductor = this.conductorService.getConductor(),
|
||||||
conductor.queryStart(conductorScope.ngModel.conductor.outer.start);
|
conductorScope = this.conductorScope(),
|
||||||
conductor.queryEnd(conductorScope.ngModel.conductor.outer.end);
|
repScope = this.scope,
|
||||||
repScope.$broadcast('telemetry:query:bounds', bounds(
|
lastObservedBounds,
|
||||||
conductor.queryStart(),
|
broadcastBounds;
|
||||||
conductor.queryEnd(),
|
|
||||||
conductor.domain()
|
// Combine start/end times into a single object
|
||||||
));
|
function bounds(start, end) {
|
||||||
|
return {
|
||||||
|
start: conductor.displayStart(),
|
||||||
|
end: conductor.displayEnd(),
|
||||||
|
domain: conductor.domain()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function boundsAreStable(newlyObservedBounds) {
|
||||||
|
return !lastObservedBounds ||
|
||||||
|
(lastObservedBounds.start === newlyObservedBounds.start &&
|
||||||
|
lastObservedBounds.end === newlyObservedBounds.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConductorInner() {
|
function updateConductorInner() {
|
||||||
conductor.displayStart(conductorScope.ngModel.conductor.inner.start);
|
conductor.displayStart(conductorScope.conductor.inner.start);
|
||||||
conductor.displayEnd(conductorScope.ngModel.conductor.inner.end);
|
conductor.displayEnd(conductorScope.conductor.inner.end);
|
||||||
repScope.$broadcast('telemetry:display:bounds', bounds(
|
lastObservedBounds = lastObservedBounds || bounds();
|
||||||
conductor.displayStart(),
|
broadcastBounds();
|
||||||
conductor.displayEnd(),
|
|
||||||
conductor.domain()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDomain(value) {
|
function updateDomain(value) {
|
||||||
@ -104,19 +118,25 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
broadcastBounds = this.throttle(function () {
|
||||||
|
var newlyObservedBounds = bounds();
|
||||||
|
|
||||||
|
if (boundsAreStable(newlyObservedBounds)) {
|
||||||
|
repScope.$broadcast('telemetry:display:bounds', bounds());
|
||||||
|
lastObservedBounds = undefined;
|
||||||
|
} else {
|
||||||
|
lastObservedBounds = newlyObservedBounds;
|
||||||
|
broadcastBounds();
|
||||||
|
}
|
||||||
|
}, THROTTLE_MS);
|
||||||
|
|
||||||
conductorScope.ngModel = {};
|
conductorScope.ngModel = {};
|
||||||
conductorScope.ngModel.conductor = {
|
conductorScope.ngModel.conductor =
|
||||||
outer: bounds(conductor.queryStart(), conductor.queryEnd()),
|
{ outer: bounds(), inner: bounds() };
|
||||||
inner: bounds(conductor.displayStart(), conductor.displayEnd())
|
|
||||||
};
|
|
||||||
conductorScope.ngModel.options =
|
conductorScope.ngModel.options =
|
||||||
conductor.domainOptions().map(makeOption);
|
conductor.domainOptions().map(makeOption);
|
||||||
conductorScope.ngModel.domain = conductor.domain();
|
conductorScope.ngModel.domain = conductor.domain();
|
||||||
|
|
||||||
conductorScope
|
|
||||||
.$watch('ngModel.conductor.outer.start', updateConductorOuter);
|
|
||||||
conductorScope
|
|
||||||
.$watch('ngModel.conductor.outer.end', updateConductorOuter);
|
|
||||||
conductorScope
|
conductorScope
|
||||||
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
||||||
conductorScope
|
conductorScope
|
||||||
@ -125,11 +145,10 @@ define(
|
|||||||
.$watch('ngModel.domain', updateDomain);
|
.$watch('ngModel.domain', updateDomain);
|
||||||
|
|
||||||
repScope.$on('telemetry:view', updateConductorInner);
|
repScope.$on('telemetry:view', updateConductorInner);
|
||||||
}
|
};
|
||||||
|
|
||||||
ConductorRepresenter.prototype.conductorScope = function (s) {
|
ConductorRepresenter.prototype.conductorScope = function (s) {
|
||||||
return (this.cScope = arguments.length > 0 ?
|
return (this.cScope = arguments.length > 0 ? s : this.cScope);
|
||||||
s : this.cScope);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
// Handle a specific representation of a specific domain object
|
||||||
@ -143,11 +162,7 @@ define(
|
|||||||
|
|
||||||
// Create a new scope for the conductor
|
// Create a new scope for the conductor
|
||||||
this.conductorScope(this.scope.$new());
|
this.conductorScope(this.scope.$new());
|
||||||
wireScope(
|
this.wireScope();
|
||||||
this.conductorService.getConductor(),
|
|
||||||
this.conductorScope(),
|
|
||||||
this.scope
|
|
||||||
);
|
|
||||||
this.conductorElement =
|
this.conductorElement =
|
||||||
this.$compile(TEMPLATE)(this.conductorScope());
|
this.$compile(TEMPLATE)(this.conductorScope());
|
||||||
this.element.after(this.conductorElement[0]);
|
this.element.after(this.conductorElement[0]);
|
||||||
|
@ -22,8 +22,7 @@
|
|||||||
/*global define*/
|
/*global define*/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
['./ConductorTelemetrySeries'],
|
function () {
|
||||||
function (ConductorTelemetrySeries) {
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,32 +41,6 @@ define(
|
|||||||
this.telemetryService = telemetryService;
|
this.telemetryService = telemetryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip out any realtime data series that is outside of the conductor's
|
|
||||||
// bounds.
|
|
||||||
ConductorTelemetryDecorator.prototype.pruneNonDisplayable = function (packaged) {
|
|
||||||
var conductor = this.conductorService.getConductor(),
|
|
||||||
repackaged = {};
|
|
||||||
|
|
||||||
function filterSource(packagedBySource) {
|
|
||||||
var repackagedBySource = {};
|
|
||||||
|
|
||||||
Object.keys(packagedBySource).forEach(function (k) {
|
|
||||||
repackagedBySource[k] = new ConductorTelemetrySeries(
|
|
||||||
packagedBySource[k],
|
|
||||||
conductor
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return repackagedBySource;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(packaged).forEach(function (source) {
|
|
||||||
repackaged[source] = filterSource(packaged[source]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return repackaged;
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
|
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
|
||||||
var conductor = this.conductorService.getConductor(),
|
var conductor = this.conductorService.getConductor(),
|
||||||
start = conductor.displayStart(),
|
start = conductor.displayStart(),
|
||||||
@ -88,21 +61,14 @@ define(
|
|||||||
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
|
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return this.telemetryService
|
return this.telemetryService
|
||||||
.requestTelemetry(this.amendRequests(requests))
|
.requestTelemetry(this.amendRequests(requests));
|
||||||
.then(function (packaged) {
|
|
||||||
return self.pruneNonDisplayable(packaged);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
|
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
function internalCallback(packagedSeries) {
|
|
||||||
return callback(self.pruneNonDisplayable(packagedSeries));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.telemetryService
|
return this.telemetryService
|
||||||
.subscribe(internalCallback, this.amendRequests(requests));
|
.subscribe(callback, this.amendRequests(requests));
|
||||||
};
|
};
|
||||||
|
|
||||||
return ConductorTelemetryDecorator;
|
return ConductorTelemetryDecorator;
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*global define*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bound a series of telemetry such that it only includes
|
|
||||||
* points from within the time conductor's displayable window.
|
|
||||||
*
|
|
||||||
* @param {TelemetrySeries} series the telemetry series
|
|
||||||
* @param {platform/features/conductor.TimeConductor} the
|
|
||||||
* time conductor instance which bounds this series
|
|
||||||
* @constructor
|
|
||||||
* @implements {TelemetrySeries}
|
|
||||||
*/
|
|
||||||
function ConductorTelemetrySeries(series, conductor) {
|
|
||||||
var max = series.getPointCount() - 1,
|
|
||||||
domain = conductor.domain();
|
|
||||||
|
|
||||||
function binSearch(min, max, value) {
|
|
||||||
var mid = Math.floor((min + max) / 2);
|
|
||||||
|
|
||||||
return min > max ? min :
|
|
||||||
series.getDomainValue(mid, domain) < value ?
|
|
||||||
binSearch(mid + 1, max, value) :
|
|
||||||
binSearch(min, mid - 1, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startIndex = binSearch(0, max, conductor.displayStart());
|
|
||||||
this.endIndex = binSearch(0, max, conductor.displayEnd());
|
|
||||||
this.series = series;
|
|
||||||
this.domain = domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConductorTelemetrySeries.prototype.getPointCount = function () {
|
|
||||||
return Math.max(0, this.endIndex - this.startIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorTelemetrySeries.prototype.getDomainValue = function (i, d) {
|
|
||||||
d = d || this.domain;
|
|
||||||
return this.series.getDomainValue(i + this.startIndex, d);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) {
|
|
||||||
return this.series.getRangeValue(i + this.startIndex, r);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorTelemetrySeries;
|
|
||||||
}
|
|
||||||
);
|
|
@ -41,37 +41,11 @@ define(
|
|||||||
* @param {number} end the initial end time
|
* @param {number} end the initial end time
|
||||||
*/
|
*/
|
||||||
function TimeConductor(start, end, domains) {
|
function TimeConductor(start, end, domains) {
|
||||||
this.inner = { start: start, end: end };
|
this.range = { start: start, end: end };
|
||||||
this.outer = { start: start, end: end };
|
|
||||||
this.domains = domains;
|
this.domains = domains;
|
||||||
this.activeDomain = domains[0].key;
|
this.activeDomain = domains[0].key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set (if called with an argument) the start time for queries.
|
|
||||||
* @param {number} [value] the start time to set
|
|
||||||
* @returns {number} the start time
|
|
||||||
*/
|
|
||||||
TimeConductor.prototype.queryStart = function (value) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.outer.start = value;
|
|
||||||
}
|
|
||||||
return this.outer.start;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set (if called with an argument) the end time for queries.
|
|
||||||
* @param {number} [value] the end time to set
|
|
||||||
* @returns {number} the end time
|
|
||||||
*/
|
|
||||||
TimeConductor.prototype.queryEnd = function (value) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.outer.end = value;
|
|
||||||
}
|
|
||||||
return this.outer.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set (if called with an argument) the start time for displays.
|
* Get or set (if called with an argument) the start time for displays.
|
||||||
* @param {number} [value] the start time to set
|
* @param {number} [value] the start time to set
|
||||||
@ -79,9 +53,9 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductor.prototype.displayStart = function (value) {
|
TimeConductor.prototype.displayStart = function (value) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.inner.start = value;
|
this.range.start = value;
|
||||||
}
|
}
|
||||||
return this.inner.start;
|
return this.range.start;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,9 +65,9 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductor.prototype.displayEnd = function (value) {
|
TimeConductor.prototype.displayEnd = function (value) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.inner.end = value;
|
this.range.end = value;
|
||||||
}
|
}
|
||||||
return this.inner.end;
|
return this.range.end;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,7 +44,8 @@ define(
|
|||||||
];
|
];
|
||||||
|
|
||||||
describe("ConductorRepresenter", function () {
|
describe("ConductorRepresenter", function () {
|
||||||
var mockConductorService,
|
var mockThrottle,
|
||||||
|
mockConductorService,
|
||||||
mockCompile,
|
mockCompile,
|
||||||
testViews,
|
testViews,
|
||||||
mockScope,
|
mockScope,
|
||||||
@ -64,6 +65,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
mockThrottle = jasmine.createSpy('throttle');
|
||||||
mockConductorService = jasmine.createSpyObj(
|
mockConductorService = jasmine.createSpyObj(
|
||||||
'conductorService',
|
'conductorService',
|
||||||
['getConductor']
|
['getConductor']
|
||||||
@ -82,8 +84,12 @@ define(
|
|||||||
mockCompile.andReturn(mockCompiledTemplate);
|
mockCompile.andReturn(mockCompiledTemplate);
|
||||||
mockCompiledTemplate.andReturn(mockNewElement);
|
mockCompiledTemplate.andReturn(mockNewElement);
|
||||||
mockScope.$new.andReturn(mockNewScope);
|
mockScope.$new.andReturn(mockNewScope);
|
||||||
|
mockThrottle.andCallFake(function (fn) {
|
||||||
|
return fn;
|
||||||
|
});
|
||||||
|
|
||||||
representer = new ConductorRepresenter(
|
representer = new ConductorRepresenter(
|
||||||
|
mockThrottle,
|
||||||
mockConductorService,
|
mockConductorService,
|
||||||
mockCompile,
|
mockCompile,
|
||||||
testViews,
|
testViews,
|
||||||
@ -121,15 +127,13 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes conductor state in scope", function () {
|
it("exposes conductor state in scope", function () {
|
||||||
mockConductor.queryStart.andReturn(42);
|
|
||||||
mockConductor.queryEnd.andReturn(12321);
|
|
||||||
mockConductor.displayStart.andReturn(1977);
|
mockConductor.displayStart.andReturn(1977);
|
||||||
mockConductor.displayEnd.andReturn(1984);
|
mockConductor.displayEnd.andReturn(1984);
|
||||||
representer.represent(testViews[0], {});
|
representer.represent(testViews[0], {});
|
||||||
|
|
||||||
expect(mockNewScope.ngModel.conductor).toEqual({
|
expect(mockNewScope.ngModel.conductor).toEqual({
|
||||||
inner: { start: 1977, end: 1984 },
|
inner: { start: 1977, end: 1984 },
|
||||||
outer: { start: 42, end: 12321 }
|
outer: { start: 1977, end: 1984 }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,20 +160,56 @@ define(
|
|||||||
testState.inner.end
|
testState.inner.end
|
||||||
);
|
);
|
||||||
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
|
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
|
||||||
|
});
|
||||||
|
|
||||||
fireWatch(
|
describe("when bounds are changing", function () {
|
||||||
mockNewScope,
|
var mockThrottledFn = jasmine.createSpy('throttledFn'),
|
||||||
'ngModel.conductor.outer.start',
|
testBounds;
|
||||||
testState.outer.start
|
|
||||||
);
|
|
||||||
expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977);
|
|
||||||
|
|
||||||
fireWatch(
|
function fireThrottledFn() {
|
||||||
mockNewScope,
|
mockThrottle.mostRecentCall.args[0]();
|
||||||
'ngModel.conductor.outer.end',
|
}
|
||||||
testState.outer.end
|
|
||||||
);
|
beforeEach(function () {
|
||||||
expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321);
|
mockThrottle.andReturn(mockThrottledFn);
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
testBounds = { start: 0, end: 1000 };
|
||||||
|
mockNewScope.conductor.inner = testBounds;
|
||||||
|
mockConductor.displayStart.andCallFake(function () {
|
||||||
|
return testBounds.start;
|
||||||
|
});
|
||||||
|
mockConductor.displayEnd.andCallFake(function () {
|
||||||
|
return testBounds.end;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not broadcast while bounds are changing", function () {
|
||||||
|
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||||
|
testBounds.start = 100;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||||
|
testBounds.end = 500;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||||
|
fireThrottledFn();
|
||||||
|
testBounds.start = 200;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||||
|
testBounds.end = 400;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||||
|
fireThrottledFn();
|
||||||
|
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does broadcast when bounds have stabilized", function () {
|
||||||
|
expect(mockScope.$broadcast).not.toHaveBeenCalled();
|
||||||
|
testBounds.start = 100;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||||
|
testBounds.end = 500;
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||||
|
fireThrottledFn();
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.start', testBounds.start);
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.end', testBounds.end);
|
||||||
|
fireThrottledFn();
|
||||||
|
expect(mockScope.$broadcast).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes domain selection in scope", function () {
|
it("exposes domain selection in scope", function () {
|
||||||
|
@ -43,9 +43,9 @@ define(
|
|||||||
|
|
||||||
it("initializes a time conductor around the current time", function () {
|
it("initializes a time conductor around the current time", function () {
|
||||||
var conductor = conductorService.getConductor();
|
var conductor = conductorService.getConductor();
|
||||||
expect(conductor.queryStart() <= TEST_NOW).toBeTruthy();
|
expect(conductor.displayStart() <= TEST_NOW).toBeTruthy();
|
||||||
expect(conductor.queryEnd() >= TEST_NOW).toBeTruthy();
|
expect(conductor.displayEnd() >= TEST_NOW).toBeTruthy();
|
||||||
expect(conductor.queryEnd() > conductor.queryStart())
|
expect(conductor.displayEnd() > conductor.displayStart())
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,27 +156,6 @@ define(
|
|||||||
// }]);
|
// }]);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
it("prunes historical values to the displayable range", function () {
|
|
||||||
var packagedTelemetry;
|
|
||||||
decorator.requestTelemetry([{ source: "abc", key: "xyz" }]);
|
|
||||||
packagedTelemetry = mockPromise.then.mostRecentCall.args[0]({
|
|
||||||
"abc": { "xyz": mockSeries }
|
|
||||||
});
|
|
||||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
|
||||||
.toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("prunes subscribed values to the displayable range", function () {
|
|
||||||
var mockCallback = jasmine.createSpy('callback'),
|
|
||||||
packagedTelemetry;
|
|
||||||
decorator.subscribe(mockCallback, [{ source: "abc", key: "xyz" }]);
|
|
||||||
mockTelemetryService.subscribe.mostRecentCall.args[0]({
|
|
||||||
"abc": { "xyz": mockSeries }
|
|
||||||
});
|
|
||||||
packagedTelemetry = mockCallback.mostRecentCall.args[0];
|
|
||||||
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
|
||||||
.toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../src/ConductorTelemetrySeries", "./TestTimeConductor"],
|
|
||||||
function (ConductorTelemetrySeries, TestTimeConductor) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
describe("ConductorTelemetrySeries", function () {
|
|
||||||
var mockSeries,
|
|
||||||
mockConductor,
|
|
||||||
testArray,
|
|
||||||
series;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testArray = [ -10, 0, 42, 1977, 12321 ];
|
|
||||||
|
|
||||||
mockSeries = jasmine.createSpyObj(
|
|
||||||
'series',
|
|
||||||
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
|
||||||
);
|
|
||||||
mockConductor = new TestTimeConductor();
|
|
||||||
|
|
||||||
mockSeries.getPointCount.andCallFake(function () {
|
|
||||||
return testArray.length;
|
|
||||||
});
|
|
||||||
mockSeries.getDomainValue.andCallFake(function (i) {
|
|
||||||
return testArray[i];
|
|
||||||
});
|
|
||||||
mockSeries.getRangeValue.andCallFake(function (i) {
|
|
||||||
return testArray[i] * 2;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockConductor.displayStart.andReturn(0);
|
|
||||||
mockConductor.displayEnd.andReturn(2000);
|
|
||||||
|
|
||||||
series = new ConductorTelemetrySeries(
|
|
||||||
mockSeries,
|
|
||||||
mockConductor
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reduces the apparent size of a series", function () {
|
|
||||||
expect(series.getPointCount()).toEqual(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("maps domain value indexes to the displayable range", function () {
|
|
||||||
[0, 1, 2].forEach(function (i) {
|
|
||||||
expect(series.getDomainValue(i))
|
|
||||||
.toEqual(mockSeries.getDomainValue(i + 1));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("maps range value indexes to the displayable range", function () {
|
|
||||||
[0, 1, 2].forEach(function (i) {
|
|
||||||
expect(series.getRangeValue(i))
|
|
||||||
.toEqual(mockSeries.getRangeValue(i + 1));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -43,19 +43,13 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("provides accessors for query/display start/end times", function () {
|
it("provides accessors for query/display start/end times", function () {
|
||||||
expect(conductor.queryStart()).toEqual(testStart);
|
|
||||||
expect(conductor.queryEnd()).toEqual(testEnd);
|
|
||||||
expect(conductor.displayStart()).toEqual(testStart);
|
expect(conductor.displayStart()).toEqual(testStart);
|
||||||
expect(conductor.displayEnd()).toEqual(testEnd);
|
expect(conductor.displayEnd()).toEqual(testEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides setters for query/display start/end times", function () {
|
it("provides setters for query/display start/end times", function () {
|
||||||
expect(conductor.queryStart(1)).toEqual(1);
|
|
||||||
expect(conductor.queryEnd(2)).toEqual(2);
|
|
||||||
expect(conductor.displayStart(3)).toEqual(3);
|
expect(conductor.displayStart(3)).toEqual(3);
|
||||||
expect(conductor.displayEnd(4)).toEqual(4);
|
expect(conductor.displayEnd(4)).toEqual(4);
|
||||||
expect(conductor.queryStart()).toEqual(1);
|
|
||||||
expect(conductor.queryEnd()).toEqual(2);
|
|
||||||
expect(conductor.displayStart()).toEqual(3);
|
expect(conductor.displayStart()).toEqual(3);
|
||||||
expect(conductor.displayEnd()).toEqual(4);
|
expect(conductor.displayEnd()).toEqual(4);
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,5 @@
|
|||||||
"ConductorRepresenter",
|
"ConductorRepresenter",
|
||||||
"ConductorService",
|
"ConductorService",
|
||||||
"ConductorTelemetryDecorator",
|
"ConductorTelemetryDecorator",
|
||||||
"ConductorTelemetrySeries",
|
|
||||||
"TimeConductor"
|
"TimeConductor"
|
||||||
]
|
]
|
||||||
|
@ -66,7 +66,6 @@ define(
|
|||||||
cachedObjects = [],
|
cachedObjects = [],
|
||||||
updater,
|
updater,
|
||||||
lastBounds,
|
lastBounds,
|
||||||
throttledRequery,
|
|
||||||
handle;
|
handle;
|
||||||
|
|
||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
@ -188,15 +187,10 @@ define(
|
|||||||
function changeDisplayBounds(event, bounds) {
|
function changeDisplayBounds(event, bounds) {
|
||||||
self.pending = true;
|
self.pending = true;
|
||||||
releaseSubscription();
|
releaseSubscription();
|
||||||
throttledRequery();
|
subscribe($scope.domainObject);
|
||||||
setBasePanZoom(bounds);
|
setBasePanZoom(bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reestablish/reissue request for telemetry
|
|
||||||
throttledRequery = throttle(function () {
|
|
||||||
subscribe($scope.domainObject);
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||||
this.updateValues = updateValues;
|
this.updateValues = updateValues;
|
||||||
|
|
||||||
|
@ -54,7 +54,13 @@
|
|||||||
{
|
{
|
||||||
"key": "menu",
|
"key": "menu",
|
||||||
"implementation": "actions/ContextMenuAction.js",
|
"implementation": "actions/ContextMenuAction.js",
|
||||||
"depends": [ "$compile", "$document", "$window", "$rootScope", "agentService" ]
|
"depends": [
|
||||||
|
"$compile",
|
||||||
|
"$document",
|
||||||
|
"$rootScope",
|
||||||
|
"popupService",
|
||||||
|
"agentService"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -43,40 +43,52 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param $compile Angular's $compile service
|
* @param $compile Angular's $compile service
|
||||||
* @param $document the current document
|
* @param $document the current document
|
||||||
* @param $window the active window
|
|
||||||
* @param $rootScope Angular's root scope
|
* @param $rootScope Angular's root scope
|
||||||
* @param actionContexr the context in which the action
|
* @param {platform/commonUI/general.PopupService} popupService
|
||||||
|
* @param actionContext the context in which the action
|
||||||
* should be performed
|
* should be performed
|
||||||
* @implements {Action}
|
* @implements {Action}
|
||||||
*/
|
*/
|
||||||
function ContextMenuAction($compile, $document, $window, $rootScope, agentService, actionContext) {
|
function ContextMenuAction(
|
||||||
|
$compile,
|
||||||
|
$document,
|
||||||
|
$rootScope,
|
||||||
|
popupService,
|
||||||
|
agentService,
|
||||||
|
actionContext
|
||||||
|
) {
|
||||||
this.$compile = $compile;
|
this.$compile = $compile;
|
||||||
this.agentService = agentService;
|
this.agentService = agentService;
|
||||||
this.actionContext = actionContext;
|
this.actionContext = actionContext;
|
||||||
|
this.popupService = popupService;
|
||||||
this.getDocument = function () { return $document; };
|
this.getDocument = function () { return $document; };
|
||||||
this.getWindow = function () { return $window; };
|
|
||||||
this.getRootScope = function () { return $rootScope; };
|
this.getRootScope = function () { return $rootScope; };
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenuAction.prototype.perform = function () {
|
ContextMenuAction.prototype.perform = function () {
|
||||||
var $compile = this.$compile,
|
var $compile = this.$compile,
|
||||||
$document = this.getDocument(),
|
$document = this.getDocument(),
|
||||||
$window = this.getWindow(),
|
|
||||||
$rootScope = this.getRootScope(),
|
$rootScope = this.getRootScope(),
|
||||||
actionContext = this.actionContext,
|
actionContext = this.actionContext,
|
||||||
winDim = [$window.innerWidth, $window.innerHeight],
|
eventCoords = [
|
||||||
eventCoors = [actionContext.event.pageX, actionContext.event.pageY],
|
actionContext.event.pageX,
|
||||||
|
actionContext.event.pageY
|
||||||
|
],
|
||||||
menuDim = GestureConstants.MCT_MENU_DIMENSIONS,
|
menuDim = GestureConstants.MCT_MENU_DIMENSIONS,
|
||||||
body = $document.find('body'),
|
body = $document.find('body'),
|
||||||
scope = $rootScope.$new(),
|
scope = $rootScope.$new(),
|
||||||
goLeft = eventCoors[0] + menuDim[0] > winDim[0],
|
initiatingEvent = this.agentService.isMobile() ?
|
||||||
goUp = eventCoors[1] + menuDim[1] > winDim[1],
|
'touchstart' : 'mousedown',
|
||||||
initiatingEvent = this.agentService.isMobile() ? 'touchstart' : 'mousedown',
|
menu,
|
||||||
menu;
|
popup;
|
||||||
|
|
||||||
// Remove the context menu
|
// Remove the context menu
|
||||||
function dismiss() {
|
function dismiss() {
|
||||||
menu.remove();
|
if (popup) {
|
||||||
|
popup.dismiss();
|
||||||
|
popup = undefined;
|
||||||
|
}
|
||||||
|
scope.$destroy();
|
||||||
body.off("mousedown", dismiss);
|
body.off("mousedown", dismiss);
|
||||||
dismissExistingMenu = undefined;
|
dismissExistingMenu = undefined;
|
||||||
}
|
}
|
||||||
@ -91,21 +103,17 @@ define(
|
|||||||
|
|
||||||
// Set up the scope, including menu positioning
|
// Set up the scope, including menu positioning
|
||||||
scope.domainObject = actionContext.domainObject;
|
scope.domainObject = actionContext.domainObject;
|
||||||
scope.menuStyle = {};
|
scope.menuClass = { "context-menu-holder": true };
|
||||||
scope.menuStyle[goLeft ? "right" : "left"] =
|
|
||||||
(goLeft ? (winDim[0] - eventCoors[0]) : eventCoors[0]) + 'px';
|
|
||||||
scope.menuStyle[goUp ? "bottom" : "top"] =
|
|
||||||
(goUp ? (winDim[1] - eventCoors[1]) : eventCoors[1]) + 'px';
|
|
||||||
scope.menuClass = {
|
|
||||||
"go-left": goLeft,
|
|
||||||
"go-up": goUp,
|
|
||||||
"context-menu-holder": true
|
|
||||||
};
|
|
||||||
// Create the context menu
|
// Create the context menu
|
||||||
menu = $compile(MENU_TEMPLATE)(scope);
|
menu = $compile(MENU_TEMPLATE)(scope);
|
||||||
|
|
||||||
// Add the menu to the body
|
popup = this.popupService.display(menu, eventCoords, {
|
||||||
body.append(menu);
|
marginX: -menuDim[0],
|
||||||
|
marginY: -menuDim[1]
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.menuClass['go-left'] = popup.goesLeft();
|
||||||
|
scope.menuClass['go-up'] = popup.goesUp();
|
||||||
|
|
||||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||||
menu.on(initiatingEvent, function (event) {
|
menu.on(initiatingEvent, function (event) {
|
||||||
|
@ -41,13 +41,14 @@ define(
|
|||||||
mockMenu,
|
mockMenu,
|
||||||
mockDocument,
|
mockDocument,
|
||||||
mockBody,
|
mockBody,
|
||||||
mockWindow,
|
mockPopupService,
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockEvent,
|
mockEvent,
|
||||||
|
mockPopup,
|
||||||
mockActionContext,
|
mockActionContext,
|
||||||
action;
|
action;
|
||||||
|
|
||||||
@ -57,36 +58,47 @@ define(
|
|||||||
mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS);
|
mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS);
|
||||||
mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS);
|
mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS);
|
||||||
mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS);
|
mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS);
|
||||||
mockWindow = { innerWidth: MENU_DIMENSIONS[0] * 4, innerHeight: MENU_DIMENSIONS[1] * 4 };
|
mockPopupService =
|
||||||
|
jasmine.createSpyObj("popupService", ["display"]);
|
||||||
|
mockPopup = jasmine.createSpyObj("popup", [
|
||||||
|
"dismiss",
|
||||||
|
"goesLeft",
|
||||||
|
"goesUp"
|
||||||
|
]);
|
||||||
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
|
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
|
||||||
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
||||||
mockScope = {};
|
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||||
mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]);
|
mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]);
|
||||||
mockEvent.pageX = 0;
|
mockEvent.pageX = 123;
|
||||||
mockEvent.pageY = 0;
|
mockEvent.pageY = 321;
|
||||||
|
|
||||||
mockCompile.andReturn(mockCompiledTemplate);
|
mockCompile.andReturn(mockCompiledTemplate);
|
||||||
mockCompiledTemplate.andReturn(mockMenu);
|
mockCompiledTemplate.andReturn(mockMenu);
|
||||||
mockDocument.find.andReturn(mockBody);
|
mockDocument.find.andReturn(mockBody);
|
||||||
mockRootScope.$new.andReturn(mockScope);
|
mockRootScope.$new.andReturn(mockScope);
|
||||||
|
mockPopupService.display.andReturn(mockPopup);
|
||||||
|
|
||||||
mockActionContext = {key: 'menu', domainObject: mockDomainObject, event: mockEvent};
|
mockActionContext = {key: 'menu', domainObject: mockDomainObject, event: mockEvent};
|
||||||
|
|
||||||
action = new ContextMenuAction(
|
action = new ContextMenuAction(
|
||||||
mockCompile,
|
mockCompile,
|
||||||
mockDocument,
|
mockDocument,
|
||||||
mockWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockActionContext
|
mockActionContext
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(" adds a menu to the DOM when perform is called", function () {
|
it("displays a popup when performed", function () {
|
||||||
action.perform();
|
action.perform();
|
||||||
expect(mockBody.append).toHaveBeenCalledWith(mockMenu);
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
|
mockMenu,
|
||||||
|
[ mockEvent.pageX, mockEvent.pageY ],
|
||||||
|
jasmine.any(Object)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prevents the default context menu behavior", function () {
|
it("prevents the default context menu behavior", function () {
|
||||||
@ -94,29 +106,22 @@ define(
|
|||||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("positions menus where clicked", function () {
|
it("adds classes to menus based on position", function () {
|
||||||
mockEvent.pageX = 10;
|
var booleans = [ false, true ];
|
||||||
mockEvent.pageY = 5;
|
|
||||||
action.perform();
|
booleans.forEach(function (goLeft) {
|
||||||
expect(mockScope.menuStyle.left).toEqual("10px");
|
booleans.forEach(function (goUp) {
|
||||||
expect(mockScope.menuStyle.top).toEqual("5px");
|
mockPopup.goesLeft.andReturn(goLeft);
|
||||||
expect(mockScope.menuStyle.right).toBeUndefined();
|
mockPopup.goesUp.andReturn(goUp);
|
||||||
expect(mockScope.menuStyle.bottom).toBeUndefined();
|
action.perform();
|
||||||
expect(mockScope.menuClass['go-up']).toBeFalsy();
|
expect(!!mockScope.menuClass['go-up'])
|
||||||
expect(mockScope.menuClass['go-left']).toBeFalsy();
|
.toEqual(goUp);
|
||||||
|
expect(!!mockScope.menuClass['go-left'])
|
||||||
|
.toEqual(goLeft);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("repositions menus near the screen edge", function () {
|
|
||||||
mockEvent.pageX = mockWindow.innerWidth - 10;
|
|
||||||
mockEvent.pageY = mockWindow.innerHeight - 5;
|
|
||||||
action.perform();
|
|
||||||
expect(mockScope.menuStyle.right).toEqual("10px");
|
|
||||||
expect(mockScope.menuStyle.bottom).toEqual("5px");
|
|
||||||
expect(mockScope.menuStyle.left).toBeUndefined();
|
|
||||||
expect(mockScope.menuStyle.top).toBeUndefined();
|
|
||||||
expect(mockScope.menuClass['go-up']).toBeTruthy();
|
|
||||||
expect(mockScope.menuClass['go-left']).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes a menu when body is clicked", function () {
|
it("removes a menu when body is clicked", function () {
|
||||||
// Show the menu
|
// Show the menu
|
||||||
@ -133,7 +138,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Menu should have been removed
|
// Menu should have been removed
|
||||||
expect(mockMenu.remove).toHaveBeenCalled();
|
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||||
|
|
||||||
// Listener should have been detached from body
|
// Listener should have been detached from body
|
||||||
expect(mockBody.off).toHaveBeenCalledWith(
|
expect(mockBody.off).toHaveBeenCalledWith(
|
||||||
@ -149,7 +154,7 @@ define(
|
|||||||
// Verify precondition
|
// Verify precondition
|
||||||
expect(mockMenu.remove).not.toHaveBeenCalled();
|
expect(mockMenu.remove).not.toHaveBeenCalled();
|
||||||
|
|
||||||
// Find and fire body's mousedown listener
|
// Find and fire menu's click listener
|
||||||
mockMenu.on.calls.forEach(function (call) {
|
mockMenu.on.calls.forEach(function (call) {
|
||||||
if (call.args[0] === 'click') {
|
if (call.args[0] === 'click') {
|
||||||
call.args[1]();
|
call.args[1]();
|
||||||
@ -157,7 +162,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Menu should have been removed
|
// Menu should have been removed
|
||||||
expect(mockMenu.remove).toHaveBeenCalled();
|
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps a menu when menu is clicked", function () {
|
it("keeps a menu when menu is clicked", function () {
|
||||||
@ -171,7 +176,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Menu should have been removed
|
// Menu should have been removed
|
||||||
expect(mockMenu.remove).not.toHaveBeenCalled();
|
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||||
|
|
||||||
// Listener should have been detached from body
|
// Listener should have been detached from body
|
||||||
expect(mockBody.off).not.toHaveBeenCalled();
|
expect(mockBody.off).not.toHaveBeenCalled();
|
||||||
@ -182,8 +187,8 @@ define(
|
|||||||
action = new ContextMenuAction(
|
action = new ContextMenuAction(
|
||||||
mockCompile,
|
mockCompile,
|
||||||
mockDocument,
|
mockDocument,
|
||||||
mockWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockActionContext
|
mockActionContext
|
||||||
);
|
);
|
||||||
@ -194,6 +199,8 @@ define(
|
|||||||
call.args[1](mockEvent);
|
call.args[1](mockEvent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
|||||||
<groupId>gov.nasa.arc.wtd</groupId>
|
<groupId>gov.nasa.arc.wtd</groupId>
|
||||||
<artifactId>open-mct-web</artifactId>
|
<artifactId>open-mct-web</artifactId>
|
||||||
<name>Open MCT Web</name>
|
<name>Open MCT Web</name>
|
||||||
<version>0.8.1-SNAPSHOT</version>
|
<version>0.8.2-SNAPSHOT</version>
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user