Merge branch 'open-master' into open1256

This commit is contained in:
Victor Woeltjen 2015-06-23 13:14:17 -07:00
commit dc4ce59dbd
46 changed files with 1228 additions and 160 deletions

View File

@ -9,6 +9,7 @@
"platform/commonUI/general", "platform/commonUI/general",
"platform/commonUI/inspect", "platform/commonUI/inspect",
"platform/containment", "platform/containment",
"platform/execution",
"platform/telemetry", "platform/telemetry",
"platform/features/layout", "platform/features/layout",
"platform/features/pages", "platform/features/pages",

1
example/worker/README.md Normal file
View File

@ -0,0 +1 @@
Example of running a Web Worker using the `workerService`.

View File

@ -0,0 +1,16 @@
{
"extensions": {
"indicators": [
{
"implementation": "FibonacciIndicator.js",
"depends": [ "workerService", "$rootScope" ]
}
],
"workers": [
{
"key": "example.fibonacci",
"scriptUrl": "FibonacciWorker.js"
}
]
}
}

View File

@ -0,0 +1,70 @@
/*****************************************************************************
* 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";
/**
* Displays Fibonacci numbers in the status area.
* @constructor
*/
function FibonacciIndicator(workerService, $rootScope) {
var latest,
counter = 0,
worker = workerService.run('example.fibonacci');
function requestNext() {
worker.postMessage([counter]);
counter += 1;
}
function handleResponse(event) {
latest = event.data;
$rootScope.$apply();
requestNext();
}
worker.onmessage = handleResponse;
requestNext();
return {
getGlyph: function () {
return "?";
},
getText: function () {
return latest;
},
getGlyphClass: function () {
return "";
},
getDescription: function () {
return "";
}
};
}
return FibonacciIndicator;
}
);

View File

@ -0,0 +1,15 @@
/*global self*/
(function () {
"use strict";
// Calculate fibonacci numbers inefficiently.
// We can do this because we're on a background thread, and
// won't halt the UI.
function fib(n) {
return n < 2 ? n : (fib(n - 1) + fib(n - 2));
}
self.onmessage = function (event) {
self.postMessage(fib(event.data));
};
}());

View File

