From 3b195e9c7d8d63b04faa0663bfcf1a47e5c7df8b Mon Sep 17 00:00:00 2001 From: Nikhil Date: Fri, 13 Dec 2019 15:36:01 -0800 Subject: [PATCH] Example imagery vue (#2525) * WIP: imagery vue refactor * cleaup * show orange border when paused. * resize image and thumbs wrappers. * scrollToBottom fixed. * fixed lint errors * use multipane vue component for resize + cleanup + style adjustments. * added min-height to image pane and thumbs-layout pane. * remove old plugin and using es6 const. * using ES6 imports. * clean up + formatting changes. * updated as per review comments. * extracted styles from vue component. * fixed lint errors. * updated as per review comments + cleanup. --- platform/features/imagery/bundle.js | 86 ------ .../imagery/res/templates/imagery.html | 58 ---- .../src/controllers/ImageryController.js | 284 ------------------ .../src/directives/MCTBackgroundImage.js | 110 ------- .../imagery/src/policies/ImageryViewPolicy.js | 59 ---- .../test/controllers/ImageryControllerSpec.js | 271 ----------------- .../test/directives/MCTBackgroundImageSpec.js | 126 -------- .../test/policies/ImageryViewPolicySpec.js | 84 ------ src/MCT.js | 3 + src/installDefaultBundles.js | 2 - src/plugins/imagery/ImageryViewProvider.js | 47 +++ .../imagery/components/ImageryViewLayout.vue | 249 +++++++++++++++ .../components/imagery-view-layout.scss | 32 ++ src/plugins/imagery/plugin.js | 8 + src/plugins/plugins.js | 3 + src/styles/vue-styles.scss | 1 + webpack.config.js | 1 + 17 files changed, 344 insertions(+), 1080 deletions(-) delete mode 100644 platform/features/imagery/bundle.js delete mode 100644 platform/features/imagery/res/templates/imagery.html delete mode 100644 platform/features/imagery/src/controllers/ImageryController.js delete mode 100644 platform/features/imagery/src/directives/MCTBackgroundImage.js delete mode 100644 platform/features/imagery/src/policies/ImageryViewPolicy.js delete mode 100644 platform/features/imagery/test/controllers/ImageryControllerSpec.js delete mode 100644 platform/features/imagery/test/directives/MCTBackgroundImageSpec.js delete mode 100644 platform/features/imagery/test/policies/ImageryViewPolicySpec.js create mode 100644 src/plugins/imagery/ImageryViewProvider.js create mode 100644 src/plugins/imagery/components/ImageryViewLayout.vue create mode 100644 src/plugins/imagery/components/imagery-view-layout.scss create mode 100644 src/plugins/imagery/plugin.js diff --git a/platform/features/imagery/bundle.js b/platform/features/imagery/bundle.js deleted file mode 100644 index ce0745519f..0000000000 --- a/platform/features/imagery/bundle.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/policies/ImageryViewPolicy", - "./src/controllers/ImageryController", - "./src/directives/MCTBackgroundImage", - "./res/templates/imagery.html" -], function ( - ImageryViewPolicy, - ImageryController, - MCTBackgroundImage, - imageryTemplate -) { - - return { - name:"platform/features/imagery", - definition: { - "name": "Plot view for telemetry", - "extensions": { - "views": [ - { - "name": "Imagery", - "key": "imagery", - "cssClass": "icon-image", - "template": imageryTemplate, - "priority": "preferred", - "needs": [ - "telemetry" - ], - "editable": false - } - ], - "policies": [ - { - "category": "view", - "implementation": ImageryViewPolicy, - "depends": [ - "openmct" - ] - } - ], - "controllers": [ - { - "key": "ImageryController", - "implementation": ImageryController, - "depends": [ - "$scope", - "$window", - "$element", - "openmct" - ] - } - ], - "directives": [ - { - "key": "mctBackgroundImage", - "implementation": MCTBackgroundImage, - "depends": [ - "$document" - ] - } - ] - } - } - }; -}); diff --git a/platform/features/imagery/res/templates/imagery.html b/platform/features/imagery/res/templates/imagery.html deleted file mode 100644 index 8aedff7163..0000000000 --- a/platform/features/imagery/res/templates/imagery.html +++ /dev/null @@ -1,58 +0,0 @@ -
- -
-
- - - - - - - -
- -
-
-
-
- -
-
- - {{imagery.getTime()}} -
-
- - -
-
-
- -
-
- -
{{imagery.getTime(image)}}
-
-
-
-
diff --git a/platform/features/imagery/src/controllers/ImageryController.js b/platform/features/imagery/src/controllers/ImageryController.js deleted file mode 100644 index ef69903304..0000000000 --- a/platform/features/imagery/src/controllers/ImageryController.js +++ /dev/null @@ -1,284 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements views of image telemetry. - * @namespace platform/features/imagery - */ - -define( - [ - 'zepto', - 'lodash' - ], - function ($, _) { - - /** - * Controller for the "Imagery" view of a domain object which - * provides image telemetry. - * @constructor - * @memberof platform/features/imagery - */ - - function ImageryController($scope, $window, element, openmct) { - this.$scope = $scope; - this.$window = $window; - this.openmct = openmct; - this.date = ""; - this.time = ""; - this.zone = ""; - this.imageUrl = ""; - this.requestCount = 0; - this.scrollable = $(".l-image-thumbs-wrapper"); - this.autoScroll = openmct.time.clock() ? true : false; - this.$scope.imageHistory = []; - this.$scope.filters = { - brightness: 100, - contrast: 100 - }; - - this.subscribe = this.subscribe.bind(this); - this.stopListening = this.stopListening.bind(this); - this.updateValues = this.updateValues.bind(this); - this.updateHistory = this.updateHistory.bind(this); - this.onBoundsChange = this.onBoundsChange.bind(this); - this.onScroll = this.onScroll.bind(this); - this.setSelectedImage = this.setSelectedImage.bind(this); - - this.subscribe(this.$scope.domainObject); - - this.$scope.$on('$destroy', this.stopListening); - this.openmct.time.on('bounds', this.onBoundsChange); - this.scrollable.on('scroll', this.onScroll); - } - - ImageryController.prototype.subscribe = function (domainObject) { - this.date = ""; - this.imageUrl = ""; - this.openmct.objects.get(domainObject.getId()) - .then(function (object) { - this.domainObject = object; - var metadata = this.openmct - .telemetry - .getMetadata(this.domainObject); - this.timeKey = this.openmct.time.timeSystem().key; - this.timeFormat = this.openmct - .telemetry - .getValueFormatter(metadata.value(this.timeKey)); - this.imageFormat = this.openmct - .telemetry - .getValueFormatter(metadata.valuesForHints(['image'])[0]); - this.unsubscribe = this.openmct.telemetry - .subscribe(this.domainObject, function (datum) { - this.updateHistory(datum); - this.updateValues(datum); - }.bind(this)); - - this.requestHistory(this.openmct.time.bounds()); - }.bind(this)); - }; - - ImageryController.prototype.requestHistory = function (bounds) { - this.requestCount++; - this.$scope.imageHistory = []; - var requestId = this.requestCount; - this.openmct.telemetry - .request(this.domainObject, bounds) - .then(function (values) { - if (this.requestCount > requestId) { - return Promise.resolve('Stale request'); - } - - values.forEach(function (datum) { - this.updateHistory(datum); - }, this); - - this.updateValues(values[values.length - 1]); - }.bind(this)); - }; - - ImageryController.prototype.stopListening = function () { - this.openmct.time.off('bounds', this.onBoundsChange); - this.scrollable.off('scroll', this.onScroll); - if (this.unsubscribe) { - this.unsubscribe(); - delete this.unsubscribe; - } - }; - - /** - * Responds to bound change event be requesting new - * historical data if the bound change was manual. - * @private - * @param {object} [newBounds] new bounds object - * @param {boolean} [tick] true when change is automatic - */ - ImageryController.prototype.onBoundsChange = function (newBounds, tick) { - if (this.domainObject && !tick) { - this.requestHistory(newBounds); - } - }; - - /** - * Updates displayable values to match those of the most - * recently received datum. - * @param {object} [datum] the datum - * @private - */ - ImageryController.prototype.updateValues = function (datum) { - if (this.isPaused) { - this.nextDatum = datum; - return; - } - this.time = this.timeFormat.format(datum); - this.imageUrl = this.imageFormat.format(datum); - - }; - - /** - * Appends given imagery datum to running history. - * @private - * @param {object} [datum] target telemetry datum - * @returns {boolean} falsy when a duplicate datum is given - */ - ImageryController.prototype.updateHistory = function (datum) { - if (!this.datumMatchesMostRecent(datum)) { - var index = _.sortedIndex(this.$scope.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat)); - this.$scope.imageHistory.splice(index, 0, datum); - return true; - } else { - return false; - } - }; - - /** - * Checks to see if the given datum is the same as the most recent in history. - * @private - * @param {object} [datum] target telemetry datum - * @returns {boolean} true if datum is most recent in history, false otherwise - */ - ImageryController.prototype.datumMatchesMostRecent = function (datum) { - if (this.$scope.imageHistory.length !== 0) { - var datumTime = this.timeFormat.format(datum); - var datumURL = this.imageFormat.format(datum); - var lastHistoryTime = this.timeFormat.format(this.$scope.imageHistory.slice(-1)[0]); - var lastHistoryURL = this.imageFormat.format(this.$scope.imageHistory.slice(-1)[0]); - - return datumTime === lastHistoryTime && datumURL === lastHistoryURL; - } - return false; - }; - - ImageryController.prototype.onScroll = function (event) { - this.$window.requestAnimationFrame(function () { - var thumbnailWrapperHeight = this.scrollable[0].offsetHeight; - var thumbnailWrapperWidth = this.scrollable[0].offsetWidth; - if (this.scrollable[0].scrollLeft < - (this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - (thumbnailWrapperWidth) || - this.scrollable[0].scrollTop < - (this.scrollable[0].scrollHeight - this.scrollable[0].clientHeight) - (thumbnailWrapperHeight)) { - this.autoScroll = false; - } else { - this.autoScroll = true; - } - }.bind(this)); - }; - - /** - * Force history imagery div to scroll to bottom. - */ - ImageryController.prototype.scrollToBottom = function () { - if (this.autoScroll) { - this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight; - } - }; - - - /** - * Get the time portion (hours, minutes, seconds) of the - * timestamp associated with the incoming image telemetry - * if no parameter is given, or of a provided datum. - * @param {object} [datum] target telemetry datum - * @returns {string} the time - */ - ImageryController.prototype.getTime = function (datum) { - return datum ? - this.timeFormat.format(datum) : - this.time; - }; - - /** - * Get the URL of the most recent image telemetry if no - * parameter is given, or of a provided datum. - * @param {object} [datum] target telemetry datum - * @returns {string} URL for telemetry image - */ - ImageryController.prototype.getImageUrl = function (datum) { - return datum ? - this.imageFormat.format(datum) : - this.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 - */ - ImageryController.prototype.paused = function (state) { - if (arguments.length > 0 && state !== this.isPaused) { - this.unselectAllImages(); - this.isPaused = state; - if (this.nextDatum) { - this.updateValues(this.nextDatum); - delete this.nextDatum; - } else { - this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]); - } - this.autoScroll = true; - } - return this.isPaused; - }; - - /** - * Set the selected image on the state for the large imagery div to use. - * @param {object} [image] the image object to get url from. - */ - ImageryController.prototype.setSelectedImage = function (image) { - this.imageUrl = this.getImageUrl(image); - this.time = this.getTime(image); - this.paused(true); - this.unselectAllImages(); - image.selected = true; - }; - - /** - * Loop through the history imagery data to set all images to unselected. - */ - ImageryController.prototype.unselectAllImages = function () { - for (var i = 0; i < this.$scope.imageHistory.length; i++) { - this.$scope.imageHistory[i].selected = false; - } - }; - return ImageryController; - } -); diff --git a/platform/features/imagery/src/directives/MCTBackgroundImage.js b/platform/features/imagery/src/directives/MCTBackgroundImage.js deleted file mode 100644 index ddae082fa0..0000000000 --- a/platform/features/imagery/src/directives/MCTBackgroundImage.js +++ /dev/null @@ -1,110 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * 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 the value of `mct-background-image`is falsy, no image - * will be displayed (immediately.) - * - * Optionally, a `filters` attribute may be specified as an - * object with `brightness` and/or `contrast` properties, - * whose values are percentages. A value of 100 will make - * no changes to the image's brightness or contrast. - * - * @constructor - * @memberof platform/features/imagery - */ - function MCTBackgroundImage($document) { - function link(scope, element) { - // 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 requested = 0, loaded = 0; - - function updateFilters(filters) { - var styleValue = filters ? - Object.keys(filters).map(function (k) { - return k + "(" + filters[k] + "%)"; - }).join(' ') : - ""; - element.css('filter', styleValue); - element.css('webkitFilter', styleValue); - } - - 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', 'none'); - } else { - image = $document[0].createElement('img'); - image.src = url; - image.onload = useImage; - } - - requested += 1; - } - - scope.$watch('mctBackgroundImage', nextImage); - scope.$watchCollection('filters', updateFilters); - } - - return { - restrict: "A", - scope: { - mctBackgroundImage: "=", - filters: "=" - }, - link: link - }; - } - - return MCTBackgroundImage; - } -); - diff --git a/platform/features/imagery/src/policies/ImageryViewPolicy.js b/platform/features/imagery/src/policies/ImageryViewPolicy.js deleted file mode 100644 index 43421e4da4..0000000000 --- a/platform/features/imagery/src/policies/ImageryViewPolicy.js +++ /dev/null @@ -1,59 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../../../../src/api/objects/object-utils' -], function ( - objectUtils -) { - /** - * Policy preventing the Imagery view from being made available for - * domain objects which do not have associated image telemetry. - * @implements {Policy.} - * @constructor - */ - function ImageryViewPolicy(openmct) { - this.openmct = openmct; - } - - ImageryViewPolicy.prototype.hasImageTelemetry = function (domainObject) { - var newDO = objectUtils.toNewFormat( - domainObject.getModel(), - domainObject.getId() - ); - - var metadata = this.openmct.telemetry.getMetadata(newDO); - var values = metadata.valuesForHints(['image']); - return values.length >= 1; - }; - - ImageryViewPolicy.prototype.allow = function (view, domainObject) { - if (view.key === 'imagery' || view.key === 'historical-imagery') { - return this.hasImageTelemetry(domainObject); - } - - return true; - }; - - return ImageryViewPolicy; -}); - diff --git a/platform/features/imagery/test/controllers/ImageryControllerSpec.js b/platform/features/imagery/test/controllers/ImageryControllerSpec.js deleted file mode 100644 index 7edc1c6710..0000000000 --- a/platform/features/imagery/test/controllers/ImageryControllerSpec.js +++ /dev/null @@ -1,271 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - "zepto", - "../../src/controllers/ImageryController" - ], - function ($, ImageryController) { - - var MOCK_ELEMENT_TEMPLATE = - '
'; - - xdescribe("The Imagery controller", function () { - var $scope, - openmct, - oldDomainObject, - newDomainObject, - unsubscribe, - metadata, - prefix, - controller, - requestPromise, - mockWindow, - mockElement; - - beforeEach(function () { - $scope = jasmine.createSpyObj('$scope', ['$on', '$watch']); - oldDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId'] - ); - newDomainObject = { name: 'foo' }; - oldDomainObject.getId.and.returnValue('testID'); - openmct = { - objects: jasmine.createSpyObj('objectAPI', [ - 'get' - ]), - time: jasmine.createSpyObj('timeAPI', [ - 'timeSystem', - 'clock', - 'on', - 'off', - 'bounds' - ]), - telemetry: jasmine.createSpyObj('telemetryAPI', [ - 'subscribe', - 'request', - 'getValueFormatter', - 'getMetadata' - ]) - }; - metadata = jasmine.createSpyObj('metadata', [ - 'value', - 'valuesForHints' - ]); - metadata.value.and.returnValue("timestamp"); - metadata.valuesForHints.and.returnValue(["value"]); - - prefix = "formatted "; - unsubscribe = jasmine.createSpy('unsubscribe'); - openmct.telemetry.subscribe.and.returnValue(unsubscribe); - openmct.time.timeSystem.and.returnValue({ - key: 'testKey' - }); - $scope.domainObject = oldDomainObject; - openmct.objects.get.and.returnValue(Promise.resolve(newDomainObject)); - openmct.telemetry.getMetadata.and.returnValue(metadata); - openmct.telemetry.getValueFormatter.and.callFake(function (property) { - var formatter = - jasmine.createSpyObj("formatter-" + property, ['format']); - var isTime = (property === "timestamp"); - formatter.format.and.callFake(function (datum) { - return (isTime ? prefix : "") + datum[property]; - }); - return formatter; - }); - - requestPromise = new Promise(function (resolve) { - setTimeout(function () { - resolve([{ - timestamp: 1434600258123, - value: 'some/url' - }]); - }, 10); - }); - - openmct.telemetry.request.and.returnValue(requestPromise); - mockElement = $(MOCK_ELEMENT_TEMPLATE); - mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']); - mockWindow.requestAnimationFrame.and.callFake(function (f) { - return f(); - }); - - controller = new ImageryController( - $scope, - mockWindow, - mockElement, - openmct - ); - }); - - describe("when loaded", function () { - var callback, - boundsListener, - bounds; - - beforeEach(function () { - return requestPromise.then(function () { - openmct.time.on.calls.all().forEach(function (call) { - if (call.args[0] === "bounds") { - boundsListener = call.args[1]; - } - }); - callback = - openmct.telemetry.subscribe.calls.mostRecent().args[1]; - }); - }); - - it("requests history", function () { - expect(openmct.telemetry.request).toHaveBeenCalledWith( - newDomainObject, bounds - ); - expect(controller.getTime()).toEqual(prefix + 1434600258123); - expect(controller.getImageUrl()).toEqual('some/url'); - }); - - - it("exposes the latest telemetry values", function () { - callback({ - timestamp: 1434600259456, - value: "some/other/url" - }); - - expect(controller.getTime()).toEqual(prefix + 1434600259456); - expect(controller.getImageUrl()).toEqual("some/other/url"); - }); - - it("allows updates to be paused and unpaused", function () { - var newTimestamp = 1434600259456, - newUrl = "some/other/url", - initialTimestamp = controller.getTime(), - initialUrl = controller.getImageUrl(); - - expect(initialTimestamp).not.toBe(prefix + newTimestamp); - expect(initialUrl).not.toBe(newUrl); - expect(controller.paused()).toBeFalsy(); - - controller.paused(true); - expect(controller.paused()).toBeTruthy(); - callback({ timestamp: newTimestamp, value: newUrl }); - - expect(controller.getTime()).toEqual(initialTimestamp); - expect(controller.getImageUrl()).toEqual(initialUrl); - - controller.paused(false); - expect(controller.paused()).toBeFalsy(); - expect(controller.getTime()).toEqual(prefix + newTimestamp); - expect(controller.getImageUrl()).toEqual(newUrl); - }); - - it("forwards large image view to latest image in history on un-pause", function () { - $scope.imageHistory = [ - { utc: 1434600258122, url: 'some/url1', selected: false}, - { utc: 1434600258123, url: 'some/url2', selected: false} - ]; - controller.paused(true); - controller.paused(false); - - expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1])); - }); - - it("subscribes to telemetry", function () { - expect(openmct.telemetry.subscribe).toHaveBeenCalledWith( - newDomainObject, - jasmine.any(Function) - ); - }); - - it("requests telemetry", function () { - expect(openmct.telemetry.request).toHaveBeenCalledWith( - newDomainObject, - bounds - ); - }); - - it("unsubscribes and unlistens when scope is destroyed", function () { - expect(unsubscribe).not.toHaveBeenCalled(); - - $scope.$on.calls.all().forEach(function (call) { - if (call.args[0] === '$destroy') { - call.args[1](); - } - }); - expect(unsubscribe).toHaveBeenCalled(); - expect(openmct.time.off) - .toHaveBeenCalledWith('bounds', jasmine.any(Function)); - }); - - it("listens for bounds event and responds to tick and manual change", function () { - var mockBounds = {start: 1434600000000, end: 1434600500000}; - expect(openmct.time.on).toHaveBeenCalled(); - openmct.telemetry.request.calls.reset(); - boundsListener(mockBounds, true); - expect(openmct.telemetry.request).not.toHaveBeenCalled(); - boundsListener(mockBounds, false); - expect(openmct.telemetry.request).toHaveBeenCalledWith(newDomainObject, mockBounds); - }); - - it ("doesnt append duplicate datum", function () { - var mockDatum = {value: 'image/url', timestamp: 1434700000000}; - var mockDatum2 = {value: 'image/url', timestamp: 1434700000000}; - var mockDatum3 = {value: 'image/url', url: 'someval', timestamp: 1434700000000}; - expect(controller.updateHistory(mockDatum)).toBe(true); - expect(controller.updateHistory(mockDatum)).toBe(false); - expect(controller.updateHistory(mockDatum)).toBe(false); - expect(controller.updateHistory(mockDatum2)).toBe(false); - expect(controller.updateHistory(mockDatum3)).toBe(false); - }); - - describe("when user clicks on imagery thumbnail", function () { - var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false}; - - it("pauses and adds selected class to imagery thumbnail", function () { - controller.setSelectedImage(mockDatum); - expect(controller.paused()).toBeTruthy(); - expect(mockDatum.selected).toBeTruthy(); - }); - - it("unselects previously selected image", function () { - $scope.imageHistory = [{ utc: 1434600258123, url: 'some/url', selected: true}]; - controller.unselectAllImages(); - expect($scope.imageHistory[0].selected).toBeFalsy(); - }); - - it("updates larger image url and time", function () { - controller.setSelectedImage(mockDatum); - expect(controller.getImageUrl()).toEqual(controller.getImageUrl(mockDatum)); - expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc)); - }); - }); - - }); - - it("initially shows an empty string for date/time", function () { - expect(controller.getTime()).toEqual(""); - expect(controller.getImageUrl()).toEqual(""); - }); - }); - } -); - diff --git a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js b/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js deleted file mode 100644 index dd6182be54..0000000000 --- a/platform/features/imagery/test/directives/MCTBackgroundImageSpec.js +++ /dev/null @@ -1,126 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTBackgroundImage"], - function (MCTBackgroundImage) { - - 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', - '$watchCollection' - ]); - mockElement = jasmine.createSpyObj('element', ['css']); - testImage = {}; - - mockDocument[0].createElement.and.returnValue(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("="); - }); - - describe("once linked", function () { - beforeEach(function () { - directive.link(mockScope, mockElement, {}); - }); - - it("watches for changes to the URL", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - 'mctBackgroundImage', - jasmine.any(Function) - ); - }); - - it("updates images in-order, even when they load out-of-order", function () { - var firstOnload; - - mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); - firstOnload = testImage.onload; - - mockScope.$watch.calls.mostRecent().args[1]("some/url/1"); - - // Resolve in a different order - testImage.onload(); - firstOnload(); - - // Should still have taken the more recent value - expect(mockElement.css.calls.mostRecent().args).toEqual([ - "background-image", - "url('some/url/1')" - ]); - }); - - it("clears the background image when undefined is passed in", function () { - mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); - testImage.onload(); - mockScope.$watch.calls.mostRecent().args[1](undefined); - - expect(mockElement.css.calls.mostRecent().args).toEqual([ - "background-image", - "none" - ]); - }); - - it("updates filters on change", function () { - var filters = { brightness: 123, contrast: 21 }; - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === 'filters') { - call.args[1](filters); - } - }); - expect(mockElement.css).toHaveBeenCalledWith( - 'filter', - 'brightness(123%) contrast(21%)' - ); - }); - - it("clears filters when none are present", function () { - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === 'filters') { - call.args[1](undefined); - } - }); - expect(mockElement.css) - .toHaveBeenCalledWith('filter', ''); - }); - }); - }); - } -); - diff --git a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js b/platform/features/imagery/test/policies/ImageryViewPolicySpec.js deleted file mode 100644 index de61a91100..0000000000 --- a/platform/features/imagery/test/policies/ImageryViewPolicySpec.js +++ /dev/null @@ -1,84 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2018, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/policies/ImageryViewPolicy"], - function (ImageryViewPolicy) { - - describe("Imagery view policy", function () { - var testView, - openmct, - mockDomainObject, - mockTelemetry, - mockMetadata, - policy; - - beforeEach(function () { - testView = { key: "imagery" }; - mockMetadata = jasmine.createSpyObj('metadata', [ - "valuesForHints" - ]); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getModel', 'getCapability'] - ); - mockTelemetry = jasmine.createSpyObj( - 'telemetry', - ['getMetadata'] - ); - mockDomainObject.getCapability.and.callFake(function (c) { - return c === 'telemetry' ? mockTelemetry : undefined; - }); - mockDomainObject.getId.and.returnValue("some-id"); - mockDomainObject.getModel.and.returnValue({ name: "foo" }); - mockTelemetry.getMetadata.and.returnValue(mockMetadata); - mockMetadata.valuesForHints.and.returnValue(["bar"]); - - openmct = { telemetry: mockTelemetry }; - - policy = new ImageryViewPolicy(openmct); - }); - - it("checks for hints indicating image telemetry", function () { - policy.allow(testView, mockDomainObject); - expect(mockMetadata.valuesForHints) - .toHaveBeenCalledWith(["image"]); - }); - - it("allows the imagery view for domain objects with image telemetry", function () { - expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); - }); - - it("disallows the imagery view for domain objects without image telemetry", function () { - mockMetadata.valuesForHints.and.returnValue([]); - expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); - }); - - it("allows other views", function () { - testView.key = "somethingElse"; - expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); - }); - - }); - } -); - diff --git a/src/MCT.js b/src/MCT.js index 3337afb29b..c07dd39840 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -33,6 +33,7 @@ define([ './adapter/indicators/legacy-indicators-plugin', './plugins/buildInfo/plugin', './ui/registries/ViewRegistry', + './plugins/imagery/plugin', './ui/registries/InspectorViewRegistry', './ui/registries/ToolbarRegistry', './ui/router/ApplicationRouter', @@ -59,6 +60,7 @@ define([ LegacyIndicatorsPlugin, buildInfoPlugin, ViewRegistry, + ImageryPlugin, InspectorViewRegistry, ToolbarRegistry, ApplicationRouter, @@ -257,6 +259,7 @@ define([ this.install(RemoveActionPlugin.default()); this.install(this.plugins.FolderView()); this.install(this.plugins.Tabs()); + this.install(ImageryPlugin.default()); this.install(this.plugins.FlexibleLayout()); this.install(this.plugins.GoToOriginalAction()); this.install(this.plugins.ImportExport()); diff --git a/src/installDefaultBundles.js b/src/installDefaultBundles.js index 6f0e07f02b..a2732cfe1f 100644 --- a/src/installDefaultBundles.js +++ b/src/installDefaultBundles.js @@ -38,7 +38,6 @@ const DEFAULTS = [ 'platform/exporters', 'platform/telemetry', 'platform/features/clock', - 'platform/features/imagery', 'platform/features/hyperlink', 'platform/features/timeline', 'platform/forms', @@ -82,7 +81,6 @@ define([ '../platform/execution/bundle', '../platform/exporters/bundle', '../platform/features/clock/bundle', - '../platform/features/imagery/bundle', '../platform/features/my-items/bundle', '../platform/features/hyperlink/bundle', '../platform/features/static-markup/bundle', diff --git a/src/plugins/imagery/ImageryViewProvider.js b/src/plugins/imagery/ImageryViewProvider.js new file mode 100644 index 0000000000..5697030320 --- /dev/null +++ b/src/plugins/imagery/ImageryViewProvider.js @@ -0,0 +1,47 @@ +import ImageryViewLayout from './components/ImageryViewLayout.vue'; +import Vue from 'vue'; + +export default function ImageryViewProvider(openmct) { + const type = 'example.imagery'; + + const hasImageTelemetry = function (domainObject) { + const metadata = openmct.telemetry.getMetadata(domainObject); + if (!metadata) { + return false; + } + + return metadata.valuesForHints(['image']).length > 0; + }; + + return { + key: type, + name: 'Imagery Layout', + cssClass: 'icon-image', + canView: function (domainObject) { + return hasImageTelemetry(domainObject); + }, + view: function (domainObject) { + let component; + + return { + show: function (element) { + component = new Vue({ + components: { + ImageryViewLayout + }, + provide: { + openmct, + domainObject + }, + el: element, + template: '' + }); + }, + destroy: function () { + component.$destroy(); + component = undefined; + } + }; + } + } +} diff --git a/src/plugins/imagery/components/ImageryViewLayout.vue b/src/plugins/imagery/components/ImageryViewLayout.vue new file mode 100644 index 0000000000..9a08fb91e9 --- /dev/null +++ b/src/plugins/imagery/components/ImageryViewLayout.vue @@ -0,0 +1,249 @@ + + + diff --git a/src/plugins/imagery/components/imagery-view-layout.scss b/src/plugins/imagery/components/imagery-view-layout.scss new file mode 100644 index 0000000000..0a86127853 --- /dev/null +++ b/src/plugins/imagery/components/imagery-view-layout.scss @@ -0,0 +1,32 @@ +.c-imagery-layout { + display: flex; + flex-direction: column; + overflow: auto; + + .main-image-wrapper { + display: flex; + flex-direction: column; + height: 100%; + padding-bottom: 5px; + } + + .main-image { + background-position: center; + background-repeat: no-repeat; + background-size: contain; + height: 100%; + + &.unnsynced{ + @include sUnsynced(); + } + } + + .l-image-controller { + padding: 5px 0 0 0; + } + + .thumbs-layout { + margin-top: 5px; + overflow: auto; + } +} diff --git a/src/plugins/imagery/plugin.js b/src/plugins/imagery/plugin.js new file mode 100644 index 0000000000..fdbaeba18c --- /dev/null +++ b/src/plugins/imagery/plugin.js @@ -0,0 +1,8 @@ +import ImageryViewProvider from './ImageryViewProvider'; + +export default function () { + return function install(openmct) { + openmct.objectViews.addProvider(new ImageryViewProvider(openmct)); + }; +} + diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index e831676939..26a0b7a34c 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -28,6 +28,7 @@ define([ './autoflow/AutoflowTabularPlugin', './timeConductor/plugin', '../../example/imagery/plugin', + './imagery/plugin', '../../platform/import-export/bundle', './summaryWidget/plugin', './URLIndicatorPlugin/URLIndicatorPlugin', @@ -57,6 +58,7 @@ define([ AutoflowPlugin, TimeConductorPlugin, ExampleImagery, + ImageryPlugin, ImportExport, SummaryWidget, URLIndicatorPlugin, @@ -162,6 +164,7 @@ define([ }; plugins.ExampleImagery = ExampleImagery; + plugins.ImageryPlugin = ImageryPlugin; plugins.Plot = PlotPlugin; plugins.TelemetryTable = TelemetryTablePlugin; diff --git a/src/styles/vue-styles.scss b/src/styles/vue-styles.scss index 0a29abc358..4a9611bd32 100644 --- a/src/styles/vue-styles.scss +++ b/src/styles/vue-styles.scss @@ -14,6 +14,7 @@ @import "../plugins/folderView/components/grid-view.scss"; @import "../plugins/folderView/components/list-item.scss"; @import "../plugins/folderView/components/list-view.scss"; +@import "../plugins/imagery/components/imagery-view-layout.scss"; @import "../plugins/telemetryTable/components/table-row.scss"; @import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss"; @import "../plugins/tabs/components/tabs.scss"; diff --git a/webpack.config.js b/webpack.config.js index c935beafb1..8013a20ded 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,6 +31,7 @@ const webpackConfig = { }, resolve: { alias: { + "@": path.join(__dirname, "src"), "legacyRegistry": path.join(__dirname, "src/legacyRegistry"), "saveAs": "file-saver", "csv": "comma-separated-values",