From c0311be1921c39578757977020077410317fa8b4 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 20 Jun 2016 15:49:04 -0700 Subject: [PATCH 1/5] [Browse] Inspector shown when object switched to edit mode. Fixes #1031 --- platform/commonUI/browse/bundle.js | 13 +++ .../commonUI/browse/res/templates/browse.html | 2 +- .../browse/src/InspectorPaneController.js | 79 +++++++++++++++ .../test/InspectorPaneControllerSpec.js | 96 +++++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 platform/commonUI/browse/src/InspectorPaneController.js create mode 100644 platform/commonUI/browse/test/InspectorPaneControllerSpec.js diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js index 9fb7015456..8f62473b54 100644 --- a/platform/commonUI/browse/bundle.js +++ b/platform/commonUI/browse/bundle.js @@ -23,6 +23,7 @@ define([ "./src/BrowseController", "./src/PaneController", + "./src/InspectorPaneController", "./src/BrowseObjectController", "./src/MenuArrowController", "./src/navigation/NavigationService", @@ -44,6 +45,7 @@ define([ ], function ( BrowseController, PaneController, + InspectorPaneController, BrowseObjectController, MenuArrowController, NavigationService, @@ -124,6 +126,17 @@ define([ "depends": [ "$scope" ] + }, + { + "key": "InspectorPaneController", + "implementation": InspectorPaneController, + "priority": "preferred", + "depends": [ + "$scope", + "agentService", + "$window", + "navigationService" + ] } ], "representations": [ diff --git a/platform/commonUI/browse/res/templates/browse.html b/platform/commonUI/browse/res/templates/browse.html index 75fcaaeb0a..b5e9561b15 100644 --- a/platform/commonUI/browse/res/templates/browse.html +++ b/platform/commonUI/browse/res/templates/browse.html @@ -57,7 +57,7 @@ ng-class="{ collapsed : !modelPaneTree.visible() }">
diff --git a/platform/commonUI/browse/src/InspectorPaneController.js b/platform/commonUI/browse/src/InspectorPaneController.js new file mode 100644 index 0000000000..ef8e3883f7 --- /dev/null +++ b/platform/commonUI/browse/src/InspectorPaneController.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. + *****************************************************************************/ + + +define( + ["./PaneController"], + function (PaneController) { + + /** + * Pane controller that reveals inspector, if hidden, when object + * switches to edit mode. + * + * @param $scope + * @param agentService + * @param $window + * @param navigationService + * @constructor + */ + function InspectorPaneController($scope, agentService, $window, navigationService) { + PaneController.call(this, $scope, agentService, $window); + + var statusListener, + self = this; + + function showInspector(statuses) { + if (statuses.indexOf('editing') !== -1 && !self.visible()) { + self.toggle(); + } + } + + function attachStatusListener(domainObject) { + // Remove existing status listener if existing + if (statusListener) { + statusListener(); + } + + if (domainObject.hasCapability("status")) { + statusListener = domainObject.getCapability("status").listen(showInspector); + } + return statusListener; + } + + var domainObject = navigationService.getNavigation(); + if (domainObject) { + attachStatusListener(domainObject); + } + + var navigationListener = navigationService.addListener(attachStatusListener); + + $scope.$on("$destroy", function () { + statusListener(); + navigationListener(); + }); + } + + InspectorPaneController.prototype = Object.create(PaneController.prototype); + + return InspectorPaneController; + } +); diff --git a/platform/commonUI/browse/test/InspectorPaneControllerSpec.js b/platform/commonUI/browse/test/InspectorPaneControllerSpec.js new file mode 100644 index 0000000000..635396902b --- /dev/null +++ b/platform/commonUI/browse/test/InspectorPaneControllerSpec.js @@ -0,0 +1,96 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +define( + ["../src/InspectorPaneController"], + function (InspectorPaneController) { + + describe("The InspectorPaneController", function () { + var mockScope, + mockAgentService, + mockDomainObject, + mockWindow, + mockStatusCapability, + mockNavigationService, + mockNavigationUnlistener, + mockStatusUnlistener, + controller; + + beforeEach(function () { + mockScope = jasmine.createSpyObj("$scope", ["$on"]); + mockWindow = jasmine.createSpyObj("$window", ["open"]); + mockAgentService = jasmine.createSpyObj( + "agentService", + ["isMobile", "isPhone", "isTablet", "isPortrait", "isLandscape"] + ); + + mockNavigationUnlistener = jasmine.createSpy("navigationUnlistener"); + mockNavigationService = jasmine.createSpyObj( + "navigationService", + ["getNavigation", "addListener"] + ); + mockNavigationService.addListener.andReturn(mockNavigationUnlistener); + + mockStatusUnlistener = jasmine.createSpy("statusUnlistener"); + mockStatusCapability = jasmine.createSpyObj( + "statusCapability", + ["listen"] + ); + mockStatusCapability.listen.andReturn(mockStatusUnlistener); + + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + [ + 'getId', + 'getModel', + 'getCapability', + 'hasCapability' + ] + ); + mockDomainObject.getId.andReturn("domainObject"); + mockDomainObject.getModel.andReturn({}); + mockDomainObject.hasCapability.andReturn(true); + mockDomainObject.getCapability.andReturn(mockStatusCapability); + + controller = new InspectorPaneController(mockScope, mockAgentService, mockWindow, mockNavigationService); + }); + + it("listens for changes to navigation and attaches a status" + + " listener", function () { + expect(mockNavigationService.addListener).toHaveBeenCalledWith(jasmine.any(Function)); + mockNavigationService.addListener.mostRecentCall.args[0](mockDomainObject); + expect(mockStatusCapability.listen).toHaveBeenCalledWith(jasmine.any(Function)); + }); + + it("if hidden, shows the inspector when domain object switches to" + + " edit mode", function () { + controller.toggle(); + // test pre-condition that inspector is hidden + expect(controller.visible()).toBe(false); + mockNavigationService.addListener.mostRecentCall.args[0](mockDomainObject); + mockStatusCapability.listen.mostRecentCall.args[0](["editing"]); + expect(controller.visible()).toBe(true); + }); + + }); + } +); From 51079b0252deca43d0e40e54fe24e7819990deca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Tue, 21 Jun 2016 19:43:09 +0200 Subject: [PATCH 2/5] [API] X-Powered-By" Express header disabled. Fixes #1036 To improve the security avoiding a possible fingerprinting attack Ref.: http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header --- app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.js b/app.js index 8e7bb15ec2..4b7e28906b 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,8 @@ process.exit(0); } + app.disable('x-powered-by'); + // Override bundles.json for HTTP requests app.use('/' + BUNDLE_FILE, function (req, res) { var bundles; From ea1780364b9ff8aeadadbddb4e46d9f0a3fab74d Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Jun 2016 20:09:58 -0700 Subject: [PATCH 3/5] [Dialog Service] Dismiss individual dialogs. Fixes #254 --- .../src/DialogLaunchController.js | 126 ++++++++++-------- platform/commonUI/dialog/src/DialogService.js | 67 +++++----- .../commonUI/dialog/test/DialogServiceSpec.js | 41 +++++- .../edit/src/actions/SaveInProgressDialog.js | 7 +- .../edit/test/actions/SaveActionSpec.js | 33 +++-- .../edit/test/actions/SaveAsActionSpec.js | 33 +++-- .../src/controllers/BannerController.js | 8 +- .../src/NotificationIndicatorController.js | 3 - .../NotificationIndicatorControllerSpec.js | 15 --- .../entanglement/src/actions/CopyAction.js | 13 +- .../test/actions/CopyActionSpec.js | 6 +- 11 files changed, 209 insertions(+), 143 deletions(-) diff --git a/example/notifications/src/DialogLaunchController.js b/example/notifications/src/DialogLaunchController.js index f35d008cd0..e7f867d303 100644 --- a/example/notifications/src/DialogLaunchController.js +++ b/example/notifications/src/DialogLaunchController.js @@ -44,30 +44,31 @@ define( periodically with the progress of an ongoing process. */ $scope.launchProgress = function (knownProgress) { - var model = { - title: "Progress Dialog Example", - progress: 0, - hint: "Do not navigate away from this page or close this browser tab while this operation is in progress.", - actionText: "Calculating...", - unknownProgress: !knownProgress, - unknownDuration: false, - severity: "info", - options: [ - { - label: "Cancel Operation", - callback: function () { - $log.debug("Operation cancelled"); - dialogService.dismiss(); + var dialog, + model = { + title: "Progress Dialog Example", + progress: 0, + hint: "Do not navigate away from this page or close this browser tab while this operation is in progress.", + actionText: "Calculating...", + unknownProgress: !knownProgress, + unknownDuration: false, + severity: "info", + options: [ + { + label: "Cancel Operation", + callback: function () { + $log.debug("Operation cancelled"); + dialog.dismiss(); + } + }, + { + label: "Do something else...", + callback: function () { + $log.debug("Something else pressed"); + } } - }, - { - label: "Do something else...", - callback: function () { - $log.debug("Something else pressed"); - } - } - ] - }; + ] + }; function incrementProgress() { model.progress = Math.min(100, Math.floor(model.progress + Math.random() * 30)); @@ -77,7 +78,9 @@ define( } } - if (dialogService.showBlockingMessage(model)) { + dialog = dialogService.showBlockingMessage(model); + + if (dialog) { //Do processing here model.actionText = "Processing 100 objects..."; if (knownProgress) { @@ -93,29 +96,31 @@ define( Demonstrates launching an error dialog */ $scope.launchError = function () { - var model = { - title: "Error Dialog Example", - actionText: "Something happened, and it was not good.", - severity: "error", - options: [ - { - label: "Try Again", - callback: function () { - $log.debug("Try Again Pressed"); - dialogService.dismiss(); + var dialog, + model = { + title: "Error Dialog Example", + actionText: "Something happened, and it was not good.", + severity: "error", + options: [ + { + label: "Try Again", + callback: function () { + $log.debug("Try Again Pressed"); + dialog.dismiss(); + } + }, + { + label: "Cancel", + callback: function () { + $log.debug("Cancel Pressed"); + dialog.dismiss(); + } } - }, - { - label: "Cancel", - callback: function () { - $log.debug("Cancel Pressed"); - dialogService.dismiss(); - } - } - ] - }; + ] + }; + dialog = dialogService.showBlockingMessage(model); - if (!dialogService.showBlockingMessage(model)) { + if (!dialog) { $log.error("Could not display modal dialog"); } }; @@ -124,22 +129,25 @@ define( Demonstrates launching an error dialog */ $scope.launchInfo = function () { - var model = { - title: "Info Dialog Example", - actionText: "This is an example of a blocking info" + - " dialog. This dialog can be used to draw the user's" + - " attention to an event.", - severity: "info", - primaryOption: { - label: "OK", - callback: function () { - $log.debug("OK Pressed"); - dialogService.dismiss(); + var dialog, + model = { + title: "Info Dialog Example", + actionText: "This is an example of a blocking info" + + " dialog. This dialog can be used to draw the user's" + + " attention to an event.", + severity: "info", + primaryOption: { + label: "OK", + callback: function () { + $log.debug("OK Pressed"); + dialog.dismiss(); + } } - } - }; + }; - if (!dialogService.showBlockingMessage(model)) { + dialog = dialogService.showBlockingMessage(model); + + if (!dialog) { $log.error("Could not display modal dialog"); } }; diff --git a/platform/commonUI/dialog/src/DialogService.js b/platform/commonUI/dialog/src/DialogService.js index 778a172147..f3e888b6e0 100644 --- a/platform/commonUI/dialog/src/DialogService.js +++ b/platform/commonUI/dialog/src/DialogService.js @@ -39,25 +39,28 @@ define( this.overlayService = overlayService; this.$q = $q; this.$log = $log; - this.overlay = undefined; - this.dialogVisible = false; + this.activeOverlay = undefined; } - // Stop showing whatever overlay is currently active - // (e.g. because the user hit cancel) - DialogService.prototype.dismiss = function () { - var overlay = this.overlay; - if (overlay) { - overlay.dismiss(); + /** + * @private + */ + DialogService.prototype.dismissOverlay = function (overlay) { + //Dismiss the overlay + overlay.dismiss(); + + //If dialog is the current active one, dismiss it + if (overlay === this.activeOverlay) { + this.activeOverlay = undefined; } - this.dialogVisible = false; }; DialogService.prototype.getDialogResponse = function (key, model, resultGetter, typeClass) { // We will return this result as a promise, because user // input is asynchronous. var deferred = this.$q.defer(), - self = this; + self = this, + overlay; // Confirm function; this will be passed in to the // overlay-dialog template and associated with a @@ -65,9 +68,7 @@ define( function confirm(value) { // Pass along the result deferred.resolve(resultGetter ? resultGetter() : value); - - // Stop showing the dialog - self.dismiss(); + self.dismissOverlay(overlay); } // Cancel function; this will be passed in to the @@ -75,7 +76,7 @@ define( // Cancel or X button click function cancel() { deferred.reject(); - self.dismiss(); + self.dismissOverlay(overlay); } // Add confirm/cancel callbacks @@ -85,15 +86,11 @@ define( if (this.canShowDialog(model)) { // Add the overlay using the OverlayService, which // will handle actual insertion into the DOM - this.overlay = this.overlayService.createOverlay( + overlay = this.activeOverlay = this.overlayService.createOverlay( key, model, typeClass || "t-dialog" ); - - // Track that a dialog is already visible, to - // avoid spawning multiple dialogs at once. - this.dialogVisible = true; } else { deferred.reject(); } @@ -156,7 +153,7 @@ define( * otherwise */ DialogService.prototype.canShowDialog = function (dialogModel) { - if (this.dialogVisible) { + if (this.activeOverlay) { // Only one dialog should be shown at a time. // The application design should be such that // we never even try to do this. @@ -183,6 +180,11 @@ define( * button is clicked */ + /** + * @typedef DialogHandle + * @property {function} dismiss a function to dismiss the given dialog + */ + /** * A description of the model options that may be passed to the * showBlockingMessage method. Note that the DialogModel desribed @@ -222,21 +224,26 @@ define( * the user can take if necessary * @param {DialogModel} dialogModel defines options for the dialog * @param {typeClass} string tells overlayService that this overlay should use appropriate CSS class - * @returns {boolean} + * @returns {boolean | {DialogHandle}} */ DialogService.prototype.showBlockingMessage = function (dialogModel) { if (this.canShowDialog(dialogModel)) { // Add the overlay using the OverlayService, which // will handle actual insertion into the DOM - this.overlay = this.overlayService.createOverlay( - "overlay-blocking-message", - dialogModel, - "t-dialog-sm" - ); - // Track that a dialog is already visible, to - // avoid spawning multiple dialogs at once. - this.dialogVisible = true; - return true; + var self = this, + overlay = this.overlayService.createOverlay( + "overlay-blocking-message", + dialogModel, + "t-dialog-sm" + ); + + this.activeOverlay = overlay; + + return { + dismiss: function () { + self.dismissOverlay(overlay); + } + }; } else { return false; } diff --git a/platform/commonUI/dialog/test/DialogServiceSpec.js b/platform/commonUI/dialog/test/DialogServiceSpec.js index 2d801eb028..663f9fbeda 100644 --- a/platform/commonUI/dialog/test/DialogServiceSpec.js +++ b/platform/commonUI/dialog/test/DialogServiceSpec.js @@ -122,7 +122,7 @@ define( it("invokes the overlay service with the correct parameters when" + " a blocking dialog is requested", function () { var dialogModel = {}; - expect(dialogService.showBlockingMessage(dialogModel)).toBe(true); + expect(dialogService.showBlockingMessage(dialogModel)).not.toBe(false); expect(mockOverlayService.createOverlay).toHaveBeenCalledWith( "overlay-blocking-message", dialogModel, @@ -130,6 +130,45 @@ define( ); }); + describe("the blocking message dialog", function () { + var dialogModel = {}; + var dialogHandle; + + beforeEach(function () { + dialogHandle = dialogService.showBlockingMessage(dialogModel); + }); + + it("returns a handle to the dialog", function () { + expect(dialogHandle).not.toBe(undefined); + }); + + it("dismissing the dialog dismisses the overlay", function () { + dialogHandle.dismiss(); + expect(mockOverlay.dismiss).toHaveBeenCalled(); + }); + + it("individual dialogs can be dismissed", function () { + var secondDialogHandle, + secondMockOverlay; + + dialogHandle.dismiss(); + + secondMockOverlay = jasmine.createSpyObj( + "overlay", + ["dismiss"] + ); + mockOverlayService.createOverlay.andReturn(secondMockOverlay); + secondDialogHandle = dialogService.showBlockingMessage(dialogModel); + + //Dismiss the first dialog. It should only dismiss if it + // is active + dialogHandle.dismiss(); + expect(secondMockOverlay.dismiss).not.toHaveBeenCalled(); + secondDialogHandle.dismiss(); + expect(secondMockOverlay.dismiss).toHaveBeenCalled(); + }); + }); + }); } ); diff --git a/platform/commonUI/edit/src/actions/SaveInProgressDialog.js b/platform/commonUI/edit/src/actions/SaveInProgressDialog.js index c80989b8e0..56c2c1ad86 100644 --- a/platform/commonUI/edit/src/actions/SaveInProgressDialog.js +++ b/platform/commonUI/edit/src/actions/SaveInProgressDialog.js @@ -1,10 +1,11 @@ define([], function () { function SaveInProgressDialog(dialogService) { this.dialogService = dialogService; + this.dialog = undefined; } SaveInProgressDialog.prototype.show = function () { - this.dialogService.showBlockingMessage({ + this.dialog = this.dialogService.showBlockingMessage({ title: "Saving...", hint: "Do not navigate away from this page or close this browser tab while this message is displayed.", unknownProgress: true, @@ -13,7 +14,9 @@ define([], function () { }; SaveInProgressDialog.prototype.hide = function () { - this.dialogService.dismiss(); + if (this.dialog) { + this.dialog.dismiss(); + } }; return SaveInProgressDialog; diff --git a/platform/commonUI/edit/test/actions/SaveActionSpec.js b/platform/commonUI/edit/test/actions/SaveActionSpec.js index b81da3d7de..a24e3f4587 100644 --- a/platform/commonUI/edit/test/actions/SaveActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveActionSpec.js @@ -70,7 +70,7 @@ define( }; dialogService = jasmine.createSpyObj( "dialogService", - ["showBlockingMessage", "dismiss"] + ["showBlockingMessage"] ); mockDomainObject.hasCapability.andReturn(true); @@ -111,17 +111,28 @@ define( expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate"); }); - it("shows a dialog while saving", function () { - mockEditorCapability.save.andReturn(new Promise(function () {})); - action.perform(); - expect(dialogService.showBlockingMessage).toHaveBeenCalled(); - expect(dialogService.dismiss).not.toHaveBeenCalled(); - }); + describe("a blocking dialog", function () { + var mockDialogHandle; - it("hides a dialog when saving is complete", function () { - action.perform(); - expect(dialogService.showBlockingMessage).toHaveBeenCalled(); - expect(dialogService.dismiss).toHaveBeenCalled(); + beforeEach(function () { + mockDialogHandle = jasmine.createSpyObj("dialogHandle", ["dismiss"]); + dialogService.showBlockingMessage.andReturn(mockDialogHandle); + }); + + + it("shows a dialog while saving", function () { + mockEditorCapability.save.andReturn(new Promise(function () { + })); + action.perform(); + expect(dialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogHandle.dismiss).not.toHaveBeenCalled(); + }); + + it("hides a dialog when saving is complete", function () { + action.perform(); + expect(dialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogHandle.dismiss).toHaveBeenCalled(); + }); }); }); diff --git a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js index 37e35aa5af..b949f6b447 100644 --- a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js @@ -101,8 +101,7 @@ define( "dialogService", [ "getUserInput", - "showBlockingMessage", - "dismiss" + "showBlockingMessage" ] ); mockDialogService.getUserInput.andReturn(mockPromise(undefined)); @@ -171,17 +170,27 @@ define( expect(mockDialogService.getUserInput).toHaveBeenCalled(); }); - it("shows a blocking dialog while waiting for save", function () { - mockEditorCapability.save.andReturn(new Promise(function () {})); - action.perform(); - expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); - expect(mockDialogService.dismiss).not.toHaveBeenCalled(); - }); + describe("a blocking dialog", function () { + var mockDialogHandle; + + beforeEach(function () { + mockDialogHandle = jasmine.createSpyObj("dialogHandle", ["dismiss"]); + mockDialogService.showBlockingMessage.andReturn(mockDialogHandle); + }); + + it("indicates that a save is taking place", function () { + mockEditorCapability.save.andReturn(new Promise(function () {})); + action.perform(); + expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogHandle.dismiss).not.toHaveBeenCalled(); + }); + + it("is hidden after saving", function () { + action.perform(); + expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogHandle.dismiss).toHaveBeenCalled(); + }); - it("hides the blocking dialog after saving", function () { - action.perform(); - expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); - expect(mockDialogService.dismiss).toHaveBeenCalled(); }); }); diff --git a/platform/commonUI/general/src/controllers/BannerController.js b/platform/commonUI/general/src/controllers/BannerController.js index 5b0cdd61bc..098bacfa9f 100644 --- a/platform/commonUI/general/src/controllers/BannerController.js +++ b/platform/commonUI/general/src/controllers/BannerController.js @@ -54,17 +54,17 @@ define( }; $scope.maximize = function (notification) { if (notification.model.severity !== "info") { - + var dialog; notification.model.cancel = function () { - dialogService.dismiss(); + dialog.dismiss(); }; //If the notification is dismissed by the user, close // the dialog. notification.onDismiss(function () { - dialogService.dismiss(); + dialog.dismiss(); }); - dialogService.showBlockingMessage(notification.model); + dialog = dialogService.showBlockingMessage(notification.model); } }; } diff --git a/platform/commonUI/notification/src/NotificationIndicatorController.js b/platform/commonUI/notification/src/NotificationIndicatorController.js index 8a1bdbca2a..f5343df46d 100644 --- a/platform/commonUI/notification/src/NotificationIndicatorController.js +++ b/platform/commonUI/notification/src/NotificationIndicatorController.js @@ -49,9 +49,6 @@ define( //Launch the message list dialog with the models // from the notifications messages: notificationService.notifications - }, - cancel: function () { - dialogService.dismiss(); } }); diff --git a/platform/commonUI/notification/test/NotificationIndicatorControllerSpec.js b/platform/commonUI/notification/test/NotificationIndicatorControllerSpec.js index 222c2fc87b..adcc912b60 100644 --- a/platform/commonUI/notification/test/NotificationIndicatorControllerSpec.js +++ b/platform/commonUI/notification/test/NotificationIndicatorControllerSpec.js @@ -54,22 +54,7 @@ define( expect(mockDialogService.getDialogResponse).toHaveBeenCalled(); expect(mockDialogService.getDialogResponse.mostRecentCall.args[0]).toBe('overlay-message-list'); expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].dialog).toBeDefined(); - expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined(); - //Invoke the cancel callback - mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel(); - expect(mockDialogService.dismiss).toHaveBeenCalled(); }); - - it("provides a means of dismissing the message list", function () { - expect(mockScope.showNotificationsList).toBeDefined(); - mockScope.showNotificationsList(); - expect(mockDialogService.getDialogResponse).toHaveBeenCalled(); - expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined(); - //Invoke the cancel callback - mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel(); - expect(mockDialogService.dismiss).toHaveBeenCalled(); - }); - }); } ); diff --git a/platform/entanglement/src/actions/CopyAction.js b/platform/entanglement/src/actions/CopyAction.js index 0c5ab99755..ce40f3bbd2 100644 --- a/platform/entanglement/src/actions/CopyAction.js +++ b/platform/entanglement/src/actions/CopyAction.js @@ -86,7 +86,9 @@ define( severity: "info" }); } else if (phase.toLowerCase() === "copying") { - this.dialogService.dismiss(); + if (this.dialog) { + this.dialog.dismiss(); + } if (!this.notification) { this.notification = this.notificationService .notify({ @@ -115,7 +117,8 @@ define( } function error(errorDetails) { - var errorMessage = { + var errorDialog, + errorMessage = { title: "Error copying objects.", severity: "error", hint: errorDetails.message, @@ -123,12 +126,12 @@ define( options: [{ label: "OK", callback: function () { - self.dialogService.dismiss(); + errorDialog.dismiss(); } }] }; - self.dialogService.dismiss(); + self.dialog.dismiss(); if (self.notification) { self.notification.dismiss(); // Clear the progress notification } @@ -136,7 +139,7 @@ define( //Show a minimized notification of error for posterity self.notificationService.notify(errorMessage); //Display a blocking message - self.dialogService.showBlockingMessage(errorMessage); + errorDialog = self.dialogService.showBlockingMessage(errorMessage); } function notification(details) { diff --git a/platform/entanglement/test/actions/CopyActionSpec.js b/platform/entanglement/test/actions/CopyActionSpec.js index 176b5baef8..8d9b6397b6 100644 --- a/platform/entanglement/test/actions/CopyActionSpec.js +++ b/platform/entanglement/test/actions/CopyActionSpec.js @@ -44,6 +44,7 @@ define( notificationService, notification, dialogService, + mockDialog, mockLog, abstractComposePromise, progress = {phase: "copying", totalObjects: 10, processed: 1}; @@ -120,9 +121,12 @@ define( .andReturn(locationServicePromise); dialogService = jasmine.createSpyObj('dialogService', - ['showBlockingMessage', 'dismiss'] + ['showBlockingMessage'] ); + mockDialog = jasmine.createSpyObj("dialog", ["dismiss"]); + dialogService.showBlockingMessage.andReturn(mockDialog); + notification = jasmine.createSpyObj('notification', ['dismiss', 'model'] ); From 59e18b9a798cc47669b41aab243ad1bc75e7f9f4 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Jun 2016 14:44:39 -0700 Subject: [PATCH 4/5] [Search] Amended ClickAwayController to trigger digest via instead of . Fixes #1065 --- platform/commonUI/general/bundle.js | 2 +- .../src/controllers/ClickAwayController.js | 4 ++-- .../test/controllers/ClickAwayControllerSpec.js | 16 +++++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js index a7dccb0326..39f7631a55 100644 --- a/platform/commonUI/general/bundle.js +++ b/platform/commonUI/general/bundle.js @@ -259,7 +259,7 @@ define([ "implementation": ClickAwayController, "depends": [ "$document", - "$timeout" + "$scope" ] }, { diff --git a/platform/commonUI/general/src/controllers/ClickAwayController.js b/platform/commonUI/general/src/controllers/ClickAwayController.js index 18d9ca41c5..e8eee318e2 100644 --- a/platform/commonUI/general/src/controllers/ClickAwayController.js +++ b/platform/commonUI/general/src/controllers/ClickAwayController.js @@ -34,7 +34,7 @@ define( * @param $scope the scope in which this controller is active * @param $document the document element, injected by Angular */ - function ClickAwayController($document, $timeout) { + function ClickAwayController($document, $scope) { var self = this; this.state = false; @@ -44,7 +44,7 @@ define( // `clickaway` action occurs after `toggle` if `toggle` is // triggered by a click/mouseup. this.clickaway = function () { - $timeout(function () { + $scope.$apply(function () { self.deactivate(); }); }; diff --git a/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js b/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js index e2b9f62c91..bb9209014f 100644 --- a/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js +++ b/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js @@ -26,7 +26,7 @@ define( describe("The click-away controller", function () { var mockDocument, - mockTimeout, + mockScope, controller; beforeEach(function () { @@ -34,10 +34,11 @@ define( "$document", ["on", "off"] ); - mockTimeout = jasmine.createSpy('timeout'); + mockScope = jasmine.createSpyObj('$scope', ['$apply']); + controller = new ClickAwayController( mockDocument, - mockTimeout + mockScope ); }); @@ -77,18 +78,15 @@ define( }); it("deactivates and detaches listener on document click", function () { - var callback, timeout; + var callback, apply; controller.setState(true); callback = mockDocument.on.mostRecentCall.args[1]; callback(); - timeout = mockTimeout.mostRecentCall.args[0]; - timeout(); + apply = mockScope.$apply.mostRecentCall.args[0]; + apply(); expect(controller.isActive()).toEqual(false); expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback); }); - - - }); } ); From 307b6787070fca9c8a212c65cc6d2ba82fba23e3 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 14 Jul 2016 15:25:09 -0700 Subject: [PATCH 5/5] [Fixed] Create new object type based on Telemetry Panel 'Fixed' view. Fixes #1075 --- main.js | 1 + platform/features/fixed/bundle.js | 222 ++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 platform/features/fixed/bundle.js diff --git a/main.js b/main.js index 77b56d294b..268c70060d 100644 --- a/main.js +++ b/main.js @@ -77,6 +77,7 @@ define([ './platform/exporters/bundle', './platform/telemetry/bundle', './platform/features/clock/bundle', + './platform/features/fixed/bundle', './platform/features/imagery/bundle', './platform/features/layout/bundle', './platform/features/pages/bundle', diff --git a/platform/features/fixed/bundle.js b/platform/features/fixed/bundle.js new file mode 100644 index 0000000000..121501588b --- /dev/null +++ b/platform/features/fixed/bundle.js @@ -0,0 +1,222 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([ + "text!../layout/res/templates/fixed.html", + 'legacyRegistry' +], function ( + fixedTemplate, + legacyRegistry +) { + + legacyRegistry.register("platform/features/fixed", { + "name": "Fixed position components.", + "description": "Plug in adding Fixed Position object type.", + "extensions": { + "views": [ + { + "key": "fixed-display", + "name": "Fixed Position Display", + "glyph": "3", + "type": "telemetry.fixed", + "template": fixedTemplate, + "uses": [ + "composition" + ], + "editable": true, + "toolbar": { + "sections": [ + { + "items": [ + { + "method": "add", + "glyph": "\u002b", + "control": "menu-button", + "text": "Add", + "options": [ + { + "name": "Box", + "glyph": "\u00e0", + "key": "fixed.box" + }, + { + "name": "Line", + "glyph": "\u00e2", + "key": "fixed.line" + }, + { + "name": "Text", + "glyph": "\u00e4", + "key": "fixed.text" + }, + { + "name": "Image", + "glyph": "\u00e3", + "key": "fixed.image" + } + ] + } + ] + }, + { + "items": [ + { + "method": "order", + "glyph": "á", + "control": "menu-button", + "options": [ + { + "name": "Move to Top", + "glyph": "\u00eb", + "key": "top" + }, + { + "name": "Move Up", + "glyph": "\u005e", + "key": "up" + }, + { + "name": "Move Down", + "glyph": "\u0076", + "key": "down" + }, + { + "name": "Move to Bottom", + "glyph": "\u00ee", + "key": "bottom" + } + ] + }, + { + "property": "fill", + "glyph": "", + "control": "color" + }, + { + "property": "stroke", + "glyph": "â", + "control": "color" + }, + { + "property": "color", + "glyph": "ä", + "mandatory": true, + "control": "color" + }, + { + "property": "url", + "glyph": "ã", + "control": "dialog-button", + "title": "Image Properties", + "dialog": { + "control": "textfield", + "name": "Image URL", + "required": true + } + }, + { + "property": "text", + "glyph": "G", + "control": "dialog-button", + "title": "Text Properties", + "dialog": { + "control": "textfield", + "name": "Text", + "required": true + } + }, + { + "method": "showTitle", + "glyph": "ç", + "control": "button", + "description": "Show telemetry element title." + }, + { + "method": "hideTitle", + "glyph": "å", + "control": "button", + "description": "Hide telemetry element title." + } + ] + }, + { + "items": [ + { + "method": "remove", + "control": "button", + "glyph": "Z" + } + ] + } + ] + } + } + ], + "types": [ + { + "key": "telemetry.fixed", + "name": "Fixed Position Display", + "glyph": "3", + "description": "A panel for collecting telemetry" + + " elements in a fixed position display.", + "priority": 899, + "delegates": [ + "telemetry" + ], + "features": "creation", + "contains": [ + { + "has": "telemetry" + } + ], + "model": { + "composition": [] + }, + "properties": [ + { + "name": "Layout Grid", + "control": "composite", + "items": [ + { + "name": "Horizontal grid (px)", + "control": "textfield", + "cssclass": "l-small l-numeric" + }, + { + "name": "Vertical grid (px)", + "control": "textfield", + "cssclass": "l-small l-numeric" + } + ], + "pattern": "^(\\d*[1-9]\\d*)?$", + "property": "layoutGrid", + "conversion": "number[]" + } + ], + "views": [ + "fixed-display" + ] + } + ] + } + }); +});