@ -2,19 +2,26 @@
"extensions": { "extensions": {
"routes": [ "routes": [
{ {
"when": "/browse", "when": "/browse/:ids*",
"templateUrl": "templates/browse.html" "templateUrl": "templates/browse.html",
"reloadOnSearch": false
}, },
{ {
"when": "", "when": "",
"templateUrl": "templates/browse.html" "templateUrl": "templates/browse.html",
"reloadOnSearch": false
} }
], ],
"controllers": [ "controllers": [
{ {
"key": "BrowseController", "key": "BrowseController",
"implementation": "BrowseController.js", "implementation": "BrowseController.js",
"depends": [ "$scope", "objectService", "navigationService" ] "depends": [ "$scope", "$route", "$location", "objectService", "navigationService" ]
},
{
"key": "BrowseObjectController",
"implementation": "BrowseObjectController.js",
"depends": [ "$scope", "$location", "$route" ]
}, },
{ {
"key": "CreateMenuController", "key": "CreateMenuController",

View File

@ -19,7 +19,7 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<span> <span ng-controller="BrowseObjectController">
<div class="object-browse-bar bar abs"> <div class="object-browse-bar bar abs">
<div class="items-select left abs"> <div class="items-select left abs">
<mct-representation key="'object-header'" mct-object="domainObject"> <mct-representation key="'object-header'" mct-object="domainObject">

View File

@ -29,7 +29,8 @@ define(
function () { function () {
"use strict"; "use strict";
var ROOT_OBJECT = "ROOT"; var ROOT_ID = "ROOT",
DEFAULT_PATH = "mine";
/** /**
* The BrowseController is used to populate the initial scope in Browse * The BrowseController is used to populate the initial scope in Browse
@ -40,35 +41,98 @@ define(
* *
* @constructor * @constructor
*/ */
function BrowseController($scope, objectService, navigationService) { function BrowseController($scope, $route, $location, objectService, navigationService) {
var path = [ROOT_ID].concat(
($route.current.params.ids || DEFAULT_PATH).split("/")
);
function updateRoute(domainObject) {
var context = domainObject &&
domainObject.getCapability('context'),
objectPath = context ? context.getPath() : [],
ids = objectPath.map(function (domainObject) {
return domainObject.getId();
}),
priorRoute = $route.current,
// Act as if params HADN'T changed to avoid page reload
unlisten;
unlisten = $scope.$on('$locationChangeSuccess', function () {
$route.current = priorRoute;
unlisten();
});
$location.path("/browse/" + ids.slice(1).join("/"));
}
// Callback for updating the in-scope reference to the object // Callback for updating the in-scope reference to the object
// that is currently navigated-to. // that is currently navigated-to.
function setNavigation(domainObject) { function setNavigation(domainObject) {
$scope.navigatedObject = domainObject; $scope.navigatedObject = domainObject;
$scope.treeModel.selectedObject = domainObject; $scope.treeModel.selectedObject = domainObject;
navigationService.setNavigation(domainObject); navigationService.setNavigation(domainObject);
updateRoute(domainObject);
}
function navigateTo(domainObject) {
// Check if an object has been navigated-to already...
// If not, or if an ID path has been explicitly set in the URL,
// navigate to the URL-specified object.
if (!navigationService.getNavigation() || $route.current.params.ids) {
// If not, pick a default as the last
// root-level component (usually "mine")
navigationService.setNavigation(domainObject);
$scope.navigatedObject = domainObject;
} else {
// Otherwise, just expose the currently navigated object.
$scope.navigatedObject = navigationService.getNavigation();
updateRoute($scope.navigatedObject);
}
}
function findObject(domainObjects, id) {
var i;
for (i = 0; i < domainObjects.length; i += 1) {
if (domainObjects[i].getId() === id) {
return domainObjects[i];
}
}
}
// Navigate to the domain object identified by path[index],
// which we expect to find in the composition of the passed
// domain object.
function doNavigate(domainObject, index) {
var composition = domainObject.useCapability("composition");
if (composition) {
composition.then(function (c) {
var nextObject = findObject(c, path[index]);
if (nextObject) {
if (index + 1 >= path.length) {
navigateTo(nextObject);
} else {
doNavigate(nextObject, index + 1);
}
} else {
// Couldn't find the next element of the path
// so navigate to the last path object we did find
navigateTo(domainObject);
}
});
} else {
// Similar to above case; this object has no composition,
// so navigate to it instead of subsequent path elements.
navigateTo(domainObject);
}
} }
// Load the root object, put it in the scope. // Load the root object, put it in the scope.
// Also, load its immediate children, and (possibly) // Also, load its immediate children, and (possibly)
// navigate to one of them, so that navigation state has // navigate to one of them, so that navigation state has
// a useful initial value. // a useful initial value.
objectService.getObjects([ROOT_OBJECT]).then(function (objects) { objectService.getObjects([path[0]]).then(function (objects) {
var composition = objects[ROOT_OBJECT].useCapability("composition"); $scope.domainObject = objects[path[0]];
$scope.domainObject = objects[ROOT_OBJECT]; doNavigate($scope.domainObject, 1);
if (composition) {
composition.then(function (c) {
// Check if an object has been navigated-to already...
if (!navigationService.getNavigation()) {
// If not, pick a default as the last
// root-level component (usually "mine")
navigationService.setNavigation(c[c.length - 1]);
} else {
// Otherwise, just expose it in the scope
$scope.navigatedObject = navigationService.getNavigation();
}
});
}
}); });
// Provide a model for the tree to modify // Provide a model for the tree to modify

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* 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*/
define(
[],
function () {
"use strict";
/**
* Controller for the `browse-object` representation of a domain
* object (the right-hand side of Browse mode.)
* @constructor
*/
function BrowseObjectController($scope, $location, $route) {
function setViewForDomainObject(domainObject) {
var locationViewKey = $location.search().view;
function selectViewIfMatching(view) {
if (view.key === locationViewKey) {
$scope.representation = $scope.representation || {};
$scope.representation.selected = view;
}
}
if (locationViewKey) {
((domainObject && domainObject.useCapability('view')) || [])
.forEach(selectViewIfMatching);
}
}
function updateQueryParam(viewKey) {
var unlisten, priorRoute = $route.current;
if (viewKey) {
$location.search('view', viewKey);
unlisten = $scope.$on('$locationChangeSuccess', function () {
$route.current = priorRoute;
unlisten();
});
}
}
$scope.$watch('domainObject', setViewForDomainObject);
$scope.$watch('representation.selected.key', updateQueryParam);
}
return BrowseObjectController;
}
);

View File

@ -31,10 +31,13 @@ define(
describe("The browse controller", function () { describe("The browse controller", function () {
var mockScope, var mockScope,
mockRoute,
mockLocation,
mockObjectService, mockObjectService,
mockNavigationService, mockNavigationService,
mockRootObject, mockRootObject,
mockDomainObject, mockDomainObject,
mockNextObject,
controller; controller;
function mockPromise(value) { function mockPromise(value) {
@ -50,6 +53,11 @@ define(
"$scope", "$scope",
[ "$on", "$watch" ] [ "$on", "$watch" ]
); );
mockRoute = { current: { params: {} } };
mockLocation = jasmine.createSpyObj(
"$location",
[ "path" ]
);
mockObjectService = jasmine.createSpyObj( mockObjectService = jasmine.createSpyObj(
"objectService", "objectService",
[ "getObjects" ] [ "getObjects" ]
@ -71,25 +79,38 @@ define(
"domainObject", "domainObject",
[ "getId", "getCapability", "getModel", "useCapability" ] [ "getId", "getCapability", "getModel", "useCapability" ]
); );
mockNextObject = jasmine.createSpyObj(
"nextObject",
[ "getId", "getCapability", "getModel", "useCapability" ]
);
mockObjectService.getObjects.andReturn(mockPromise({ mockObjectService.getObjects.andReturn(mockPromise({
ROOT: mockRootObject ROOT: mockRootObject
})); }));
mockRootObject.useCapability.andReturn(mockPromise([
mockDomainObject
]));
mockDomainObject.useCapability.andReturn(mockPromise([
mockNextObject
]));
mockNextObject.useCapability.andReturn(undefined);
mockNextObject.getId.andReturn("next");
mockDomainObject.getId.andReturn("mine");
controller = new BrowseController( controller = new BrowseController(
mockScope, mockScope,
mockRoute,
mockLocation,
mockObjectService, mockObjectService,
mockNavigationService mockNavigationService
); );
}); });
it("uses composition to set the navigated object, if there is none", function () { it("uses composition to set the navigated object, if there is none", function () {
mockRootObject.useCapability.andReturn(mockPromise([
mockDomainObject
]));
controller = new BrowseController( controller = new BrowseController(
mockScope, mockScope,
mockRoute,
mockLocation,
mockObjectService, mockObjectService,
mockNavigationService mockNavigationService
); );
@ -98,12 +119,11 @@ define(
}); });
it("does not try to override navigation", function () { it("does not try to override navigation", function () {
// This behavior is needed if object navigation has been
// determined by query string parameters
mockRootObject.useCapability.andReturn(mockPromise([null]));
mockNavigationService.getNavigation.andReturn(mockDomainObject); mockNavigationService.getNavigation.andReturn(mockDomainObject);
controller = new BrowseController( controller = new BrowseController(
mockScope, mockScope,
mockRoute,
mockLocation,
mockObjectService, mockObjectService,
mockNavigationService mockNavigationService
); );
@ -130,6 +150,76 @@ define(
); );
}); });
it("uses route parameters to choose initially-navigated object", function () {
mockRoute.current.params.ids = "mine/next";
controller = new BrowseController(
mockScope,
mockRoute,
mockLocation,
mockObjectService,
mockNavigationService
);
expect(mockScope.navigatedObject).toBe(mockNextObject);
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockNextObject);
});
it("handles invalid IDs by going as far as possible", function () {
// Idea here is that if we get a bad path of IDs,
// browse controller should traverse down it until
// it hits an invalid ID.
mockRoute.current.params.ids = "mine/junk";
controller = new BrowseController(
mockScope,
mockRoute,
mockLocation,
mockObjectService,
mockNavigationService
);
expect(mockScope.navigatedObject).toBe(mockDomainObject);
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockDomainObject);
});
it("handles compositionless objects by going as far as possible", function () {
// Idea here is that if we get a path which passes
// through an object without a composition, browse controller
// should stop at it since remaining IDs cannot be loaded.
mockRoute.current.params.ids = "mine/next/junk";
controller = new BrowseController(
mockScope,
mockRoute,
mockLocation,
mockObjectService,
mockNavigationService
);
expect(mockScope.navigatedObject).toBe(mockNextObject);
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockNextObject);
});
it("updates the displayed route to reflect current navigation", function () {
var mockContext = jasmine.createSpyObj('context', ['getPath']),
mockUnlisten = jasmine.createSpy('unlisten');
mockContext.getPath.andReturn(
[mockRootObject, mockDomainObject, mockNextObject]
);
mockNextObject.getCapability.andCallFake(function (c) {
return c === 'context' && mockContext;
});
mockScope.$on.andReturn(mockUnlisten);
// Provide a navigation change
mockNavigationService.addListener.mostRecentCall.args[0](
mockNextObject
);
expect(mockLocation.path).toHaveBeenCalledWith("/browse/mine/next");
// Exercise the Angular workaround
mockScope.$on.mostRecentCall.args[1]();
expect(mockUnlisten).toHaveBeenCalled();
});
}); });
} }
); );

View File

@ -0,0 +1,99 @@
/*****************************************************************************
* 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/BrowseObjectController"],
function (BrowseObjectController) {
"use strict";
describe("The browse object controller", function () {
var mockScope,
mockLocation,
mockRoute,
mockUnlisten,
controller;
// Utility function; look for a $watch on scope and fire it
function fireWatch(expr, value) {
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockRoute = { current: { params: {} } };
mockLocation = jasmine.createSpyObj(
"$location",
[ "path", "search" ]
);
mockUnlisten = jasmine.createSpy("unlisten");
mockScope.$on.andReturn(mockUnlisten);
controller = new BrowseObjectController(
mockScope,
mockLocation,
mockRoute
);
});
it("updates query parameters when selected view changes", function () {
fireWatch("representation.selected.key", "xyz");
expect(mockLocation.search).toHaveBeenCalledWith('view', "xyz");
// Exercise the Angular workaround
mockScope.$on.mostRecentCall.args[1]();
expect(mockUnlisten).toHaveBeenCalled();
});
it("sets the active view from query parameters", function () {
var mockDomainObject = jasmine.createSpyObj(
"domainObject",
['getId', 'getModel', 'getCapability', 'useCapability']
),
testViews = [
{ key: 'abc' },
{ key: 'def', someKey: 'some value' },
{ key: 'xyz' }
];
mockDomainObject.useCapability.andCallFake(function (c) {
return (c === 'view') && testViews;
});
mockLocation.search.andReturn({ view: 'def' });
fireWatch('domainObject', mockDomainObject);
expect(mockScope.representation.selected)
.toEqual(testViews[1]);
});
});
}
);

View File

@ -1,5 +1,6 @@
[ [
"BrowseController", "BrowseController",
"BrowseObjectController",
"creation/CreateAction", "creation/CreateAction",
"creation/CreateActionProvider", "creation/CreateActionProvider",
"creation/CreateMenuController", "creation/CreateMenuController",

View File

@ -84,7 +84,7 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* line 5, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 5, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
html, body, div, span, applet, object, iframe, html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre, h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code, a, abbr, acronym, address, big, cite, code,
@ -105,38 +105,38 @@ time, mark, audio, video {
font-size: 100%; font-size: 100%;
vertical-align: baseline; } vertical-align: baseline; }
/* line 22, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 22, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
html { html {
line-height: 1; } line-height: 1; }
/* line 24, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 24, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
ol, ul { ol, ul {
list-style: none; } list-style: none; }
/* line 26, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 26, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
table { table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; } border-spacing: 0; }
/* line 28, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 28, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
caption, th, td { caption, th, td {
text-align: left; text-align: left;
font-weight: normal; font-weight: normal;
vertical-align: middle; } vertical-align: middle; }
/* line 30, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 30, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
q, blockquote { q, blockquote {
quotes: none; } quotes: none; }
/* line 103, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 103, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
q:before, q:after, blockquote:before, blockquote:after { q:before, q:after, blockquote:before, blockquote:after {
content: ""; content: "";
content: none; } content: none; }
/* line 32, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 32, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
a img { a img {
border: none; } border: none; }
/* line 116, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ /* line 116, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary {
display: block; } display: block; }
@ -2732,6 +2732,115 @@ label.checkbox.custom {
.l-time-controller .knob.knob-r .range-value { .l-time-controller .knob.knob-r .range-value {
left: 14px; } left: 14px; }
/*****************************************************************************
* 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.
*****************************************************************************/
/* line 26, ../sass/edit/_editor.scss */
.edit-main .edit-corner,
.edit-main .edit-handle {
position: absolute;
z-index: 2; }
/* line 32, ../sass/edit/_editor.scss */
.edit-main .edit-corner {
width: 15px;
height: 15px; }
/* line 35, ../sass/edit/_editor.scss */
.edit-main .edit-corner.edit-resize-nw {
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
border-bottom-right-radius: 5px;
cursor: nw-resize;
top: 0;
left: 0; }
/* line 40, ../sass/edit/_editor.scss */
.edit-main .edit-corner.edit-resize-se {
-moz-border-radius-topleft: 5px;
-webkit-border-top-left-radius: 5px;
border-top-left-radius: 5px;
cursor: se-resize;
bottom: 0;
right: 0; }
/* line 45, ../sass/edit/_editor.scss */
.edit-main .edit-corner.edit-resize-sw {
-moz-border-radius-topright: 5px;
-webkit-border-top-right-radius: 5px;
border-top-right-radius: 5px;
cursor: sw-resize;
bottom: 0;
left: 0; }
/* line 53, ../sass/edit/_editor.scss */
.edit-main .edit-handle {
top: 15px;
right: 15px;
bottom: 15px;
left: 15px; }
/* line 55, ../sass/edit/_editor.scss */
.edit-main .edit-handle.edit-move {
cursor: move;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1; }
/* line 65, ../sass/edit/_editor.scss */
.edit-main .edit-handle.edit-resize-n {
top: 0px;
bottom: auto;
height: 15px;
cursor: n-resize; }
/* line 70, ../sass/edit/_editor.scss */
.edit-main .edit-handle.edit-resize-e {
right: 0px;
left: auto;
width: 15px;
cursor: e-resize; }
/* line 75, ../sass/edit/_editor.scss */
.edit-main .edit-handle.edit-resize-s {
bottom: 0px;
top: auto;
height: 15px;
cursor: s-resize; }
/* line 80, ../sass/edit/_editor.scss */
.edit-main .edit-handle.edit-resize-w {
left: 0px;
right: auto;
width: 15px;
cursor: w-resize; }
/* line 89, ../sass/edit/_editor.scss */
.edit-main .frame.child-frame.panel:hover {
-moz-box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px;
-webkit-box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px;
box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px;
border-color: #0099cc;
z-index: 2; }
/* line 93, ../sass/edit/_editor.scss */
.edit-main .frame.child-frame.panel:hover .view-switcher {
opacity: 1; }
/* line 96, ../sass/edit/_editor.scss */
.edit-main .frame.child-frame.panel:hover .edit-corner {
background-color: rgba(0, 153, 204, 0.8); }
/* line 98, ../sass/edit/_editor.scss */
.edit-main .frame.child-frame.panel:hover .edit-corner:hover {
background-color: #0099cc; }
/* line 1, ../sass/features/_imagery.scss */ /* line 1, ../sass/features/_imagery.scss */
.l-image-main-wrapper, .l-image-main-wrapper,
.l-image-main, .l-image-main,
@ -3986,21 +4095,15 @@ input[type="text"] {
left: 5px; } left: 5px; }
/* line 54, ../sass/user-environ/_frame.scss */ /* line 54, ../sass/user-environ/_frame.scss */
.frame.frame-template .view-switcher { .frame.frame-template .view-switcher {
opacity: 0; } opacity: 0;
/* line 58, ../sass/user-environ/_frame.scss */ z-index: 10; }
/* line 59, ../sass/user-environ/_frame.scss */
.frame.frame-template:hover .view-switcher { .frame.frame-template:hover .view-switcher {
opacity: 1; } opacity: 1; }
/* line 66, ../sass/user-environ/_frame.scss */ /* line 67, ../sass/user-environ/_frame.scss */
.frame .view-switcher .name { .frame .view-switcher .name {
display: none; } display: none; }
/* line 73, ../sass/user-environ/_frame.scss */
.edit-main .frame.child-frame.panel:hover {
border-color: #0099cc;
-moz-box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px;
-webkit-box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px;
box-shadow: rgba(0, 0, 0, 0.7) 0 3px 10px; }
/***************************************************************************** /*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government * Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
@ -4219,51 +4322,55 @@ input[type="text"] {
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* line 24, ../sass/helpers/_bubbles.scss */ /* line 24, ../sass/helpers/_bubbles.scss */
.bubble-container {
pointer-events: none; }
/* line 31, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper { .l-infobubble-wrapper {
-moz-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px; -moz-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
-webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px; -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px; box-shadow: rgba(0, 0, 0, 0.4) 0 1px 5px;
position: relative; position: relative;
z-index: 50; } z-index: 50; }
/* line 29, ../sass/helpers/_bubbles.scss */ /* line 36, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble { .l-infobubble-wrapper .l-infobubble {
display: inline-block; display: inline-block;
min-width: 100px; min-width: 100px;
max-width: 300px; max-width: 300px;
padding: 5px 10px; } padding: 5px 10px; }
/* line 34, ../sass/helpers/_bubbles.scss */ /* line 41, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble:before { .l-infobubble-wrapper .l-infobubble:before {
content: ""; content: "";
position: absolute; position: absolute;
width: 0; width: 0;
height: 0; } height: 0; }
/* line 40, ../sass/helpers/_bubbles.scss */ /* line 47, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble table { .l-infobubble-wrapper .l-infobubble table {
width: 100%; } width: 100%; }
/* line 43, ../sass/helpers/_bubbles.scss */ /* line 50, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble table tr td { .l-infobubble-wrapper .l-infobubble table tr td {
padding: 2px 0; padding: 2px 0;
vertical-align: top; } vertical-align: top; }
/* line 50, ../sass/helpers/_bubbles.scss */ /* line 57, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble table tr td.label { .l-infobubble-wrapper .l-infobubble table tr td.label {
padding-right: 10px; padding-right: 10px;
white-space: nowrap; } white-space: nowrap; }
/* line 54, ../sass/helpers/_bubbles.scss */ /* line 61, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble table tr td.value { .l-infobubble-wrapper .l-infobubble table tr td.value {
white-space: nowrap; } white-space: nowrap; }
/* line 58, ../sass/helpers/_bubbles.scss */ /* line 65, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble table tr td.align-wrap { .l-infobubble-wrapper .l-infobubble table tr td.align-wrap {
white-space: normal; } white-space: normal; }
/* line 64, ../sass/helpers/_bubbles.scss */ /* line 71, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .l-infobubble .title { .l-infobubble-wrapper .l-infobubble .title {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-bottom: 5px; } margin-bottom: 5px; }
/* line 71, ../sass/helpers/_bubbles.scss */ /* line 78, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-left { .l-infobubble-wrapper.arw-left {
margin-left: 20px; } margin-left: 20px; }
/* line 73, ../sass/helpers/_bubbles.scss */ /* line 80, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-left .l-infobubble::before { .l-infobubble-wrapper.arw-left .l-infobubble::before {
right: 100%; right: 100%;
width: 0; width: 0;
@ -4271,10 +4378,10 @@ input[type="text"] {
border-top: 6.66667px solid transparent; border-top: 6.66667px solid transparent;
border-bottom: 6.66667px solid transparent; border-bottom: 6.66667px solid transparent;
border-right: 10px solid #ddd; } border-right: 10px solid #ddd; }
/* line 79, ../sass/helpers/_bubbles.scss */ /* line 86, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-right { .l-infobubble-wrapper.arw-right {
margin-right: 20px; } margin-right: 20px; }
/* line 81, ../sass/helpers/_bubbles.scss */ /* line 88, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-right .l-infobubble::before { .l-infobubble-wrapper.arw-right .l-infobubble::before {
left: 100%; left: 100%;
width: 0; width: 0;
@ -4282,16 +4389,16 @@ input[type="text"] {
border-top: 6.66667px solid transparent; border-top: 6.66667px solid transparent;
border-bottom: 6.66667px solid transparent; border-bottom: 6.66667px solid transparent;
border-left: 10px solid #ddd; } border-left: 10px solid #ddd; }
/* line 88, ../sass/helpers/_bubbles.scss */ /* line 95, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-top .l-infobubble::before { .l-infobubble-wrapper.arw-top .l-infobubble::before {
top: 20px; } top: 20px; }
/* line 94, ../sass/helpers/_bubbles.scss */ /* line 101, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-btm .l-infobubble::before { .l-infobubble-wrapper.arw-btm .l-infobubble::before {
bottom: 20px; } bottom: 20px; }
/* line 99, ../sass/helpers/_bubbles.scss */ /* line 106, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-down { .l-infobubble-wrapper.arw-down {
margin-bottom: 10px; } margin-bottom: 10px; }
/* line 101, ../sass/helpers/_bubbles.scss */ /* line 108, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-down .l-infobubble::before { .l-infobubble-wrapper.arw-down .l-infobubble::before {
left: 50%; left: 50%;
top: 100%; top: 100%;
@ -4299,21 +4406,21 @@ input[type="text"] {
border-left: 5px solid transparent; border-left: 5px solid transparent;
border-right: 5px solid transparent; border-right: 5px solid transparent;
border-top: 7.5px solid #ddd; } border-top: 7.5px solid #ddd; }
/* line 110, ../sass/helpers/_bubbles.scss */ /* line 117, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper .arw { .l-infobubble-wrapper .arw {
z-index: 2; } z-index: 2; }
/* line 113, ../sass/helpers/_bubbles.scss */ /* line 120, ../sass/helpers/_bubbles.scss */
.l-infobubble-wrapper.arw-up .arw.arw-down, .l-infobubble-wrapper.arw-down .arw.arw-up { .l-infobubble-wrapper.arw-up .arw.arw-down, .l-infobubble-wrapper.arw-down .arw.arw-up {
display: none; } display: none; }
/* line 120, ../sass/helpers/_bubbles.scss */ /* line 127, ../sass/helpers/_bubbles.scss */
.l-thumbsbubble-wrapper .arw-up { .l-thumbsbubble-wrapper .arw-up {
width: 0; width: 0;
height: 0; height: 0;
border-left: 6.66667px solid transparent; border-left: 6.66667px solid transparent;
border-right: 6.66667px solid transparent; border-right: 6.66667px solid transparent;
border-bottom: 10px solid #4d4d4d; } border-bottom: 10px solid #4d4d4d; }
/* line 123, ../sass/helpers/_bubbles.scss */ /* line 130, ../sass/helpers/_bubbles.scss */
.l-thumbsbubble-wrapper .arw-down { .l-thumbsbubble-wrapper .arw-down {
width: 0; width: 0;
height: 0; height: 0;
@ -4321,7 +4428,7 @@ input[type="text"] {
border-right: 6.66667px solid transparent; border-right: 6.66667px solid transparent;
border-top: 10px solid #4d4d4d; } border-top: 10px solid #4d4d4d; }
/* line 127, ../sass/helpers/_bubbles.scss */ /* line 134, ../sass/helpers/_bubbles.scss */
.s-infobubble { .s-infobubble {
-moz-border-radius: 2px; -moz-border-radius: 2px;
-webkit-border-radius: 2px; -webkit-border-radius: 2px;
@ -4332,22 +4439,22 @@ input[type="text"] {
background: #ddd; background: #ddd;
color: #666; color: #666;
font-size: 0.8rem; } font-size: 0.8rem; }
/* line 134, ../sass/helpers/_bubbles.scss */ /* line 141, ../sass/helpers/_bubbles.scss */
.s-infobubble .title { .s-infobubble .title {
color: #333333; color: #333333;
font-weight: bold; } font-weight: bold; }
/* line 139, ../sass/helpers/_bubbles.scss */ /* line 146, ../sass/helpers/_bubbles.scss */
.s-infobubble tr td { .s-infobubble tr td {
border-top: 1px solid #c4c4c4; border-top: 1px solid #c4c4c4;
font-size: 0.9em; } font-size: 0.9em; }
/* line 143, ../sass/helpers/_bubbles.scss */ /* line 150, ../sass/helpers/_bubbles.scss */
.s-infobubble tr:first-child td { .s-infobubble tr:first-child td {
border-top: none; } border-top: none; }
/* line 147, ../sass/helpers/_bubbles.scss */ /* line 154, ../sass/helpers/_bubbles.scss */
.s-infobubble .value { .s-infobubble .value {
color: #333333; } color: #333333; }
/* line 152, ../sass/helpers/_bubbles.scss */ /* line 159, ../sass/helpers/_bubbles.scss */
.s-thumbsbubble { .s-thumbsbubble {
background: #4d4d4d; background: #4d4d4d;
color: #b3b3b3; } color: #b3b3b3; }

View File

@ -46,6 +46,7 @@
@import "controls/lists"; @import "controls/lists";
@import "controls/menus"; @import "controls/menus";
@import "controls/time-controller"; @import "controls/time-controller";
@import "edit/editor";
@import "features/imagery"; @import "features/imagery";
@import "features/time-display"; @import "features/time-display";
@import "forms/mixins"; @import "forms/mixins";

View File

@ -19,3 +19,86 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
.edit-main {
$handleD: 15px;
$cr: 5px;
.edit-corner,
.edit-handle {
position: absolute;
z-index: 2;
}
.edit-corner {
width: $handleD;
height: $handleD;
&.edit-resize-nw {
@include border-bottom-right-radius($cr);
cursor: nw-resize;
top: 0; left: 0;
}
&.edit-resize-se {
@include border-top-left-radius($cr);
cursor: se-resize;
bottom: 0; right: 0;
}
&.edit-resize-sw {
@include border-top-right-radius($cr);
cursor: sw-resize;
bottom: 0; left: 0;
}
}
.edit-handle {
top: $handleD; right: $handleD; bottom: $handleD; left: $handleD;
&.edit-move {
$m: 0; //$handleD;
cursor: move;
left: $m;
right: $m;
top: $m;
bottom: $m;
z-index: 1;
}
&.edit-resize-n {
top: 0px; bottom: auto;
height: $handleD;
cursor: n-resize;
}
&.edit-resize-e {
right: 0px; left: auto;
width: $handleD;
cursor: e-resize;
}
&.edit-resize-s {
bottom: 0px; top: auto;
height: $handleD;
cursor: s-resize;
}
&.edit-resize-w {
left: 0px; right: auto;
width: $handleD;
cursor: w-resize;
}
}
.frame.child-frame.panel {
&:hover {
@include boxShdwLarge();
border-color: $colorKey;
z-index: 2;
.view-switcher {
opacity: 1;
}
.edit-corner {
background-color: rgba($colorKey, 0.8);
&:hover {
background-color: rgba($colorKey, 1);
}
}
}
}
}

View File

@ -19,6 +19,13 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
//************************************************* GENERAL
.bubble-container {
pointer-events: none;
}
//************************************************* LAYOUT //************************************************* LAYOUT
.l-infobubble-wrapper { .l-infobubble-wrapper {

View File

@ -54,6 +54,7 @@
.view-switcher { .view-switcher {
//display: none; //display: none;
opacity: 0; opacity: 0;
z-index: 10;
} }
&:hover .view-switcher { &:hover .view-switcher {
// Show the view switcher on frame hover // Show the view switcher on frame hover
@ -68,10 +69,3 @@
} }
} }
} }
.edit-main .frame.child-frame.panel {
&:hover {
border-color: $colorKey;
@include boxShdwLarge();
}
}

View File

@ -53,13 +53,15 @@ define(
// Get list of views, read from capability // Get list of views, read from capability
function updateOptions(views) { function updateOptions(views) {
if (Array.isArray(views)) {
$timeout(function () { $timeout(function () {
$scope.ngModel.selected = findMatchingOption( $scope.ngModel.selected = findMatchingOption(
views || [], views,
($scope.ngModel || {}).selected ($scope.ngModel || {}).selected
); );
}, 0); }, 0);
} }
}
// Update view options when the in-scope results of using the // Update view options when the in-scope results of using the
// view capability change. // view capability change.

View File

@ -44,7 +44,7 @@
"constants": [ "constants": [
{ {
"key": "INFO_HOVER_DELAY", "key": "INFO_HOVER_DELAY",
"value": 500 "value": 2000
} }
] ]
} }

View File

@ -1,6 +1,8 @@
<mct-container key="bubble" <mct-container
key="bubble"
bubble-title="{{parameters.title}}" bubble-title="{{parameters.title}}"
bubble-layout="{{parameters.layout}}"> bubble-layout="{{parameters.layout}}"
>
<mct-include key="info-table" <mct-include key="info-table"
ng-model="ngModel"> ng-model="ngModel">
</mct-include> </mct-include>

View File

@ -23,7 +23,8 @@
define({ 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\">" +
"<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" + "<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
"</mct-include>" + "</mct-include>" +
"</mct-container>", "</mct-container>",

View File

@ -184,6 +184,11 @@
{ {
"key": "now", "key": "now",
"implementation": "services/Now.js" "implementation": "services/Now.js"
},
{
"key": "throttle",
"implementation": "services/Throttle.js",
"depends": [ "$timeout" ]
} }
], ],
"roots": [ "roots": [

View File

@ -0,0 +1,63 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* Throttler for function executions, registered as the `throttle`
* service.
*
* Usage:
*
* throttle(fn, delay, [apply])
*
* Returns a function that, when invoked, will invoke `fn` after
* `delay` milliseconds, only if no other invocations are pending.
* The optional argument `apply` determines whether.
*
* The returned function will itself return a `Promise` which will
* resolve to the returned value of `fn` whenever that is invoked.
*
* @returns {Function}
*/
function Throttle($timeout) {
/**
* Throttle this function.
* @param {Function} fn the function to throttle
* @param {number} [delay] the delay, in milliseconds, before
* executing this function; defaults to 0.
* @param {boolean} apply true if a `$apply` call should be
* invoked after this function executes; defaults to
* `false`.
*/
return function (fn, delay, apply) {
var activeTimeout;
// Clear active timeout, so that next invocation starts
// a new one.
function clearActiveTimeout() {
activeTimeout = undefined;
}
// Defaults
delay = delay || 0;
apply = apply || false;
return function () {
// Start a timeout if needed
if (!activeTimeout) {
activeTimeout = $timeout(fn, delay, apply);
activeTimeout.then(clearActiveTimeout);
}
// Return whichever timeout is active (to get
// a promise for the results of fn)
return activeTimeout;
};
};
}
return Throttle;
}
);

View File

@ -0,0 +1,49 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/services/Throttle"],
function (Throttle) {
"use strict";
describe("The 'throttle' service", function () {
var throttle,
mockTimeout,
mockFn,
mockPromise;
beforeEach(function () {
mockTimeout = jasmine.createSpy("$timeout");
mockPromise = jasmine.createSpyObj("promise", ["then"]);
mockFn = jasmine.createSpy("fn");
mockTimeout.andReturn(mockPromise);
throttle = new Throttle(mockTimeout);
});
it("provides functions which run on a timeout", function () {
var throttled = throttle(mockFn);
// Verify precondition: Not called at throttle-time
expect(mockTimeout).not.toHaveBeenCalled();
expect(throttled()).toEqual(mockPromise);
expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
});
it("schedules only one timeout at a time", function () {
var throttled = throttle(mockFn);
throttled();
throttled();
throttled();
expect(mockTimeout.calls.length).toEqual(1);
});
it("schedules additional invocations after resolution", function () {
var throttled = throttle(mockFn);
throttled();
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
throttled();
mockPromise.then.mostRecentCall.args[0]();
throttled();
expect(mockTimeout.calls.length).toEqual(3);
});
});
}
);

View File

@ -24,6 +24,7 @@
"objects/DomainObjectProvider", "objects/DomainObjectProvider",
"services/Now", "services/Now",
"services/Throttle",
"types/MergeModels", "types/MergeModels",
"types/TypeCapability", "types/TypeCapability",

View File

@ -0,0 +1 @@
Contains services which manage execution and flow control (e.g. for concurrency.)

View File

@ -0,0 +1,11 @@
{
"extensions": {
"services": [
{
"key": "workerService",
"implementation": "WorkerService.js",
"depends": [ "$window", "workers[]" ]
}
]
}
}

View File

@ -0,0 +1,68 @@
/*****************************************************************************
* 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";
/**
* Handles the execution of WebWorkers.
* @constructor
*/
function WorkerService($window, workers) {
var workerUrls = {},
Worker = $window.Worker;
function addWorker(worker) {
var key = worker.key;
if (!workerUrls[key]) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
}
}
(workers || []).forEach(addWorker);
return {
/**
* Start running a new web worker. This will run a worker
* that has been registered under the `workers` category
* of extension.
*
* @param {string} key symbolic identifier for the worker
* @returns {Worker} the running Worker
*/
run: function (key) {
var scriptUrl = workerUrls[key];
return scriptUrl && Worker && new Worker(scriptUrl);
}
};
}
return WorkerService;
}
);

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* 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,jasmine*/
define(
["../src/WorkerService"],
function (WorkerService) {
"use strict";
describe("The worker service", function () {
var mockWindow,
testWorkers,
mockWorker,
service;
beforeEach(function () {
mockWindow = jasmine.createSpyObj('$window', ['Worker']);
testWorkers = [
{
key: 'abc',
scriptUrl: 'c.js',
bundle: { path: 'a', sources: 'b' }
},
{
key: 'xyz',
scriptUrl: 'z.js',
bundle: { path: 'x', sources: 'y' }
},
{
key: 'xyz',
scriptUrl: 'bad.js',
bundle: { path: 'bad', sources: 'bad' }
}
];
mockWorker = {};
mockWindow.Worker.andReturn(mockWorker);
service = new WorkerService(mockWindow, testWorkers);
});
it("instantiates workers at registered paths", function () {
expect(service.run('abc')).toBe(mockWorker);
expect(mockWindow.Worker).toHaveBeenCalledWith('a/b/c.js');
});
it("prefers the first worker when multiple keys are found", function () {
expect(service.run('xyz')).toBe(mockWorker);
expect(mockWindow.Worker).toHaveBeenCalledWith('x/y/z.js');
});
it("returns undefined for unknown workers", function () {
expect(service.run('def')).toBeUndefined();
});
});
}
);

View File

@ -0,0 +1,3 @@
[
"WorkerService"
]

View File

@ -35,7 +35,7 @@
</div> </div>
<div class="abs object-holder"> <div class="abs object-holder">
<mct-representation key="representation.selected.key" <mct-representation key="representation.selected.key"
mct-object="domainObject"> mct-object="representation.selected.key && domainObject">
</mct-representation> </mct-representation>
</div> </div>
</div> </div>

View File

@ -34,50 +34,62 @@
<!-- Drag handles --> <!-- Drag handles -->
<span ng-show="domainObject.hasCapability('editor')"> <span ng-show="domainObject.hasCapability('editor')">
<span style="position: absolute; left: 12px; right: 12px; top: 12px; bottom: 12px; cursor: move;"
<span
class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])" mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<!--
<span style="position: absolute; left: 0px; width: 12px; top: 12px; bottom: 12px; cursor: w-resize;" <span
class="edit-handle edit-resize-w"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,0])" mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,0])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<span style="position: absolute; right: 0px; width: 12px; top: 12px; bottom: 12px; cursor: e-resize;" <span
class="edit-handle edit-resize-e"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,0])" mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,0])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<span style="position: absolute; left: 12px; right: 12px; top: 0px; height: 12px; cursor: n-resize;" <span
class="edit-handle edit-resize-n"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [0,-1])" mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [0,-1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<span style="position: absolute; left: 12px; right: 12px; bottom: 0px; height: 12px; cursor: s-resize;" <span
class="edit-handle edit-resize-s"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [0,1])" mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [0,1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
-->
<span style="position: absolute; left: 0px; width: 12px; top: 0px; height: 12px; cursor: nw-resize;" <span
class="edit-corner edit-resize-nw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])" mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<span style="position: absolute; right: 0px; width: 12px; top: 0px; height: 12px; cursor: ne-resize;" <!--span
class="edit-corner edit-resize-nw"
style="position: absolute; right: 0px; width: 12px; top: 0px; height: 12px; cursor: ne-resize;"
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])" mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span-->
<span style="position: absolute; left: 0px; width: 12px; bottom: 0px; height: 12px; cursor: sw-resize;" <span
class="edit-corner edit-resize-sw"
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])" mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">
</span> </span>
<span style="position: absolute; right: 0px; width: 12px; bottom: 0px; height: 12px; cursor: se-resize;" <span
class="edit-corner edit-resize-se"
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])" mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
mct-drag="controller.continueDrag(delta)" mct-drag="controller.continueDrag(delta)"
mct-drag-up="controller.endDrag()"> mct-drag-up="controller.endDrag()">

