diff --git a/bundles.json b/bundles.json index 6e28332374..e70e8455f2 100644 --- a/bundles.json +++ b/bundles.json @@ -11,6 +11,7 @@ "platform/containment", "platform/execution", "platform/telemetry", + "platform/features/imagery", "platform/features/layout", "platform/features/pages", "platform/features/plot", @@ -19,6 +20,7 @@ "platform/persistence/queue", "platform/policy", + "example/imagery", "example/persistence", "example/generator" ] diff --git a/example/imagery/bundle.json b/example/imagery/bundle.json new file mode 100644 index 0000000000..1adcf4758d --- /dev/null +++ b/example/imagery/bundle.json @@ -0,0 +1,42 @@ +{ + "name": "Imagery", + "description": "Example of a component that produces image telemetry.", + "extensions": { + "components": [ + { + "implementation": "ImageTelemetryProvider.js", + "type": "provider", + "provides": "telemetryService", + "depends": [ "$q", "$timeout" ] + } + ], + "types": [ + { + "key": "imagery", + "name": "Example Imagery", + "glyph": "T", + "features": "creation", + "model": { + "telemetry": {} + }, + "telemetry": { + "source": "imagery", + "domains": [ + { + "name": "Time", + "key": "time", + "format": "timestamp" + } + ], + "ranges": [ + { + "name": "Image", + "key": "url", + "format": "imageUrl" + } + ] + } + } + ] + } +} diff --git a/example/imagery/src/ImageTelemetry.js b/example/imagery/src/ImageTelemetry.js new file mode 100644 index 0000000000..127b65deba --- /dev/null +++ b/example/imagery/src/ImageTelemetry.js @@ -0,0 +1,66 @@ +/***************************************************************************** + * 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*/ + +/** + * Module defining ImageTelemetry. Created by vwoeltje on 06/22/15. + */ +define( + [], + function () { + "use strict"; + + var firstObservedTime = Date.now(), + images = [ + "http://www.nasa.gov/393811main_Palomar_ao_bouchez_10s_after_impact_4x3_946-710.png", + "http://www.nasa.gov/393821main_Palomar_ao_bouchez_15s_after_impact_4x3_946-710.png", + "http://www.nasa.gov/images/content/393801main_CfhtVeillet2_4x3_516-387.jpg", + "http://www.nasa.gov/images/content/392790main_1024_768_GeminiNorth_NightBeforeImpact_946-710.jpg" + ].map(function (url, index) { + return { + timestamp: firstObservedTime + 1000 * index, + url: url + }; + }); + + + /** + * + * @constructor + */ + function ImageTelemetry() { + return { + getPointCount: function () { + return Math.floor((Date.now() - firstObservedTime) / 1000); + }, + getDomainValue: function (i, domain) { + return images[i % images.length].timestamp; + }, + getRangeValue: function (i, range) { + return images[i % images.length].url; + } + }; + } + + return ImageTelemetry; + } +); diff --git a/example/imagery/src/ImageTelemetryProvider.js b/example/imagery/src/ImageTelemetryProvider.js new file mode 100644 index 0000000000..f541c441a4 --- /dev/null +++ b/example/imagery/src/ImageTelemetryProvider.js @@ -0,0 +1,115 @@ +/***************************************************************************** + * 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*/ + +/** + * Module defining ImageTelemetryProvider. Created by vwoeltje on 06/22/15. + */ +define( + ["./ImageTelemetry"], + function (ImageTelemetry) { + "use strict"; + + /** + * + * @constructor + */ + function ImageTelemetryProvider($q, $timeout) { + var subscriptions = []; + + // + function matchesSource(request) { + return request.source === "imagery"; + } + + // Used internally; this will be repacked by doPackage + function generateData(request) { + return { + key: request.key, + telemetry: new ImageTelemetry() + }; + } + + // + function doPackage(results) { + var packaged = {}; + results.forEach(function (result) { + packaged[result.key] = result.telemetry; + }); + // Format as expected (sources -> keys -> telemetry) + return { imagery: packaged }; + } + + function requestTelemetry(requests) { + return $timeout(function () { + return doPackage(requests.filter(matchesSource).map(generateData)); + }, 0); + } + + function handleSubscriptions() { + subscriptions.forEach(function (subscription) { + var requests = subscription.requests; + subscription.callback(doPackage( + requests.filter(matchesSource).map(generateData) + )); + }); + } + + function startGenerating() { + $timeout(function () { + handleSubscriptions(); + if (subscriptions.length > 0) { + startGenerating(); + } + }, 1000); + } + + function subscribe(callback, requests) { + var subscription = { + callback: callback, + requests: requests + }; + + function unsubscribe() { + subscriptions = subscriptions.filter(function (s) { + return s !== subscription; + }); + } + + subscriptions.push(subscription); + + if (subscriptions.length === 1) { + startGenerating(); + } + + return unsubscribe; + } + + return { + requestTelemetry: requestTelemetry, + subscribe: subscribe + }; + } + + return ImageTelemetryProvider; + } +); diff --git a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js index 6a4e5d4de3..a13a3e2360 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js @@ -68,6 +68,8 @@ define( * @returns {DomainObject} the domain object in an editable form */ getEditableObject: function (domainObject) { + var type = domainObject.getCapability('type'); + // Track the top-level domain object; this will have // some special behavior for its context capability. root = root || domainObject; @@ -77,6 +79,11 @@ define( return domainObject; } + // Don't bother wrapping non-editable objects + if (!type || !type.hasFeature('creation')) { + return domainObject; + } + // Provide an editable form of the object return new EditableDomainObject( domainObject, @@ -142,4 +149,4 @@ define( return EditableDomainObjectCache; } -); \ No newline at end of file +); diff --git a/platform/commonUI/edit/test/controllers/EditControllerSpec.js b/platform/commonUI/edit/test/controllers/EditControllerSpec.js index 260bfbd944..4e66cebd08 100644 --- a/platform/commonUI/edit/test/controllers/EditControllerSpec.js +++ b/platform/commonUI/edit/test/controllers/EditControllerSpec.js @@ -31,7 +31,7 @@ define( mockQ, mockNavigationService, mockObject, - mockCapability, + mockType, controller; beforeEach(function () { @@ -48,15 +48,18 @@ define( "domainObject", [ "getId", "getModel", "getCapability", "hasCapability" ] ); - mockCapability = jasmine.createSpyObj( - "capability", - [ "invoke" ] + mockType = jasmine.createSpyObj( + "type", + [ "hasFeature" ] ); mockNavigationService.getNavigation.andReturn(mockObject); mockObject.getId.andReturn("test"); mockObject.getModel.andReturn({ name: "Test object" }); - mockObject.getCapability.andReturn(mockCapability); + mockObject.getCapability.andCallFake(function (key) { + return key === 'type' && mockType; + }); + mockType.hasFeature.andReturn(true); controller = new EditController( mockScope, @@ -76,7 +79,7 @@ define( .toBeDefined(); // Shouldn't have been the mock capability we provided expect(controller.navigatedObject().getCapability("editor")) - .not.toEqual(mockCapability); + .not.toEqual(mockType); }); it("detaches its navigation listener when destroyed", function () { @@ -119,4 +122,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js b/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js index 586b666b02..39cac56d9f 100644 --- a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js +++ b/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js @@ -32,6 +32,7 @@ define( completionCapability, object, mockQ, + mockType, cache; @@ -40,10 +41,13 @@ define( return { getId: function () { return id; }, getModel: function () { return {}; }, - getCapability: function (name) { - return completionCapability; + getCapability: function (key) { + return { + editor: completionCapability, + type: mockType + }[key]; }, - hasCapability: function (name) { + hasCapability: function (key) { return false; } }; @@ -62,6 +66,8 @@ define( beforeEach(function () { mockQ = jasmine.createSpyObj('$q', ['when', 'all']); + mockType = jasmine.createSpyObj('type', ['hasFeature']); + mockType.hasFeature.andReturn(true); captured = {}; completionCapability = { save: function () { @@ -152,6 +158,17 @@ define( .toBe(wrappedObject); }); + it("does not wrap non-editable objects", function () { + var domainObject = new TestObject('test-id'); + + mockType.hasFeature.andCallFake(function (key) { + return key !== 'creation'; + }); + + expect(cache.getEditableObject(domainObject)) + .toBe(domainObject); + }); + }); } diff --git a/platform/commonUI/general/res/css/theme-espresso.css b/platform/commonUI/general/res/css/theme-espresso.css index 846a6102e4..5e44a592ae 100644 --- a/platform/commonUI/general/res/css/theme-espresso.css +++ b/platform/commonUI/general/res/css/theme-espresso.css @@ -84,7 +84,7 @@ * this source code distribution or the Licensing information page available * 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, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, @@ -105,38 +105,38 @@ time, mark, audio, video { font-size: 100%; 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 { 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 { 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 { border-collapse: collapse; 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 { text-align: left; font-weight: normal; 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 { 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 { content: ""; 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 { 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 { display: block; } @@ -226,20 +226,20 @@ a.disabled { @-moz-keyframes pulse { 0% { - opacity: 0.5; } + opacity: 0.2; } 100% { opacity: 1; } } @-webkit-keyframes pulse { 0% { - opacity: 0.5; } + opacity: 0.2; } 100% { opacity: 1; } } @keyframes pulse { 0% { - opacity: 0.5; } + opacity: 0.2; } 100% { opacity: 1; } } -/* line 59, ../sass/_effects.scss */ +/* line 69, ../sass/_effects.scss */ .pulse { -moz-animation-name: pulse; -webkit-animation-name: pulse; @@ -657,26 +657,26 @@ mct-container { overflow-y: auto; } /***************************************************************************** - * 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. - *****************************************************************************/ +* 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 23, ../sass/_fixed-position.scss */ .t-fixed-position.l-fixed-position { position: absolute; @@ -686,33 +686,33 @@ mct-container { left: 0; width: auto; height: auto; } - /* line 29, ../sass/_fixed-position.scss */ + /* line 33, ../sass/_fixed-position.scss */ .t-fixed-position.l-fixed-position .l-grid-holder { position: relative; height: 100%; width: 100%; } - /* line 32, ../sass/_fixed-position.scss */ + /* line 37, ../sass/_fixed-position.scss */ .t-fixed-position.l-fixed-position .l-grid-holder .l-grid { position: absolute; height: 100%; width: 100%; pointer-events: none; z-index: 0; } -/* line 42, ../sass/_fixed-position.scss */ +/* line 48, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item { position: absolute; border: 1px solid transparent; } - /* line 46, ../sass/_fixed-position.scss */ + /* line 52, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item.s-selected { -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; cursor: move; } - /* line 51, ../sass/_fixed-position.scss */ + /* line 57, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item.s-not-selected { opacity: 0.8; } - /* line 55, ../sass/_fixed-position.scss */ + /* line 61, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-box, .t-fixed-position .l-fixed-position-item .l-fixed-position-image, .t-fixed-position .l-fixed-position-item .l-fixed-position-text { @@ -721,62 +721,56 @@ mct-container { box-sizing: border-box; height: 100%; width: 100%; } - /* line 65, ../sass/_fixed-position.scss */ + /* line 72, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-image { background-size: cover; background-repeat: no-repeat; background-position: center; } - /* line 71, ../sass/_fixed-position.scss */ + /* line 78, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text { text-shadow: rgba(0, 0, 0, 0.1) 0 1px 2px; border: 1px solid transparent; font-size: 0.8rem; line-height: 100%; } - /* line 77, ../sass/_fixed-position.scss */ + /* line 84, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-static-text { padding: 1px; } - /* line 82, ../sass/_fixed-position.scss */ + /* line 89, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-telemetry .l-elem { - overflow: hidden; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: auto; - height: auto; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; - padding: 2px; - width: 50%; } - /* line 88, ../sass/_fixed-position.scss */ + display: block; + padding: 2px; } + /* line 96, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-telemetry .l-elem.l-title { - right: auto; - left: 1px; } - /* line 92, ../sass/_fixed-position.scss */ + float: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: auto; } + /* line 105, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-telemetry .l-elem.l-value { - right: 1px; - left: auto; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + float: right; + margin-left: 5px; + padding-left: 5px; + padding-right: 5px; text-align: right; } - /* line 97, ../sass/_fixed-position.scss */ + /* line 116, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-telemetry .l-elem.l-value.telem-only { - left: 1px; - width: auto; } - /* line 102, ../sass/_fixed-position.scss */ - .t-fixed-position .l-fixed-position-item .l-fixed-position-text.l-telemetry .l-elem.l-value .l-value-bg { - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; - padding: 0 4px; } -/* line 112, ../sass/_fixed-position.scss */ + margin-left: 0; + width: 100%; } +/* line 126, ../sass/_fixed-position.scss */ .t-fixed-position .l-fixed-position-item-handle { background: rgba(0, 153, 204, 0.5); cursor: crosshair; border: 1px solid #0099cc; position: absolute; } -/* line 126, ../sass/_fixed-position.scss */ +/* line 140, ../sass/_fixed-position.scss */ .edit-mode .t-fixed-position.l-fixed-position .l-grid-holder .l-grid.l-grid-x { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuMCIgeTE9IjAuNSIgeDI9IjEuMCIgeTI9IjAuNSI+PHN0b3Agb2Zmc2V0PSIxcHgiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMC4wNSIvPjxzdG9wIG9mZnNldD0iMXB4IiBzdG9wLWNvbG9yPSIjMDAwMDAwIiBzdG9wLW9wYWNpdHk9IjAuMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; @@ -784,7 +778,7 @@ mct-container { background-image: -webkit-linear-gradient(0deg, rgba(255, 255, 255, 0.05) 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 100%); background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 100%); background-repeat: repeat-x; } -/* line 130, ../sass/_fixed-position.scss */ +/* line 144, ../sass/_fixed-position.scss */ .edit-mode .t-fixed-position.l-fixed-position .l-grid-holder .l-grid.l-grid-y { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjEuMCIgeDI9IjAuNSIgeTI9IjAuMCI+PHN0b3Agb2Zmc2V0PSIxcHgiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMC4wNSIvPjxzdG9wIG9mZnNldD0iMXB4IiBzdG9wLWNvbG9yPSIjMDAwMDAwIiBzdG9wLW9wYWNpdHk9IjAuMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwLjAiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; @@ -792,10 +786,10 @@ mct-container { background-image: -webkit-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 100%); background-image: linear-gradient(0deg, rgba(255, 255, 255, 0.05) 1px, rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0) 100%); background-repeat: repeat-y; } -/* line 138, ../sass/_fixed-position.scss */ +/* line 152, ../sass/_fixed-position.scss */ .edit-mode .t-fixed-position .l-fixed-position-item:not(.s-selected) { border: 1px dotted rgba(0, 153, 204, 0.75); } - /* line 140, ../sass/_fixed-position.scss */ + /* line 154, ../sass/_fixed-position.scss */ .edit-mode .t-fixed-position .l-fixed-position-item:not(.s-selected):hover { border: 1px dotted #0099cc; } @@ -4679,8 +4673,8 @@ input[type="text"] { border-style: solid; border-width: 0.25em; border-radius: 100%; - height: 18px; - width: 18px; + height: 10px; + width: 10px; margin: 0 !important; padding: 0 !important; top: 2px; @@ -4747,20 +4741,24 @@ input[type="text"] { .autoflow .l-autoflow-header { bottom: auto; height: 22px; - line-height: 22px; } - /* line 34, ../sass/_autoflow.scss */ + line-height: 22px; + min-width: 225px; } + /* line 35, ../sass/_autoflow.scss */ .autoflow .l-autoflow-header span { vertical-align: middle; } - /* line 37, ../sass/_autoflow.scss */ + /* line 38, ../sass/_autoflow.scss */ .autoflow .l-autoflow-header .l-filter { margin-left: 5px; } - /* line 42, ../sass/_autoflow.scss */ + /* line 40, ../sass/_autoflow.scss */ + .autoflow .l-autoflow-header .l-filter input.t-filter-input { + width: 100px; } + /* line 46, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items { overflow-x: scroll; overflow-y: hidden; top: 32px; white-space: nowrap; } - /* line 48, ../sass/_autoflow.scss */ + /* line 52, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -4771,7 +4769,7 @@ input[type="text"] { padding-right: 5px; vertical-align: top; width: 225px; } - /* line 58, ../sass/_autoflow.scss */ + /* line 62, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -4783,47 +4781,47 @@ input[type="text"] { margin-bottom: 1px; margin-top: 1px; position: relative; } - /* line 67, ../sass/_autoflow.scss */ + /* line 71, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row:first-child { border-top: none; } - /* line 70, ../sass/_autoflow.scss */ + /* line 74, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row:hover { background: rgba(255, 255, 255, 0.1); } - /* line 75, ../sass/_autoflow.scss */ + /* line 79, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row.s-stale .l-autoflow-item.l { color: rgba(255, 255, 255, 0.3) !important; font-style: italic; } - /* line 76, ../sass/_autoflow.scss */ + /* line 80, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row.s-stale .l-autoflow-item.r { color: rgba(255, 255, 255, 0.5) !important; font-style: italic; } - /* line 79, ../sass/_autoflow.scss */ + /* line 83, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row:not(.s-stale) .l-autoflow-item.r { color: #b3b3b3; } - /* line 83, ../sass/_autoflow.scss */ + /* line 87, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row.first-in-group { border-top: 1px solid gray; } - /* line 86, ../sass/_autoflow.scss */ + /* line 90, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row .l-autoflow-item { display: block; } - /* line 88, ../sass/_autoflow.scss */ + /* line 92, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row .l-autoflow-item.l { float: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: auto; } - /* line 95, ../sass/_autoflow.scss */ + /* line 99, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col .l-autoflow-row .l-autoflow-item.r { -moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; float: right; margin-left: 5px; - padding-left: 2px; - padding-right: 2px; + padding-left: 5px; + padding-right: 5px; text-align: right; } - /* line 106, ../sass/_autoflow.scss */ + /* line 110, ../sass/_autoflow.scss */ .autoflow .l-autoflow-items .l-autoflow-col:first-child { border-left: none; padding-left: 0; } diff --git a/platform/commonUI/general/res/sass/_autoflow.scss b/platform/commonUI/general/res/sass/_autoflow.scss index 448a4203be..0d04d619e8 100644 --- a/platform/commonUI/general/res/sass/_autoflow.scss +++ b/platform/commonUI/general/res/sass/_autoflow.scss @@ -24,18 +24,22 @@ $colMargin: $interiorMargin; $colW: 225px; $valW: 70px; - $valPad: 2px; + $valPad: 5px; $rowH: 15px; font-size: 0.75rem; .l-autoflow-header { bottom: auto; height: $headerH; line-height: $headerH; + min-width: $colW; span { vertical-align: middle; } .l-filter { margin-left: $interiorMargin; + input.t-filter-input { + width: 100px; + } } } diff --git a/platform/commonUI/general/res/sass/_effects.scss b/platform/commonUI/general/res/sass/_effects.scss index c90a035c82..ddade4e965 100644 --- a/platform/commonUI/general/res/sass/_effects.scss +++ b/platform/commonUI/general/res/sass/_effects.scss @@ -43,16 +43,26 @@ a.disabled { @include test(); } +@mixin customKeyframes($animName: pulse, $op0: 0.5) { + @include keyframes($animName) { + 0% { opacity: $op0; } + 100% { opacity: 1; } + } + @include animation-name(pulse, 0.2); +} + @include keyframes(pulse) { - 0% { opacity: 0.5; } + 0% { opacity: 0.2; } 100% { opacity: 1; } } -@mixin pulse($dur: 500ms) { + +@mixin pulse($dur: 500ms, $iteration: infinite) { + //@include customKeyframes(pulse, 0.2); @include animation-name(pulse); @include animation-duration($dur); @include animation-direction(alternate); - @include animation-iteration-count(infinite); + @include animation-iteration-count($iteration); @include animation-timing-function(ease-in-out); } diff --git a/platform/commonUI/general/res/sass/_fixed-position.scss b/platform/commonUI/general/res/sass/_fixed-position.scss index 22694aea72..f9e8a82994 100644 --- a/platform/commonUI/general/res/sass/_fixed-position.scss +++ b/platform/commonUI/general/res/sass/_fixed-position.scss @@ -1,38 +1,44 @@ /***************************************************************************** - * 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. - *****************************************************************************/ +* 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. +*****************************************************************************/ .t-fixed-position { &.l-fixed-position { -// @include test(red); + // @include test(red); position: absolute; - top: 0; right: 0; bottom: 0; left: 0; - width: auto; height: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: auto; + height: auto; .l-grid-holder { position: relative; - height: 100%; width: 100%; + height: 100%; + width: 100%; .l-grid { -// @include test(orange); + // @include test(orange); position: absolute; - height: 100%; width: 100%; + height: 100%; + width: 100%; pointer-events: none; z-index: 0; } @@ -56,12 +62,13 @@ .l-fixed-position-image, .l-fixed-position-text { @include box-sizing(border-box); - height: 100%; width: 100%; + height: 100%; + width: 100%; } .l-fixed-position-box { } - + .l-fixed-position-image { background-size: cover; background-repeat: no-repeat; @@ -70,38 +77,45 @@ .l-fixed-position-text { @include txtShdwSubtle(); - border:1px solid transparent; + border: 1px solid transparent; font-size: 0.8rem; $p: 1px; //$interiorMarginSm; line-height: 100%; &.l-static-text { -// overflow: auto; + // overflow: auto; padding: $p; } &.l-telemetry { .l-elem { //@include absPosDefault($p); - @include absPosDefault(0); + //@include absPosDefault(0); @include box-sizing(border-box); + display: block; padding: 2px; - width: 50%; + //width: 50%; &.l-title { - right: auto; - left: $p; + //right: auto; + //left: $p; + float: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: auto; } &.l-value { -// @include test(blue); - right: $p; - left: auto; + // @include test(blue); + // right: $p; + // left: auto; + @include border-radius($smallCr); + $valPad: 5px; + float: right; + margin-left: $interiorMargin; + padding-left: $valPad; + padding-right: $valPad; text-align: right; &.telem-only { -// @include test(red); - left: $p; - width: auto; - } - .l-value-bg { - @include border-radius($smallCr); - padding: 0 4px; + margin-left: 0; + width: 100%; } } } diff --git a/platform/commonUI/general/res/sass/_mixins.scss b/platform/commonUI/general/res/sass/_mixins.scss index 8fe4f731c0..14a123d954 100644 --- a/platform/commonUI/general/res/sass/_mixins.scss +++ b/platform/commonUI/general/res/sass/_mixins.scss @@ -26,7 +26,7 @@ width: auto; height: auto; } -@mixin trans-prop-nice($props, $t) { +@mixin trans-prop-nice($props, $t: 500ms) { @if $t == 0 { @include transition-property(none); } @else { diff --git a/platform/commonUI/general/res/sass/helpers/_wait-spinner.scss b/platform/commonUI/general/res/sass/helpers/_wait-spinner.scss index 28f6e6bb28..fafae5e9ea 100644 --- a/platform/commonUI/general/res/sass/helpers/_wait-spinner.scss +++ b/platform/commonUI/general/res/sass/helpers/_wait-spinner.scss @@ -74,7 +74,7 @@ .treeview .wait-spinner { - $d: 18px; + $d: 10px; @include wait-spinner(0.25em, $colorKey); height: $d; width: $d; margin: 0 !important; diff --git a/platform/core/bundle.json b/platform/core/bundle.json index fef89bd80d..be1f181f44 100644 --- a/platform/core/bundle.json +++ b/platform/core/bundle.json @@ -70,6 +70,12 @@ "type": "decorator", "implementation": "models/CachingModelDecorator.js" }, + { + "provides": "modelService", + "type": "decorator", + "priority": "fallback", + "implementation": "models/MissingModelDecorator.js" + }, { "provides": "typeService", "type": "provider", @@ -132,6 +138,11 @@ "features": "creation", "description": "Useful for storing and organizing domain objects.", "model": { "composition": [] } + }, + { + "key": "unknown", + "name": "Unknown Type", + "glyph": "?" } ], "capabilities": [ @@ -202,4 +213,4 @@ } ] } -} \ No newline at end of file +} diff --git a/platform/core/src/models/MissingModelDecorator.js b/platform/core/src/models/MissingModelDecorator.js new file mode 100644 index 0000000000..3c82bd2726 --- /dev/null +++ b/platform/core/src/models/MissingModelDecorator.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * 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"; + + /** + * Adds placeholder domain object models for any models which + * fail to load from the underlying model service. + * @implements {ModelService} + */ + function MissingModelDecorator(modelService) { + function missingModel(id) { + return { + type: "unknown", + name: "Missing: " + id + }; + } + + return { + getModels: function (ids) { + function addMissingModels(models) { + var result = {}; + ids.forEach(function (id) { + result[id] = models[id] || missingModel(id); + }); + return result; + } + + return modelService.getModels(ids).then(addMissingModels); + } + }; + } + + return MissingModelDecorator; + } +); diff --git a/platform/features/imagery/bundle.json b/platform/features/imagery/bundle.json new file mode 100644 index 0000000000..8d8495aa3a --- /dev/null +++ b/platform/features/imagery/bundle.json @@ -0,0 +1,35 @@ +{ + "name": "Plot view for telemetry", + "extensions": { + "views": [ + { + "name": "Imagery", + "key": "imagery", + "glyph": "\u00E3", + "templateUrl": "templates/imagery.html", + "priority": "preferred", + "needs": [ "telemetry" ] + } + ], + "policies": [ + { + "category": "view", + "implementation": "policies/ImageryViewPolicy.js" + } + ], + "controllers": [ + { + "key": "ImageryController", + "implementation": "controllers/ImageryController.js", + "depends": [ "$scope", "telemetryHandler" ] + } + ], + "directives": [ + { + "key": "mctBackgroundImage", + "implementation": "directives/MCTBackgroundImage.js", + "depends": [ "$document" ] + } + ] + } +} diff --git a/platform/features/imagery/res/templates/imagery.html b/platform/features/imagery/res/templates/imagery.html new file mode 100644 index 0000000000..80f0813b2b --- /dev/null +++ b/platform/features/imagery/res/templates/imagery.html @@ -0,0 +1,67 @@ +
+ +
+ + +
+
+ +
+
+ + {{imagery.getZone()}} + {{imagery.getTime()}} + {{imagery.getDate()}} +
+
+ + + y + +
+
+
+
diff --git a/platform/features/imagery/src/controllers/ImageryController.js b/platform/features/imagery/src/controllers/ImageryController.js new file mode 100644 index 0000000000..72f72f39db --- /dev/null +++ b/platform/features/imagery/src/controllers/ImageryController.js @@ -0,0 +1,133 @@ +/***************************************************************************** + * 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( + ['moment'], + function (moment) { + "use strict"; + + var DATE_FORMAT = "YYYY-MM-DD", + TIME_FORMAT = "HH:mm:ss.SSS"; + + /** + * Controller for the "Imagery" view of a domain object which + * provides image telemetry. + */ + function ImageryController($scope, telemetryHandler) { + var date = "", + time = "", + imageUrl = "", + paused = false, + handle; + + function releaseSubscription() { + if (handle) { + handle.unsubscribe(); + handle = undefined; + } + } + + function updateValues() { + var imageObject = handle && handle.getTelemetryObjects()[0], + m; + if (imageObject && !paused) { + m = moment.utc(handle.getDomainValue(imageObject)); + date = m.format(DATE_FORMAT); + time = m.format(TIME_FORMAT); + imageUrl = handle.getRangeValue(imageObject); + } + } + + // Create a new subscription; telemetrySubscriber gets + // to do the meaningful work here. + function subscribe(domainObject) { + releaseSubscription(); + date = ""; + time = ""; + imageUrl = ""; + handle = domainObject && telemetryHandler.handle( + domainObject, + updateValues, + true // Lossless + ); + } + + // Subscribe to telemetry when a domain object becomes available + $scope.$watch('domainObject', subscribe); + + // Unsubscribe when the plot is destroyed + $scope.$on("$destroy", releaseSubscription); + + return { + /** + * Get the time portion (hours, minutes, seconds) of the + * timestamp associated with the incoming image telemetry. + * @returns {string} the time + */ + getTime: function () { + return time; + }, + /** + * Get the date portion (month, year) of the + * timestamp associated with the incoming image telemetry. + * @returns {string} the date + */ + getDate: function () { + return date; + }, + /** + * Get the time zone for the displayed time/date corresponding + * to the timestamp associated with the incoming image + * telemetry. + * @returns {string} the time + */ + getZone: function () { + return "UTC"; + }, + /** + * Get the URL of the image telemetry to display. + * @returns {string} URL for telemetry image + */ + getImageUrl: function () { + return imageUrl; + }, + /** + * Getter-setter for paused state of the view (true means + * paused, false means not.) + * @param {boolean} [state] the state to set + * @returns {boolean} the current state + */ + paused: function (state) { + if (arguments.length > 0 && state !== paused) { + paused = state; + // Switch to latest image + updateValues(); + } + return paused; + } + }; + } + + return ImageryController; + } +); diff --git a/platform/features/imagery/src/directives/MCTBackgroundImage.js b/platform/features/imagery/src/directives/MCTBackgroundImage.js new file mode 100644 index 0000000000..0e5c20e919 --- /dev/null +++ b/platform/features/imagery/src/directives/MCTBackgroundImage.js @@ -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"; + + /** + * Defines the `mct-background-image` directive. + * + * Used as an attribute, this will set the `background-image` + * property to the URL given in its value, but only after that + * image has loaded; this avoids "flashing" as images change. + * + * If `src` is falsy, no image will be displayed (immediately.) + * + */ + function MCTBackgroundImage($document) { + function link(scope, element, attrs) { + // General strategy here: + // - Keep count of how many images have been requested; this + // counter will be used as an internal identifier or sorts + // for each image that loads. + // - As the src attribute changes, begin loading those images. + // - When images do load, update the background-image property + // of the element, but only if a more recently + // requested image has not already been loaded. + // The order in which URLs are passed in and the order + // in which images are actually loaded may be different, so + // some strategy like this is necessary to ensure that images + // do not display out-of-order. + var div, requested = 0, loaded = 0; + + function nextImage(url) { + var myCounter = requested, + image; + + function useImage() { + if (loaded <= myCounter) { + loaded = myCounter; + element.css('background-image', "url('" + url + "')"); + } + } + + if (!url) { + loaded = myCounter; + element.css('background-image', undefined); + } else { + image = $document[0].createElement('img'); + image.src = url; + image.onload = useImage; + } + + requested += 1; + } + + scope.$watch('mctBackgroundImage', nextImage); + } + + return { + restrict: "A", + scope: { mctBackgroundImage: "=" }, + link: link + }; + } + + return MCTBackgroundImage; + } +); diff --git a/platform/features/imagery/src/policies/ImageryViewPolicy.js b/platform/features/imagery/src/policies/ImageryViewPolicy.js new file mode 100644 index 0000000000..40b74d96df --- /dev/null +++ b/platform/features/imagery/src/policies/ImageryViewPolicy.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * 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"; + + /** + * Policy preventing the Imagery view from being made available for + * domain objects which do not have associated image telemetry. + * @implements {Policy} + */ + function ImageryViewPolicy() { + function hasImageTelemetry(domainObject) { + var telemetry = domainObject && + domainObject.getCapability('telemetry'), + metadata = telemetry ? telemetry.getMetadata() : {}, + ranges = metadata.ranges || []; + + return ranges.some(function (range) { + return range.format === 'imageUrl' || + range.format === 'image'; + }); + } + + return { + allow: function (view, domainObject) { + if (view.key === 'imagery') { + return hasImageTelemetry(domainObject); + } + + return true; + } + }; + } + + return ImageryViewPolicy; + } +); diff --git a/platform/features/imagery/test/controllers/ImageryControllerSpec.js b/platform/features/imagery/test/controllers/ImageryControllerSpec.js new file mode 100644 index 0000000000..9bb00d6b20 --- /dev/null +++ b/platform/features/imagery/test/controllers/ImageryControllerSpec.js @@ -0,0 +1,152 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/controllers/ImageryController"], + function (ImageryController) { + "use strict"; + + describe("The Imagery controller", function () { + var mockScope, + mockTelemetryHandler, + mockHandle, + mockDomainObject, + controller; + + function invokeWatch(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']); + mockTelemetryHandler = jasmine.createSpyObj( + 'telemetryHandler', + ['handle'] + ); + mockHandle = jasmine.createSpyObj( + 'handle', + [ + 'getDomainValue', + 'getRangeValue', + 'getTelemetryObjects', + 'unsubscribe' + ] + ); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + + mockTelemetryHandler.handle.andReturn(mockHandle); + mockHandle.getTelemetryObjects.andReturn([mockDomainObject]); + + controller = new ImageryController( + mockScope, + mockTelemetryHandler + ); + invokeWatch('domainObject', mockDomainObject); + }); + + it("unsubscribes when scope is destroyed", function () { + expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); + + // Find the $destroy listener and call it + mockScope.$on.calls.forEach(function (call) { + if (call.args[0] === '$destroy') { + call.args[1](); + } + }); + expect(mockHandle.unsubscribe).toHaveBeenCalled(); + }); + + it("exposes the latest telemetry values", function () { + // 06/18/2015 4:04am UTC + var testTimestamp = 1434600258123, + testUrl = "some/url", + nextTimestamp = 1434600259456, // 4:05.456 + nextUrl = "some/other/url"; + + mockHandle.getDomainValue.andReturn(testTimestamp); + mockHandle.getRangeValue.andReturn(testUrl); + + // Call the subscription listener + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + + mockHandle.getDomainValue.andReturn(nextTimestamp); + mockHandle.getRangeValue.andReturn(nextUrl); + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:19.456"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(nextUrl); + }); + + it("allows updates to be paused", function () { + // 06/18/2015 4:04am UTC + var testTimestamp = 1434600258123, + testUrl = "some/url", + nextTimestamp = 1434600259456, // 4:05.456 + nextUrl = "some/other/url"; + + // As above, but pause in between. Expect details + // not to change this time + + mockHandle.getDomainValue.andReturn(testTimestamp); + mockHandle.getRangeValue.andReturn(testUrl); + + // Call the subscription listener + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + + expect(controller.paused()).toBeFalsy(); + controller.paused(true); // Pause! + expect(controller.paused()).toBeTruthy(); + + mockHandle.getDomainValue.andReturn(nextTimestamp); + mockHandle.getRangeValue.andReturn(nextUrl); + mockTelemetryHandler.handle.mostRecentCall.args[1](); + + expect(controller.getTime()).toEqual("04:04:18.123"); + expect(controller.getDate()).toEqual("2015-06-18"); + expect(controller.getZone()).toEqual("UTC"); + expect(controller.getImageUrl()).toEqual(testUrl); + }); + + }); + } +); + diff --git a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js new file mode 100644 index 0000000000..3c55f6f480 --- /dev/null +++ b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js @@ -0,0 +1,101 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/directives/MCTBackgroundImage"], + function (MCTBackgroundImage) { + "use strict"; + + describe("The mct-background-image directive", function () { + var mockDocument, + mockScope, + mockElement, + testImage, + directive; + + beforeEach(function () { + mockDocument = [ + jasmine.createSpyObj('document', ['createElement']) + ]; + mockScope = jasmine.createSpyObj('scope', ['$watch']); + mockElement = jasmine.createSpyObj('element', [ 'css' ]); + testImage = {}; + + mockDocument[0].createElement.andReturn(testImage); + + directive = new MCTBackgroundImage(mockDocument); + }); + + it("is applicable as an attribute", function () { + expect(directive.restrict).toEqual("A"); + }); + + it("two-way-binds its own value", function () { + expect(directive.scope.mctBackgroundImage).toEqual("="); + }); + + it("watches for changes to the URL", function () { + directive.link(mockScope, mockElement, {}); + expect(mockScope.$watch).toHaveBeenCalledWith( + 'mctBackgroundImage', + jasmine.any(Function) + ); + }); + + it("updates images in-order, even when they load out-of-order", function () { + var firstOnload; + + directive.link(mockScope, mockElement); + + mockScope.$watch.mostRecentCall.args[1]("some/url/0"); + firstOnload = testImage.onload; + + mockScope.$watch.mostRecentCall.args[1]("some/url/1"); + + // Resolve in a different order + testImage.onload(); + firstOnload(); + + // Should still have taken the more recent value + expect(mockElement.css.mostRecentCall.args).toEqual([ + "background-image", + "url('some/url/1')" + ]); + }); + + it("clears the background image when undefined is passed in", function () { + directive.link(mockScope, mockElement); + + mockScope.$watch.mostRecentCall.args[1]("some/url/0"); + testImage.onload(); + mockScope.$watch.mostRecentCall.args[1](undefined); + + expect(mockElement.css.mostRecentCall.args).toEqual([ + "background-image", + undefined + ]); + }); + }); + } +); + diff --git a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js new file mode 100644 index 0000000000..21e64cc324 --- /dev/null +++ b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js @@ -0,0 +1,80 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/policies/ImageryViewPolicy"], + function (ImageryViewPolicy) { + "use strict"; + + describe("Imagery view policy", function () { + var testView, + mockDomainObject, + mockTelemetry, + testMetadata, + policy; + + beforeEach(function () { + testView = { key: "imagery" }; + testMetadata = {}; + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + mockTelemetry = jasmine.createSpyObj( + 'telemetry', + ['getMetadata'] + ); + mockDomainObject.getCapability.andCallFake(function (c) { + return c === 'telemetry' ? mockTelemetry : undefined; + }); + mockTelemetry.getMetadata.andReturn(testMetadata); + + policy = new ImageryViewPolicy(); + }); + + it("allows the imagery view for domain objects with image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "imageUrl" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("disallows the imagery view for domain objects without image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("disallows the imagery view for domain objects without telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "imageUrl" } ]; + mockDomainObject.getCapability.andReturn(undefined); + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("allows other views", function () { + testView.key = "somethingElse"; + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + }); + } +); + diff --git a/platform/features/imagery/test/suite.json b/platform/features/imagery/test/suite.json new file mode 100644 index 0000000000..4ea822e7a2 --- /dev/null +++ b/platform/features/imagery/test/suite.json @@ -0,0 +1,5 @@ +[ + "controllers/ImageryController", + "directives/MCTBackgroundImage", + "policies/ImageryViewPolicy" +] diff --git a/platform/features/layout/res/templates/elements/telemetry.html b/platform/features/layout/res/templates/elements/telemetry.html index 601f17679b..87d17cc913 100644 --- a/platform/features/layout/res/templates/elements/telemetry.html +++ b/platform/features/layout/res/templates/elements/telemetry.html @@ -23,16 +23,17 @@ class="l-fixed-position-text l-telemetry" ng-style="{ background: ngModel.fill(), 'border-color': ngModel.stroke(), color: ngModel.color() }" > -
+ {{ngModel.value}} + + {{ngModel.name}} -
-
- {{ngModel.value}} -
+ \ No newline at end of file diff --git a/platform/features/plot/bundle.json b/platform/features/plot/bundle.json index 2a7d6e9c73..bbbea34bdc 100644 --- a/platform/features/plot/bundle.json +++ b/platform/features/plot/bundle.json @@ -25,6 +25,12 @@ "implementation": "PlotController.js", "depends": [ "$scope", "telemetryFormatter", "telemetryHandler", "throttle" ] } + ], + "policies": [ + { + "category": "view", + "implementation": "policies/PlotViewPolicy.js" + } ] } -} \ No newline at end of file +} diff --git a/platform/features/plot/src/policies/PlotViewPolicy.js b/platform/features/plot/src/policies/PlotViewPolicy.js new file mode 100644 index 0000000000..78df8c3187 --- /dev/null +++ b/platform/features/plot/src/policies/PlotViewPolicy.js @@ -0,0 +1,65 @@ +/***************************************************************************** + * 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"; + + /** + * Policy preventing the Plot view from being made available for + * domain objects which have non-numeric telemetry. + * @implements {Policy} + */ + function PlotViewPolicy() { + function hasImageTelemetry(domainObject) { + var telemetry = domainObject && + domainObject.getCapability('telemetry'), + metadata = telemetry ? telemetry.getMetadata() : {}, + ranges = metadata.ranges || []; + + // Generally, we want to allow Plot for telemetry-providing + // objects (most telemetry is plottable.) We only want to + // suppress this for telemetry which only has explicitly + // non-numeric values. + return ranges.length === 0 || ranges.some(function (range) { + // Assume format is numeric if it is undefined + // (numeric telemetry is the common case) + return range.format === undefined || + range.format === 'number'; + }); + } + + return { + allow: function (view, domainObject) { + if (view.key === 'plot') { + return hasImageTelemetry(domainObject); + } + + return true; + } + }; + } + + return PlotViewPolicy; + } +); diff --git a/platform/features/plot/test/policies/PlotViewPolicySpec.js b/platform/features/plot/test/policies/PlotViewPolicySpec.js new file mode 100644 index 0000000000..27c40221ef --- /dev/null +++ b/platform/features/plot/test/policies/PlotViewPolicySpec.js @@ -0,0 +1,79 @@ +/***************************************************************************** + * 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,xit*/ + +define( + ["../../src/policies/PlotViewPolicy"], + function (PlotViewPolicy) { + "use strict"; + + describe("Plot view policy", function () { + var testView, + mockDomainObject, + mockTelemetry, + testMetadata, + policy; + + beforeEach(function () { + testView = { key: "plot" }; + testMetadata = {}; + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getModel', 'getCapability'] + ); + mockTelemetry = jasmine.createSpyObj( + 'telemetry', + ['getMetadata'] + ); + mockDomainObject.getCapability.andCallFake(function (c) { + return c === 'telemetry' ? mockTelemetry : undefined; + }); + mockTelemetry.getMetadata.andReturn(testMetadata); + + policy = new PlotViewPolicy(); + }); + + it("allows the imagery view for domain objects with numeric telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "number" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("allows the imagery view for domain objects with unspecified telemetry", function () { + testMetadata.ranges = [ { key: "foo" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + it("disallows the imagery view for domain objects without image telemetry", function () { + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); + }); + + it("allows other views", function () { + testView.key = "somethingElse"; + testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ]; + expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); + }); + + }); + } +); + diff --git a/platform/features/plot/test/suite.json b/platform/features/plot/test/suite.json index e3fa3fb796..323d53b6b3 100644 --- a/platform/features/plot/test/suite.json +++ b/platform/features/plot/test/suite.json @@ -18,6 +18,7 @@ "elements/PlotUpdater", "modes/PlotModeOptions", "modes/PlotOverlayMode", - "modes/PlotStackMode" + "modes/PlotStackMode", + "policies/PlotViewPolicy" ]