View File

@ -90,6 +90,10 @@ define(
function lookupPanels(ids) { function lookupPanels(ids) {
var configuration = $scope.configuration || {}; var configuration = $scope.configuration || {};
// ids is read from model.composition and may be undefined;
// fall back to an array if that occurs
ids = ids || [];
// Pull panel positions from configuration // Pull panel positions from configuration
rawPositions = shallowCopy(configuration.panels || {}, ids); rawPositions = shallowCopy(configuration.panels || {}, ids);

View File

@ -23,7 +23,7 @@
{ {
"key": "PlotController", "key": "PlotController",
"implementation": "PlotController.js", "implementation": "PlotController.js",
"depends": [ "$scope", "telemetryFormatter", "telemetryHandler" ] "depends": [ "$scope", "telemetryFormatter", "telemetryHandler", "throttle" ]
} }
] ]
} }

View File

@ -51,13 +51,14 @@ define(
* *
* @constructor * @constructor
*/ */
function PlotController($scope, telemetryFormatter, telemetryHandler) { function PlotController($scope, telemetryFormatter, telemetryHandler, throttle) {
var subPlotFactory = new SubPlotFactory(telemetryFormatter), var subPlotFactory = new SubPlotFactory(telemetryFormatter),
modeOptions = new PlotModeOptions([], subPlotFactory), modeOptions = new PlotModeOptions([], subPlotFactory),
subplots = [], subplots = [],
cachedObjects = [], cachedObjects = [],
updater, updater,
handle, handle,
scheduleUpdate,
domainOffset; domainOffset;
// Populate the scope with axis information (specifically, options // Populate the scope with axis information (specifically, options
@ -89,9 +90,7 @@ define(
// Update all sub-plots // Update all sub-plots
function update() { function update() {
modeOptions.getModeHandler() scheduleUpdate();
.getSubPlots()
.forEach(updateSubplot);
} }
// Reinstantiate the plot updater (e.g. because we have a // Reinstantiate the plot updater (e.g. because we have a
@ -163,6 +162,12 @@ define(
// Unsubscribe when the plot is destroyed // Unsubscribe when the plot is destroyed
$scope.$on("$destroy", releaseSubscription); $scope.$on("$destroy", releaseSubscription);
// Create a throttled update function
scheduleUpdate = throttle(function () {
modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
return { return {
/** /**
* Get the color (as a style-friendly string) to use * Get the color (as a style-friendly string) to use

View File

@ -62,8 +62,6 @@ define(
points: buf.getLength() points: buf.getLength()
}; };
}); });
subplot.update();
} }
return { return {

View File

@ -58,8 +58,6 @@ define(
color: PlotPalette.getFloatColor(0), color: PlotPalette.getFloatColor(0),
points: buffer.getLength() points: buffer.getLength()
}]; }];
subplot.update();
} }
function plotTelemetry(prepared) { function plotTelemetry(prepared) {

View File

@ -33,6 +33,7 @@ define(
var mockScope, var mockScope,
mockFormatter, mockFormatter,
mockHandler, mockHandler,
mockThrottle,
mockHandle, mockHandle,
mockDomainObject, mockDomainObject,
mockSeries, mockSeries,
@ -56,6 +57,7 @@ define(
"telemetrySubscriber", "telemetrySubscriber",
["handle"] ["handle"]
); );
mockThrottle = jasmine.createSpy("throttle");
mockHandle = jasmine.createSpyObj( mockHandle = jasmine.createSpyObj(
"subscription", "subscription",
[ [
@ -73,12 +75,18 @@ define(
); );
mockHandler.handle.andReturn(mockHandle); mockHandler.handle.andReturn(mockHandle);
mockThrottle.andCallFake(function (fn) { return fn; });
mockHandle.getTelemetryObjects.andReturn([mockDomainObject]); mockHandle.getTelemetryObjects.andReturn([mockDomainObject]);
mockHandle.getMetadata.andReturn([{}]); mockHandle.getMetadata.andReturn([{}]);
mockHandle.getDomainValue.andReturn(123); mockHandle.getDomainValue.andReturn(123);
mockHandle.getRangeValue.andReturn(42); mockHandle.getRangeValue.andReturn(42);
controller = new PlotController(mockScope, mockFormatter, mockHandler); controller = new PlotController(
mockScope,
mockFormatter,
mockHandler,
mockThrottle
);
}); });
it("provides plot colors", function () { it("provides plot colors", function () {

View File

@ -35,8 +35,8 @@
<span class='field control date'> <span class='field control date'>
<input type='text' <input type='text'
name='date' name='date'
placeholder="YYYY-DDD" placeholder="{{format}}"
ng-pattern="/\d\d\d\d-\d\d\d/" ng-pattern="/\d\d\d\d-\d\d-\d\d/"
ng-model='datetime.date' ng-model='datetime.date'
ng-required='ngRequired || partiallyComplete'/> ng-required='ngRequired || partiallyComplete'/>
</span> </span>

View File

@ -26,7 +26,7 @@ define(
function () { function () {
"use strict"; "use strict";
var DATE_FORMAT = "YYYY-DDD"; var DATE_FORMAT = "YYYY-MM-DD";
/** /**
* Controller for the `datetime` form control. * Controller for the `datetime` form control.
@ -92,6 +92,9 @@ define(
$scope.$watch("datetime.min", update); $scope.$watch("datetime.min", update);
$scope.$watch("datetime.sec", update); $scope.$watch("datetime.sec", update);
// Expose format string for placeholder
$scope.format = DATE_FORMAT;
// Initialize forms values // Initialize forms values
updateDateTime( updateDateTime(
($scope.ngModel && $scope.field) ? ($scope.ngModel && $scope.field) ?

View File

@ -47,7 +47,7 @@ define(
it("converts date-time input into a timestamp", function () { it("converts date-time input into a timestamp", function () {
mockScope.ngModel = {}; mockScope.ngModel = {};
mockScope.field = "test"; mockScope.field = "test";
mockScope.datetime.date = "2014-332"; mockScope.datetime.date = "2014-11-28";
mockScope.datetime.hour = 22; mockScope.datetime.hour = 22;
mockScope.datetime.min = 55; mockScope.datetime.min = 55;
mockScope.datetime.sec = 13; mockScope.datetime.sec = 13;
@ -63,7 +63,7 @@ define(
// as required. // as required.
mockScope.ngModel = {}; mockScope.ngModel = {};
mockScope.field = "test"; mockScope.field = "test";
mockScope.datetime.date = "2014-332"; mockScope.datetime.date = "2014-11-28";
mockScope.datetime.hour = 22; mockScope.datetime.hour = 22;
mockScope.datetime.min = 55; mockScope.datetime.min = 55;
// mockScope.datetime.sec = 13; // mockScope.datetime.sec = 13;
@ -85,6 +85,11 @@ define(
expect(mockScope.ngModel.test).toBeUndefined(); expect(mockScope.ngModel.test).toBeUndefined();
}); });
it("exposes date-time format for placeholder", function () {
expect(mockScope.format).toEqual(jasmine.any(String));
expect(mockScope.format.length).toBeGreaterThan(0);
});
it("initializes form fields with values from ng-model", function () { it("initializes form fields with values from ng-model", function () {
mockScope.ngModel = { test: 1417215313000 }; mockScope.ngModel = { test: 1417215313000 };
mockScope.field = "test"; mockScope.field = "test";
@ -94,7 +99,7 @@ define(
} }
}); });
expect(mockScope.datetime).toEqual({ expect(mockScope.datetime).toEqual({
date: "2014-332", date: "2014-11-28",
hour: "22", hour: "22",
min: "55", min: "55",
sec: "13" sec: "13"

View File

@ -94,14 +94,17 @@ define(
function link($scope, element, attrs) { function link($scope, element, attrs) {
var activeRepresenters = representers.map(function (Representer) { var activeRepresenters = representers.map(function (Representer) {
return new Representer($scope, element, attrs); return new Representer($scope, element, attrs);
}); }),
toClear = [], // Properties to clear out of scope on change
counter = 0;
// Populate scope with any capabilities indicated by the // Populate scope with any capabilities indicated by the
// representation's extension definition // representation's extension definition
function refreshCapabilities() { function refreshCapabilities() {
var domainObject = $scope.domainObject, var domainObject = $scope.domainObject,
representation = lookup($scope.key, domainObject), representation = lookup($scope.key, domainObject),
uses = ((representation || {}).uses || []); uses = ((representation || {}).uses || []),
myCounter = counter;
if (domainObject) { if (domainObject) {
// Update model // Update model
@ -115,10 +118,16 @@ define(
" for representation ", " for representation ",
$scope.key $scope.key
].join("")); ].join(""));
$q.when( $q.when(
domainObject.useCapability(used) domainObject.useCapability(used)
).then(function (c) { ).then(function (c) {
// Avoid clobbering capabilities from
// subsequent representations;
// Angular reuses scopes.
if (counter === myCounter) {
$scope[used] = c; $scope[used] = c;
}
}); });
}); });
} }
@ -130,8 +139,7 @@ define(
function refresh() { function refresh() {
var domainObject = $scope.domainObject, var domainObject = $scope.domainObject,
representation = lookup($scope.key, domainObject), representation = lookup($scope.key, domainObject),
uses = ((representation || {}).uses || []), uses = ((representation || {}).uses || []);
gestureKeys = ((representation || {}).gestures || []);
// Create an empty object named "representation", for this // Create an empty object named "representation", for this
// representation to store local variables into. // representation to store local variables into.
@ -152,9 +160,19 @@ define(
$log.warn("No representation found for " + $scope.key); $log.warn("No representation found for " + $scope.key);
} }
// Clear out the scope from the last representation
toClear.forEach(function (property) {
delete $scope[property];
});
// Populate scope with fields associated with the current // Populate scope with fields associated with the current
// domain object (if one has been passed in) // domain object (if one has been passed in)
if (domainObject) { if (domainObject) {
// Track how many representations we've made in this scope,
// to ensure that the correct representations are matched to
// the correct object/key pairs.
counter += 1;
// Initialize any capabilities // Initialize any capabilities
refreshCapabilities(); refreshCapabilities();
@ -168,6 +186,10 @@ define(
activeRepresenters.forEach(function (representer) { activeRepresenters.forEach(function (representer) {
representer.represent(representation, domainObject); representer.represent(representation, domainObject);
}); });
// Track which properties we want to clear from scope
// next change object/key pair changes
toClear = uses.concat(['model']);
} }
} }

View File

@ -92,7 +92,13 @@ define(
function drop(e) { function drop(e) {
var event = (e || {}).originalEvent || e, var event = (e || {}).originalEvent || e,
id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE); id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE),
domainObjectType = domainObject.getModel().type;
// If currently in edit mode allow drag and drop gestures to the
// domain object. An exception to this is folders which have drop
// gestures in browse mode.
if (domainObjectType === 'folder' || domainObject.hasCapability('editor')) {
// Handle the drop; add the dropped identifier to the // Handle the drop; add the dropped identifier to the
// destination domain object's composition, and persist // destination domain object's composition, and persist
@ -102,7 +108,8 @@ define(
broadcastDrop(id, event); broadcastDrop(id, event);
}); });
} }
}
// TODO: Alert user if drag and drop is not allowed
} }
// We can only handle drops if we have access to actions... // We can only handle drops if we have access to actions...

View File

@ -212,6 +212,25 @@ define(
// Should have gotten a warning - that's an unknown key // Should have gotten a warning - that's an unknown key
expect(mockLog.warn).toHaveBeenCalled(); expect(mockLog.warn).toHaveBeenCalled();
}); });
it("clears out obsolete peroperties from scope", function () {
mctRepresentation.link(mockScope, mockElement);
mockScope.key = "def";
mockScope.domainObject = mockDomainObject;
mockDomainObject.useCapability.andReturn("some value");
// Trigger the watch
mockScope.$watch.calls[0].args[1]();
expect(mockScope.testCapability).toBeDefined();
// Change the view
mockScope.key = "xyz";
// Trigger the watch again; should clear capability from scope
mockScope.$watch.calls[0].args[1]();
expect(mockScope.testCapability).toBeUndefined();
});
}); });
} }
); );

View File

@ -132,7 +132,10 @@ define(
expect(mockEvent.dataTransfer.dropEffect).toBeDefined(); expect(mockEvent.dataTransfer.dropEffect).toBeDefined();
}); });
it("invokes compose on drop", function () { it("invokes compose on drop in edit mode", function () {
// Set the mockDomainObject to have the editor capability
mockDomainObject.hasCapability.andReturn(true);
callbacks.dragover(mockEvent); callbacks.dragover(mockEvent);
expect(mockAction.getActions).toHaveBeenCalledWith({ expect(mockAction.getActions).toHaveBeenCalledWith({
key: 'compose', key: 'compose',
@ -143,7 +146,41 @@ define(
}); });
it("broadcasts drop position", function () { it("does not invoke compose on drop in browse mode for non-folders", function () {
// Set the mockDomainObject to not have the editor capability
mockDomainObject.hasCapability.andReturn(false);
// Set the mockDomainObject to not have a type of folder
mockDomainObject.getModel.andReturn({type: 'notAFolder'});
callbacks.dragover(mockEvent);
expect(mockAction.getActions).toHaveBeenCalledWith({
key: 'compose',
selectedObject: mockDraggedObject
});
callbacks.drop(mockEvent);
expect(mockCompose.perform).not.toHaveBeenCalled();
});
it("invokes compose on drop in browse mode for folders", function () {
// Set the mockDomainObject to not have the editor capability
mockDomainObject.hasCapability.andReturn(false);
// Set the mockDomainObject to have a type of folder
mockDomainObject.getModel.andReturn({type: 'folder'});
callbacks.dragover(mockEvent);
expect(mockAction.getActions).toHaveBeenCalledWith({
key: 'compose',
selectedObject: mockDraggedObject
});
callbacks.drop(mockEvent);
expect(mockCompose.perform).toHaveBeenCalled();
});
it("broadcasts drop position (in edit mode)", function () {
// Set the mockDomainObject to have the editor capability
mockDomainObject.hasCapability.andReturn(true);
testRect.left = 42; testRect.left = 42;
testRect.top = 36; testRect.top = 36;
mockEvent.pageX = 52; mockEvent.pageX = 52;

View File

@ -35,22 +35,48 @@ define(
* @constructor * @constructor
*/ */
function TelemetryQueue() { function TelemetryQueue() {
var queue = []; // General approach here:
// * Maintain a queue as an array of objects containing key-value
// pairs. Putting values into the queue will assign to the
// earliest-available queue position for the associated key
// (appending to the array if necessary.)
// * Maintain a set of counts for each key, such that determining
// the next available queue position is easy; O(1) insertion.
// * When retrieving objects, pop off the queue and decrement
// counts. This provides O(n+k) or O(k) retrieval for a queue
// of length n with k unique keys; this depends on whether
// the browser's implementation of Array.prototype.shift is
// O(n) or O(1).
// Graphically (indexes at top, keys along side, values as *'s),
// if we have a queue that looks like:
// 0 1 2 3 4
// a * * * * *
// b * *
// c * * *
//
// And we put a new value for b, we expect:
// 0 1 2 3 4
// a * * * * *
// b * * *
// c * * *
var queue = [],
counts = {};
// Look up an object in the queue that does not have a value // Look up an object in the queue that does not have a value
// assigned to this key (or, add a new one) // assigned to this key (or, add a new one)
function getFreeObject(key) { function getFreeObject(key) {
var index = 0, object; var index = counts[key] || 0, object;
// Look for an existing queue position where we can store // Track the largest free position for this key
// a value to this key without overwriting an existing value. counts[key] = index + 1;
for (index = 0; index < queue.length; index += 1) {
if (queue[index][key] === undefined) { // If it's before the end of the queue, add it there
if (index < queue.length) {
return queue[index]; return queue[index];
} }
}
// If we made it through the loop, values have been assigned // Otherwise, values have been assigned
// to that key in all queued containers, so we need to queue // to that key in all queued containers, so we need to queue
// up a new container for key-value pairs. // up a new container for key-value pairs.
object = {}; object = {};
@ -58,6 +84,20 @@ define(
return object; return object;
} }
// Decrement counts for a specific key
function decrementCount(key) {
if (counts[key] < 2) {
delete counts[key];
} else {
counts[key] -= 1;
}
}
// Decrement all counts
function decrementCounts() {
Object.keys(counts).forEach(decrementCount);
}
return { return {
/** /**
* Check if any value groups remain in this pool. * Check if any value groups remain in this pool.
@ -74,6 +114,8 @@ define(
* @return {object} key-value pairs * @return {object} key-value pairs
*/ */
poll: function () { poll: function () {
// Decrement counts for the object that will be popped
decrementCounts();
return queue.shift(); return queue.shift();
}, },
/** /**