Compare commits
	
		
			77 Commits
		
	
	
		
			fix-select
			...
			remove-dep
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 49ff0a648c | ||
|   | 138067dca9 | ||
|   | 844280eaa5 | ||
|   | d2e2d55caf | ||
|   | f01d4071a1 | ||
|   | 06524ce967 | ||
|   | 1ec529f360 | ||
|   | cf6458c69d | ||
|   | 3316500774 | ||
|   | 0f780587c0 | ||
|   | ea69508e22 | ||
|   | 4274d8cc0b | ||
|   | 1a2048332f | ||
|   | 38a875395f | ||
|   | f601ab03e7 | ||
|   | ee1d92d4a9 | ||
|   | 548286bacd | ||
|   | 9c9006d415 | ||
|   | 3219a64d09 | ||
|   | 570aa2c02a | ||
|   | c577d2e231 | ||
|   | 6bf4b3aba8 | ||
|   | b659f205f7 | ||
|   | 40d54df567 | ||
|   | b7fa5c7ba8 | ||
|   | 3b0605d17b | ||
|   | d93e7bfd1a | ||
|   | 104cd0ed29 | ||
|   | 7fb3d86d06 | ||
|   | dbb42e9bb6 | ||
|   | d1baa1f98b | ||
|   | 5ab68c0586 | ||
|   | 3cf78f509d | ||
|   | c6053e234a | ||
|   | 964c326535 | ||
|   | baf410a364 | ||
|   | 517a40a32b | ||
|   | 8b275b206b | ||
|   | a40a31aa4c | ||
|   | 6c0c1df010 | ||
|   | c552afff17 | ||
|   | 0837129ad5 | ||
|   | 6f3e2a8fbb | ||
|   | 4189a05758 | ||
|   | 97ccaa58c7 | ||
|   | 08ef932926 | ||
|   | 1d2ed0398c | ||
|   | 5a00e0c549 | ||
|   | ebcf47733f | ||
|   | 381d7e7615 | ||
|   | 8246b47668 | ||
|   | bc5e300ba9 | ||
|   | 57efef3160 | ||
|   | dfc5a9f040 | ||
|   | 57443d227d | ||
|   | d36441db73 | ||
|   | 327782835e | ||
|   | 994f6be535 | ||
|   | 72fc8a24a5 | ||
|   | 07002c12eb | ||
|   | c688d19e15 | ||
|   | c0ce448dc3 | ||
|   | 6c479d6d59 | ||
|   | 76ba487261 | ||
|   | e3f4da19f9 | ||
|   | c7ffcbf7e0 | ||
|   | a27b3737f1 | ||
|   | 78dccf1e0a | ||
|   | 9cb7e09aef | ||
|   | 4111c12895 | ||
|   | b6ec023920 | ||
|   | e8e7067993 | ||
|   | 0e9319e97b | ||
|   | df53af7b4d | ||
|   | bcbf244fd2 | ||
|   | 7ff5febae0 | ||
|   | 019d108bb2 | 
| @@ -54,6 +54,9 @@ | ||||
|         openmct.install(openmct.plugins.AutoflowView({ | ||||
|             type: "telemetry.panel" | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.DisplayLayout({ | ||||
|             showAsView: ['summary-widget', 'example.imagery'] | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.Conductor({ | ||||
|             menuOptions: [ | ||||
|                 { | ||||
|   | ||||
| @@ -31,7 +31,6 @@ define([ | ||||
|     "./src/navigation/NavigateAction", | ||||
|     "./src/navigation/OrphanNavigationHandler", | ||||
|     "./src/windowing/NewTabAction", | ||||
|     "./src/windowing/WindowTitler", | ||||
|     "./res/templates/browse.html", | ||||
|     "./res/templates/browse-object.html", | ||||
|     "./res/templates/browse/object-header.html", | ||||
| @@ -52,7 +51,6 @@ define([ | ||||
|     NavigateAction, | ||||
|     OrphanNavigationHandler, | ||||
|     NewTabAction, | ||||
|     WindowTitler, | ||||
|     browseTemplate, | ||||
|     browseObjectTemplate, | ||||
|     objectHeaderTemplate, | ||||
| @@ -226,14 +224,6 @@ define([ | ||||
|                 } | ||||
|             ], | ||||
|             "runs": [ | ||||
|                 { | ||||
|                     "implementation": WindowTitler, | ||||
|                     "depends": [ | ||||
|                         "navigationService", | ||||
|                         "$rootScope", | ||||
|                         "$document" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "implementation": OrphanNavigationHandler, | ||||
|                     "depends": [ | ||||
|   | ||||
| @@ -1,51 +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 () { | ||||
|  | ||||
|         /** | ||||
|          * Updates the title of the current window to reflect the name | ||||
|          * of the currently navigated-to domain object. | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          */ | ||||
|         function WindowTitler(navigationService, $rootScope, $document) { | ||||
|             // Look up name of the navigated domain object... | ||||
|             function getNavigatedObjectName() { | ||||
|                 var navigatedObject = navigationService.getNavigation(); | ||||
|                 return navigatedObject && navigatedObject.getModel().name; | ||||
|             } | ||||
|  | ||||
|             // Set the window title... | ||||
|             function setTitle(name) { | ||||
|                 $document[0].title = name; | ||||
|             } | ||||
|  | ||||
|             // Watch the former, and invoke the latter | ||||
|             $rootScope.$watch(getNavigatedObjectName, setTitle); | ||||
|         } | ||||
|  | ||||
|         return WindowTitler; | ||||
|     } | ||||
| ); | ||||
| @@ -1,78 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * WindowTitlerSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../../src/windowing/WindowTitler"], | ||||
|     function (WindowTitler) { | ||||
|  | ||||
|         describe("The window titler", function () { | ||||
|             var mockNavigationService, | ||||
|                 mockRootScope, | ||||
|                 mockDocument, | ||||
|                 mockDomainObject, | ||||
|                 titler; // eslint-disable-line | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockNavigationService = jasmine.createSpyObj( | ||||
|                     'navigationService', | ||||
|                     ['getNavigation'] | ||||
|                 ); | ||||
|                 mockRootScope = jasmine.createSpyObj( | ||||
|                     '$rootScope', | ||||
|                     ['$watch'] | ||||
|                 ); | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     'domainObject', | ||||
|                     ['getModel'] | ||||
|                 ); | ||||
|                 mockDocument = [{}]; | ||||
|  | ||||
|                 mockDomainObject.getModel.and.returnValue({ name: 'Test name' }); | ||||
|                 mockNavigationService.getNavigation.and.returnValue(mockDomainObject); | ||||
|  | ||||
|                 titler = new WindowTitler( | ||||
|                     mockNavigationService, | ||||
|                     mockRootScope, | ||||
|                     mockDocument | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("listens for changes to the name of the navigated object", function () { | ||||
|                 expect(mockRootScope.$watch).toHaveBeenCalledWith( | ||||
|                     jasmine.any(Function), | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|                 expect(mockRootScope.$watch.calls.mostRecent().args[0]()) | ||||
|                     .toEqual('Test name'); | ||||
|             }); | ||||
|  | ||||
|             it("sets the title to the name of the navigated object", function () { | ||||
|                 mockRootScope.$watch.calls.mostRecent().args[1]("Some name"); | ||||
|                 expect(mockDocument[0].title).toEqual("Some name"); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -65,7 +65,8 @@ define([ | ||||
|                     "depends": [ | ||||
|                         "$document", | ||||
|                         "$compile", | ||||
|                         "$rootScope" | ||||
|                         "$rootScope", | ||||
|                         "$timeout" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <div class="l-message" | ||||
| <div class="c-message" | ||||
|      ng-class="'message-severity-' + ngModel.severity"> | ||||
|     <div class="w-message-contents"> | ||||
|         <div class="top-bar"> | ||||
|             <div class="title">{{ngModel.title}}</div> | ||||
|         <div class="c-message__top-bar"> | ||||
|             <div class="c-message__title">{{ngModel.title}}</div> | ||||
|         </div> | ||||
|         <div class="hint" ng-hide="ngModel.hint === undefined"> | ||||
|         <div class="c-message__hint" ng-hide="ngModel.hint === undefined"> | ||||
|             {{ngModel.hint}} | ||||
|             <span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span> | ||||
|         </div> | ||||
| @@ -16,17 +16,17 @@ | ||||
|                          ng-model="ngModel" | ||||
|                          ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include> | ||||
|         </div> | ||||
|         <div class="bottom-bar"> | ||||
|             <a ng-repeat="dialogOption in ngModel.options" | ||||
|                class="s-button" | ||||
|         <div class="c-overlay__button-bar"> | ||||
|             <button ng-repeat="dialogOption in ngModel.options" | ||||
|                class="c-button" | ||||
|                ng-click="dialogOption.callback()"> | ||||
|                 {{dialogOption.label}} | ||||
|             </a> | ||||
|             <a class="s-button major" | ||||
|             </button> | ||||
|             <button class="c-button c-button--major" | ||||
|                ng-if="ngModel.primaryOption" | ||||
|                ng-click="ngModel.primaryOption.callback()"> | ||||
|                 {{ngModel.primaryOption.label}} | ||||
|             </a> | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,25 +1,25 @@ | ||||
| <div class="l-message" | ||||
| <div class="c-message" | ||||
|      ng-class="'message-severity-' + ngModel.severity"> | ||||
|     <div class="w-message-contents"> | ||||
|         <div class="top-bar"> | ||||
|             <div class="title">{{ngModel.message}}</div> | ||||
|         <div class="c-message__top-bar"> | ||||
|             <div class="c-message__title">{{ngModel.message}}</div> | ||||
|         </div> | ||||
|         <div class="message-body"> | ||||
|             <mct-include key="'progress-bar'" | ||||
|                          ng-model="ngModel" | ||||
|                          ng-show="ngModel.progressPerc !== undefined"></mct-include> | ||||
|         </div> | ||||
|         <div class="bottom-bar"> | ||||
|             <a ng-repeat="dialogOption in ngModel.options" | ||||
|                 class="s-button" | ||||
|         <div class="c-overlay__button-bar"> | ||||
|             <button ng-repeat="dialogOption in ngModel.options" | ||||
|                 class="c-button" | ||||
|                 ng-click="dialogOption.callback()"> | ||||
|                     {{dialogOption.label}} | ||||
|             </a> | ||||
|             <a class="s-button major" | ||||
|             </button> | ||||
|             <button class="c-button c-button--major" | ||||
|                 ng-if="ngModel.primaryOption" | ||||
|                 ng-click="ngModel.primaryOption.callback()"> | ||||
|                     {{ngModel.primaryOption.label}} | ||||
|             </a> | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,22 +1,23 @@ | ||||
| <mct-container key="overlay"> | ||||
|     <div class="t-message-list"> | ||||
|         <div class="top-bar"> | ||||
|             <div class="dialog-title">{{ngModel.dialog.title}}</div> | ||||
|             <div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 || | ||||
|                                                                                                   ngModel.dialog.messages.length == 0">s</span> | ||||
|     <div class="t-message-list c-overlay__contents"> | ||||
|         <div class="c-overlay__top-bar"> | ||||
|             <div class="c-overlay__dialog-title">{{ngModel.dialog.title}}</div> | ||||
|             <div class="c-overlay__dialog-hint">Displaying {{ngModel.dialog.messages.length}} message<span | ||||
|                     ng-show="ngModel.dialog.messages.length > 1 || | ||||
|                             ngModel.dialog.messages.length == 0">s</span> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="w-messages"> | ||||
|         <div class="w-messages c-overlay__messages"> | ||||
|             <mct-include | ||||
|                 ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'" | ||||
|                 key="'notification-message'" ng-model="msg.model"></mct-include> | ||||
|         </div> | ||||
|         <div class="bottom-bar"> | ||||
|             <a ng-repeat="dialogAction in ngModel.dialog.actions" | ||||
|                class="s-button major" | ||||
|         <div class="c-overlay__bottom-bar"> | ||||
|             <button ng-repeat="dialogAction in ngModel.dialog.actions" | ||||
|                class="c-button c-button--major" | ||||
|                ng-click="dialogAction.action()"> | ||||
|                 {{dialogAction.label}} | ||||
|             </a> | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
| </mct-container> | ||||
|   | ||||
| @@ -22,9 +22,9 @@ | ||||
| <div class="c-overlay l-overlay-small" ng-class="{'delayEntry100ms' : ngModel.delay}"> | ||||
|     <div class="c-overlay__blocker"></div> | ||||
|     <div class="c-overlay__outer"> | ||||
|         <a ng-click="ngModel.cancel()" | ||||
|         <button ng-click="ngModel.cancel()" | ||||
|            ng-if="ngModel.cancel" | ||||
|            class="c-click-icon c-overlay__close-button icon-x-in-circle"></a> | ||||
|            class="c-click-icon c-overlay__close-button icon-x-in-circle"></button> | ||||
|         <div class="c-overlay__contents" ng-transclude></div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -44,8 +44,9 @@ define( | ||||
|          * @memberof platform/commonUI/dialog | ||||
|          * @constructor | ||||
|          */ | ||||
|         function OverlayService($document, $compile, $rootScope) { | ||||
|         function OverlayService($document, $compile, $rootScope, $timeout) { | ||||
|             this.$compile = $compile; | ||||
|             this.$timeout = $timeout; | ||||
|  | ||||
|             // Don't include $document and $rootScope directly; | ||||
|             // avoids https://docs.angularjs.org/error/ng/cpws | ||||
| @@ -93,12 +94,14 @@ define( | ||||
|             scope.key = key; | ||||
|             scope.typeClass = typeClass || 't-dialog'; | ||||
|  | ||||
|             // Create the overlay element and add it to the document's body | ||||
|             element = this.$compile(TEMPLATE)(scope); | ||||
|              | ||||
|             // Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when | ||||
|             // multiple overlays with the same z-index are active. | ||||
|             this.findBody().append(element); | ||||
|             this.$timeout(() => { | ||||
|                 // Create the overlay element and add it to the document's body | ||||
|                 element = this.$compile(TEMPLATE)(scope); | ||||
|                  | ||||
|                 // Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when | ||||
|                 // multiple overlays with the same z-index are active. | ||||
|                 this.findBody().append(element); | ||||
|             }); | ||||
|  | ||||
|             return { | ||||
|                 dismiss: dismiss | ||||
|   | ||||
| @@ -35,16 +35,20 @@ define( | ||||
|                 mockTemplate, | ||||
|                 mockElement, | ||||
|                 mockScope, | ||||
|                 mockTimeout, | ||||
|                 overlayService; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockDocument = jasmine.createSpyObj("$document", ["find"]); | ||||
|                 mockCompile = jasmine.createSpy("$compile"); | ||||
|                 mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]); | ||||
|                 mockBody = jasmine.createSpyObj("body", ["prepend"]); | ||||
|                 mockBody = jasmine.createSpyObj("body", ["append"]); | ||||
|                 mockTemplate = jasmine.createSpy("template"); | ||||
|                 mockElement = jasmine.createSpyObj("element", ["remove"]); | ||||
|                 mockScope = jasmine.createSpyObj("scope", ["$destroy"]); | ||||
|                 mockTimeout = function (callback) { | ||||
|                     callback(); | ||||
|                 } | ||||
|  | ||||
|                 mockDocument.find.and.returnValue(mockBody); | ||||
|                 mockCompile.and.returnValue(mockTemplate); | ||||
| @@ -54,7 +58,8 @@ define( | ||||
|                 overlayService = new OverlayService( | ||||
|                     mockDocument, | ||||
|                     mockCompile, | ||||
|                     mockRootScope | ||||
|                     mockRootScope, | ||||
|                     mockTimeout | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
| @@ -67,7 +72,7 @@ define( | ||||
|  | ||||
|             it("adds the templated element to the body", function () { | ||||
|                 overlayService.createOverlay("test", {}); | ||||
|                 expect(mockBody.prepend).toHaveBeenCalledWith(mockElement); | ||||
|                 expect(mockBody.append).toHaveBeenCalledWith(mockElement); | ||||
|             }); | ||||
|  | ||||
|             it("places the provided model/key in its template's scope", function () { | ||||
|   | ||||
| @@ -49,7 +49,7 @@ define( | ||||
|                     name: "Properties", | ||||
|                     rows: this.properties.map(function (property, index) { | ||||
|                         // Property definition is same as form row definition | ||||
|                         var row = Object.create(property.getDefinition()); | ||||
|                         var row = JSON.parse(JSON.stringify(property.getDefinition())); | ||||
|                         row.key = index; | ||||
|                         return row; | ||||
|                     }).filter(function (row) { | ||||
|   | ||||
| @@ -162,9 +162,6 @@ function ( | ||||
|         function saveAfterClone(clonedObject) { | ||||
|             return this.openmct.editor.save().then(() => { | ||||
|                 // Force mutation for search indexing | ||||
|                 clonedObject.useCapability('mutation', (model) => { | ||||
|                     return model; | ||||
|                 }); | ||||
|                 return clonedObject; | ||||
|             }) | ||||
|         } | ||||
| @@ -173,6 +170,14 @@ function ( | ||||
|             return fetchObject(clonedObject.getId()) | ||||
|         } | ||||
|  | ||||
|         function indexForSearch(savedObject) { | ||||
|             savedObject.useCapability('mutation', (model) => { | ||||
|                 return model; | ||||
|             }); | ||||
|  | ||||
|             return savedObject; | ||||
|         } | ||||
|  | ||||
|         function onSuccess(object) { | ||||
|             self.notificationService.info("Save Succeeded"); | ||||
|             return object; | ||||
| @@ -194,6 +199,7 @@ function ( | ||||
|             .then(undirtyOriginals) | ||||
|             .then(saveAfterClone) | ||||
|             .then(finishEditing) | ||||
|             .then(indexForSearch) | ||||
|             .then(hideBlockingDialog) | ||||
|             .then(onSuccess) | ||||
|             .catch(onFailure); | ||||
|   | ||||
| @@ -64,7 +64,6 @@ define( | ||||
|          * @returns boolean | ||||
|          */ | ||||
|         EditorCapability.prototype.inEditContext = function () { | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|         }; | ||||
|  | ||||
| @@ -74,7 +73,6 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.isEditContextRoot = function () { | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -71,6 +71,12 @@ define( | ||||
|                 openmct.editor.cancel(); | ||||
|             } | ||||
|  | ||||
|             function isFirstViewEditable(domainObject) { | ||||
|                 let firstView = openmct.objectViews.get(domainObject)[0]; | ||||
|  | ||||
|                 return firstView && firstView.canEdit && firstView.canEdit(domainObject); | ||||
|             } | ||||
|  | ||||
|             function navigateAndEdit(object) { | ||||
|                 let objectPath = object.getCapability('context').getPath(), | ||||
|                     url = '#/browse/' + objectPath | ||||
| @@ -82,7 +88,9 @@ define( | ||||
|  | ||||
|                 window.location.href = url; | ||||
|  | ||||
|                 openmct.editor.edit(); | ||||
|                 if (isFirstViewEditable(object.useCapability('adapter'))) { | ||||
|                     openmct.editor.edit(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             newModel.type = this.type.getKey(); | ||||
|   | ||||
| @@ -66,7 +66,7 @@ define( | ||||
|                 name: "Properties", | ||||
|                 rows: this.properties.map(function (property, index) { | ||||
|                     // Property definition is same as form row definition | ||||
|                     var row = Object.create(property.getDefinition()); | ||||
|                     var row = JSON.parse(JSON.stringify(property.getDefinition())); | ||||
|  | ||||
|                     // Use index as the key into the formValue; | ||||
|                     // this correlates to the indexing provided by | ||||
|   | ||||
| @@ -77,14 +77,19 @@ define([], function () { | ||||
|                 return promiseFn().then(nextFn); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         if (!this.isScheduled(id)) { | ||||
|             this.clearTransactionFns[id] = | ||||
|                 this.transactionService.addToTransaction( | ||||
|                     chain(onCommit, release), | ||||
|                     chain(onCancel, release) | ||||
|                 ); | ||||
|         /** | ||||
|          * Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence | ||||
|          * call is executed. This should prevent stale objects being persisted and overwriting fresh ones. | ||||
|          */ | ||||
|         if (this.isScheduled(id)) { | ||||
|             this.clearTransactionsFor(id); | ||||
|         } | ||||
|  | ||||
|         this.clearTransactionFns[id] = | ||||
|             this.transactionService.addToTransaction( | ||||
|                 chain(onCommit, release), | ||||
|                 chain(onCancel, release) | ||||
|             ); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -93,24 +93,33 @@ define( | ||||
|                     expect(mockOnCancel).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it("ignores subsequent calls for the same object", function () { | ||||
|                     manager.addToTransaction( | ||||
|                         testId, | ||||
|                         jasmine.createSpy(), | ||||
|                         jasmine.createSpy() | ||||
|                     ); | ||||
|                     expect(mockTransactionService.addToTransaction.calls.count()) | ||||
|                         .toEqual(1); | ||||
|                 }); | ||||
|                 describe("Adds callbacks to transaction", function () { | ||||
|                     beforeEach(function () { | ||||
|                         spyOn(manager, 'clearTransactionsFor'); | ||||
|                         manager.clearTransactionsFor.and.callThrough(); | ||||
|                     }); | ||||
|  | ||||
|                 it("accepts subsequent calls for other objects", function () { | ||||
|                     manager.addToTransaction( | ||||
|                         'other-id', | ||||
|                         jasmine.createSpy(), | ||||
|                         jasmine.createSpy() | ||||
|                     ); | ||||
|                     expect(mockTransactionService.addToTransaction.calls.count()) | ||||
|                         .toEqual(2); | ||||
|                     it("and clears pending calls if same object", function () { | ||||
|                         manager.addToTransaction( | ||||
|                             testId, | ||||
|                             jasmine.createSpy(), | ||||
|                             jasmine.createSpy() | ||||
|                         ); | ||||
|                         expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId); | ||||
|                     }); | ||||
|  | ||||
|                     it("and does not clear pending calls if different object", function () { | ||||
|                         manager.addToTransaction( | ||||
|                             'other-id', | ||||
|                             jasmine.createSpy(), | ||||
|                             jasmine.createSpy() | ||||
|                         ); | ||||
|                         expect(manager.clearTransactionsFor).not.toHaveBeenCalled(); | ||||
|                     }); | ||||
|  | ||||
|                     afterEach(function () { | ||||
|                         expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("does not remove callbacks from the transaction", function () { | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <div ng-controller="BannerController" ng-show="active.notification" | ||||
|      class="l-message-banner s-message-banner {{active.notification.model.severity}}" ng-class="{ | ||||
|      class="c-message-banner {{active.notification.model.severity}}" ng-class="{ | ||||
|      'minimized': active.notification.model.minimized, | ||||
|      'new': !active.notification.model.minimized}" | ||||
|      ng-click="maximize(active.notification)"> | ||||
|     <span class="banner-elem label"> | ||||
|     <span class="c-message-banner__message"> | ||||
|         {{active.notification.model.title}} | ||||
|     </span> | ||||
|     <span ng-show="active.notification.model.progress !== undefined || active.notification.model.unknownProgress"> | ||||
|         <mct-include key="'progress-bar'" class="banner-elem" | ||||
|         <mct-include key="'progress-bar'" class="c-message-banner__progress-bar" | ||||
|                      ng-model="active.notification.model"> | ||||
|         </mct-include> | ||||
|     </span> | ||||
| @@ -16,5 +16,5 @@ | ||||
|        ng-click="action(active.notification.model.primaryOption.callback, $event)"> | ||||
|         {{active.notification.model.primaryOption.label}} | ||||
|     </a> | ||||
|     <a class="banner-elem close icon-x" ng-click="dismiss(active.notification, $event)"></a> | ||||
|     <button class="c-message-banner__close-button c-click-icon icon-x-in-circle" ng-click="dismiss(active.notification, $event)"></button> | ||||
| </div> | ||||
|   | ||||
| @@ -54,6 +54,7 @@ define( | ||||
|                     if (isDestroyed) { | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     var removeSelectable = openmct.selection.selectable( | ||||
|                         element[0], | ||||
|                         scope.$eval(attrs.mctSelectable), | ||||
|   | ||||
| @@ -43,23 +43,10 @@ define([], function () { | ||||
|         var mutationTopic = topic('mutation'); | ||||
|         mutationTopic.listen(function (domainObject) { | ||||
|             var persistence = domainObject.getCapability('persistence'); | ||||
|             var wasActive = transactionService.isActive(); | ||||
|             cacheService.put(domainObject.getId(), domainObject.getModel()); | ||||
|  | ||||
|             if (hasChanged(domainObject)) { | ||||
|  | ||||
|                 if (!wasActive) { | ||||
|                     transactionService.startTransaction(); | ||||
|                 } | ||||
|  | ||||
|                 transactionService.addToTransaction( | ||||
|                     persistence.persist.bind(persistence), | ||||
|                     persistence.refresh.bind(persistence) | ||||
|                 ); | ||||
|  | ||||
|                 if (!wasActive) { | ||||
|                     transactionService.commit(); | ||||
|                 } | ||||
|                 persistence.persist(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -24,22 +24,27 @@ define( | ||||
|     ["../../src/runs/TransactingMutationListener"], | ||||
|     function (TransactingMutationListener) { | ||||
|  | ||||
|         xdescribe("TransactingMutationListener", function () { | ||||
|         describe("TransactingMutationListener", function () { | ||||
|             var mockTopic, | ||||
|                 mockMutationTopic, | ||||
|                 mockCacheService, | ||||
|                 mockTransactionService, | ||||
|                 mockDomainObject, | ||||
|                 mockModel, | ||||
|                 mockPersistence; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockTopic = jasmine.createSpy('topic'); | ||||
|                 mockMutationTopic = | ||||
|                     jasmine.createSpyObj('mutation', ['listen']); | ||||
|                 mockCacheService = | ||||
|                     jasmine.createSpyObj('cacheService', [ | ||||
|                         'put' | ||||
|                     ]); | ||||
|                 mockTransactionService = | ||||
|                     jasmine.createSpyObj('transactionService', [ | ||||
|                         'isActive', | ||||
|                         'startTransaction', | ||||
|                         'addToTransaction', | ||||
|                         'commit' | ||||
|                     ]); | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
| @@ -52,18 +57,24 @@ define( | ||||
|                 ); | ||||
|  | ||||
|                 mockTopic.and.callFake(function (t) { | ||||
|                     return (t === 'mutation') && mockMutationTopic; | ||||
|                     expect(t).toBe('mutation'); | ||||
|                     return mockMutationTopic; | ||||
|                 }); | ||||
|  | ||||
|                 mockDomainObject.getId.and.returnValue('mockId'); | ||||
|                 mockDomainObject.getCapability.and.callFake(function (c) { | ||||
|                     return (c === 'persistence') && mockPersistence; | ||||
|                     expect(c).toBe('persistence'); | ||||
|                     return mockPersistence; | ||||
|                 }); | ||||
|                 mockModel = {}; | ||||
|                 mockDomainObject.getModel.and.returnValue(mockModel); | ||||
|  | ||||
|                 mockPersistence.persisted.and.returnValue(true); | ||||
|  | ||||
|                 return new TransactingMutationListener( | ||||
|                     mockTopic, | ||||
|                     mockTransactionService | ||||
|                     mockTransactionService, | ||||
|                     mockCacheService | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
| @@ -72,48 +83,27 @@ define( | ||||
|                     .toHaveBeenCalledWith(jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             [false, true].forEach(function (isActive) { | ||||
|                 var verb = isActive ? "is" : "isn't"; | ||||
|             it("calls persist if the model has changed", function () { | ||||
|                 mockModel.persisted = Date.now(); | ||||
|  | ||||
|                 function onlyWhenInactive(expectation) { | ||||
|                     return isActive ? expectation.not : expectation; | ||||
|                 } | ||||
|                 //Mark the model dirty by setting the mutated date later than the last persisted date. | ||||
|                 mockModel.modified = mockModel.persisted + 1; | ||||
|  | ||||
|                 describe("when a transaction " + verb + " active", function () { | ||||
|                     var innerVerb = isActive ? "does" : "doesn't"; | ||||
|                 mockMutationTopic.listen.calls.mostRecent() | ||||
|                     .args[0](mockDomainObject); | ||||
|  | ||||
|                     beforeEach(function () { | ||||
|                         mockTransactionService.isActive.and.returnValue(isActive); | ||||
|                     }); | ||||
|                 expect(mockPersistence.persist).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|                     describe("and mutation occurs", function () { | ||||
|                         beforeEach(function () { | ||||
|                             mockMutationTopic.listen.calls.mostRecent() | ||||
|                                 .args[0](mockDomainObject); | ||||
|                         }); | ||||
|             it("does not call persist if the model has not changed", function () { | ||||
|                 mockModel.persisted = Date.now(); | ||||
|  | ||||
|                 mockModel.modified = mockModel.persisted; | ||||
|  | ||||
|                         it(innerVerb + " start a new transaction", function () { | ||||
|                             onlyWhenInactive( | ||||
|                                 expect(mockTransactionService.startTransaction) | ||||
|                             ).toHaveBeenCalled(); | ||||
|                         }); | ||||
|                 mockMutationTopic.listen.calls.mostRecent() | ||||
|                     .args[0](mockDomainObject); | ||||
|  | ||||
|                         it("adds to the active transaction", function () { | ||||
|                             expect(mockTransactionService.addToTransaction) | ||||
|                                 .toHaveBeenCalledWith( | ||||
|                                     jasmine.any(Function), | ||||
|                                     jasmine.any(Function) | ||||
|                                 ); | ||||
|                         }); | ||||
|  | ||||
|                         it(innerVerb + " immediately commit", function () { | ||||
|                             onlyWhenInactive( | ||||
|                                 expect(mockTransactionService.commit) | ||||
|                             ).toHaveBeenCalled(); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|                 expect(mockPersistence.persist).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -24,10 +24,10 @@ | ||||
|         <button ng-click="timer.clickStopButton()" | ||||
|                 ng-hide="timer.timerState == 'stopped'" | ||||
|                 title="Reset" | ||||
|                 class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button> | ||||
|                 class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"></button> | ||||
|         <button ng-click="timer.clickButton()" | ||||
|                 title="{{timer.buttonText()}}" | ||||
|                 class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button> | ||||
|                 class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major {{timer.buttonCssClass()}}"></button> | ||||
|     </div> | ||||
|     <div class="c-timer__direction {{timer.signClass()}}" | ||||
|         ng-hide="!timer.signClass()"></div> | ||||
|   | ||||
| @@ -1,22 +1,18 @@ | ||||
| <div class="t-imagery" ng-controller="ImageryController as imagery"> | ||||
| <div class="t-imagery c-imagery" ng-controller="ImageryController as imagery"> | ||||
|     <mct-split-pane class='abs' anchor="bottom" alias="imagery"> | ||||
|     <div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col" | ||||
|         ng-mouseenter="showLocalControls = true;" | ||||
|         ng-mouseleave="showLocalControls = false;"> | ||||
|         <div class="h-local-controls h-local-controls-overlay-content h-local-controls-trans s-local-controls local-controls-hidden l-flex-row"> | ||||
|             <span class="holder flex-elem grows"> | ||||
|     <div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col"> | ||||
|         <div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc"> | ||||
|             <span class="holder flex-elem grows c-imagery__lc__sliders"> | ||||
|                 <input class="icon-brightness" type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                        ng-model="filters.brightness"> | ||||
|                 </input> | ||||
|                        ng-model="filters.brightness" /> | ||||
|                 <input class="icon-contrast" type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                        ng-model="filters.contrast"> | ||||
|                 </input> | ||||
|                        ng-model="filters.contrast" /> | ||||
|             </span> | ||||
|             <span class="holder flex-elem t-reset-btn-holder"> | ||||
|             <span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn"> | ||||
|                 <a class="s-icon-button icon-reset t-btn-reset" | ||||
|                    ng-click="filters = { brightness: 100, contrast: 100 }"></a> | ||||
|             </span> | ||||
|   | ||||
| @@ -37,75 +37,17 @@ define( | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Handle persistence failures by providing the user with a | ||||
|          * dialog summarizing these failures, and giving the option | ||||
|          * to overwrite/cancel as appropriate. | ||||
|          * Discard failures | ||||
|          * @param {Array} failures persistence failures, as prepared | ||||
|          *        by PersistenceQueueHandler | ||||
|          * @memberof platform/persistence/queue.PersistenceFailureHandler# | ||||
|          */ | ||||
|         PersistenceFailureHandler.prototype.handle = function handleFailures(failures) { | ||||
|             // Prepare dialog for display | ||||
|  | ||||
|             var dialogModel = new PersistenceFailureDialog(failures), | ||||
|                 revisionErrors = dialogModel.model.revised, | ||||
|                 $q = this.$q; | ||||
|  | ||||
|             // Refresh revision information for the domain object associated | ||||
|             // with this persistence failure | ||||
|             function refresh(failure) { | ||||
|                 // Refresh the domain object to the latest from persistence | ||||
|                 return failure.persistence.refresh(); | ||||
|             } | ||||
|  | ||||
|             // Issue a new persist call for the domain object associated with | ||||
|             // this failure. | ||||
|             function persist(failure) { | ||||
|                 // Note that we reissue the persist request here, but don't | ||||
|                 // return it, to avoid a circular wait. We trust that the | ||||
|                 // PersistenceQueue will behave correctly on the next round | ||||
|                 // of flushing. | ||||
|                 failure.requeue(); | ||||
|             } | ||||
|  | ||||
|             // Retry persistence (overwrite) for this set of failed attempts | ||||
|             function retry(failuresToRetry) { | ||||
|                 var models = {}; | ||||
|  | ||||
|                 // Cache a copy of the model | ||||
|                 function cacheModel(failure) { | ||||
|                     // Clone... | ||||
|                     models[failure.id] = JSON.parse(JSON.stringify( | ||||
|                         failure.domainObject.getModel() | ||||
|                     )); | ||||
|                 } | ||||
|  | ||||
|                 // Mutate a domain object to restore its model | ||||
|                 function remutate(failure) { | ||||
|                     var model = models[failure.id]; | ||||
|                     return failure.domainObject.useCapability( | ||||
|                         "mutation", | ||||
|                         function () { | ||||
|                             return model; | ||||
|                         }, | ||||
|                         model.modified | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|                 // Cache the object models we might want to save | ||||
|                 failuresToRetry.forEach(cacheModel); | ||||
|  | ||||
|                 // Strategy here: | ||||
|                 // * Cache all of the models we might want to save (above) | ||||
|                 // * Refresh all domain objects (so they are latest versions) | ||||
|                 // * Re-insert the cached domain object models | ||||
|                 // * Invoke persistence again | ||||
|                 return $q.all(failuresToRetry.map(refresh)).then(function () { | ||||
|                     return $q.all(failuresToRetry.map(remutate)); | ||||
|                 }).then(function () { | ||||
|                     return $q.all(failuresToRetry.map(persist)); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             // Discard changes for a failed refresh | ||||
|             function discard(failure) { | ||||
|                 var persistence = | ||||
| @@ -118,19 +60,7 @@ define( | ||||
|                 return $q.all(failuresToDiscard.map(discard)); | ||||
|             } | ||||
|  | ||||
|             // Handle user input (did they choose to overwrite?) | ||||
|             function handleChoice(key) { | ||||
|                 // If so, try again | ||||
|                 if (key === PersistenceFailureConstants.OVERWRITE_KEY) { | ||||
|                     return retry(revisionErrors); | ||||
|                 } else { | ||||
|                     return discardAll(revisionErrors); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Prompt for user input, the overwrite if they said so. | ||||
|             return this.dialogService.getUserChoice(dialogModel) | ||||
|                 .then(handleChoice, handleChoice); | ||||
|             return discardAll(revisionErrors); | ||||
|         }; | ||||
|  | ||||
|         return PersistenceFailureHandler; | ||||
|   | ||||
| @@ -74,43 +74,14 @@ define( | ||||
|                 handler = new PersistenceFailureHandler(mockQ, mockDialogService); | ||||
|             }); | ||||
|  | ||||
|             it("shows a dialog to handle failures", function () { | ||||
|             it("discards on handle", function () { | ||||
|                 handler.handle(mockFailures); | ||||
|                 expect(mockDialogService.getUserChoice).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("overwrites on request", function () { | ||||
|                 mockQ.all.and.returnValue(asPromise([])); | ||||
|                 handler.handle(mockFailures); | ||||
|                 // User chooses overwrite | ||||
|                 mockPromise.then.calls.mostRecent().args[0](Constants.OVERWRITE_KEY); | ||||
|                 // Should refresh, remutate, and requeue all objects | ||||
|                 mockFailures.forEach(function (mockFailure, i) { | ||||
|                     expect(mockFailure.persistence.refresh).toHaveBeenCalled(); | ||||
|                     expect(mockFailure.requeue).toHaveBeenCalled(); | ||||
|                     expect(mockFailure.domainObject.useCapability).toHaveBeenCalledWith( | ||||
|                         'mutation', | ||||
|                         jasmine.any(Function), | ||||
|                         i // timestamp | ||||
|                     ); | ||||
|                     expect(mockFailure.domainObject.useCapability.calls.mostRecent().args[1]()) | ||||
|                         .toEqual({ id: mockFailure.id, modified: i }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it("discards on request", function () { | ||||
|                 mockQ.all.and.returnValue(asPromise([])); | ||||
|                 handler.handle(mockFailures); | ||||
|                 // User chooses overwrite | ||||
|                 mockPromise.then.calls.mostRecent().args[0](false); | ||||
|                 // Should refresh, but not remutate, and requeue all objects | ||||
|                 mockFailures.forEach(function (mockFailure) { | ||||
|                     expect(mockFailure.persistence.refresh).toHaveBeenCalled(); | ||||
|                     expect(mockFailure.requeue).not.toHaveBeenCalled(); | ||||
|                     expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
							
								
								
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							| @@ -248,7 +248,6 @@ define([ | ||||
|         this.legacyRegistry = defaultRegistry; | ||||
|         this.install(this.plugins.Plot()); | ||||
|         this.install(this.plugins.TelemetryTable()); | ||||
|         this.install(this.plugins.DisplayLayout()); | ||||
|         this.install(PreviewPlugin.default()); | ||||
|         this.install(LegacyIndicatorsPlugin()); | ||||
|         this.install(LicensesPlugin.default()); | ||||
| @@ -257,7 +256,6 @@ define([ | ||||
|         if (typeof BUILD_CONSTANTS !== 'undefined') { | ||||
|             this.install(buildInfoPlugin(BUILD_CONSTANTS)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     MCT.prototype = Object.create(EventEmitter.prototype); | ||||
| @@ -328,6 +326,12 @@ define([ | ||||
|      *        MCT; if undefined, MCT will be run in the body of the document | ||||
|      */ | ||||
|     MCT.prototype.start = function (domElement) { | ||||
|         if (!this.plugins.DisplayLayout._installed) { | ||||
|             this.install(this.plugins.DisplayLayout({ | ||||
|                 showAsView: ['summary-widget'] | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         if (!domElement) { | ||||
|             domElement = document.body; | ||||
|         } | ||||
| @@ -352,7 +356,7 @@ define([ | ||||
|         legacyRegistry.enable('adapter'); | ||||
|  | ||||
|         this.router.route(/^\/$/, () => { | ||||
|             this.router.setPath('/browse/mine'); | ||||
|             this.router.setPath('/browse/'); | ||||
|         }); | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -33,20 +33,25 @@ export default class LegacyContextMenuAction { | ||||
|     } | ||||
|  | ||||
|     invoke(objectPath) { | ||||
|         let context = { | ||||
|             category: 'contextual', | ||||
|             domainObject: this.openmct.legacyObject(objectPath) | ||||
|         } | ||||
|         let legacyAction = new this.LegacyAction(context); | ||||
|         this.openmct.objects.getRoot().then((root) => { | ||||
|             let pathWithRoot = objectPath.slice(); | ||||
|             pathWithRoot.push(root); | ||||
|  | ||||
|         if (!legacyAction.getMetadata) { | ||||
|             let metadata = Object.create(this.LegacyAction.definition); | ||||
|             metadata.context = context; | ||||
|             legacyAction.getMetadata = function () { | ||||
|                 return metadata; | ||||
|             }.bind(legacyAction); | ||||
|         } | ||||
|         legacyAction.perform(); | ||||
|             let context = { | ||||
|                 category: 'contextual', | ||||
|                 domainObject: this.openmct.legacyObject(pathWithRoot) | ||||
|             } | ||||
|             let legacyAction = new this.LegacyAction(context); | ||||
|  | ||||
|             if (!legacyAction.getMetadata) { | ||||
|                 let metadata = Object.create(this.LegacyAction.definition); | ||||
|                 metadata.context = context; | ||||
|                 legacyAction.getMetadata = function () { | ||||
|                     return metadata; | ||||
|                 }.bind(legacyAction); | ||||
|             } | ||||
|             legacyAction.perform(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath) { | ||||
|   | ||||
| @@ -137,8 +137,7 @@ define([ | ||||
|         function callbackWrapper(series) { | ||||
|             callback(createDatum(domainObject, metadata, series, series.getPointCount() - 1)); | ||||
|         } | ||||
|  | ||||
|         return capability.subscribe(callbackWrapper, request); | ||||
|         return capability.subscribe(callbackWrapper, request) || function () {}; | ||||
|     }; | ||||
|  | ||||
|     LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) { | ||||
|   | ||||
| @@ -57,8 +57,10 @@ define([ | ||||
|         }.bind(this); | ||||
|  | ||||
|         handleLegacyMutation = function (legacyObject) { | ||||
|             var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()); | ||||
|             this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject); | ||||
|             var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()), | ||||
|                 keystring = utils.makeKeyString(newStyleObject.identifier); | ||||
|  | ||||
|             this.eventEmitter.emit(keystring + ":*", newStyleObject); | ||||
|             this.eventEmitter.emit('mutation', newStyleObject); | ||||
|         }.bind(this); | ||||
|  | ||||
|   | ||||
| @@ -45,15 +45,30 @@ define([ | ||||
|             view: function (domainObject) { | ||||
|                 let $rootScope = openmct.$injector.get('$rootScope'); | ||||
|                 let templateLinker = openmct.$injector.get('templateLinker'); | ||||
|                 let scope = $rootScope.$new(); | ||||
|                 let scope = $rootScope.$new(true); | ||||
|                 let legacyObject = convertToLegacyObject(domainObject); | ||||
|                 let isDestroyed = false; | ||||
|                 let unlistenToStatus; | ||||
|                 let element; | ||||
|                 scope.domainObject = legacyObject; | ||||
|                 scope.model = legacyObject.getModel(); | ||||
|  | ||||
|                 let child; | ||||
|                 let parent; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (container) { | ||||
|                         parent = container; | ||||
|                         child = document.createElement('div'); | ||||
|                         parent.appendChild(child); | ||||
|                         let statusCapability = legacyObject.getCapability('status'); | ||||
|                         unlistenToStatus = statusCapability.listen((newStatus) => { | ||||
|                             child.classList.remove('s-status-timeconductor-unsynced'); | ||||
|  | ||||
|                             if (newStatus.includes('timeconductor-unsynced')) { | ||||
|                                 child.classList.add('s-status-timeconductor-unsynced'); | ||||
|                             } | ||||
|                         }); | ||||
|  | ||||
|                         // TODO: implement "gestures" support ? | ||||
|                         let uses = legacyView.uses || []; | ||||
|                         let promises = []; | ||||
| @@ -74,12 +89,13 @@ define([ | ||||
|                             uses.forEach(function (key, i) { | ||||
|                                 scope[key] = results[i]; | ||||
|                             }); | ||||
|                             element = openmct.$angular.element(child); | ||||
|                             templateLinker.link( | ||||
|                                 scope, | ||||
|                                 openmct.$angular.element(container), | ||||
|                                 element, | ||||
|                                 legacyView | ||||
|                             ); | ||||
|                             container.classList.add('u-contents'); | ||||
|                             child.classList.add('u-contents'); | ||||
|                         } | ||||
|  | ||||
|                         if (promises.length) { | ||||
| @@ -93,7 +109,12 @@ define([ | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         element.off(); | ||||
|                         element.remove(); | ||||
|                         scope.$destroy(); | ||||
|                         element = null; | ||||
|                         scope = null; | ||||
|                         unlistenToStatus(); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|   | ||||
| @@ -25,25 +25,34 @@ define([ | ||||
|             cssClass: representation.cssClass, | ||||
|             description: representation.description, | ||||
|             canView: function (selection) { | ||||
|                 if (!selection[0] || !selection[0].context.item) { | ||||
|                 if (selection.length === 0 || selection[0].length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 let domainObject = selection[0].context.item; | ||||
|                 return domainObject.type === typeDefinition.key; | ||||
|  | ||||
|                 let selectionContext = selection[0][0].context; | ||||
|  | ||||
|                 if (!selectionContext.item) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 return selectionContext.item.type === typeDefinition.key; | ||||
|             }, | ||||
|             view: function (selection) { | ||||
|                 let domainObject = selection[0].context.item; | ||||
|                 let domainObject = selection[0][0].context.item; | ||||
|                 let $rootScope = openmct.$injector.get('$rootScope'); | ||||
|                 let templateLinker = openmct.$injector.get('templateLinker'); | ||||
|                 let scope = $rootScope.$new(); | ||||
|                 let scope = $rootScope.$new(true); | ||||
|                 let legacyObject = convertToLegacyObject(domainObject); | ||||
|                 let isDestroyed = false; | ||||
|                 let element; | ||||
|                 scope.domainObject = legacyObject; | ||||
|                 scope.model = legacyObject.getModel(); | ||||
|  | ||||
|  | ||||
|                 return { | ||||
|                     show: function (container) { | ||||
|                         let child = document.createElement('div'); | ||||
|                         container.appendChild(child); | ||||
|                         // TODO: implement "gestures" support ? | ||||
|                         let uses = representation.uses || []; | ||||
|                         let promises = []; | ||||
| @@ -64,9 +73,10 @@ define([ | ||||
|                             uses.forEach(function (key, i) { | ||||
|                                 scope[key] = results[i]; | ||||
|                             }); | ||||
|                             element = openmct.$angular.element(child) | ||||
|                             templateLinker.link( | ||||
|                                 scope, | ||||
|                                 openmct.$angular.element(container), | ||||
|                                 element, | ||||
|                                 representation | ||||
|                             ); | ||||
|                             container.style.height = '100%'; | ||||
| @@ -83,7 +93,11 @@ define([ | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         element.off(); | ||||
|                         element.remove(); | ||||
|                         scope.$destroy(); | ||||
|                         element = null; | ||||
|                         scope = null; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -28,11 +28,6 @@ export default class Editor extends EventEmitter { | ||||
|         super(); | ||||
|         this.editing = false; | ||||
|         this.openmct = openmct; | ||||
|         document.addEventListener('drop', (event) => { | ||||
|             if (!this.isEditing()) { | ||||
|                 this.edit(); | ||||
|             } | ||||
|         }, {capture: true}); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -79,9 +74,11 @@ export default class Editor extends EventEmitter { | ||||
|      * @private | ||||
|      */ | ||||
|     cancel() { | ||||
|         this.getTransactionService().cancel(); | ||||
|         let cancelPromise = this.getTransactionService().cancel(); | ||||
|         this.editing = false; | ||||
|         this.emit('isEditing', false); | ||||
|  | ||||
|         return cancelPromise; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -98,18 +98,24 @@ define([ | ||||
|                     composition.reorder(1, 0); | ||||
|                     let newComposition = | ||||
|                         publicAPI.objects.mutate.calls.mostRecent().args[2]; | ||||
|                     let reorderPlan = listener.calls.mostRecent().args[0][0]; | ||||
|  | ||||
|                     expect(listener).toHaveBeenCalledWith(1, 0); | ||||
|                     expect(reorderPlan.oldIndex).toBe(1); | ||||
|                     expect(reorderPlan.newIndex).toBe(0); | ||||
|                     expect(newComposition[0].key).toEqual('b'); | ||||
|                     expect(newComposition[1].key).toEqual('a'); | ||||
|                     expect(newComposition[2].key).toEqual('c'); | ||||
|                 }); | ||||
|                 it('', function () { | ||||
|                     composition.reorder(0, 2); | ||||
|                     let newComposition = | ||||
|                         publicAPI.objects.mutate.calls.mostRecent().args[2]; | ||||
|                     let reorderPlan = listener.calls.mostRecent().args[0][0]; | ||||
|  | ||||
|                     expect(listener).toHaveBeenCalledWith(0, 2); | ||||
|                     expect(newComposition[0].key).toEqual('c'); | ||||
|                     expect(reorderPlan.oldIndex).toBe(0); | ||||
|                     expect(reorderPlan.newIndex).toBe(2); | ||||
|                     expect(newComposition[0].key).toEqual('b'); | ||||
|                     expect(newComposition[1].key).toEqual('c'); | ||||
|                     expect(newComposition[2].key).toEqual('a'); | ||||
|                 }) | ||||
|             }); | ||||
|   | ||||
| @@ -25,7 +25,6 @@ define([ | ||||
| ], function ( | ||||
|     _ | ||||
| ) { | ||||
|  | ||||
|     /** | ||||
|      * A CompositionCollection represents the list of domain objects contained | ||||
|      * by another domain object. It provides methods for loading this | ||||
| @@ -63,7 +62,6 @@ define([ | ||||
|         this.onProviderRemove = this.onProviderRemove.bind(this); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Listen for changes to this composition.  Supports 'add', 'remove', and | ||||
|      * 'load' events. | ||||
| @@ -76,7 +74,11 @@ define([ | ||||
|         if (!this.listeners[event]) { | ||||
|             throw new Error('Event not supported by composition: ' + event); | ||||
|         } | ||||
|  | ||||
|         if (!this.mutationListener) { | ||||
|             this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => { | ||||
|                 this.domainObject = newDomainObject; | ||||
|             }) | ||||
|         } | ||||
|         if (this.provider.on && this.provider.off) { | ||||
|             if (event === 'add') { | ||||
|                 this.provider.on( | ||||
| @@ -132,6 +134,10 @@ define([ | ||||
|  | ||||
|         this.listeners[event].splice(index, 1); | ||||
|         if (this.listeners[event].length === 0) { | ||||
|             if (this.mutationListener) { | ||||
|                 this.mutationListener(); | ||||
|                 delete this.mutationListener; | ||||
|             } | ||||
|             // Remove provider listener if this is the last callback to | ||||
|             // be removed. | ||||
|             if (this.provider.off && this.provider.on) { | ||||
| @@ -236,19 +242,15 @@ define([ | ||||
|      * @name remove | ||||
|      */ | ||||
|     CompositionCollection.prototype.reorder = function (oldIndex, newIndex, skipMutate) { | ||||
|         if (!skipMutate) { | ||||
|             this.provider.reorder(this.domainObject, oldIndex, newIndex); | ||||
|         } else { | ||||
|             this.emit('reorder', oldIndex, newIndex); | ||||
|         } | ||||
|         this.provider.reorder(this.domainObject, oldIndex, newIndex); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Handle reorder from provider. | ||||
|      * @private | ||||
|      */ | ||||
|     CompositionCollection.prototype.onProviderReorder = function (oldIndex, newIndex) { | ||||
|         this.reorder(oldIndex, newIndex, true); | ||||
|     CompositionCollection.prototype.onProviderReorder = function (reorderMap) { | ||||
|         this.emit('reorder', reorderMap); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -206,8 +206,32 @@ define([ | ||||
|  | ||||
|     DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) { | ||||
|         let newComposition = domainObject.composition.slice(); | ||||
|         newComposition[newIndex] = domainObject.composition[oldIndex]; | ||||
|         newComposition[oldIndex] = domainObject.composition[newIndex]; | ||||
|         let removeId = oldIndex > newIndex ? oldIndex + 1 : oldIndex; | ||||
|         let insertPosition = oldIndex < newIndex ? newIndex + 1 : newIndex; | ||||
|         //Insert object in new position | ||||
|         newComposition.splice(insertPosition, 0, domainObject.composition[oldIndex]); | ||||
|         newComposition.splice(removeId, 1); | ||||
|  | ||||
|         let reorderPlan = [{ | ||||
|             oldIndex, | ||||
|             newIndex | ||||
|         }]; | ||||
|  | ||||
|         if (oldIndex > newIndex) { | ||||
|             for (let i = newIndex; i < oldIndex; i++) { | ||||
|                 reorderPlan.push({ | ||||
|                     oldIndex: i, | ||||
|                     newIndex: i + 1 | ||||
|                 }); | ||||
|             } | ||||
|         } else { | ||||
|             for (let i = oldIndex + 1; i <= newIndex; i++) { | ||||
|                 reorderPlan.push({ | ||||
|                     oldIndex: i, | ||||
|                     newIndex: i - 1 | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         this.publicAPI.objects.mutate(domainObject, 'composition', newComposition); | ||||
|  | ||||
|         let id = objectUtils.makeKeyString(domainObject.identifier); | ||||
| @@ -221,9 +245,9 @@ define([ | ||||
|  | ||||
|         function notify(listener) { | ||||
|             if (listener.context) { | ||||
|                 listener.callback.call(listener.context, oldIndex, newIndex); | ||||
|                 listener.callback.call(listener.context, reorderPlan); | ||||
|             } else { | ||||
|                 listener.callback(oldIndex, newIndex); | ||||
|                 listener.callback(reorderPlan); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|   | ||||
| @@ -21,8 +21,10 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './object-utils.js', | ||||
|     'lodash' | ||||
| ], function ( | ||||
|     utils, | ||||
|     _ | ||||
| ) { | ||||
|     var ANY_OBJECT_EVENT = "mutation"; | ||||
| @@ -41,7 +43,9 @@ define([ | ||||
|     } | ||||
|  | ||||
|     function qualifiedEventName(object, eventName) { | ||||
|         return [object.identifier.key, eventName].join(':'); | ||||
|         var keystring = utils.makeKeyString(object.identifier); | ||||
|  | ||||
|         return [keystring, eventName].join(':'); | ||||
|     } | ||||
|  | ||||
|     MutableObject.prototype.stopListening = function () { | ||||
|   | ||||
| @@ -226,7 +226,20 @@ define([ | ||||
|                     (identifier.namespace === identifiers[0].namespace && | ||||
|                         identifier.key === identifiers[0].key); | ||||
|             }); | ||||
|     } | ||||
|     }; | ||||
|  | ||||
|     ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) { | ||||
|         return this.get(identifier).then((domainObject) => { | ||||
|             path.push(domainObject); | ||||
|             let location = domainObject.location; | ||||
|  | ||||
|             if (location) { | ||||
|                 return this.getOriginalPath(utils.parseKeyString(location), path); | ||||
|             } else { | ||||
|                 return path; | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Uniquely identifies a domain object. | ||||
|   | ||||
| @@ -27,10 +27,16 @@ | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     @mixin legacyMessage() { | ||||
|         flex: 0 1 auto; | ||||
|         font-family: symbolsfont; | ||||
|         font-size: $messageIconD; // Singleton message in a dialog | ||||
|         margin-right: $interiorMarginLg; | ||||
|     } | ||||
|  | ||||
|     .c-message { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         padding: $interiorMarginLg; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-left: $interiorMarginLg; | ||||
| @@ -58,7 +64,44 @@ | ||||
|         &__title, | ||||
|         &__action-text { | ||||
|             font-size: 1.2em; // TEMP | ||||
|         } | ||||
|  | ||||
|         &--simple { | ||||
|             // Icon and text elements only | ||||
|             &:before { | ||||
|                 font-size: 30px !important; | ||||
|             } | ||||
|  | ||||
|             [class*='__text'] { | ||||
|                 font-size: 1.25em; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /************************** LEGACY */ | ||||
|         &.message-severity-info:before { | ||||
|             @include legacyMessage(); | ||||
|             content: $glyph-icon-info; | ||||
|             color: $colorInfo; | ||||
|         } | ||||
|  | ||||
|         &.message-severity-alert:before { | ||||
|             @include legacyMessage(); | ||||
|             content: $glyph-icon-alert-rect; | ||||
|             color: $colorWarningLo; | ||||
|         } | ||||
|  | ||||
|         &.message-severity-error:before { | ||||
|             @include legacyMessage(); | ||||
|             content: $glyph-icon-alert-triangle; | ||||
|             color: $colorWarningLo; | ||||
|         } | ||||
|  | ||||
|         // Messages in a list | ||||
|         .c-overlay__messages & { | ||||
|             padding: $interiorMarginLg; | ||||
|             &:before { | ||||
|                 font-size: $messageListIconD; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|   | ||||
| @@ -56,9 +56,11 @@ | ||||
|         } | ||||
|  | ||||
|         &__close-button { | ||||
|             $p: $interiorMarginSm; | ||||
|             $p: $interiorMargin; | ||||
|             border-radius: 100% !important; | ||||
|             color: $overlayColorFg; | ||||
|             display: inline-block; | ||||
|             font-size: 1.25em; | ||||
|             position: absolute; | ||||
|             top: $p; right: $p; | ||||
|         } | ||||
| @@ -151,6 +153,7 @@ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .t-dialog-sm .l-overlay-small, // Legacy dialog support | ||||
|         .l-overlay-fit { | ||||
|             .c-overlay__outer { | ||||
|                 @include overlaySizing(auto); | ||||
|   | ||||
| @@ -280,7 +280,11 @@ define([ | ||||
|         if (!provider) { | ||||
|             return Promise.reject('No provider found'); | ||||
|         } | ||||
|         return provider.request.apply(provider, arguments); | ||||
|         return provider.request.apply(provider, arguments).catch((rejected) => { | ||||
|             this.openmct.notifications.error('Error requesting telemetry data, see console for details'); | ||||
|             console.error(rejected); | ||||
|             return Promise.reject(rejected); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -66,10 +66,11 @@ export default { | ||||
|  | ||||
|             this.items.splice(index, 1); | ||||
|         }, | ||||
|         reorder(oldIndex, newIndex) { | ||||
|             let objectAtOldIndex = this.items[oldIndex]; | ||||
|             this.$set(this.items, oldIndex, this.items[newIndex]); | ||||
|             this.$set(this.items, newIndex, objectAtOldIndex); | ||||
|         reorder(reorderPlan) { | ||||
|             let oldItems = this.items.slice(); | ||||
|             reorderPlan.forEach((reorderEvent) => { | ||||
|                 this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|   | ||||
| @@ -93,11 +93,11 @@ | ||||
|             this.primaryTelemetryObjects.splice(index,1); | ||||
|             primary = undefined; | ||||
|         }, | ||||
|         reorderPrimary(oldIndex, newIndex) { | ||||
|             let objectAtOldIndex = this.primaryTelemetryObjects[oldIndex]; | ||||
|             this.$set(this.primaryTelemetryObjects, oldIndex, this.primaryTelemetryObjects[newIndex]); | ||||
|             this.$set(this.primaryTelemetryObjects, newIndex, objectAtOldIndex); | ||||
|  | ||||
|         reorderPrimary(reorderPlan) { | ||||
|             let oldComposition = this.primaryTelemetryObjects.slice(); | ||||
|             reorderPlan.forEach(reorderEvent => { | ||||
|                 this.$set(this.primaryTelemetryObjects, reorderEvent.newIndex, oldComposition[reorderEvent.oldIndex]); | ||||
|             }); | ||||
|         }, | ||||
|         addSecondary(primary) { | ||||
|             return (domainObject) => { | ||||
|   | ||||
| @@ -28,11 +28,17 @@ define([], function () { | ||||
|             key: "layout", | ||||
|             description: "A toolbar for objects inside a display layout.", | ||||
|             forSelection: function (selection) { | ||||
|                 // Apply the layout toolbar if the selected object | ||||
|                 // is inside a layout, or the main layout is selected. | ||||
|                 return (selection && | ||||
|                     ((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') || | ||||
|                         (selection[0].context.item && selection[0].context.item.type === 'layout'))); | ||||
|                 if (!selection || selection.length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 let selectionPath = selection[0]; | ||||
|                 let selectedObject = selectionPath[0]; | ||||
|                 let selectedParent = selectionPath[1]; | ||||
|  | ||||
|                 // Apply the layout toolbar if the selected object is inside a layout, or the main layout is selected. | ||||
|                 return (selectedParent && selectedParent.context.item && selectedParent.context.item.type === 'layout') || | ||||
|                     (selectedObject.context.item && selectedObject.context.item.type === 'layout'); | ||||
|             }, | ||||
|             toolbar: function (selection) { | ||||
|                 const DIALOG_FORM = { | ||||
| @@ -73,190 +79,72 @@ define([], function () { | ||||
|                     return openmct.$injector.get('dialogService').getUserInput(form, {}); | ||||
|                 } | ||||
|  | ||||
|                 function getPath() { | ||||
|                     return `configuration.items[${selection[0].context.index}]`; | ||||
|                 function getPath(selectionPath) { | ||||
|                     return `configuration.items[${selectionPath[0].context.index}]`; | ||||
|                 } | ||||
|  | ||||
|                 let selectedParent = selection[1] && selection[1].context.item, | ||||
|                     selectedObject = selection[0].context.item, | ||||
|                     layoutItem = selection[0].context.layoutItem, | ||||
|                     toolbar = []; | ||||
|  | ||||
|                 if (selectedObject && selectedObject.type === 'layout') { | ||||
|                     toolbar.push({ | ||||
|                         control: "menu", | ||||
|                         domainObject: selectedObject, | ||||
|                         method: function (option) { | ||||
|                             let name = option.name.toLowerCase(); | ||||
|                             let form = DIALOG_FORM[name]; | ||||
|                             if (form) { | ||||
|                                 getUserInput(form) | ||||
|                                     .then(element => selection[0].context.addElement(name, element)); | ||||
|                             } else { | ||||
|                                 selection[0].context.addElement(name); | ||||
|                             } | ||||
|                         }, | ||||
|                         key: "add", | ||||
|                         icon: "icon-plus", | ||||
|                         label: "Add", | ||||
|                         options: [ | ||||
|                             { | ||||
|                                 "name": "Box", | ||||
|                                 "class": "icon-box-round-corners" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "name": "Line", | ||||
|                                 "class": "icon-line-horz" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "name": "Text", | ||||
|                                 "class": "icon-font" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "name": "Image", | ||||
|                                 "class": "icon-image" | ||||
|                             } | ||||
|                         ] | ||||
|                 function getAllTypes(selection) { | ||||
|                     return selection.filter(selectionPath => { | ||||
|                         let type = selectionPath[0].context.layoutItem.type; | ||||
|                         return type === 'text-view' || | ||||
|                             type === 'telemetry-view' || | ||||
|                             type === 'box-view' || | ||||
|                             type === 'image-view' || | ||||
|                             type === 'line-view' || | ||||
|                             type === 'subobject-view'; | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (!layoutItem) { | ||||
|                     return toolbar; | ||||
|                 } | ||||
|  | ||||
|                 let separator = { | ||||
|                     control: "separator" | ||||
|                 }; | ||||
|                 let remove = { | ||||
|                     control: "button", | ||||
|                     domainObject: selectedParent, | ||||
|                     icon: "icon-trash", | ||||
|                     title: "Delete the selected object", | ||||
|                     method: function () { | ||||
|                         let removeItem = selection[1].context.removeItem; | ||||
|                         let prompt = openmct.overlays.dialog({ | ||||
|                             iconClass: 'alert', | ||||
|                             message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, | ||||
|                             buttons: [ | ||||
|                 function getAddButton(selection, selectionPath) { | ||||
|                     if (selection.length === 1) { | ||||
|                         selectionPath = selectionPath || selection[0]; | ||||
|                         return { | ||||
|                             control: "menu", | ||||
|                             domainObject: selectionPath[0].context.item, | ||||
|                             method: function (option) { | ||||
|                                 let name = option.name.toLowerCase(); | ||||
|                                 let form = DIALOG_FORM[name]; | ||||
|                                 if (form) { | ||||
|                                     getUserInput(form) | ||||
|                                         .then(element => selectionPath[0].context.addElement(name, element)); | ||||
|                                 } else { | ||||
|                                     selectionPath[0].context.addElement(name); | ||||
|                                 } | ||||
|                             }, | ||||
|                             key: "add", | ||||
|                             icon: "icon-plus", | ||||
|                             label: "Add", | ||||
|                             options: [ | ||||
|                                 { | ||||
|                                     label: 'Ok', | ||||
|                                     emphasis: 'true', | ||||
|                                     callback: function () { | ||||
|                                         removeItem(layoutItem, selection[0].context.index); | ||||
|                                         prompt.dismiss(); | ||||
|                                     } | ||||
|                                     "name": "Box", | ||||
|                                     "class": "icon-box-round-corners" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     label: 'Cancel', | ||||
|                                     callback: function () { | ||||
|                                         prompt.dismiss(); | ||||
|                                     } | ||||
|                                     "name": "Line", | ||||
|                                     "class": "icon-line-horz" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Text", | ||||
|                                     "class": "icon-font" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Image", | ||||
|                                     "class": "icon-image" | ||||
|                                 } | ||||
|                             ] | ||||
|                         }); | ||||
|                         }; | ||||
|                     } | ||||
|                 }; | ||||
|                 let stackOrder = { | ||||
|                     control: "menu", | ||||
|                     domainObject: selectedParent, | ||||
|                     icon: "icon-layers", | ||||
|                     title: "Move the selected object above or below other objects", | ||||
|                     options: [ | ||||
|                         { | ||||
|                             name: "Move to Top", | ||||
|                             value: "top", | ||||
|                             class: "icon-arrow-double-up" | ||||
|                         }, | ||||
|                         { | ||||
|                             name: "Move Up", | ||||
|                             value: "up", | ||||
|                             class: "icon-arrow-up" | ||||
|                         }, | ||||
|                         { | ||||
|                             name: "Move Down", | ||||
|                             value: "down", | ||||
|                             class: "icon-arrow-down" | ||||
|                         }, | ||||
|                         { | ||||
|                             name: "Move to Bottom", | ||||
|                             value: "bottom", | ||||
|                             class: "icon-arrow-double-down" | ||||
|                         } | ||||
|                     ], | ||||
|                     method: function (option) { | ||||
|                         selection[1].context.orderItem(option.value, selection[0].context.index); | ||||
|                     } | ||||
|                 }; | ||||
|                 let useGrid = { | ||||
|                     control: "toggle-button", | ||||
|                     domainObject: selectedParent, | ||||
|                     property: function () { | ||||
|                         return getPath() + ".useGrid"; | ||||
|                     }, | ||||
|                     options: [ | ||||
|                         { | ||||
|                             value: false, | ||||
|                             icon: "icon-grid-snap-to", | ||||
|                             title: "Grid snapping enabled" | ||||
|                         }, | ||||
|                         { | ||||
|                             value: true, | ||||
|                             icon: "icon-grid-snap-no", | ||||
|                             title: "Grid snapping disabled" | ||||
|                         } | ||||
|                     ] | ||||
|                 }; | ||||
|                 let x = { | ||||
|                     control: "input", | ||||
|                     type: "number", | ||||
|                     domainObject: selectedParent, | ||||
|                     property: function () { | ||||
|                         return getPath() + ".x"; | ||||
|                     }, | ||||
|                     label: "X:", | ||||
|                     title: "X position" | ||||
|                 }, | ||||
|                 y = { | ||||
|                     control: "input", | ||||
|                     type: "number", | ||||
|                     domainObject: selectedParent, | ||||
|                     property: function () { | ||||
|                         return getPath() + ".y"; | ||||
|                     }, | ||||
|                     label: "Y:", | ||||
|                     title: "Y position", | ||||
|                 }, | ||||
|                 width = { | ||||
|                     control: 'input', | ||||
|                     type: 'number', | ||||
|                     domainObject: selectedParent, | ||||
|                     property: function () { | ||||
|                         return getPath() + ".width"; | ||||
|                     }, | ||||
|                     label: 'W:', | ||||
|                     title: 'Resize object width' | ||||
|                 }, | ||||
|                 height = { | ||||
|                     control: 'input', | ||||
|                     type: 'number', | ||||
|                     domainObject: selectedParent, | ||||
|                     property: function () { | ||||
|                         return getPath() + ".height"; | ||||
|                     }, | ||||
|                     label: 'H:', | ||||
|                     title: 'Resize object height' | ||||
|                 }; | ||||
|                 } | ||||
|  | ||||
|                 if (layoutItem.type === 'subobject-view') { | ||||
|                     if (toolbar.length > 0) { | ||||
|                         toolbar.push(separator); | ||||
|                     } | ||||
|  | ||||
|                     toolbar.push({ | ||||
|                 function getToggleFrameButton(selectedParent, selection) { | ||||
|                     return { | ||||
|                         control: "toggle-button", | ||||
|                         domainObject: selectedParent, | ||||
|                         property: function () { | ||||
|                             return getPath() + ".hasFrame"; | ||||
|                         applicableSelectedItems: selection.filter(selectionPath =>  | ||||
|                             selectionPath[0].context.layoutItem.type === 'subobject-view' | ||||
|                         ), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath) + ".hasFrame"; | ||||
|                         }, | ||||
|                         options: [ | ||||
|                             { | ||||
| @@ -270,52 +158,186 @@ define([], function () { | ||||
|                                 title: "Frame hidden" | ||||
|                             } | ||||
|                         ] | ||||
|                     }); | ||||
|                     toolbar.push(separator); | ||||
|                     toolbar.push(stackOrder); | ||||
|                     toolbar.push(x); | ||||
|                     toolbar.push(y); | ||||
|                     toolbar.push(width); | ||||
|                     toolbar.push(height); | ||||
|                     toolbar.push(useGrid); | ||||
|                     toolbar.push(separator); | ||||
|                     toolbar.push(remove); | ||||
|                 } else { | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getRemoveButton(selectedParent, selectionPath, selection) { | ||||
|                     return { | ||||
|                         control: "button", | ||||
|                         domainObject: selectedParent, | ||||
|                         icon: "icon-trash", | ||||
|                         title: "Delete the selected object", | ||||
|                         method: function () { | ||||
|                             let removeItem = selectionPath[1].context.removeItem; | ||||
|                             let prompt = openmct.overlays.dialog({ | ||||
|                                 iconClass: 'alert', | ||||
|                                 message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, | ||||
|                                 buttons: [ | ||||
|                                     { | ||||
|                                         label: 'Ok', | ||||
|                                         emphasis: 'true', | ||||
|                                         callback: function () { | ||||
|                                             removeItem(getAllTypes(selection)); | ||||
|                                             prompt.dismiss(); | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         label: 'Cancel', | ||||
|                                         callback: function () { | ||||
|                                             prompt.dismiss(); | ||||
|                                         } | ||||
|                                     } | ||||
|                                 ] | ||||
|                             }); | ||||
|                         } | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getStackOrder(selectedParent, selectionPath) { | ||||
|                     return { | ||||
|                         control: "menu", | ||||
|                         domainObject: selectedParent, | ||||
|                         icon: "icon-layers", | ||||
|                         title: "Move the selected object above or below other objects", | ||||
|                         options: [ | ||||
|                             { | ||||
|                                 name: "Move to Top", | ||||
|                                 value: "top", | ||||
|                                 class: "icon-arrow-double-up" | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: "Move Up", | ||||
|                                 value: "up", | ||||
|                                 class: "icon-arrow-up" | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: "Move Down", | ||||
|                                 value: "down", | ||||
|                                 class: "icon-arrow-down" | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: "Move to Bottom", | ||||
|                                 value: "bottom", | ||||
|                                 class: "icon-arrow-double-down" | ||||
|                             } | ||||
|                         ], | ||||
|                         method: function (option) { | ||||
|                             selectionPath[1].context.orderItem(option.value, getAllTypes(selection)); | ||||
|                         } | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getXInput(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: getAllTypes(selection), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".x"; | ||||
|                             }, | ||||
|                             label: "X:", | ||||
|                             title: "X position" | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getYInput(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: getAllTypes(selection), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".y"; | ||||
|                             }, | ||||
|                             label: "Y:", | ||||
|                             title: "Y position", | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getWidthInput(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: 'input', | ||||
|                             type: 'number', | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: getAllTypes(selection), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".width"; | ||||
|                             }, | ||||
|                             label: 'W:', | ||||
|                             title: 'Resize object width' | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getHeightInput(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: 'input', | ||||
|                             type: 'number', | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: getAllTypes(selection), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".height"; | ||||
|                             }, | ||||
|                             label: 'H:', | ||||
|                             title: 'Resize object height' | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getX2Input(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                                 return selectionPath[0].context.layoutItem.type === 'line-view'; | ||||
|                             }), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".x2"; | ||||
|                             }, | ||||
|                             label: "X2:", | ||||
|                             title: "X2 position" | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getY2Input(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                                 return selectionPath[0].context.layoutItem.type === 'line-view'; | ||||
|                             }), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".y2"; | ||||
|                             }, | ||||
|                             label: "Y2:", | ||||
|                             title: "Y2 position", | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getTextSizeMenu(selectedParent, selection) { | ||||
|                     const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128]; | ||||
|                     let fill = { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         property: function () { | ||||
|                             return getPath() + ".fill"; | ||||
|                         }, | ||||
|                         icon: "icon-paint-bucket", | ||||
|                         title: "Set fill color" | ||||
|                     }, | ||||
|                     stroke = { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         property: function () { | ||||
|                             return getPath() + ".stroke"; | ||||
|                         }, | ||||
|                         icon: "icon-line-horz", | ||||
|                         title: "Set border color" | ||||
|                     }, | ||||
|                     color = { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         property: function () { | ||||
|                             return getPath() + ".color"; | ||||
|                         }, | ||||
|                         icon: "icon-font", | ||||
|                         mandatory: true, | ||||
|                         title: "Set text color", | ||||
|                         preventNone: true | ||||
|                     }, | ||||
|                     size = { | ||||
|                     return { | ||||
|                         control: "select-menu", | ||||
|                         domainObject: selectedParent, | ||||
|                         property: function () { | ||||
|                             return getPath() + ".size"; | ||||
|                         applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                             let type = selectionPath[0].context.layoutItem.type; | ||||
|                             return type === 'text-view' || type === 'telemetry-view'; | ||||
|                         }), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath) + ".size"; | ||||
|                         }, | ||||
|                         title: "Set text size", | ||||
|                         options: TEXT_SIZE.map(size => { | ||||
| @@ -324,13 +346,128 @@ define([], function () { | ||||
|                             }; | ||||
|                         }) | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                     if (layoutItem.type === 'telemetry-view') { | ||||
|                         let displayMode = { | ||||
|                 function getFillMenu(selectedParent, selection) { | ||||
|                     return { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                             let type = selectionPath[0].context.layoutItem.type; | ||||
|                             return type === 'text-view' || | ||||
|                                 type === 'telemetry-view' || | ||||
|                                 type === 'box-view'; | ||||
|                         }), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath) + ".fill"; | ||||
|                         }, | ||||
|                         icon: "icon-paint-bucket", | ||||
|                         title: "Set fill color" | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getStrokeMenu(selectedParent, selection) { | ||||
|                     return { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                             let type = selectionPath[0].context.layoutItem.type; | ||||
|                             return type === 'text-view' || | ||||
|                                 type === 'telemetry-view' || | ||||
|                                 type === 'box-view' || | ||||
|                                 type === 'image-view' || | ||||
|                                 type === 'line-view'; | ||||
|                         }), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath) + ".stroke"; | ||||
|                         }, | ||||
|                         icon: "icon-line-horz", | ||||
|                         title: "Set border color" | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getTextColorMenu(selectedParent, selection) { | ||||
|                     return { | ||||
|                         control: "color-picker", | ||||
|                         domainObject: selectedParent, | ||||
|                         applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                             let type = selectionPath[0].context.layoutItem.type; | ||||
|                             return type === 'text-view' || type === 'telemetry-view'; | ||||
|                         }), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath) + ".color"; | ||||
|                         }, | ||||
|                         icon: "icon-font", | ||||
|                         mandatory: true, | ||||
|                         title: "Set text color", | ||||
|                         preventNone: true | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getURLButton(selectedParent, selection) { | ||||
|                     return { | ||||
|                         control: "button", | ||||
|                         domainObject: selectedParent, | ||||
|                         applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                             return selectionPath[0].context.layoutItem.type === 'image-view'; | ||||
|                         }), | ||||
|                         property: function (selectionPath) { | ||||
|                             return getPath(selectionPath); | ||||
|                         }, | ||||
|                         icon: "icon-image", | ||||
|                         title: "Edit image properties", | ||||
|                         dialog: DIALOG_FORM['image'] | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function getTextButton(selectedParent, selection) { | ||||
|                         return { | ||||
|                             control: "button", | ||||
|                             domainObject: selectedParent, | ||||
|                             applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                                 return selectionPath[0].context.layoutItem.type === 'text-view'; | ||||
|                             }), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath); | ||||
|                             }, | ||||
|                             icon: "icon-gear", | ||||
|                             title: "Edit text properties", | ||||
|                             dialog: DIALOG_FORM['text'] | ||||
|                         }; | ||||
|                 } | ||||
|  | ||||
|                 function getTelemetryValueMenu(selectionPath, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "select-menu", | ||||
|                             domainObject: selectionPath[1].context.item, | ||||
|                             applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                                 return selectionPath[0].context.layoutItem.type === 'telemetry-view'; | ||||
|                             }), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".value"; | ||||
|                             }, | ||||
|                             title: "Set value", | ||||
|                             options: openmct.telemetry.getMetadata(selectionPath[0].context.item).values().map(value => { | ||||
|                                 return { | ||||
|                                     name: value.name, | ||||
|                                     value: value.key | ||||
|                                 } | ||||
|                             }) | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function getDisplayModeMenu(selectedParent, selection) { | ||||
|                     if (selection.length === 1) { | ||||
|                         return { | ||||
|                             control: "select-menu", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath() + ".displayMode"; | ||||
|                             applicableSelectedItems: selection.filter(selectionPath => { | ||||
|                                 return selectionPath[0].context.layoutItem.type === 'telemetry-view'; | ||||
|                             }), | ||||
|                             property: function (selectionPath) { | ||||
|                                 return getPath(selectionPath) + ".displayMode"; | ||||
|                             }, | ||||
|                             title: "Set display mode", | ||||
|                             options: [ | ||||
| @@ -347,146 +484,196 @@ define([], function () { | ||||
|                                     value: "value" | ||||
|                                 } | ||||
|                             ] | ||||
|                         }, | ||||
|                         value = { | ||||
|                             control: "select-menu", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath() + ".value"; | ||||
|                             }, | ||||
|                             title: "Set value", | ||||
|                             options: openmct.telemetry.getMetadata(selectedObject).values().map(value => { | ||||
|                                 return { | ||||
|                                     name: value.name, | ||||
|                                     value: value.key | ||||
|                                 } | ||||
|                             }) | ||||
|                         }; | ||||
|                         toolbar = [ | ||||
|                             displayMode, | ||||
|                             separator, | ||||
|                             value, | ||||
|                             separator, | ||||
|                             fill, | ||||
|                             stroke, | ||||
|                             color, | ||||
|                             separator, | ||||
|                             size, | ||||
|                             separator, | ||||
|                             stackOrder, | ||||
|                             x, | ||||
|                             y, | ||||
|                             height, | ||||
|                             width, | ||||
|                             useGrid, | ||||
|                             separator, | ||||
|                             remove | ||||
|                         ]; | ||||
|                     } else if (layoutItem.type === 'text-view') { | ||||
|                         let text = { | ||||
|                             control: "button", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath(); | ||||
|                             }, | ||||
|                             icon: "icon-gear", | ||||
|                             title: "Edit text properties", | ||||
|                             dialog: DIALOG_FORM['text'] | ||||
|                         }; | ||||
|                         toolbar = [ | ||||
|                             fill, | ||||
|                             stroke, | ||||
|                             separator, | ||||
|                             color, | ||||
|                             size, | ||||
|                             separator, | ||||
|                             stackOrder, | ||||
|                             x, | ||||
|                             y, | ||||
|                             height, | ||||
|                             width, | ||||
|                             useGrid, | ||||
|                             separator, | ||||
|                             text, | ||||
|                             separator, | ||||
|                             remove | ||||
|                         ]; | ||||
|                     } else if (layoutItem.type === 'box-view') { | ||||
|                         toolbar = [ | ||||
|                             fill, | ||||
|                             stroke, | ||||
|                             separator, | ||||
|                             stackOrder, | ||||
|                             x, | ||||
|                             y, | ||||
|                             height, | ||||
|                             width, | ||||
|                             useGrid, | ||||
|                             separator, | ||||
|                             remove | ||||
|                         ]; | ||||
|                     } else if (layoutItem.type === 'image-view') { | ||||
|                         let url = { | ||||
|                             control: "button", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath(); | ||||
|                             }, | ||||
|                             icon: "icon-image", | ||||
|                             title: "Edit image properties", | ||||
|                             dialog: DIALOG_FORM['image'] | ||||
|                         }; | ||||
|                         toolbar = [ | ||||
|                             stroke, | ||||
|                             separator, | ||||
|                             stackOrder, | ||||
|                             x, | ||||
|                             y, | ||||
|                             height, | ||||
|                             width, | ||||
|                             useGrid, | ||||
|                             separator, | ||||
|                             url, | ||||
|                             separator, | ||||
|                             remove | ||||
|                         ]; | ||||
|                     } else if (layoutItem.type === 'line-view') { | ||||
|                         let x2 = { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath() + ".x2"; | ||||
|                             }, | ||||
|                             label: "X2:", | ||||
|                             title: "X2 position" | ||||
|                         }, | ||||
|                         y2 = { | ||||
|                             control: "input", | ||||
|                             type: "number", | ||||
|                             domainObject: selectedParent, | ||||
|                             property: function () { | ||||
|                                 return getPath() + ".y2"; | ||||
|                             }, | ||||
|                             label: "Y2:", | ||||
|                             title: "Y2 position", | ||||
|                         }; | ||||
|                         toolbar = [ | ||||
|                             stroke, | ||||
|                             separator, | ||||
|                             stackOrder, | ||||
|                             x, | ||||
|                             y, | ||||
|                             x2, | ||||
|                             y2, | ||||
|                             useGrid, | ||||
|                             separator, | ||||
|                             remove | ||||
|                         ]; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return toolbar; | ||||
|                 function getSeparator() { | ||||
|                     return { | ||||
|                         control: "separator" | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 function isMainLayoutSelected(selectionPath) { | ||||
|                     let selectedObject = selectionPath[0].context.item; | ||||
|                     return selectedObject && selectedObject.type === 'layout' && | ||||
|                         !selectionPath[0].context.layoutItem; | ||||
|                 } | ||||
|  | ||||
|                 if (isMainLayoutSelected(selection[0])) { | ||||
|                     return [getAddButton(selection)]; | ||||
|                 } | ||||
|  | ||||
|                 let toolbar = { | ||||
|                     'add-menu': [], | ||||
|                     'toggle-frame': [], | ||||
|                     'display-mode': [], | ||||
|                     'telemetry-value': [], | ||||
|                     'style': [], | ||||
|                     'text-style': [], | ||||
|                     'position': [], | ||||
|                     'text': [], | ||||
|                     'url': [], | ||||
|                     'remove': [], | ||||
|                 }; | ||||
|  | ||||
|                 selection.forEach(selectionPath => { | ||||
|                     let selectedParent = selectionPath[1].context.item; | ||||
|                     let layoutItem = selectionPath[0].context.layoutItem; | ||||
|  | ||||
|                     if (layoutItem.type === 'subobject-view') { | ||||
|                         if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') { | ||||
|                             toolbar['add-menu'] = [getAddButton(selection, selectionPath)]; | ||||
|                         } | ||||
|                         if (toolbar['toggle-frame'].length === 0) { | ||||
|                             toolbar['toggle-frame'] = [getToggleFrameButton(selectedParent, selection)]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getHeightInput(selectedParent, selection), | ||||
|                                 getWidthInput(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'telemetry-view') { | ||||
|                         if (toolbar['display-mode'].length === 0) { | ||||
|                             toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selection)]; | ||||
|                         } | ||||
|                         if (toolbar['telemetry-value'].length === 0) { | ||||
|                             toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selection)]; | ||||
|                         } | ||||
|                         if (toolbar['style'].length < 2) { | ||||
|                             toolbar['style'] = [ | ||||
|                                 getFillMenu(selectedParent, selection), | ||||
|                                 getStrokeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['text-style'].length === 0) { | ||||
|                             toolbar['text-style'] = [ | ||||
|                                 getTextColorMenu(selectedParent, selection), | ||||
|                                 getTextSizeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getHeightInput(selectedParent, selection), | ||||
|                                 getWidthInput(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'text-view') { | ||||
|                         if (toolbar['style'].length < 2) { | ||||
|                             toolbar['style'] = [ | ||||
|                                 getFillMenu(selectedParent, selection), | ||||
|                                 getStrokeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['text-style'].length === 0) { | ||||
|                             toolbar['text-style'] = [ | ||||
|                                 getTextColorMenu(selectedParent, selection), | ||||
|                                 getTextSizeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getHeightInput(selectedParent, selection), | ||||
|                                 getWidthInput(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['text'].length === 0) { | ||||
|                             toolbar['text'] = [getTextButton(selectedParent, selection)]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'box-view') { | ||||
|                         if (toolbar['style'].length < 2) { | ||||
|                             toolbar['style'] = [ | ||||
|                                 getFillMenu(selectedParent, selection), | ||||
|                                 getStrokeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getHeightInput(selectedParent, selection), | ||||
|                                 getWidthInput(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'image-view') { | ||||
|                         if (toolbar['style'].length === 0) { | ||||
|                             toolbar['style'] = [ | ||||
|                                 getStrokeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getHeightInput(selectedParent, selection), | ||||
|                                 getWidthInput(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['url'].length === 0) { | ||||
|                             toolbar['url'] = [getURLButton(selectedParent, selection)]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'line-view') { | ||||
|                         if (toolbar['style'].length === 0) { | ||||
|                             toolbar['style'] = [ | ||||
|                                 getStrokeMenu(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['position'].length === 0) { | ||||
|                             toolbar['position'] = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|                                 getXInput(selectedParent, selection), | ||||
|                                 getYInput(selectedParent, selection), | ||||
|                                 getX2Input(selectedParent, selection), | ||||
|                                 getY2Input(selectedParent, selection) | ||||
|                             ]; | ||||
|                         } | ||||
|                         if (toolbar['remove'].length === 0) { | ||||
|                             toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)]; | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 let toolbarArray = Object.values(toolbar); | ||||
|                 return _.flatten(toolbarArray.reduce((accumulator, group, index) => { | ||||
|                     group = group.filter(control => control !== undefined); | ||||
|  | ||||
|                     if (group.length > 0) { | ||||
|                         accumulator.push(group); | ||||
|  | ||||
|                         if (index < toolbarArray.length - 1) { | ||||
|                             accumulator.push(getSeparator()); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     return accumulator; | ||||
|                 }, [])); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -95,7 +95,7 @@ define( | ||||
|          * @param {number[]} pixelDelta the offset from the | ||||
|          *        original position, in pixels | ||||
|          */ | ||||
|         LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) { | ||||
|         LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) { | ||||
|             var gridDelta = toGridDelta(this.gridSize, pixelDelta); | ||||
|             return { | ||||
|                 position: max(add( | ||||
| @@ -109,6 +109,16 @@ define( | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) { | ||||
|             var gridDelta = toGridDelta(this.gridSize, pixelDelta); | ||||
|             return { | ||||
|                 position: max(add( | ||||
|                     this.rawPosition.position, | ||||
|                     multiply(gridDelta, this.posFactor) | ||||
|                 ), [0, 0]) | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         return LayoutDrag; | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -23,7 +23,8 @@ | ||||
| <template> | ||||
|     <layout-frame :item="item" | ||||
|                   :grid-size="gridSize" | ||||
|                   @endDrag="(item, updates) => $emit('endDrag', item, updates)"> | ||||
|                   @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|                   @endMove="() => $emit('endMove')"> | ||||
|         <div class="c-box-view" | ||||
|              :style="style"> | ||||
|         </div> | ||||
| @@ -54,8 +55,7 @@ | ||||
|                 x: 1, | ||||
|                 y: 1, | ||||
|                 width: 10,  | ||||
|                 height: 5, | ||||
|                 useGrid: true | ||||
|                 height: 5 | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|   | ||||
| @@ -23,8 +23,11 @@ | ||||
| <template> | ||||
|     <div class="l-layout" | ||||
|          @dragover="handleDragOver" | ||||
|          @click="bypassSelection" | ||||
|          @drop="handleDrop"> | ||||
|          @click.capture="bypassSelection" | ||||
|          @drop="handleDrop" | ||||
|          :class="{ | ||||
|             'is-multi-selected': selectedLayoutItems.length > 1 | ||||
|             }"> | ||||
|         <!-- Background grid --> | ||||
|         <div class="l-layout__grid-holder c-grid"> | ||||
|             <div class="c-grid__x l-grid l-grid-x" | ||||
| @@ -39,18 +42,38 @@ | ||||
|                    :is="item.type" | ||||
|                    :item="item" | ||||
|                    :key="item.id" | ||||
|                    :gridSize="item.useGrid ? gridSize : [1, 1]" | ||||
|                    :gridSize="gridSize" | ||||
|                    :initSelect="initSelectIndex === index" | ||||
|                    :index="index" | ||||
|                    @endDrag="endDrag" | ||||
|         > | ||||
|                    :multiSelect="selectedLayoutItems.length > 1" | ||||
|                    @move="move" | ||||
|                    @endMove="endMove" | ||||
|                    @endLineResize='endLineResize'> | ||||
|         </component> | ||||
|         <edit-marquee v-if='showMarquee' | ||||
|                       :gridSize="gridSize" | ||||
|                       :selectedLayoutItems="selectedLayoutItems" | ||||
|                       @endResize="endResize"> | ||||
|         </edit-marquee> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     @mixin displayMarquee($c) { | ||||
|         > .c-frame-edit { | ||||
|             // All other frames | ||||
|             //@include test($c, 0.4); | ||||
|             display: block; | ||||
|         } | ||||
|         > .c-frame > .c-frame-edit { | ||||
|             // Line object frame | ||||
|             //@include test($c, 0.4); | ||||
|             display: block; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .l-layout { | ||||
|         @include abs(); | ||||
|         display: flex; | ||||
| @@ -70,7 +93,7 @@ | ||||
|         .l-shell__main-container { | ||||
|             &[s-selected], | ||||
|             &[s-selected-parent] { | ||||
|                 // Display grid in main layout holder when editing | ||||
|                 // Display grid and allow edit marquee to display in main layout holder when editing | ||||
|                 > .l-layout { | ||||
|                     background: $editUIGridColorBg; | ||||
|  | ||||
| @@ -84,7 +107,7 @@ | ||||
|         .l-layout__frame { | ||||
|             &[s-selected], | ||||
|             &[s-selected-parent] { | ||||
|                 // Display grid in nested layouts when editing | ||||
|                 // Display grid and allow edit marquee to display in nested layouts when editing | ||||
|                 > * > * > .l-layout { | ||||
|                     background: $editUIGridColorBg; | ||||
|                     box-shadow: inset $editUIGridColorFg 0 0 2px 1px; | ||||
| @@ -95,10 +118,21 @@ | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /*********************** EDIT MARQUEE CONTROL */ | ||||
|         *[s-selected-parent] { | ||||
|             > .l-layout { | ||||
|                 // When main shell layout is the parent | ||||
|                 @include displayMarquee(deeppink); | ||||
|             } | ||||
|             > * > * > * { | ||||
|                 // When a sub-layout is the parent | ||||
|                 @include displayMarquee(blue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
|     import uuid from 'uuid'; | ||||
|  | ||||
| @@ -108,6 +142,7 @@ | ||||
|     import TextView from './TextView.vue' | ||||
|     import LineView from './LineView.vue' | ||||
|     import ImageView from './ImageView.vue' | ||||
|     import EditMarquee from './EditMarquee.vue' | ||||
|  | ||||
|     const ITEM_TYPE_VIEW_MAP = { | ||||
|         'subobject-view': SubobjectView, | ||||
| @@ -123,9 +158,11 @@ | ||||
|         down: -1, | ||||
|         bottom: Number.NEGATIVE_INFINITY | ||||
|     }; | ||||
|  | ||||
|     const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/'; | ||||
|  | ||||
|     let components = ITEM_TYPE_VIEW_MAP; | ||||
|     components['edit-marquee'] = EditMarquee; | ||||
|  | ||||
|     function getItemDefinition(itemType, ...options) { | ||||
|         let itemView = ITEM_TYPE_VIEW_MAP[itemType]; | ||||
|  | ||||
| @@ -141,7 +178,8 @@ | ||||
|             let domainObject = JSON.parse(JSON.stringify(this.domainObject)); | ||||
|             return { | ||||
|                 internalDomainObject: domainObject, | ||||
|                 initSelectIndex: undefined | ||||
|                 initSelectIndex: undefined, | ||||
|                 selection: [] | ||||
|             }; | ||||
|         }, | ||||
|         computed: { | ||||
| @@ -150,82 +188,115 @@ | ||||
|             }, | ||||
|             layoutItems() { | ||||
|                 return this.internalDomainObject.configuration.items; | ||||
|             }, | ||||
|             selectedLayoutItems() { | ||||
|                 return this.layoutItems.filter(item => { | ||||
|                     return this.itemIsInCurrentSelection(item); | ||||
|                 }); | ||||
|             }, | ||||
|             showMarquee() { | ||||
|                 let selectionPath = this.selection[0]; | ||||
|                 let singleSelectedLine = this.selection.length === 1 && | ||||
|                     selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view'; | ||||
|                 return selectionPath && selectionPath.length > 1 && !singleSelectedLine; | ||||
|             } | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|         inject: ['openmct', 'options'], | ||||
|         props: ['domainObject'], | ||||
|         components: ITEM_TYPE_VIEW_MAP, | ||||
|         components: components, | ||||
|         methods: { | ||||
|             addElement(itemType, element) { | ||||
|                 this.addItem(itemType + '-view', element); | ||||
|             }, | ||||
|             setSelection(selection) { | ||||
|                 if (selection.length === 0) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (this.removeSelectionListener) { | ||||
|                     this.removeSelectionListener(); | ||||
|                 } | ||||
|  | ||||
|                 let itemIndex = selection[0].context.index; | ||||
|  | ||||
|                 if (itemIndex !== undefined) { | ||||
|                     this.attachSelectionListener(itemIndex); | ||||
|                 } | ||||
|                 this.selection = selection; | ||||
|             }, | ||||
|             attachSelectionListener(index) { | ||||
|                 let path = `configuration.items[${index}].useGrid`; | ||||
|                 this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) { | ||||
|                     let item = this.layoutItems[index]; | ||||
|  | ||||
|                     if (value) { | ||||
|                         item.x = Math.round(item.x / this.gridSize[0]); | ||||
|                         item.y = Math.round(item.y / this.gridSize[1]); | ||||
|                         item.width = Math.round(item.width / this.gridSize[0]); | ||||
|                         item.height = Math.round(item.height / this.gridSize[1]); | ||||
|  | ||||
|                         if (item.x2) { | ||||
|                             item.x2 = Math.round(item.x2 / this.gridSize[0]); | ||||
|                         } | ||||
|                         if (item.y2) { | ||||
|                             item.y2 = Math.round(item.y2 / this.gridSize[1]); | ||||
|                         } | ||||
|                     } else { | ||||
|                         item.x = this.gridSize[0] * item.x; | ||||
|                         item.y = this.gridSize[1] * item.y; | ||||
|                         item.width = this.gridSize[0] * item.width; | ||||
|                         item.height = this.gridSize[1] * item.height; | ||||
|  | ||||
|                         if (item.x2) { | ||||
|                             item.x2 = this.gridSize[0] * item.x2; | ||||
|                         } | ||||
|                         if (item.y2) { | ||||
|                             item.y2 = this.gridSize[1] * item.y2; | ||||
|                         } | ||||
|                     } | ||||
|                     item.useGrid = value; | ||||
|                     this.mutate(`configuration.items[${index}]`, item); | ||||
|                 }.bind(this)); | ||||
|             itemIsInCurrentSelection(item) { | ||||
|                 return this.selection.some(selectionPath => | ||||
|                     selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id); | ||||
|             }, | ||||
|             bypassSelection($event) { | ||||
|                 if (this.dragInProgress) { | ||||
|                     if ($event) { | ||||
|                         $event.stopImmediatePropagation(); | ||||
|                     } | ||||
|                     this.dragInProgress = false; | ||||
|                     return; | ||||
|                 } | ||||
|             }, | ||||
|             endDrag(item, updates) { | ||||
|             endLineResize(item, updates) { | ||||
|                 this.dragInProgress = true; | ||||
|                 setTimeout(function () { | ||||
|                     this.dragInProgress = false; | ||||
|                 }.bind(this), 0); | ||||
|  | ||||
|                 let index = this.layoutItems.indexOf(item); | ||||
|                 Object.assign(item, updates); | ||||
|                 this.mutate(`configuration.items[${index}]`, item); | ||||
|             }, | ||||
|             endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) { | ||||
|                 this.dragInProgress = true; | ||||
|                 this.layoutItems.forEach(item => { | ||||
|                     if (this.itemIsInCurrentSelection(item)) { | ||||
|                         let itemXInMarqueeSpace = item.x - marqueeStart.x; | ||||
|                         let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth); | ||||
|                         item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x; | ||||
|  | ||||
|                         let itemYInMarqueeSpace = item.y - marqueeStart.y; | ||||
|                         let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight); | ||||
|                         item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y; | ||||
|  | ||||
|                         if (item.x2) { | ||||
|                             let itemX2InMarqueeSpace = item.x2 - marqueeStart.x; | ||||
|                             let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth); | ||||
|                             item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x; | ||||
|                         } else { | ||||
|                             item.width = Math.round(item.width * scaleWidth); | ||||
|                         } | ||||
|  | ||||
|                         if (item.y2) { | ||||
|                             let itemY2InMarqueeSpace = item.y2 - marqueeStart.y; | ||||
|                             let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight); | ||||
|                             item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y; | ||||
|                         } else { | ||||
|                             item.height = Math.round(item.height * scaleHeight); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|                 this.mutate("configuration.items", this.layoutItems); | ||||
|             }, | ||||
|             move(gridDelta) { | ||||
|                 this.dragInProgress = true; | ||||
|  | ||||
|                 if (!this.initialPositions) { | ||||
|                     this.initialPositions = {}; | ||||
|                     _.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => { | ||||
|                         if (selectedItem.type === 'line-view') { | ||||
|                             this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2]; | ||||
|                         } else { | ||||
|                             this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y]; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 let layoutItems = this.layoutItems.map(item => { | ||||
|                     if (this.initialPositions[item.id]) { | ||||
|                         let startingPosition = this.initialPositions[item.id]; | ||||
|                         let [startingX, startingY, startingX2, startingY2] = startingPosition; | ||||
|                         item.x = startingX + gridDelta[0]; | ||||
|                         item.y = startingY + gridDelta[1]; | ||||
|  | ||||
|                         if (item.x2) { | ||||
|                             item.x2 = startingX2 + gridDelta[0]; | ||||
|                         } | ||||
|  | ||||
|                         if (item.y2) { | ||||
|                             item.y2 = startingY2 + gridDelta[1]; | ||||
|                         } | ||||
|                     } | ||||
|                     return item; | ||||
|                 }); | ||||
|             }, | ||||
|             endMove() { | ||||
|                 this.mutate('configuration.items', this.layoutItems); | ||||
|                 this.initialPositions = undefined; | ||||
|             }, | ||||
|             mutate(path, value) { | ||||
|                 this.openmct.objects.mutate(this.internalDomainObject, path, value); | ||||
|             }, | ||||
| @@ -283,9 +354,8 @@ | ||||
|                 } | ||||
|             }, | ||||
|             isTelemetry(domainObject) { | ||||
|                 if (this.openmct.telemetry.isTelemetryObject(domainObject) | ||||
|                     && domainObject.type !== 'summary-widget' | ||||
|                     && domainObject.type !== 'example.imagery') { | ||||
|                 if (this.openmct.telemetry.isTelemetryObject(domainObject) &&  | ||||
|                     !this.options.showAsView.includes(domainObject.type)) { | ||||
|                     return true; | ||||
|                 } else { | ||||
|                     return false; | ||||
| @@ -314,11 +384,15 @@ | ||||
|                     this.objectViewMap[keyString] = true; | ||||
|                 } | ||||
|             }, | ||||
|             removeItem(item, index) { | ||||
|             removeItem(selectedItems) { | ||||
|                 let indices = []; | ||||
|                 this.initSelectIndex = -1; | ||||
|                 this.layoutItems.splice(index, 1); | ||||
|                 selectedItems.forEach(selectedItem => { | ||||
|                     indices.push(selectedItem[0].context.index); | ||||
|                     this.untrackItem(selectedItem[0].context.layoutItem); | ||||
|                 }); | ||||
|                 _.pullAt(this.layoutItems, indices); | ||||
|                 this.mutate("configuration.items", this.layoutItems); | ||||
|                 this.untrackItem(item); | ||||
|                 this.$el.click(); | ||||
|             }, | ||||
|             untrackItem(item) { | ||||
| @@ -384,20 +458,74 @@ | ||||
|                 this.mutate("configuration.items", layoutItems); | ||||
|                 this.$el.click(); | ||||
|             }, | ||||
|             orderItem(position, index) { | ||||
|             orderItem(position, selectedItems) { | ||||
|                 let delta = ORDERS[position]; | ||||
|                 let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0); | ||||
|                 let item = this.layoutItems[index]; | ||||
|                 let indices = []; | ||||
|                 let newIndex = -1; | ||||
|                 let items = []; | ||||
|  | ||||
|                 if (newIndex !== index) { | ||||
|                     this.layoutItems.splice(index, 1); | ||||
|                     this.layoutItems.splice(newIndex, 0, item); | ||||
|                     this.mutate('configuration.items', this.layoutItems); | ||||
|                 Object.assign(items, this.layoutItems); | ||||
|                 this.selectedLayoutItems.forEach(selectedItem => { | ||||
|                     indices.push(this.layoutItems.indexOf(selectedItem)); | ||||
|                 }); | ||||
|                 indices.sort((a, b) => a - b); | ||||
|  | ||||
|                     if (this.removeSelectionListener) { | ||||
|                         this.removeSelectionListener(); | ||||
|                         this.attachSelectionListener(newIndex); | ||||
|                 if (position === 'top' || position === 'up') { | ||||
|                     indices.reverse(); | ||||
|                 } | ||||
|  | ||||
|                 if (position === 'top' || position === 'bottom') { | ||||
|                     this.moveToTopOrBottom(position, indices, items, delta); | ||||
|                 } else { | ||||
|                     this.moveUpOrDown(position, indices, items, delta); | ||||
|                 } | ||||
|  | ||||
|                 this.mutate('configuration.items', this.layoutItems); | ||||
|             }, | ||||
|             moveUpOrDown(position, indices, items, delta) { | ||||
|                 let previousItemIndex = -1; | ||||
|                 let newIndex = -1; | ||||
|  | ||||
|                 indices.forEach((itemIndex, index) => { | ||||
|                     let isAdjacentItemSelected = position === 'up' ? | ||||
|                         itemIndex + 1 === previousItemIndex : | ||||
|                         itemIndex - 1 === previousItemIndex; | ||||
|  | ||||
|                     if (index > 0 && isAdjacentItemSelected) { | ||||
|                         if (position === 'up') { | ||||
|                             newIndex -= 1; | ||||
|                         } else { | ||||
|                             newIndex += 1; | ||||
|                         } | ||||
|                     } else { | ||||
|                         newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0); | ||||
|                     } | ||||
|  | ||||
|                     previousItemIndex = itemIndex; | ||||
|                     this.updateItemOrder(newIndex, itemIndex, items); | ||||
|                 }); | ||||
|             }, | ||||
|             moveToTopOrBottom(position, indices, items, delta) { | ||||
|                 let newIndex = -1; | ||||
|  | ||||
|                 indices.forEach((itemIndex, index) => { | ||||
|                     if (index === 0) { | ||||
|                         newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0); | ||||
|                     } else { | ||||
|                         if (position === 'top') { | ||||
|                             newIndex -= 1; | ||||
|                         } else { | ||||
|                             newIndex += 1; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     this.updateItemOrder(newIndex, itemIndex, items); | ||||
|                 }); | ||||
|             }, | ||||
|             updateItemOrder(newIndex, itemIndex, items) { | ||||
|                 if (newIndex !== itemIndex) { | ||||
|                     this.layoutItems.splice(itemIndex, 1); | ||||
|                     this.layoutItems.splice(newIndex, 0, items[itemIndex]); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| @@ -413,14 +541,10 @@ | ||||
|             this.composition.load(); | ||||
|         }, | ||||
|         destroyed: function () { | ||||
|             this.openmct.off('change', this.setSelection); | ||||
|             this.openmct.selection.off('change', this.setSelection); | ||||
|             this.composition.off('add', this.addChild); | ||||
|             this.composition.off('remove', this.removeChild); | ||||
|             this.unlisten(); | ||||
|  | ||||
|             if (this.removeSelectionListener) { | ||||
|                 this.removeSelectionListener(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										233
									
								
								src/plugins/displayLayout/components/EditMarquee.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/plugins/displayLayout/components/EditMarquee.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| <template> | ||||
|         <!-- Resize handles --> | ||||
|         <div class="c-frame-edit" :style="style"> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--nw" | ||||
|                  @mousedown="startResize([1,1], [-1,-1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--ne" | ||||
|                  @mousedown="startResize([0,1], [1,-1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--sw" | ||||
|                  @mousedown="startResize([1,0], [-1,1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--se" | ||||
|                  @mousedown="startResize([0,0], [1,1], $event)"></div> | ||||
|         </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .c-frame-edit { | ||||
|         // In Layouts, this is the editing rect and handles | ||||
|         display: none; // Set to display: block in DisplayLayout.vue | ||||
|         pointer-events: none; | ||||
|         @include abs(); | ||||
|         border: $editMarqueeBorder; | ||||
|  | ||||
|         &__handle { | ||||
|             $d: 6px; | ||||
|             $o: floor($d * -0.5); | ||||
|             background: $editFrameColorHandleFg; | ||||
|             box-shadow: $editFrameColorHandleBg 0 0 0 2px; | ||||
|             pointer-events: all; | ||||
|             position: absolute; | ||||
|             width: $d; height: $d; | ||||
|             top: auto; right: auto; bottom: auto; left: auto; | ||||
|  | ||||
|             &:before { | ||||
|                 // Extended hit area | ||||
|                 @include abs(-10px); | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 z-index: 0; | ||||
|             } | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $editUIColor; | ||||
|             } | ||||
|  | ||||
|             &--nwse { | ||||
|                 cursor: nwse-resize; | ||||
|             } | ||||
|  | ||||
|             &--nw { | ||||
|                 cursor: nw-resize; | ||||
|                 left: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &--ne { | ||||
|                 cursor: ne-resize; | ||||
|                 right: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &--se { | ||||
|                 cursor: se-resize; | ||||
|                 right: $o; bottom: $o; | ||||
|             } | ||||
|  | ||||
|             &--sw { | ||||
|                 cursor: sw-resize; | ||||
|                 left: $o; bottom: $o; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
|     import LayoutDrag from './../LayoutDrag' | ||||
|  | ||||
|     export default { | ||||
|         inject: ['openmct'], | ||||
|         props: { | ||||
|             selectedLayoutItems: Array, | ||||
|             gridSize: Array | ||||
|         },         | ||||
|         data() { | ||||
|             return { | ||||
|                 dragPosition: undefined | ||||
|             } | ||||
|         }, | ||||
|         computed: { | ||||
|             style() { | ||||
|                 let x = Number.POSITIVE_INFINITY; | ||||
|                 let y = Number.POSITIVE_INFINITY; | ||||
|                 let width = Number.NEGATIVE_INFINITY; | ||||
|                 let height = Number.NEGATIVE_INFINITY; | ||||
|  | ||||
|                 this.selectedLayoutItems.forEach(item => { | ||||
|                     if (item.x2) { | ||||
|                         let lineWidth = Math.abs(item.x - item.x2); | ||||
|                         let lineMinX = Math.min(item.x, item.x2); | ||||
|                         x = Math.min(lineMinX, x); | ||||
|                         width = Math.max(lineWidth + lineMinX, width); | ||||
|                     } else { | ||||
|                         x = Math.min(item.x, x); | ||||
|                         width = Math.max(item.width + item.x, width); | ||||
|                     } | ||||
|  | ||||
|                     if (item.y2) { | ||||
|                         let lineHeight = Math.abs(item.y - item.y2); | ||||
|                         let lineMinY = Math.min(item.y, item.y2); | ||||
|                         y = Math.min(lineMinY, y); | ||||
|                         height = Math.max(lineHeight + lineMinY, height); | ||||
|                     } else { | ||||
|                         y = Math.min(item.y, y); | ||||
|                         height = Math.max(item.height + item.y, height); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 if (this.dragPosition) { | ||||
|                     [x, y] = this.dragPosition.position; | ||||
|                     [width, height] = this.dragPosition.dimensions; | ||||
|                 } else { | ||||
|                     width = width - x; | ||||
|                     height = height - y; | ||||
|                 } | ||||
|  | ||||
|                 this.marqueePosition = { | ||||
|                     x: x, | ||||
|                     y: y, | ||||
|                     width: width, | ||||
|                     height: height | ||||
|                 } | ||||
|                 return this.getMarqueeStyle(x, y, width, height); | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
|             getMarqueeStyle(x, y, width, height) { | ||||
|                 return { | ||||
|                     left: (this.gridSize[0] * x) + 'px', | ||||
|                     top: (this.gridSize[1] * y) + 'px', | ||||
|                     width: (this.gridSize[0] * width) + 'px', | ||||
|                     height: (this.gridSize[1] * height) + 'px' | ||||
|                 }; | ||||
|             }, | ||||
|             updatePosition(event) { | ||||
|                 let currentPosition = [event.pageX, event.pageY]; | ||||
|                 this.initialPosition = this.initialPosition || currentPosition; | ||||
|                 this.delta = currentPosition.map(function (value, index) { | ||||
|                     return value - this.initialPosition[index]; | ||||
|                 }.bind(this)); | ||||
|             }, | ||||
|             startResize(posFactor, dimFactor, event) { | ||||
|                 document.body.addEventListener('mousemove', this.continueResize); | ||||
|                 document.body.addEventListener('mouseup', this.endResize); | ||||
|                 this.marqueeStartPosition = { | ||||
|                     position: [this.marqueePosition.x, this.marqueePosition.y], | ||||
|                     dimensions: [this.marqueePosition.width, this.marqueePosition.height] | ||||
|                 }; | ||||
|                 this.updatePosition(event); | ||||
|                 this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize); | ||||
|                 event.preventDefault(); | ||||
|             }, | ||||
|             continueResize(event) { | ||||
|                 event.preventDefault(); | ||||
|                 this.updatePosition(event); | ||||
|                 this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta); | ||||
|             }, | ||||
|             endResize(event) { | ||||
|                 document.body.removeEventListener('mousemove', this.continueResize); | ||||
|                 document.body.removeEventListener('mouseup', this.endResize); | ||||
|                 this.continueResize(event); | ||||
|  | ||||
|                 let marqueeStartWidth = this.marqueeStartPosition.dimensions[0]; | ||||
|                 let marqueeStartHeight = this.marqueeStartPosition.dimensions[1]; | ||||
|                 let marqueeStartX = this.marqueeStartPosition.position[0]; | ||||
|                 let marqueeStartY = this.marqueeStartPosition.position[1]; | ||||
|  | ||||
|                 let marqueeEndX = this.dragPosition.position[0]; | ||||
|                 let marqueeEndY = this.dragPosition.position[1]; | ||||
|                 let marqueeEndWidth = this.dragPosition.dimensions[0]; | ||||
|                 let marqueeEndHeight = this.dragPosition.dimensions[1]; | ||||
|  | ||||
|                 let scaleWidth =  marqueeEndWidth / marqueeStartWidth; | ||||
|                 let scaleHeight =  marqueeEndHeight / marqueeStartHeight; | ||||
|  | ||||
|                 let marqueeStart = { | ||||
|                     x: marqueeStartX, | ||||
|                     y: marqueeStartY, | ||||
|                     height: marqueeStartWidth, | ||||
|                     width: marqueeStartHeight | ||||
|                 }; | ||||
|                 let marqueeEnd = { | ||||
|                     x: marqueeEndX, | ||||
|                     y: marqueeEndY, | ||||
|                     width: marqueeEndWidth, | ||||
|                     height: marqueeEndHeight | ||||
|                 }; | ||||
|                 let marqueeOffset = { | ||||
|                     x: marqueeEnd.x - marqueeStart.x, | ||||
|                     y: marqueeEnd.y - marqueeStart.y | ||||
|                 }; | ||||
|  | ||||
|                 this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset); | ||||
|                 this.dragPosition = undefined; | ||||
|                 this.initialPosition = undefined; | ||||
|                 this.marqueeStartPosition = undefined; | ||||
|                 this.delta = undefined; | ||||
|                 event.preventDefault(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| @@ -23,7 +23,8 @@ | ||||
| <template> | ||||
|     <layout-frame :item="item" | ||||
|                   :grid-size="gridSize" | ||||
|                   @endDrag="(item, updates) => $emit('endDrag', item, updates)"> | ||||
|                   @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|                   @endMove="() => $emit('endMove')"> | ||||
|         <div class="c-image-view" | ||||
|              :style="style"> | ||||
|         </div> | ||||
| @@ -56,8 +57,7 @@ | ||||
|                 y: 1, | ||||
|                 width: 10, | ||||
|                 height: 5, | ||||
|                 url: element.url, | ||||
|                 useGrid: true | ||||
|                 url: element.url | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|   | ||||
| @@ -24,25 +24,14 @@ | ||||
|     <div class="l-layout__frame c-frame" | ||||
|          :class="{ | ||||
|              'no-frame': !item.hasFrame, | ||||
|              'u-inspectable': inspectable, | ||||
|              'is-resizing': isResizing | ||||
|              'u-inspectable': inspectable | ||||
|          }" | ||||
|          :style="style"> | ||||
|  | ||||
|         <slot></slot> | ||||
|  | ||||
|         <!-- Drag handles --> | ||||
|         <div class="c-frame-edit"> | ||||
|             <div class="c-frame-edit__move" | ||||
|                  @mousedown="startDrag([1,1], [0,0], $event, 'move')"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--nw" | ||||
|                  @mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--ne" | ||||
|                  @mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--sw" | ||||
|                  @mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div> | ||||
|             <div class="c-frame-edit__handle c-frame-edit__handle--se" | ||||
|                  @mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div> | ||||
|         <div class="c-frame-edit__move" | ||||
|              @mousedown="startMove([1,1], [0,0], $event)"> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
| @@ -50,7 +39,7 @@ | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /******************************* FRAME */ | ||||
|     /******************* FRAME */ | ||||
|     .c-frame { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
| @@ -59,124 +48,15 @@ | ||||
|         > *:first-child { | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|  | ||||
|         &:not(.no-frame) { | ||||
|             background: $colorBodyBg; | ||||
|             border: $browseFrameBorder; | ||||
|             padding: $interiorMargin; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     .c-frame-edit { | ||||
|         // In Layouts, this is the editing rect and handles | ||||
|         // In Fixed Position, this is a wrapper element | ||||
|         @include abs(); | ||||
|     .c-frame-edit__move { | ||||
|         display: none; | ||||
|  | ||||
|         &__move { | ||||
|             @include abs(); | ||||
|             cursor: move; | ||||
|         } | ||||
|  | ||||
|         &__handle { | ||||
|             $d: 6px; | ||||
|             $o: floor($d * -0.5); | ||||
|             background: $editFrameColorHandleFg; | ||||
|             box-shadow: $editFrameColorHandleBg 0 0 0 2px; | ||||
|             display: none; // Set to block via s-selected selector | ||||
|             position: absolute; | ||||
|             width: $d; height: $d; | ||||
|             top: auto; right: auto; bottom: auto; left: auto; | ||||
|  | ||||
|             &:before { | ||||
|                 // Extended hit area | ||||
|                 @include abs(-10px); | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 z-index: 0; | ||||
|             } | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $editUIColor; | ||||
|             } | ||||
|  | ||||
|             &--nwse { | ||||
|                 cursor: nwse-resize; | ||||
|             } | ||||
|  | ||||
|             &--nw { | ||||
|                 cursor: nw-resize; | ||||
|                 left: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &--ne { | ||||
|                 cursor: ne-resize; | ||||
|                 right: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &--se { | ||||
|                 cursor: se-resize; | ||||
|                 right: $o; bottom: $o; | ||||
|             } | ||||
|  | ||||
|             &--sw { | ||||
|                 cursor: sw-resize; | ||||
|                 left: $o; bottom: $o; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-so-view.has-complex-content + .c-frame-edit { | ||||
|         // Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects | ||||
|         // Make the __move element a more affordable drag UI element | ||||
|         .c-frame-edit__move { | ||||
|             @include userSelectNone(); | ||||
|             background: $editFrameMovebarColorBg; | ||||
|             box-shadow: rgba(black, 0.2) 0 1px; | ||||
|             bottom: auto; | ||||
|             height: 0; // Height is set on hover on s-selected.c-frame | ||||
|             opacity: 0.8; | ||||
|             max-height: 100%; | ||||
|             overflow: hidden; | ||||
|             text-align: center; | ||||
|  | ||||
|             &:before { | ||||
|                 // Grippy | ||||
|                 $h: 4px; | ||||
|                 $tbOffset: ($editFrameMovebarH - $h) / 2; | ||||
|                 $lrOffset: 25%; | ||||
|                 @include grippy($editFrameMovebarColorFg); | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 position: absolute; | ||||
|                 top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset; | ||||
|             } | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $editFrameHovMovebarColorBg; | ||||
|                 &:before { @include grippy($editFrameHovMovebarColorFg); } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .is-editing { | ||||
|         /******************* STYLES FOR C-FRAME WHILE EDITING */ | ||||
|         .c-frame { | ||||
|             $moveBarOutDelay: 500ms; | ||||
|             &.no-frame { | ||||
|                 border: $editFrameBorder; // Base border style for a frame element while editing. | ||||
|             } | ||||
|  | ||||
|             &-edit { | ||||
|                 display: contents; | ||||
|             } | ||||
|  | ||||
|             &-edit__move, | ||||
|             .c-so-view { | ||||
|                 transition: $transOut; | ||||
|                 transition-delay: $moveBarOutDelay; | ||||
|             } | ||||
|  | ||||
|             &:not([s-selected]) { | ||||
|                 &:hover { | ||||
|                     border: $editFrameBorderHov; | ||||
| @@ -188,37 +68,110 @@ | ||||
|                 border: $editFrameSelectedBorder; | ||||
|                 box-shadow: $editFrameSelectedShdw; | ||||
|  | ||||
|                 > .c-frame-edit { | ||||
|                     [class*='__handle'] { | ||||
|                 .c-frame-edit__move { | ||||
|                     cursor: move; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /******************* DEFAULT STYLES FOR -EDIT__MOVE */ | ||||
|         // All object types | ||||
|         .c-frame-edit__move { | ||||
|             @include abs(); | ||||
|             display: block; | ||||
|         } | ||||
|  | ||||
|         // Has-complex-content objects | ||||
|         .c-so-view.has-complex-content { | ||||
|             transition: $transOut; | ||||
|             transition-delay: $moveBarOutDelay; | ||||
|  | ||||
|             > .c-so-view__local-controls { | ||||
|                 transition: transform 250ms ease-in-out; | ||||
|                 transition-delay: $moveBarOutDelay; | ||||
|             } | ||||
|  | ||||
|             + .c-frame-edit__move { | ||||
|                 display: none; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         .l-layout { | ||||
|             /******************* 0 - 1 ITEM SELECTED */ | ||||
|             &:not(.is-multi-selected) { | ||||
|                 > .l-layout__frame[s-selected] { | ||||
|                     > .c-so-view.has-complex-content { | ||||
|                         > .c-so-view__local-controls { | ||||
|                             transition: transform $transOutTime ease-in-out; | ||||
|                             transition-delay: $moveBarOutDelay; | ||||
|                         } | ||||
|  | ||||
|                         + .c-frame-edit__move { | ||||
|                             transition: $transOut; | ||||
|                             transition-delay: $moveBarOutDelay; | ||||
|                             @include userSelectNone(); | ||||
|                             background: $editFrameMovebarColorBg; | ||||
|                             box-shadow: rgba(black, 0.2) 0 1px; | ||||
|                             bottom: auto; | ||||
|                             display: block; | ||||
|                             height: 0; // Height is set on hover below | ||||
|                             opacity: 0.8; | ||||
|                             max-height: 100%; | ||||
|                             overflow: hidden; | ||||
|                             text-align: center; | ||||
|  | ||||
|                             &:before { | ||||
|                                 // Grippy | ||||
|                                 $h: 4px; | ||||
|                                 $tbOffset: ($editFrameMovebarH - $h) / 2; | ||||
|                                 $lrOffset: 25%; | ||||
|                                 @include grippy($editFrameMovebarColorFg); | ||||
|                                 content: ''; | ||||
|                                 display: block; | ||||
|                                 position: absolute; | ||||
|                                 top: $tbOffset; | ||||
|                                 right: $lrOffset; | ||||
|                                 bottom: $tbOffset; | ||||
|                                 left: $lrOffset; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     &:hover { | ||||
|                         > .c-so-view.has-complex-content { | ||||
|                             transition: $transIn; | ||||
|                             transition-delay: 0s; | ||||
|                             padding-top: $editFrameMovebarH + $interiorMarginSm; | ||||
|  | ||||
|                             > .c-so-view__local-controls { | ||||
|                                 transform: translateY($editFrameMovebarH); | ||||
|                                 transition: transform $transInTime ease-in-out; | ||||
|                                 transition-delay: 0s; | ||||
|                             } | ||||
|  | ||||
|                             + .c-frame-edit__move { | ||||
|                                 transition: $transIn; | ||||
|                                 transition-delay: 0s; | ||||
|                                 height: $editFrameMovebarH; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             /******************* > 1 ITEMS SELECTED */ | ||||
|             &.is-multi-selected { | ||||
|                 .l-layout__frame[s-selected] { | ||||
|                     > .c-so-view.has-complex-content + .c-frame-edit__move { | ||||
|                         display: block; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .l-layout__frame:not(.is-resizing) { | ||||
|             // Show and animate the __move bar for sub-object views with complex content | ||||
|             &:hover > .c-so-view.has-complex-content { | ||||
|                 // Move content down so the __move bar doesn't cover it. | ||||
|                 padding-top: $editFrameMovebarH; | ||||
|                 transition: $transIn; | ||||
|  | ||||
|                 &.c-so-view--no-frame { | ||||
|                     // Move content down with a bit more space | ||||
|                     padding-top: $editFrameMovebarH + $interiorMarginSm; | ||||
|                 } | ||||
|  | ||||
|                 // Show the move bar | ||||
|                 + .c-frame-edit .c-frame-edit__move { | ||||
|                     height: $editFrameMovebarH; | ||||
|                     transition: $transIn; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
|     import LayoutDrag from './../LayoutDrag' | ||||
|  | ||||
| @@ -228,21 +181,9 @@ | ||||
|             item: Object, | ||||
|             gridSize: Array | ||||
|         }, | ||||
|         data() { | ||||
|             return { | ||||
|                 dragPosition: undefined, | ||||
|                 isResizing: undefined | ||||
|             } | ||||
|         }, | ||||
|         computed: { | ||||
|             style() { | ||||
|                 let {x, y, width, height} = this.item; | ||||
|  | ||||
|                 if (this.dragPosition) { | ||||
|                     [x, y] = this.dragPosition.position; | ||||
|                     [width, height] = this.dragPosition.dimensions; | ||||
|                 } | ||||
|  | ||||
|                 return { | ||||
|                     left: (this.gridSize[0] * x) + 'px', | ||||
|                     top: (this.gridSize[1] * y) + 'px', | ||||
| @@ -264,36 +205,40 @@ | ||||
|                     return value - this.initialPosition[index]; | ||||
|                 }.bind(this)); | ||||
|             }, | ||||
|             startDrag(posFactor, dimFactor, event, type) { | ||||
|                 document.body.addEventListener('mousemove', this.continueDrag); | ||||
|                 document.body.addEventListener('mouseup', this.endDrag); | ||||
|  | ||||
|             startMove(posFactor, dimFactor, event) { | ||||
|                 document.body.addEventListener('mousemove', this.continueMove); | ||||
|                 document.body.addEventListener('mouseup', this.endMove); | ||||
|                 this.dragPosition = { | ||||
|                     position: [this.item.x, this.item.y], | ||||
|                     dimensions: [this.item.width, this.item.height] | ||||
|                     position: [this.item.x, this.item.y] | ||||
|                 }; | ||||
|                 this.updatePosition(event); | ||||
|                 this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize); | ||||
|                 this.isResizing = type === 'resize'; | ||||
|                 event.preventDefault(); | ||||
|             }, | ||||
|             continueDrag(event) { | ||||
|             continueMove(event) { | ||||
|                 event.preventDefault(); | ||||
|                 this.updatePosition(event); | ||||
|                 this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta); | ||||
|                 let newPosition = this.activeDrag.getAdjustedPosition(this.delta); | ||||
|  | ||||
|                 if (!_.isEqual(newPosition, this.dragPosition)) { | ||||
|                     this.dragPosition = newPosition; | ||||
|                     this.$emit('move', this.toGridDelta(this.delta)); | ||||
|                 } | ||||
|             }, | ||||
|             endDrag(event) { | ||||
|                 document.body.removeEventListener('mousemove', this.continueDrag); | ||||
|                 document.body.removeEventListener('mouseup', this.endDrag); | ||||
|                 this.continueDrag(event); | ||||
|                 let [x, y] = this.dragPosition.position; | ||||
|                 let [width, height] = this.dragPosition.dimensions; | ||||
|                 this.$emit('endDrag', this.item, {x, y, width, height}); | ||||
|             endMove(event) { | ||||
|                 document.body.removeEventListener('mousemove', this.continueMove); | ||||
|                 document.body.removeEventListener('mouseup', this.endMove); | ||||
|                 this.continueMove(event); | ||||
|                 this.$emit('endMove'); | ||||
|                 this.dragPosition = undefined; | ||||
|                 this.initialPosition = undefined; | ||||
|                 this.delta = undefined; | ||||
|                 this.isResizing = undefined; | ||||
|                 event.preventDefault(); | ||||
|             }, | ||||
|             toGridDelta(pixelDelta) { | ||||
|                 return pixelDelta.map((v, i) => { | ||||
|                     return Math.round(v / this.gridSize[i]); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -30,9 +30,9 @@ | ||||
|             </line> | ||||
|         </svg> | ||||
|  | ||||
|         <div class="c-frame-edit"> | ||||
|             <div class="c-frame-edit__move" | ||||
|                  @mousedown="startDrag($event)"></div> | ||||
|         <div class="c-frame-edit__move" | ||||
|              @mousedown="startDrag($event)"></div> | ||||
|         <div class="c-frame-edit" v-if="showFrameEdit"> | ||||
|             <div class="c-frame-edit__handle" | ||||
|                  :class="startHandleClass" | ||||
|                  @mousedown="startDrag($event, 'start')"></div> | ||||
| @@ -66,8 +66,7 @@ | ||||
|                 y: 10, | ||||
|                 x2: 10, | ||||
|                 y2: 5, | ||||
|                 stroke: '#717171', | ||||
|                 useGrid: true | ||||
|                 stroke: '#717171' | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
| @@ -76,24 +75,31 @@ | ||||
|             gridSize: Array, | ||||
|             initSelect: Boolean, | ||||
|             index: Number, | ||||
|             multiSelect: Boolean | ||||
|         }, | ||||
|         data() { | ||||
|             return { | ||||
|                 dragPosition: undefined | ||||
|                 dragPosition: undefined, | ||||
|                 dragging: undefined, | ||||
|                 selection: [] | ||||
|             }; | ||||
|         }, | ||||
|         computed: { | ||||
|             showFrameEdit() { | ||||
|                 let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem; | ||||
|                 return !this.multiSelect && layoutItem && layoutItem.id === this.item.id; | ||||
|             }, | ||||
|             position() { | ||||
|                 let {x, y, x2, y2} = this.item; | ||||
|                 if (this.dragPosition) { | ||||
|                 if (this.dragging && this.dragPosition) { | ||||
|                     ({x, y, x2, y2} = this.dragPosition); | ||||
|                 } | ||||
|                 return {x, y, x2, y2}; | ||||
|             }, | ||||
|             style() { | ||||
|                 let {x, y, x2, y2} = this.position; | ||||
|                 let width = this.gridSize[0] * Math.abs(x - x2); | ||||
|                 let height = this.gridSize[1] * Math.abs(y - y2); | ||||
|                 let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1); | ||||
|                 let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1); | ||||
|                 let left = this.gridSize[0] * Math.min(x, x2); | ||||
|                 let top = this.gridSize[1] * Math.min(y, y2); | ||||
|                 return { | ||||
| @@ -175,13 +181,27 @@ | ||||
|                 event.preventDefault(); | ||||
|                 let pxDeltaX = this.startPosition[0] - event.pageX; | ||||
|                 let pxDeltaY = this.startPosition[1] - event.pageY; | ||||
|                 this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY); | ||||
|                 let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY); | ||||
|  | ||||
|                 if (!this.dragging) { | ||||
|                     if (!_.isEqual(newPosition, this.dragPosition)) { | ||||
|                         let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]]; | ||||
|                         this.dragPosition = newPosition; | ||||
|                         this.$emit('move', this.toGridDelta(gridDelta)); | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.dragPosition = newPosition; | ||||
|                 } | ||||
|             }, | ||||
|             endDrag(event) { | ||||
|                 document.body.removeEventListener('mousemove', this.continueDrag); | ||||
|                 document.body.removeEventListener('mouseup', this.endDrag); | ||||
|                 let {x, y, x2, y2} = this.dragPosition; | ||||
|                 this.$emit('endDrag', this.item, {x, y, x2, y2}); | ||||
|                 if (!this.dragging) { | ||||
|                     this.$emit('endMove'); | ||||
|                 } else { | ||||
|                     this.$emit('endLineResize', this.item, {x, y, x2, y2}); | ||||
|                 } | ||||
|                 this.dragPosition = undefined; | ||||
|                 this.dragging = undefined; | ||||
|                 event.preventDefault(); | ||||
| @@ -191,6 +211,7 @@ | ||||
|                 let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]? | ||||
|                 let {x, y, x2, y2} = this.item; | ||||
|                 let dragPosition = {x, y, x2, y2}; | ||||
|  | ||||
|                 if (this.dragging === 'start') { | ||||
|                     dragPosition.x -= gridDeltaX; | ||||
|                     dragPosition.y -= gridDeltaY; | ||||
| @@ -205,6 +226,14 @@ | ||||
|                     dragPosition.y2 -= gridDeltaY; | ||||
|                 } | ||||
|                 return dragPosition; | ||||
|             }, | ||||
|             setSelection(selection) { | ||||
|                 this.selection = selection; | ||||
|             }, | ||||
|             toGridDelta(pixelDelta) { | ||||
|                 return pixelDelta.map((v, i) => { | ||||
|                     return Math.round(v / this.gridSize[i]); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         watch: { | ||||
| @@ -217,6 +246,7 @@ | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.openmct.selection.on('change', this.setSelection); | ||||
|             this.context = { | ||||
|                 layoutItem: this.item, | ||||
|                 index: this.index | ||||
| @@ -228,6 +258,7 @@ | ||||
|             if (this.removeSelectable) { | ||||
|                 this.removeSelectable(); | ||||
|             } | ||||
|             this.openmct.selection.off('change', this.setSelection); | ||||
|         } | ||||
|     } | ||||
|  </script> | ||||
|   | ||||
| @@ -22,7 +22,9 @@ | ||||
| <template> | ||||
|     <layout-frame :item="item" | ||||
|                   :grid-size="gridSize" | ||||
|                   @endDrag="(item, updates) => $emit('endDrag', item, updates)"> | ||||
|                   :title="domainObject && domainObject.name" | ||||
|                   @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|                   @endMove="() => $emit('endMove')"> | ||||
|         <object-frame v-if="domainObject" | ||||
|                       :domain-object="domainObject" | ||||
|                       :object-path="objectPath" | ||||
| @@ -66,8 +68,7 @@ | ||||
|                 x: position[0], | ||||
|                 y: position[1], | ||||
|                 identifier: domainObject.identifier, | ||||
|                 hasFrame: hasFrameByDefault(domainObject.type), | ||||
|                 useGrid: true | ||||
|                 hasFrame: hasFrameByDefault(domainObject.type) | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|   | ||||
| @@ -23,7 +23,8 @@ | ||||
|  <template> | ||||
|      <layout-frame :item="item" | ||||
|                    :grid-size="gridSize" | ||||
|                    @endDrag="(item, updates) => $emit('endDrag', item, updates)"> | ||||
|                    @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|                    @endMove="() => $emit('endMove')"> | ||||
|         <div class="c-telemetry-view" | ||||
|              :style="styleObject" | ||||
|              v-if="domainObject"> | ||||
| @@ -96,10 +97,9 @@ | ||||
|                 displayMode: 'all', | ||||
|                 value: metadata.getDefaultDisplayValue(), | ||||
|                 stroke: "transparent", | ||||
|                 fill: "", | ||||
|                 fill: "transparent", | ||||
|                 color: "", | ||||
|                 size: "13px", | ||||
|                 useGrid: true | ||||
|                 size: "13px" | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
| @@ -176,7 +176,8 @@ | ||||
|                 let options = { | ||||
|                     start: bounds.start, | ||||
|                     end: bounds.end, | ||||
|                     size: 1 | ||||
|                     size: 1, | ||||
|                     strategy: 'latest' | ||||
|                 }; | ||||
|                 this.openmct.telemetry.request(this.domainObject, options) | ||||
|                     .then(data => { | ||||
|   | ||||
| @@ -23,7 +23,8 @@ | ||||
|  <template> | ||||
|     <layout-frame :item="item" | ||||
|                   :grid-size="gridSize" | ||||
|                   @endDrag="(item, updates) => $emit('endDrag', item, updates)"> | ||||
|                   @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|                   @endMove="() => $emit('endMove')"> | ||||
|         <div class="c-text-view" | ||||
|              :style="style"> | ||||
|             {{ item.text }} | ||||
| @@ -59,8 +60,7 @@ | ||||
|                 y: 1, | ||||
|                 width: 10, | ||||
|                 height: 5, | ||||
|                 text: element.text, | ||||
|                 useGrid: true | ||||
|                 text: element.text | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|   | ||||
| @@ -25,8 +25,7 @@ import Vue from 'vue' | ||||
| import objectUtils from '../../api/objects/object-utils.js' | ||||
| import DisplayLayoutType from './DisplayLayoutType.js' | ||||
| import DisplayLayoutToolbar from './DisplayLayoutToolbar.js' | ||||
|  | ||||
| export default function () { | ||||
| export default function DisplayLayoutPlugin(options) { | ||||
|     return function (openmct) { | ||||
|         openmct.objectViews.addProvider({ | ||||
|             key: 'layout.view', | ||||
| @@ -47,7 +46,8 @@ export default function () { | ||||
|                             template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>', | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectUtils | ||||
|                                 objectUtils, | ||||
|                                 options | ||||
|                             }, | ||||
|                             el: container, | ||||
|                             data () { | ||||
| @@ -60,6 +60,7 @@ export default function () { | ||||
|                     getSelectionContext() { | ||||
|                         return { | ||||
|                             item: domainObject, | ||||
|                             supportsMultiSelect: true, | ||||
|                             addElement: component && component.$refs.displayLayout.addElement, | ||||
|                             removeItem: component && component.$refs.displayLayout.removeItem, | ||||
|                             orderItem: component && component.$refs.displayLayout.orderItem | ||||
| @@ -83,5 +84,6 @@ export default function () { | ||||
|                 return true; | ||||
|             } | ||||
|         }); | ||||
|         DisplayLayoutPlugin._installed = true; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -42,7 +42,10 @@ | ||||
|  | ||||
|                 <!-- Checkbox list, NOT editing --> | ||||
|                 <template v-if="filter.possibleValues && !isEditing"> | ||||
|                     <span>{{persistedFilters[filter.comparator].join(', ')}}</span> | ||||
|                     <span  | ||||
|                         v-if="persistedFilters[filter.comparator]"> | ||||
|                         {{persistedFilters[filter.comparator].join(', ')}} | ||||
|                     </span> | ||||
|                 </template> | ||||
|             </div> | ||||
|         </li> | ||||
|   | ||||
| @@ -5,11 +5,11 @@ | ||||
|             <span class="c-disclosure-triangle is-enabled flex-elem" | ||||
|               :class="{'c-disclosure-triangle--expanded': expanded}"></span> | ||||
|             <div class="c-tree__item__label"> | ||||
|                 <div class="t-object-label l-flex-row flex-elem grows"> | ||||
|                     <div class="t-item-icon flex-elem" | ||||
|                 <div class="c-object-label"> | ||||
|                     <div class="c-object-label__type-icon" | ||||
|                          :class="objectCssClass"> | ||||
|                     </div> | ||||
|                     <div class="t-title-label flex-elem grows">{{ filterObject.name }}</div> | ||||
|                     <div class="c-object-label__name flex-elem grows">{{ filterObject.name }}</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
| @@ -69,16 +69,17 @@ export default { | ||||
|                 } | ||||
|             } else { | ||||
|                 if (!this.updatedFilters[key]) { | ||||
|                     this.updatedFilters[key] = {}; | ||||
|                     this.$set(this.updatedFilters, key, {}); | ||||
|                 } | ||||
|                 this.updatedFilters[key][comparator] = [value ? valueName : undefined]; | ||||
|                 this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]); | ||||
|             } | ||||
|  | ||||
|             this.$emit('updateFilters', this.keyString, this.updatedFilters); | ||||
|         }, | ||||
|         updateTextFilter(key, comparator, value) { | ||||
|             if (!this.updatedFilters[key]) { | ||||
|                 this.updatedFilters[key] = {}; | ||||
|                 this.$set(this.updatedFilters, key, {}); | ||||
|                 this.$set(this.updatedFilters[key], comparator, ''); | ||||
|             } | ||||
|             this.updatedFilters[key][comparator] = value; | ||||
|             this.$emit('updateFilters', this.keyString, this.updatedFilters); | ||||
|   | ||||
| @@ -23,17 +23,18 @@ export default { | ||||
|         FilterObject | ||||
|     }, | ||||
|     inject: [ | ||||
|         'openmct', | ||||
|         'providedObject' | ||||
|         'openmct' | ||||
|     ], | ||||
|     data() { | ||||
|         let providedObject = this.openmct.selection.get()[0][0].context.item; | ||||
|         let persistedFilters = {}; | ||||
|  | ||||
|         if (this.providedObject.configuration && this.providedObject.configuration.filters) { | ||||
|             persistedFilters = this.providedObject.configuration.filters; | ||||
|         if (providedObject.configuration && providedObject.configuration.filters) { | ||||
|             persistedFilters = providedObject.configuration.filters; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             providedObject, | ||||
|             persistedFilters, | ||||
|             children: {} | ||||
|         } | ||||
| @@ -73,13 +74,14 @@ export default { | ||||
|         this.composition.on('add', this.addChildren); | ||||
|         this.composition.on('remove', this.removeChildren); | ||||
|         this.composition.load(); | ||||
|  | ||||
|         this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters); | ||||
|         this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.composition.off('add', this.addChildren); | ||||
|         this.composition.off('remove', this.removeChildren); | ||||
|         this.unobserve(); | ||||
|         this.unobserveAllMutation(); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -33,23 +33,20 @@ define([ | ||||
|             key: 'filters-inspector', | ||||
|             name: 'Filters Inspector View', | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0) { | ||||
|                 if (selection.length === 0 || selection[0].length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 let object = selection[0].context.item; | ||||
|                 let object = selection[0][0].context.item; | ||||
|  | ||||
|                 return object && supportedObjectTypesArray.some(type => object.type === type); | ||||
|             }, | ||||
|             view: function (selection) { | ||||
|                 let component; | ||||
|                 let providedObject = selection[0].context.item; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component = new Vue({ | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 providedObject | ||||
|                                 openmct | ||||
|                             }, | ||||
|                             components: { | ||||
|                                 FiltersView: FiltersView.default | ||||
| @@ -59,8 +56,10 @@ define([ | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                         if (component) { | ||||
|                             component.$destroy(); | ||||
|                             component = undefined; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|   | ||||
| @@ -106,9 +106,6 @@ | ||||
|     .c-fl { | ||||
|         @include abs(); | ||||
|         display: flex; | ||||
|         flex-direction: column; // TEMP: only needed to support temp-toolbar element | ||||
|  | ||||
|         > * + * {  margin-top: $interiorMargin; } | ||||
|  | ||||
|         .temp-toolbar { | ||||
|             flex: 0 0 auto; | ||||
| @@ -116,7 +113,8 @@ | ||||
|  | ||||
|         &__container-holder { | ||||
|             display: flex; | ||||
|             flex: 1 1 100%; // Must needs to be 100% to work | ||||
|             flex: 1 1 100%; // Must be 100% to work | ||||
|             overflow: auto; | ||||
|  | ||||
|             // Columns by default | ||||
|             flex-direction: row; | ||||
| @@ -292,11 +290,6 @@ | ||||
|             margin-bottom: $interiorMargin; | ||||
|         } | ||||
|  | ||||
|         &__object-view { | ||||
|             flex: 1 1 auto; | ||||
|             overflow: auto; | ||||
|         } | ||||
|  | ||||
|         &__size-indicator { | ||||
|             $size: 35px; | ||||
|  | ||||
| @@ -422,6 +415,7 @@ import Container from '../utils/container'; | ||||
| import Frame from '../utils/frame'; | ||||
| import ResizeHandle from  './resizeHandle.vue'; | ||||
| import DropHint from './dropHint.vue'; | ||||
| import RemoveAction from '../../remove/RemoveAction.js'; | ||||
|  | ||||
| const MIN_CONTAINER_SIZE = 5; | ||||
|  | ||||
| @@ -513,7 +507,7 @@ export default { | ||||
|                 remove associated domainObjects from composition | ||||
|             */ | ||||
|             container.frames.forEach(f => { | ||||
|                 this.composition.remove({identifier: f.domainObjectIdentifier}); | ||||
|                 this.removeFromComposition(f.domainObjectIdentifier); | ||||
|             }); | ||||
|  | ||||
|             this.containers.splice(containerIndex, 1); | ||||
| @@ -528,6 +522,7 @@ export default { | ||||
|             } | ||||
|  | ||||
|             sizeToFill(this.containers); | ||||
|             this.setSelectionToParent(); | ||||
|             this.persist(); | ||||
|         }, | ||||
|         moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) { | ||||
| @@ -561,20 +556,23 @@ export default { | ||||
|         deleteFrame(frameId) { | ||||
|             let container = this.containers | ||||
|                 .filter(c => c.frames.some(f => f.id === frameId))[0]; | ||||
|             let containerIndex = this.containers.indexOf(container); | ||||
|             let frame = container | ||||
|                 .frames | ||||
|                 .filter((f => f.id === frameId))[0]; | ||||
|             let frameIndex = container.frames.indexOf(frame); | ||||
|  | ||||
|             /* | ||||
|                 remove associated domainObject from composition | ||||
|             */ | ||||
|             this.composition.remove({identifier: frame.domainObjectIdentifier}); | ||||
|  | ||||
|             container.frames.splice(frameIndex, 1); | ||||
|             sizeToFill(container.frames); | ||||
|             this.persist(containerIndex); | ||||
|             this.removeFromComposition(frame.domainObjectIdentifier) | ||||
|                 .then(() => { | ||||
|                     sizeToFill(container.frames) | ||||
|                     this.setSelectionToParent(); | ||||
|                 }); | ||||
|         }, | ||||
|         removeFromComposition(identifier) { | ||||
|             return this.openmct.objects.get(identifier).then((childDomainObject) => { | ||||
|                 this.RemoveAction.removeFromComposition(this.domainObject, childDomainObject); | ||||
|             }); | ||||
|         }, | ||||
|         setSelectionToParent() { | ||||
|             this.$el.click(); | ||||
|         }, | ||||
|         allowContainerDrop(event, index) { | ||||
|             if (!event.dataTransfer.types.includes('containerid')) { | ||||
| @@ -665,6 +663,8 @@ export default { | ||||
|         this.composition.on('remove', this.removeChildObject); | ||||
|         this.composition.on('add', this.addFrame); | ||||
|  | ||||
|         this.RemoveAction = new RemoveAction(this.openmct); | ||||
|  | ||||
|         this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|   | ||||
| @@ -79,12 +79,14 @@ export default { | ||||
|         }, | ||||
|         setSelection() { | ||||
|             this.$nextTick(function () { | ||||
|                 let childContext = this.$refs.objectFrame.getSelectionContext(); | ||||
|                 childContext.item = this.domainObject; | ||||
|                 childContext.type = 'frame'; | ||||
|                 childContext.frameId = this.frame.id; | ||||
|                 this.unsubscribeSelection = this.openmct.selection.selectable( | ||||
|                     this.$refs.frame, childContext, false); | ||||
|                 if (this.$refs && this.$refs.objectFrame) { | ||||
|                     let childContext = this.$refs.objectFrame.getSelectionContext(); | ||||
|                     childContext.item = this.domainObject; | ||||
|                     childContext.type = 'frame'; | ||||
|                     childContext.frameId = this.frame.id; | ||||
|                     this.unsubscribeSelection = this.openmct.selection.selectable( | ||||
|                         this.$refs.frame, childContext, false); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         initDrag(event) { | ||||
|   | ||||
| @@ -79,10 +79,12 @@ export default { | ||||
|     mounted() { | ||||
|         document.addEventListener('dragstart', this.setDragging); | ||||
|         document.addEventListener('dragend', this.unsetDragging); | ||||
|         document.addEventListener('drop', this.unsetDragging); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         document.removeEventListener('dragstart', this.setDragging); | ||||
|         document.removeEventListener('dragend', this.unsetDragging); | ||||
|         document.removeEventListener('drop', this.unsetDragging); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -27,28 +27,22 @@ function ToolbarProvider(openmct) { | ||||
|         key: "flex-layout", | ||||
|         description: "A toolbar for objects inside a Flexible Layout.", | ||||
|         forSelection: function (selection) { | ||||
|             let context = selection[0].context; | ||||
|             let context = selection[0][0].context; | ||||
|  | ||||
|             return (context && context.type && | ||||
|                 (context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame')); | ||||
|         }, | ||||
|         toolbar: function (selection) { | ||||
|  | ||||
|             let primary = selection[0], | ||||
|                 secondary = selection[1], | ||||
|                 tertiary = selection[2], | ||||
|             let selectionPath = selection[0], | ||||
|                 primary = selectionPath[0], | ||||
|                 secondary = selectionPath[1], | ||||
|                 tertiary = selectionPath[2], | ||||
|                 deleteFrame, | ||||
|                 toggleContainer, | ||||
|                 deleteContainer, | ||||
|                 addContainer, | ||||
|                 toggleFrame, | ||||
|                 separator; | ||||
|  | ||||
|             separator = { | ||||
|                 control: "separator", | ||||
|                 domainObject: selection[0].context.item, | ||||
|                 key: "separator" | ||||
|             }; | ||||
|                 toggleFrame; | ||||
|  | ||||
|             toggleContainer = { | ||||
|                 control: 'toggle-button', | ||||
| @@ -69,6 +63,12 @@ function ToolbarProvider(openmct) { | ||||
|                 ] | ||||
|             }; | ||||
|  | ||||
|             function getSeparator() { | ||||
|                 return { | ||||
|                     control: "separator" | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             if (primary.context.type === 'frame') { | ||||
|                 let frameId = primary.context.frameId; | ||||
|                 let layoutObject = tertiary.context.item; | ||||
| @@ -77,11 +77,11 @@ function ToolbarProvider(openmct) { | ||||
|                     .containers; | ||||
|                 let container = containers | ||||
|                     .filter(c => c.frames.some(f => f.id === frameId))[0]; | ||||
|                 let frame = container | ||||
|                 let containerIndex = containers.indexOf(container); | ||||
|                 let frame = container && container | ||||
|                     .frames | ||||
|                     .filter((f => f.id === frameId))[0]; | ||||
|                 let containerIndex = containers.indexOf(container); | ||||
|                 let frameIndex = container.frames.indexOf(frame); | ||||
|                 let frameIndex = container && container.frames.indexOf(frame); | ||||
|  | ||||
|                 deleteFrame = { | ||||
|                     control: "button", | ||||
| @@ -202,9 +202,9 @@ function ToolbarProvider(openmct) { | ||||
|             let toolbar = [ | ||||
|                 toggleContainer, | ||||
|                 addContainer, | ||||
|                 toggleFrame ? separator: undefined, | ||||
|                 toggleFrame ? getSeparator() : undefined, | ||||
|                 toggleFrame, | ||||
|                 deleteFrame || deleteContainer ? separator : undefined, | ||||
|                 deleteFrame || deleteContainer ? getSeparator() : undefined, | ||||
|                 deleteFrame, | ||||
|                 deleteContainer | ||||
|             ]; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|         </div> | ||||
|         <div class="c-grid-item__controls"> | ||||
|             <div class="icon-people" title='Shared'></div> | ||||
|             <button class="c-click-icon icon-info c-info-button" title='More Info'></button> | ||||
|             <button class="c-icon-button icon-info c-info-button" title='More Info'></button> | ||||
|             <div class="icon-pointer-right c-pointer-icon"></div> | ||||
|         </div> | ||||
|     </a> | ||||
|   | ||||
| @@ -14,6 +14,7 @@ export default { | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.composition = this.openmct.composition.get(this.domainObject); | ||||
|         this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|         if (!this.composition) { | ||||
|             return; | ||||
|         } | ||||
| @@ -34,7 +35,7 @@ export default { | ||||
|             this.items.push({ | ||||
|                 model: child, | ||||
|                 type: type.definition, | ||||
|                 isAlias: this.domainObject.identifier.key !== child.location, | ||||
|                 isAlias: this.keystring !== child.location, | ||||
|                 objectPath: [child].concat(this.openmct.router.path), | ||||
|                 objectKeyString: this.openmct.objects.makeKeyString(child.identifier) | ||||
|             }); | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|     </div> | ||||
|  | ||||
|     <div class="c-ne__local-controls--hidden"> | ||||
|         <button class="c-click-icon c-click-icon--major icon-trash" | ||||
|         <button class="c-icon-button c-icon-button--major icon-trash" | ||||
|            title="Delete this entry" | ||||
|            @click="deleteEntry"> | ||||
|         </button> | ||||
|   | ||||
| @@ -25,176 +25,235 @@ define([ | ||||
| ], function ( | ||||
|     uuid | ||||
| ) { | ||||
|     function isTelemetry(domainObject) { | ||||
|         if (openmct.telemetry.isTelemetryObject(domainObject) | ||||
|             && domainObject.type !== 'summary-widget' | ||||
|             && domainObject.type !== 'example.imagery') { | ||||
|             return true; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function migrateDisplayLayout(domainObject, childObjects) { | ||||
|         const DEFAULT_GRID_SIZE = [32, 32]; | ||||
|         let migratedObject = Object.assign({}, domainObject); | ||||
|         let panels = migratedObject.configuration.layout.panels; | ||||
|         let items = []; | ||||
|  | ||||
|         Object.keys(panels).forEach(key => { | ||||
|             let panel = panels[key]; | ||||
|             let domainObject = childObjects[key]; | ||||
|  | ||||
|             if (isTelemetry(domainObject)) { | ||||
|                 items.push({ | ||||
|                     width: panel.dimensions[0], | ||||
|                     height: panel.dimensions[1], | ||||
|                     x: panel.position[0], | ||||
|                     y: panel.position[1], | ||||
|                     useGrid: true, | ||||
|                     identifier: domainObject.identifier, | ||||
|                     id: uuid(), | ||||
|                     type: 'telemetry-view', | ||||
|                     displayMode: 'all', | ||||
|                     value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(), | ||||
|                     stroke: "transparent", | ||||
|                     fill: "", | ||||
|                     color: "", | ||||
|                     size: "13px" | ||||
|     return function Migrations(openmct) { | ||||
|         function getColumnNameKeyMap(domainObject) { | ||||
|             let composition = openmct.composition.get(domainObject); | ||||
|             if (composition) { | ||||
|                 return composition.load().then(composees => { | ||||
|                     return composees.reduce((nameKeyMap, composee) => { | ||||
|                         let metadata = openmct.telemetry.getMetadata(composee); | ||||
|                         if (metadata !== undefined) { | ||||
|                             metadata.values().forEach(value => { | ||||
|                                 nameKeyMap[value.name] = value.key; | ||||
|                             }); | ||||
|                         } | ||||
|                         return nameKeyMap; | ||||
|                     }, {}); | ||||
|                 }); | ||||
|             } else { | ||||
|                 return Promise.resolve([]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function isTelemetry(domainObject) { | ||||
|             if (openmct.telemetry.isTelemetryObject(domainObject) | ||||
|                 && domainObject.type !== 'summary-widget' | ||||
|                 && domainObject.type !== 'example.imagery') { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function migrateDisplayLayout(domainObject, childObjects) { | ||||
|             const DEFAULT_GRID_SIZE = [32, 32]; | ||||
|             let migratedObject = Object.assign({}, domainObject); | ||||
|             let panels = migratedObject.configuration.layout.panels; | ||||
|             let items = []; | ||||
|  | ||||
|             Object.keys(panels).forEach(key => { | ||||
|                 let panel = panels[key]; | ||||
|                 let domainObject = childObjects[key]; | ||||
|                 let identifier = undefined; | ||||
|  | ||||
|                 if (isTelemetry(domainObject)) { | ||||
|                     // If object is a telemetry point, convert it to a plot and | ||||
|                     // replace the object in migratedObject composition with the plot. | ||||
|                     identifier = { | ||||
|                         key: uuid(), | ||||
|                         namespace: migratedObject.identifier.namespace | ||||
|                     }; | ||||
|                     let plotObject = { | ||||
|                         identifier: identifier, | ||||
|                         location: domainObject.location, | ||||
|                         name: domainObject.name, | ||||
|                         type: "telemetry.plot.overlay" | ||||
|                     }; | ||||
|                     let plotType = openmct.types.get('telemetry.plot.overlay'); | ||||
|                     plotType.definition.initialize(plotObject); | ||||
|                     plotObject.composition.push(domainObject.identifier); | ||||
|                     openmct.objects.mutate(plotObject, 'persisted', Date.now()); | ||||
|  | ||||
|                     let keyString = openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                     let clonedComposition = Object.assign([], migratedObject.composition); | ||||
|                     clonedComposition.forEach((identifier, index) => { | ||||
|                         if (openmct.objects.makeKeyString(identifier) === keyString) { | ||||
|                             migratedObject.composition[index] = plotObject.identifier; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 items.push({ | ||||
|                     width: panel.dimensions[0], | ||||
|                     height: panel.dimensions[1], | ||||
|                     x: panel.position[0], | ||||
|                     y: panel.position[1], | ||||
|                     useGrid: true, | ||||
|                     identifier: domainObject.identifier, | ||||
|                     identifier: identifier || domainObject.identifier, | ||||
|                     id: uuid(), | ||||
|                     type: 'subobject-view', | ||||
|                     hasFrame: panel.hasFrame | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|             }); | ||||
|  | ||||
|         migratedObject.configuration.items = items; | ||||
|         migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE; | ||||
|         delete migratedObject.layoutGrid; | ||||
|         delete migratedObject.configuration.layout; | ||||
|         return migratedObject; | ||||
|     } | ||||
|  | ||||
|     function migrateFixedPositionConfiguration(elements, telemetryObjects) { | ||||
|         const DEFAULT_STROKE = "transparent"; | ||||
|         const DEFAULT_SIZE = "13px"; | ||||
|         const DEFAULT_COLOR = ""; | ||||
|         const DEFAULT_FILL = ""; | ||||
|         let items = []; | ||||
|  | ||||
|         elements.forEach(element => { | ||||
|             let item = { | ||||
|                 x: element.x, | ||||
|                 y: element.y, | ||||
|                 width: element.width, | ||||
|                 height: element.height, | ||||
|                 useGrid: element.useGrid, | ||||
|                 id: uuid() | ||||
|             }; | ||||
|  | ||||
|             if (element.type === "fixed.telemetry") { | ||||
|                 item.type = "telemetry-view"; | ||||
|                 item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                 item.fill = element.fill || DEFAULT_FILL; | ||||
|                 item.color = element.color || DEFAULT_COLOR; | ||||
|                 item.size = element.size || DEFAULT_SIZE; | ||||
|                 item.identifier = telemetryObjects[element.id].identifier; | ||||
|                 item.displayMode = element.titled ? 'all' : 'value'; | ||||
|                 item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue(); | ||||
|             } else if (element.type === 'fixed.box') { | ||||
|                 item.type = "box-view"; | ||||
|                 item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                 item.fill = element.fill || DEFAULT_FILL; | ||||
|             } else if (element.type === 'fixed.line') { | ||||
|                 item.type = "line-view"; | ||||
|                 item.x2 = element.x2; | ||||
|                 item.y2 = element.y2; | ||||
|                 item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                 delete item.height; | ||||
|                 delete item.width; | ||||
|             } else if (element.type === 'fixed.text') { | ||||
|                 item.type = "text-view"; | ||||
|                 item.text = element.text; | ||||
|                 item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                 item.fill = element.fill || DEFAULT_FILL; | ||||
|                 item.color = element.color || DEFAULT_COLOR; | ||||
|                 item.size = element.size || DEFAULT_SIZE; | ||||
|             } else if (element.type === 'fixed.image') { | ||||
|                 item.type = "image-view"; | ||||
|                 item.url =element.url; | ||||
|                 item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|             } | ||||
|  | ||||
|             items.push(item); | ||||
|         }); | ||||
|  | ||||
|         return items; | ||||
|     } | ||||
|  | ||||
|     return [ | ||||
|         { | ||||
|             check(domainObject) { | ||||
|                 return domainObject.type === 'layout' && domainObject.configuration.layout; | ||||
|             }, | ||||
|             migrate(domainObject) { | ||||
|                 let childObjects = {}; | ||||
|                 let promises = Object.keys(domainObject.configuration.layout.panels).map(key => { | ||||
|                     return openmct.objects.get(key) | ||||
|                         .then(object => { | ||||
|                             childObjects[key] = object; | ||||
|                         }); | ||||
|                 }); | ||||
|  | ||||
|                 return Promise.all(promises) | ||||
|                     .then(function () { | ||||
|                         return migrateDisplayLayout(domainObject, childObjects); | ||||
|                     }); | ||||
|             }, | ||||
|         }, | ||||
|         { | ||||
|             check(domainObject) { | ||||
|                 return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display']; | ||||
|             }, | ||||
|             migrate(domainObject) { | ||||
|                 const DEFAULT_GRID_SIZE = [64, 16]; | ||||
|                 let newLayoutObject = { | ||||
|                     identifier: domainObject.identifier, | ||||
|                     location: domainObject.location, | ||||
|                     name: domainObject.name, | ||||
|                     type: "layout" | ||||
|                 }; | ||||
|                 let layoutType = openmct.types.get('layout'); | ||||
|                 layoutType.definition.initialize(newLayoutObject); | ||||
|                 newLayoutObject.composition = domainObject.composition; | ||||
|                 newLayoutObject.configuration.layoutGrid = domainObject.layoutGrid || DEFAULT_GRID_SIZE; | ||||
|  | ||||
|                 let elements = domainObject.configuration['fixed-display'].elements; | ||||
|                 let telemetryObjects = {}; | ||||
|                 let promises = elements.map(element => { | ||||
|                     if (element.id) { | ||||
|                         return openmct.objects.get(element.id) | ||||
|                             .then(object => { | ||||
|                                 telemetryObjects[element.id] = object; | ||||
|                             }); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 return Promise.all(promises) | ||||
|                     .then(function () { | ||||
|                         newLayoutObject.configuration.items = | ||||
|                             migrateFixedPositionConfiguration(elements, telemetryObjects); | ||||
|                         return newLayoutObject; | ||||
|                     }); | ||||
|             } | ||||
|             migratedObject.configuration.items = items; | ||||
|             migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE; | ||||
|             delete migratedObject.layoutGrid; | ||||
|             delete migratedObject.configuration.layout; | ||||
|             return migratedObject; | ||||
|         } | ||||
|     ]; | ||||
|  | ||||
|         function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) { | ||||
|             const DEFAULT_STROKE = "transparent"; | ||||
|             const DEFAULT_SIZE = "13px"; | ||||
|             const DEFAULT_COLOR = ""; | ||||
|             const DEFAULT_FILL = ""; | ||||
|             let items = []; | ||||
|  | ||||
|             elements.forEach(element => { | ||||
|                 let item = { | ||||
|                     x: element.x, | ||||
|                     y: element.y, | ||||
|                     width: element.width, | ||||
|                     height: element.height, | ||||
|                     id: uuid() | ||||
|                 }; | ||||
|  | ||||
|                 if (!element.useGrid) { | ||||
|                     item.x = Math.round(item.x / gridSize[0]); | ||||
|                     item.y = Math.round(item.y / gridSize[1]); | ||||
|                     item.width = Math.round(item.width / gridSize[0]); | ||||
|                     item.height = Math.round(item.height / gridSize[1]); | ||||
|                 } | ||||
|  | ||||
|                 if (element.type === "fixed.telemetry") { | ||||
|                     item.type = "telemetry-view"; | ||||
|                     item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                     item.fill = element.fill || DEFAULT_FILL; | ||||
|                     item.color = element.color || DEFAULT_COLOR; | ||||
|                     item.size = element.size || DEFAULT_SIZE; | ||||
|                     item.identifier = telemetryObjects[element.id].identifier; | ||||
|                     item.displayMode = element.titled ? 'all' : 'value'; | ||||
|                     item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue(); | ||||
|                 } else if (element.type === 'fixed.box') { | ||||
|                     item.type = "box-view"; | ||||
|                     item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                     item.fill = element.fill || DEFAULT_FILL; | ||||
|                 } else if (element.type === 'fixed.line') { | ||||
|                     item.type = "line-view"; | ||||
|                     item.x2 = element.x2; | ||||
|                     item.y2 = element.y2; | ||||
|                     item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                     delete item.height; | ||||
|                     delete item.width; | ||||
|                 } else if (element.type === 'fixed.text') { | ||||
|                     item.type = "text-view"; | ||||
|                     item.text = element.text; | ||||
|                     item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                     item.fill = element.fill || DEFAULT_FILL; | ||||
|                     item.color = element.color || DEFAULT_COLOR; | ||||
|                     item.size = element.size || DEFAULT_SIZE; | ||||
|                 } else if (element.type === 'fixed.image') { | ||||
|                     item.type = "image-view"; | ||||
|                     item.url =element.url; | ||||
|                     item.stroke = element.stroke || DEFAULT_STROKE; | ||||
|                 } | ||||
|  | ||||
|                 items.push(item); | ||||
|             }); | ||||
|  | ||||
|             return items; | ||||
|         } | ||||
|  | ||||
|         return [ | ||||
|             { | ||||
|                 check(domainObject) { | ||||
|                     return domainObject.type === 'layout' && domainObject.configuration.layout; | ||||
|                 }, | ||||
|                 migrate(domainObject) { | ||||
|                     let childObjects = {}; | ||||
|                     let promises = Object.keys(domainObject.configuration.layout.panels).map(key => { | ||||
|                         return openmct.objects.get(key) | ||||
|                             .then(object => { | ||||
|                                 childObjects[key] = object; | ||||
|                             }); | ||||
|                     }); | ||||
|  | ||||
|                     return Promise.all(promises) | ||||
|                         .then(function () { | ||||
|                             return migrateDisplayLayout(domainObject, childObjects); | ||||
|                         }); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 check(domainObject) { | ||||
|                     return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display']; | ||||
|                 }, | ||||
|                 migrate(domainObject) { | ||||
|                     const DEFAULT_GRID_SIZE = [64, 16]; | ||||
|                     let newLayoutObject = { | ||||
|                         identifier: domainObject.identifier, | ||||
|                         location: domainObject.location, | ||||
|                         name: domainObject.name, | ||||
|                         type: "layout" | ||||
|                     }; | ||||
|                     let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE; | ||||
|                     let layoutType = openmct.types.get('layout'); | ||||
|                     layoutType.definition.initialize(newLayoutObject); | ||||
|                     newLayoutObject.composition = domainObject.composition; | ||||
|                     newLayoutObject.configuration.layoutGrid = gridSize; | ||||
|  | ||||
|                     let elements = domainObject.configuration['fixed-display'].elements; | ||||
|                     let telemetryObjects = {}; | ||||
|                     let promises = elements.map(element => { | ||||
|                         if (element.id) { | ||||
|                             return openmct.objects.get(element.id) | ||||
|                                 .then(object => { | ||||
|                                     telemetryObjects[element.id] = object; | ||||
|                                 }); | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     return Promise.all(promises) | ||||
|                         .then(function () { | ||||
|                             newLayoutObject.configuration.items = | ||||
|                                 migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize); | ||||
|                             return newLayoutObject; | ||||
|                         }); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 check(domainObject) { | ||||
|                     return domainObject.type === 'table' && | ||||
|                         domainObject.configuration.table; | ||||
|                 }, | ||||
|                 migrate(domainObject) { | ||||
|                     let currentTableConfiguration = domainObject.configuration.table || {}; | ||||
|                     let currentColumnConfiguration = currentTableConfiguration.columns || {}; | ||||
|                     return getColumnNameKeyMap(domainObject).then(nameKeyMap => { | ||||
|                         let hiddenColumns = Object.keys(currentColumnConfiguration).filter(columnName => { | ||||
|                             return currentColumnConfiguration[columnName] === false; | ||||
|                         }).reduce((hiddenColumnsMap, hiddenColumnName) => { | ||||
|                             let key = nameKeyMap[hiddenColumnName]; | ||||
|                             hiddenColumnsMap[key] = true; | ||||
|                             return hiddenColumnsMap; | ||||
|                         }, {}); | ||||
|  | ||||
|                         domainObject.configuration.hiddenColumns = hiddenColumns; | ||||
|                         delete domainObject.configuration.table; | ||||
|                         return domainObject; | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         ]; | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -20,19 +20,21 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import migrations from './Migrations.js' | ||||
| import Migrations from './Migrations.js' | ||||
|  | ||||
| export default function () { | ||||
|     function needsMigration(domainObject) {         | ||||
|         return migrations.some(m => m.check(domainObject)); | ||||
|     } | ||||
|  | ||||
|     function migrateObject(domainObject) { | ||||
|         return migrations.filter(m => m.check(domainObject))[0] | ||||
|             .migrate(domainObject); | ||||
|     } | ||||
|  | ||||
|     return function (openmct) { | ||||
|         let migrations = Migrations(openmct); | ||||
|  | ||||
|         function needsMigration(domainObject) { | ||||
|             return migrations.some(m => m.check(domainObject)); | ||||
|         } | ||||
|  | ||||
|         function migrateObject(domainObject) { | ||||
|             return migrations.filter(m => m.check(domainObject))[0] | ||||
|                 .migrate(domainObject); | ||||
|         } | ||||
|  | ||||
|         let wrappedFunction = openmct.objects.get; | ||||
|         openmct.objects.get = function migrate(identifier) { | ||||
|             return wrappedFunction.apply(openmct.objects, [identifier]) | ||||
| @@ -46,6 +48,6 @@ export default function () { | ||||
|                     } | ||||
|                     return object; | ||||
|                 }); | ||||
|         }         | ||||
|         } | ||||
|     }; | ||||
|  } | ||||
| } | ||||
|   | ||||
| @@ -136,7 +136,7 @@ | ||||
|             <span class="t-object-alert t-alert-unsynced" | ||||
|                   title="This plot is not currently displaying the latest data. | ||||
|                 Reset Pan/zoom to return to view latest data."></span> | ||||
|             <div class="gl-plot-display-area"> | ||||
|             <div class="gl-plot-display-area has-local-controls has-cursor-guides"> | ||||
|                 <mct-ticks axis="xAxis"> | ||||
|                     <div class="gl-plot-hash hash-v" | ||||
|                          ng-repeat="tick in ticks track by tick.value" | ||||
| @@ -147,7 +147,6 @@ | ||||
|                      </div> | ||||
|                 </mct-ticks> | ||||
|  | ||||
|  | ||||
|                 <mct-ticks axis="yAxis"> | ||||
|                      <div class="gl-plot-hash hash-h" | ||||
|                           ng-repeat="tick in ticks track by tick.value" | ||||
| @@ -155,7 +154,6 @@ | ||||
|                      </div> | ||||
|                 </mct-ticks> | ||||
|  | ||||
|  | ||||
|                 <mct-chart config="config" | ||||
|                            series="series" | ||||
|                            rectangles="rectangles" | ||||
| @@ -164,23 +162,37 @@ | ||||
|                            the-y-axis="yAxis"> | ||||
|                 </mct-chart> | ||||
|  | ||||
|                 <div class="h-local-controls h-local-controls-overlay-content" | ||||
|                      ng-show="plotHistory.length"> | ||||
|                     <div class="l-btn-set"> | ||||
|                         <a class="s-button icon-arrow-left" | ||||
|                            ng-click="plot.back()" | ||||
|                            title="Restore previous pan/zoom"> | ||||
|                         </a> | ||||
|                         <a class="s-button icon-reset" | ||||
|                            ng-click="plot.clear()" | ||||
|                            title="Reset pan/zoom"> | ||||
|                         </a> | ||||
|                 <div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"> | ||||
|                     <div class="c-button-set c-button-set--strip-h"> | ||||
|                         <button class="c-button icon-minus" | ||||
|                                 ng-click="plot.zoom('out', 0.2)" | ||||
|                                 title="Zoom out"> | ||||
|                         </button> | ||||
|                         <button class="c-button icon-plus" | ||||
|                                 ng-click="plot.zoom('in', 0.2)" | ||||
|                                 title="Zoom in"> | ||||
|                         </button> | ||||
|                     </div> | ||||
|                     <div class="c-button-set c-button-set--strip-h" | ||||
|                          ng-disabled="!plotHistory.length"> | ||||
|                         <button class="c-button icon-arrow-left" | ||||
|                                 ng-click="plot.back()" | ||||
|                                 title="Restore previous pan/zoom"> | ||||
|                         </button> | ||||
|                         <button class="c-button icon-reset" | ||||
|                                 ng-click="plot.clear()" | ||||
|                                 title="Reset pan/zoom"> | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <span class="t-wait-spinner loading" ng-show="plot.isRequestPending()"> | ||||
|                 </span> | ||||
|  | ||||
|                 <!--Cursor guides--> | ||||
|                 <div class="c-cursor-guide--v js-cursor-guide--v" | ||||
|                     ng-show="plot.cursorGuide"> | ||||
|                 </div> | ||||
|                 <div class="c-cursor-guide--h js-cursor-guide--h" | ||||
|                     ng-show="plot.cursorGuide"> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="gl-plot-axis-area gl-plot-x" | ||||
|   | ||||
| @@ -20,31 +20,34 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="PlotController as controller" | ||||
|     class="abs holder holder-plot has-control-bar" | ||||
|     ng-class="{ | ||||
|         'loading': !!pending | ||||
|     }" | ||||
|     > | ||||
|     class="abs holder holder-plot has-control-bar"> | ||||
|     <div class="l-control-bar" ng-show="!controller.hideExportButtons"> | ||||
|          <span class="c-button-set c-button-set--strip"> | ||||
|             <a class="c-button icon-download" | ||||
|          <span class="c-button-set c-button-set--strip-h"> | ||||
|             <button class="c-button icon-download" | ||||
|                ng-click="controller.exportPNG()" | ||||
|                title="Export This View's Data as PNG"> | ||||
|                  <span class="c-button__label">PNG</span> | ||||
|             </a> | ||||
|             <a class="c-button" | ||||
|             </button> | ||||
|             <button class="c-button" | ||||
|                ng-click="controller.exportJPG()" | ||||
|                title="Export This View's Data as JPG"> | ||||
|                  <span class="c-button__label">JPG</span> | ||||
|             </a> | ||||
|             </button> | ||||
|         </span> | ||||
|         <button class="c-button icon-crosshair" | ||||
|                 ng-class="{ 'is-active': controller.cursorGuide }" | ||||
|                 ng-click="controller.toggleCursorGuide($event)" | ||||
|                 title="Toggle cursor guides"> | ||||
|         </button> | ||||
|     </div> | ||||
|  | ||||
|     <div class="l-view-section"> | ||||
|         <div class="c-loading--overlay loading" | ||||
|             ng-show="!!pending"></div> | ||||
|         <mct-plot config="controller.config" | ||||
|                   series="series" | ||||
|                   the-y-axis="yAxis" | ||||
|                   the-x-axis="xAxis"> | ||||
|                   </mct-plot> | ||||
|         </mct-plot> | ||||
|     </div> | ||||
| </span> | ||||
|   | ||||
| @@ -20,32 +20,39 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="StackedPlotController as stackedPlot" | ||||
|       class="abs holder holder-plot has-control-bar t-plot-stacked" | ||||
|       ng-class="{ | ||||
|           'loading': !!currentRequest.pending | ||||
|       }"> | ||||
|  | ||||
|       class="abs holder holder-plot has-control-bar t-plot-stacked"> | ||||
|     <div class="l-control-bar" ng-show="!stackedPlot.hideExportButtons"> | ||||
|        <span class="c-button-set c-button-set--strip"> | ||||
|           <a class="c-button icon-download" | ||||
|        <span class="c-button-set c-button-set--strip-h"> | ||||
|           <button class="c-button icon-download" | ||||
|              ng-click="stackedPlot.exportPNG()" | ||||
|              title="Export This View's Data as PNG"> | ||||
|               <span class="c-button__label">PNG</span> | ||||
|           </a> | ||||
|           <a class="c-button" | ||||
|           </button> | ||||
|           <button class="c-button" | ||||
|              ng-click="stackedPlot.exportJPG()" | ||||
|              title="Export This View's Data as JPG"> | ||||
|               <span class="c-button__label">JPG</span> | ||||
|           </a> | ||||
|       </span> | ||||
|           </button> | ||||
|         </span> | ||||
|         <button class="c-button icon-crosshair" | ||||
|                 ng-class="{ 'is-active': stackedPlot.cursorGuide }" | ||||
|                 ng-click="stackedPlot.toggleCursorGuide($event)" | ||||
|                 title="Toggle cursor guides"> | ||||
|         </button> | ||||
|     </div> | ||||
|     <div class="l-view-section"> | ||||
|         <div class="gl-plot child-frame" | ||||
|         <div class="c-loading--overlay loading" | ||||
|              ng-show="!!currentRequest.pending"></div> | ||||
|         <div class="gl-plot child-frame u-inspectable" | ||||
|             ng-repeat="telemetryObject in telemetryObjects" | ||||
|             ng-class="{ | ||||
|                 's-status-timeconductor-unsynced': telemetryObject | ||||
|                     .getCapability('status') | ||||
|                     .get('timeconductor-unsynced') | ||||
|             }" | ||||
|             mct-selectable="{ | ||||
|                 item: telemetryObject.useCapability('adapter'), | ||||
|                 oldItem: telemetryObject | ||||
|             }"> | ||||
|             <mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot> | ||||
|         </div> | ||||
|   | ||||
| @@ -70,13 +70,15 @@ define([ | ||||
|         this.listenTo(this.$scope, '$destroy', this.destroy, this); | ||||
|         this.listenTo(config.series, 'add', this.addSeries, this); | ||||
|         this.listenTo(config.series, 'remove', this.resetAllSeries, this); | ||||
|  | ||||
|         config.series.forEach(this.addSeries, this); | ||||
|     }; | ||||
|  | ||||
|     PlotOptionsController.prototype.addSeries = function (series, index) { | ||||
|         this.$scope.plotSeries[index] = series; | ||||
|         series.locateOldObject(this.$scope.domainObject); | ||||
|  | ||||
|         this.$timeout(function () { | ||||
|             this.$scope.plotSeries[index] = series; | ||||
|             series.locateOldObject(this.$scope.domainObject); | ||||
|         }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     PlotOptionsController.prototype.resetAllSeries = function (series, index) { | ||||
|   | ||||
| @@ -78,6 +78,7 @@ define([ | ||||
|         this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); | ||||
|         this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this); | ||||
|  | ||||
|         this.watchForMarquee(); | ||||
|  | ||||
| @@ -92,6 +93,12 @@ define([ | ||||
|         this.$scope.series = this.config.series.models; | ||||
|         this.$scope.legend = this.config.legend; | ||||
|  | ||||
|         this.cursorGuideVertical = this.$element[0].querySelector('.js-cursor-guide--v'); | ||||
|         this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h'); | ||||
|         this.cursorGuide = false; | ||||
|  | ||||
|         this.listenTo(this.$scope, 'cursorguide', this.toggleCursorGuide, this); | ||||
|  | ||||
|         this.listenTo(this.$scope, '$destroy', this.destroy, this); | ||||
|         this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this); | ||||
|         this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this); | ||||
| @@ -143,6 +150,9 @@ define([ | ||||
|             y: this.yScale.invert(this.positionOverElement.y) | ||||
|         }; | ||||
|  | ||||
|         if (this.cursorGuide) { | ||||
|             this.updateCrosshairs($event); | ||||
|         } | ||||
|         this.highlightValues(this.positionOverPlot.x); | ||||
|         this.updateMarquee(); | ||||
|         this.updatePan(); | ||||
| @@ -150,6 +160,11 @@ define([ | ||||
|         $event.preventDefault(); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.updateCrosshairs = function ($event) { | ||||
|         this.cursorGuideVertical.style.left = ($event.clientX - this.chartElementBounds.x) + 'px'; | ||||
|         this.cursorGuideHorizontal.style.top = ($event.clientY - this.chartElementBounds.y) + 'px'; | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.trackChartElementBounds = function ($event) { | ||||
|         if ($event.target === this.$canvas[1]) { | ||||
|             this.chartElementBounds = $event.target.getBoundingClientRect(); | ||||
| @@ -266,6 +281,103 @@ define([ | ||||
|         this.marquee = undefined; | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) { | ||||
|         this.freeze(); | ||||
|         this.trackHistory(); | ||||
|         var currentXaxis = this.$scope.xAxis.get('displayRange'), | ||||
|             currentYaxis = this.$scope.yAxis.get('displayRange'), | ||||
|             xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor, | ||||
|             yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor; | ||||
|  | ||||
|         if (zoomDirection === 'in') { | ||||
|             this.$scope.xAxis.set('displayRange', { | ||||
|                 min: currentXaxis.min + xAxisDist, | ||||
|                 max: currentXaxis.max - xAxisDist | ||||
|             }); | ||||
|  | ||||
|             this.$scope.yAxis.set('displayRange', { | ||||
|                 min: currentYaxis.min + yAxisDist, | ||||
|                 max: currentYaxis.max - yAxisDist | ||||
|             }); | ||||
|         } else if (zoomDirection === 'out') { | ||||
|             this.$scope.xAxis.set('displayRange', { | ||||
|                 min: currentXaxis.min - xAxisDist, | ||||
|                 max: currentXaxis.max + xAxisDist | ||||
|             }); | ||||
|  | ||||
|             this.$scope.yAxis.set('displayRange', { | ||||
|                 min: currentYaxis.min - yAxisDist, | ||||
|                 max: currentYaxis.max + yAxisDist | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         this.$scope.$emit('user:viewport:change:end'); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.wheelZoom = function (event) { | ||||
|         const ZOOM_AMT = 0.1; | ||||
|         event.preventDefault(); | ||||
|  | ||||
|         if (!this.positionOverPlot) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.freeze(); | ||||
|         window.clearTimeout(this.stillZooming); | ||||
|  | ||||
|         let xDisplayRange = this.$scope.xAxis.get('displayRange'), | ||||
|             yDisplayRange = this.$scope.yAxis.get('displayRange'), | ||||
|             xAxisDist = (xDisplayRange.max - xDisplayRange.min), | ||||
|             yAxisDist = (yDisplayRange.max - yDisplayRange.min), | ||||
|             xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x, | ||||
|             xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min, | ||||
|             yDistMouseToMax = yDisplayRange.max - this.positionOverPlot.y, | ||||
|             yDistMouseToMin = this.positionOverPlot.y - yDisplayRange.min, | ||||
|             xAxisMaxDist = xDistMouseToMax / xAxisDist, | ||||
|             xAxisMinDist = xDistMouseToMin / xAxisDist, | ||||
|             yAxisMaxDist = yDistMouseToMax / yAxisDist, | ||||
|             yAxisMinDist = yDistMouseToMin / yAxisDist; | ||||
|  | ||||
|         let plotHistoryStep; | ||||
|  | ||||
|         if (!plotHistoryStep) { | ||||
|             plotHistoryStep = { | ||||
|                 x: xDisplayRange, | ||||
|                 y: yDisplayRange | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         if (event.wheelDelta < 0) { | ||||
|  | ||||
|             this.$scope.xAxis.set('displayRange', { | ||||
|                 min: xDisplayRange.min + ((xAxisDist * ZOOM_AMT) * xAxisMinDist), | ||||
|                 max: xDisplayRange.max - ((xAxisDist * ZOOM_AMT) * xAxisMaxDist) | ||||
|             }); | ||||
|  | ||||
|             this.$scope.yAxis.set('displayRange', { | ||||
|                 min: yDisplayRange.min + ((yAxisDist * ZOOM_AMT) * yAxisMinDist), | ||||
|                 max: yDisplayRange.max - ((yAxisDist * ZOOM_AMT) * yAxisMaxDist) | ||||
|             }); | ||||
|         } else if (event.wheelDelta >= 0) { | ||||
|  | ||||
|             this.$scope.xAxis.set('displayRange', { | ||||
|                 min: xDisplayRange.min - ((xAxisDist * ZOOM_AMT) * xAxisMinDist), | ||||
|                 max: xDisplayRange.max + ((xAxisDist * ZOOM_AMT) * xAxisMaxDist) | ||||
|             }); | ||||
|  | ||||
|             this.$scope.yAxis.set('displayRange', { | ||||
|                 min: yDisplayRange.min - ((yAxisDist * ZOOM_AMT) * yAxisMinDist), | ||||
|                 max: yDisplayRange.max + ((yAxisDist * ZOOM_AMT) * yAxisMaxDist) | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         this.stillZooming = window.setTimeout(function () { | ||||
|             this.plotHistory.push(plotHistoryStep); | ||||
|             plotHistoryStep = undefined; | ||||
|             this.$scope.$emit('user:viewport:change:end'); | ||||
|         }.bind(this), 250); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.startPan = function ($event) { | ||||
|         this.trackMousePosition($event); | ||||
|         this.freeze(); | ||||
| @@ -362,5 +474,9 @@ define([ | ||||
|         this.stopListening(); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.toggleCursorGuide = function ($event) { | ||||
|         this.cursorGuide = !this.cursorGuide; | ||||
|     }; | ||||
|  | ||||
|     return MCTPlotController; | ||||
| }); | ||||
|   | ||||
| @@ -59,6 +59,7 @@ define([ | ||||
|         this.openmct = openmct; | ||||
|         this.objectService = objectService; | ||||
|         this.exportImageService = exportImageService; | ||||
|         this.cursorGuide = false; | ||||
|  | ||||
|         $scope.pending = 0; | ||||
|  | ||||
| @@ -218,6 +219,7 @@ define([ | ||||
|  | ||||
|     PlotController.prototype.stopLoading = function () { | ||||
|         this.$scope.pending -= 1; | ||||
|         this.$scope.$digest(); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
| @@ -258,8 +260,8 @@ define([ | ||||
|     PlotController.prototype.updateFiltersAndResubscribe = function (updatedFilters) { | ||||
|         this.config.series.forEach(function (series) { | ||||
|             series.updateFiltersAndRefresh(updatedFilters[series.keyString]); | ||||
|         }) | ||||
|     } | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Export view as JPG. | ||||
| @@ -283,6 +285,11 @@ define([ | ||||
|             }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     PlotController.prototype.toggleCursorGuide = function ($event) { | ||||
|         this.cursorGuide = !this.cursorGuide; | ||||
|         this.$scope.$broadcast('cursorguide', $event); | ||||
|     }; | ||||
|  | ||||
|     return PlotController; | ||||
|  | ||||
| }); | ||||
|   | ||||
| @@ -35,6 +35,8 @@ define([ | ||||
|  | ||||
|         this.$element = $element; | ||||
|         this.exportImageService = exportImageService; | ||||
|         this.$scope = $scope; | ||||
|         this.cursorGuide = false; | ||||
|  | ||||
|         $scope.telemetryObjects = []; | ||||
|  | ||||
| @@ -77,6 +79,15 @@ define([ | ||||
|                     $scope.$broadcast('plot:tickWidth', _.max(tickWidthMap)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function compositionReorder(reorderPlan) { | ||||
|                 let oldComposition = telemetryObjects.slice(); | ||||
|  | ||||
|                 reorderPlan.forEach((reorder) => { | ||||
|                     telemetryObjects[reorder.newIndex] = oldComposition[reorder.oldIndex]; | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             thisRequest.pending += 1; | ||||
|             openmct.objects.get(domainObject.getId()) | ||||
|                 .then(function (obj) { | ||||
| @@ -87,10 +98,12 @@ define([ | ||||
|                     composition = openmct.composition.get(obj); | ||||
|                     composition.on('add', addChild); | ||||
|                     composition.on('remove', removeChild); | ||||
|                     composition.on('reorder', compositionReorder); | ||||
|                     composition.load(); | ||||
|                     unlisten = function () { | ||||
|                         composition.off('add', addChild); | ||||
|                         composition.off('remove', removeChild); | ||||
|                         composition.off('reorder', compositionReorder); | ||||
|                     }; | ||||
|                 }); | ||||
|         } | ||||
| @@ -145,5 +158,10 @@ define([ | ||||
|             }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     StackedPlotController.prototype.toggleCursorGuide = function ($event) { | ||||
|         this.cursorGuide = !this.cursorGuide; | ||||
|         this.$scope.$broadcast('cursorguide', $event); | ||||
|     }; | ||||
|  | ||||
|     return StackedPlotController; | ||||
| }); | ||||
|   | ||||
| @@ -85,16 +85,17 @@ export default class RemoveAction { | ||||
|         ); | ||||
|  | ||||
|         this.openmct.objects.mutate(parent, 'composition', composition); | ||||
|  | ||||
|         if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) { | ||||
|             this.openmct.editor.save(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath) { | ||||
|         let object = objectPath[0]; | ||||
|         let objectType = object && this.openmct.types.get(object.type); | ||||
|         let parent = objectPath[1]; | ||||
|         let parentType = parent && this.openmct.types.get(parent.type); | ||||
|  | ||||
|         return objectType.definition.creatable && | ||||
|             parentType && | ||||
|         return parentType && | ||||
|             parentType.definition.creatable && | ||||
|             Array.isArray(parent.composition); | ||||
|     } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|         <span class="t-configuration"> </span> | ||||
|         <span class="t-value-inputs"> </span> | ||||
|     </span> | ||||
|     <span class="flex-elem local-control local-controls-hidden l-condition-action-buttons-wrapper"> | ||||
|     <span class="flex-elem c-local-controls--show-on-hover l-condition-action-buttons-wrapper"> | ||||
|         <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this condition"></a> | ||||
|         <a class="s-icon-button icon-trash t-delete" title="Delete this condition"></a> | ||||
|     </span> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div class="c-sw-rule"> | ||||
|     <div class="c-sw-rule__ui l-compact-form has-local-controls l-widget-rule s-widget-rule"> | ||||
|     <div class="c-sw-rule__ui l-compact-form l-widget-rule s-widget-rule has-local-controls"> | ||||
|         <div class="c-sw-rule__ui__header widget-rule-header"> | ||||
|             <div class="c-sw-rule__grippy-wrapper"> | ||||
|                 <div class="c-sw-rule__grippy t-grippy local-control local-controls-hidden"></div> | ||||
| @@ -11,7 +11,7 @@ | ||||
|             </div> | ||||
|             <div class="flex-elem rule-title">Default Title</div> | ||||
|             <div class="flex-elem rule-description grows">Rule description goes here</div> | ||||
|             <div class="flex-elem local-control local-controls-hidden l-rule-action-buttons-wrapper"> | ||||
|             <div class="flex-elem c-local-controls--show-on-hover l-rule-action-buttons-wrapper"> | ||||
|                 <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this rule"></a> | ||||
|                 <a class="s-icon-button icon-trash t-delete" title="Delete this rule"></a> | ||||
|             </div> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|                 <span class="equal-to hidden"> equal to </span> | ||||
|                 <span class="t-value-inputs"></span> | ||||
|             </span> | ||||
|             <span class="flex-elem local-control local-controls-hidden l-widget-test-data-item-action-buttons-wrapper"> | ||||
|             <span class="flex-elem c-local-controls--show-on-hover l-widget-test-data-item-action-buttons-wrapper"> | ||||
|                 <a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this test value"></a> | ||||
|                 <a class="s-icon-button icon-trash t-delete" title="Delete this test value"></a> | ||||
|             </span> | ||||
|   | ||||
| @@ -3,11 +3,9 @@ | ||||
|         <div id="widgetIcon" class="c-sw__icon js-sw__icon"></div> | ||||
|         <div id="widgetLabel" class="label widget-label c-sw__label js-sw__label">Default Static Name</div> | ||||
|     </a> | ||||
|     <div class="c-summary-widget__message holder flex-elem t-message-inline l-message message-severity-alert t-message-widget-no-data"> | ||||
|         <div class="w-message-contents l-message-body-only"> | ||||
|             <div class="message-body"> | ||||
|                 You must add at least one telemetry object to edit this widget. | ||||
|             </div> | ||||
|     <div class="js-summary-widget__message c-summary-widget__message c-message c-message--simple message-severity-alert"> | ||||
|         <div class="c-summary-widget__text"> | ||||
|             You must add at least one telemetry object to edit this widget. | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="c-sw-edit__ui holder l-flex-accordion flex-elem grows widget-edit-holder expanded-widget-test-data expanded-widget-rules"> | ||||
|   | ||||
| @@ -237,6 +237,7 @@ define ([ | ||||
|         }); | ||||
|         delete this.compositionObjs[objectId]; | ||||
|         this.subscriptions[objectId](); //unsubscribe from telemetry source | ||||
|         delete this.subscriptions[objectId]; | ||||
|         this.eventEmitter.emit('remove', identifier); | ||||
|  | ||||
|         if (_.isEmpty(this.compositionObjs)) { | ||||
|   | ||||
| @@ -43,7 +43,7 @@ define([ | ||||
|         return evaluator.requestLatest(options) | ||||
|             .then(function (latestDatum) { | ||||
|                 this.pool.release(evaluator); | ||||
|                 return [latestDatum]; | ||||
|                 return latestDatum ? [latestDatum] : []; | ||||
|             }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,10 @@ define([ | ||||
|             strategy: 'latest', | ||||
|             size: 1 | ||||
|         }).then(function (results) { | ||||
|             if (this.destroyed || this.hasUpdated || this.renderTracker !== renderTracker) { | ||||
|             if (this.destroyed || | ||||
|                 this.hasUpdated || | ||||
|                 this.renderTracker !== renderTracker || | ||||
|                 results.length === 0) { | ||||
|                 return; | ||||
|             } | ||||
|             this.updateState(results[results.length - 1]); | ||||
|   | ||||
| @@ -1,17 +1,18 @@ | ||||
| <template> | ||||
|     <div class="c-tabs-view"> | ||||
|         <div class="c-tabs-view__tabs-holder c-compact-button-holder" | ||||
|         <div class="c-tabs-view__tabs-holder c-tabs" | ||||
|             :class="{ | ||||
|                 'is-dragging': isDragging, | ||||
|                 'is-mouse-over': allowDrop | ||||
|             }"> | ||||
|             <div class="c-drop-hint" | ||||
|                  @drop="onDrop" | ||||
|                  ref="dropHint"> | ||||
|                  @dragenter="dragenter" | ||||
|                  @dragleave="dragleave"> | ||||
|             </div> | ||||
|             <div class="c-tabs-view__empty-message" | ||||
|                  v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div> | ||||
|             <button class="c-tabs-view__tab c-compact-button" | ||||
|             <button class="c-tabs-view__tab c-tab" | ||||
|                 v-for="(tab,index) in tabsList" | ||||
|                 :key="index" | ||||
|                 :class="[ | ||||
| @@ -25,8 +26,9 @@ | ||||
|         <div class="c-tabs-view__object-holder" | ||||
|             v-for="(tab, index) in tabsList" | ||||
|             :key="index" | ||||
|             :class="{'invisible': !isCurrent(tab)}"> | ||||
|             <div class="c-tabs-view__object-name l-browse-bar__object-name--w" | ||||
|             :class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"> | ||||
|             <div v-if="currentTab" | ||||
|                  class="c-tabs-view__object-name l-browse-bar__object-name--w" | ||||
|                  :class="currentTab.type.definition.cssClass"> | ||||
|                 <div class="l-browse-bar__object-name"> | ||||
|                     {{currentTab.domainObject.name}} | ||||
| @@ -53,15 +55,28 @@ | ||||
|         } | ||||
|  | ||||
|         &__tabs-holder { | ||||
|             @include userSelectNone(); | ||||
|             flex: 0 0 auto; | ||||
|             min-height: $h; | ||||
|         } | ||||
|  | ||||
|         &__tab { | ||||
|             &:before { | ||||
|                 margin-right: $interiorMarginSm; | ||||
|                 opacity: 0.7; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__object-holder { | ||||
|             flex: 1 1 auto; | ||||
|             display: flex; | ||||
|             flex-direction: column; | ||||
|  | ||||
|             &--hidden { | ||||
|                 height: 1000px; | ||||
|                 width: 1000px; | ||||
|                 position: absolute; | ||||
|                 left: -9999px; | ||||
|                 top: -9999px; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__object-name { | ||||
| @@ -72,10 +87,14 @@ | ||||
|         } | ||||
|  | ||||
|         &__object { | ||||
|             display: flex; | ||||
|             flex-flow: column nowrap; | ||||
|             flex: 1 1 auto; | ||||
|             height: 0; // Chrome 73 oveflow bug fix | ||||
|         } | ||||
|  | ||||
|         &__empty-message { | ||||
|             background: rgba($colorBodyFg, 0.1); | ||||
|             color: rgba($colorBodyFg, 0.7); | ||||
|             font-style: italic; | ||||
|             text-align: center; | ||||
| @@ -140,6 +159,13 @@ export default { | ||||
|                 this.showTab(this.tabsList[this.tabsList.length - 1]); | ||||
|             } | ||||
|         }, | ||||
|         onReorder(reorderPlan) { | ||||
|             let oldTabs = this.tabsList.slice(); | ||||
|  | ||||
|             reorderPlan.forEach(reorderEvent => { | ||||
|                 this.$set(this.tabsList, reorderEvent.newIndex, oldTabs[reorderEvent.oldIndex]); | ||||
|             }); | ||||
|         }, | ||||
|         onDrop(e) { | ||||
|             this.setCurrentTab = true; | ||||
|         }, | ||||
| @@ -166,31 +192,20 @@ export default { | ||||
|         if (this.composition) { | ||||
|             this.composition.on('add', this.addItem); | ||||
|             this.composition.on('remove', this.removeItem); | ||||
|             this.composition.on('reorder', this.onReorder); | ||||
|             this.composition.load(); | ||||
|         } | ||||
|  | ||||
|         document.addEventListener('dragstart', this.dragstart); | ||||
|         document.addEventListener('dragend', this.dragend); | ||||
|  | ||||
|         let dropHint = this.$refs.dropHint; | ||||
|  | ||||
|         if (dropHint) { | ||||
|             dropHint.addEventListener('dragenter', this.dragenter); | ||||
|             dropHint.addEventListener('dragleave', this.dragleave); | ||||
|         } | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.composition.off('add', this.addItem); | ||||
|         this.composition.off('remove', this.removeItem); | ||||
|         this.composition.off('reorder', this.onReorder); | ||||
|  | ||||
|         document.removeEventListener('dragstart', this.dragstart); | ||||
|         document.removeEventListener('dragend', this.dragend); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         let dropHint = this.$refs.dropHint; | ||||
|  | ||||
|         dropHint.removeEventListener('dragenter', this.dragenter); | ||||
|         dropHint.removeEventListener('dragleave', this.dragleave); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -31,6 +31,7 @@ define([ | ||||
|  | ||||
|             openmct.types.addType('tabs', { | ||||
|                 name: "Tabs View", | ||||
|                 description: 'Add multiple objects of any type to this view, and quickly navigate between them with tabs', | ||||
|                 creatable: true, | ||||
|                 cssClass: 'icon-tabs-view', | ||||
|                 initialize(domainObject) { | ||||
|   | ||||
| @@ -37,15 +37,15 @@ define([ | ||||
|             key: 'table-configuration', | ||||
|             name: 'Telemetry Table Configuration', | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0) { | ||||
|                 if (selection.length === 0 || selection[0].length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 let object = selection[0].context.item; | ||||
|                 let object = selection[0][0].context.item; | ||||
|                 return object && object.type === 'table'; | ||||
|             }, | ||||
|             view: function (selection) { | ||||
|                 let component; | ||||
|                 let domainObject = selection[0].context.item; | ||||
|                 let domainObject = selection[0][0].context.item; | ||||
|                 let tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct); | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
| @@ -62,8 +62,11 @@ define([ | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                         if (component) { | ||||
|                             component.$destroy(); | ||||
|                             component = undefined; | ||||
|                         } | ||||
|  | ||||
|                         tableConfiguration = undefined; | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -59,7 +59,9 @@ define([ | ||||
|             this.filterObserver = undefined; | ||||
|  | ||||
|             this.createTableRowCollections(); | ||||
|  | ||||
|             openmct.time.on('bounds', this.refreshData); | ||||
|             openmct.time.on('timeSystem', this.refreshData); | ||||
|         } | ||||
|  | ||||
|         initialize() { | ||||
| @@ -73,13 +75,17 @@ define([ | ||||
|  | ||||
|         createTableRowCollections() { | ||||
|             this.boundedRows = new BoundedTableRowCollection(this.openmct); | ||||
|  | ||||
|             //By default, sort by current time system, ascending. | ||||
|             this.filteredRows = new FilteredTableRowCollection(this.boundedRows); | ||||
|             this.filteredRows.sortBy({ | ||||
|  | ||||
|             //Fetch any persisted default sort | ||||
|             let sortOptions = this.configuration.getConfiguration().sortOptions; | ||||
|  | ||||
|             //If no persisted sort order, default to sorting by time system, ascending. | ||||
|             sortOptions = sortOptions || { | ||||
|                 key: this.openmct.time.timeSystem().key, | ||||
|                 direction: 'asc' | ||||
|             }); | ||||
|             }; | ||||
|             this.filteredRows.sortBy(sortOptions); | ||||
|         } | ||||
|  | ||||
|         loadComposition() { | ||||
| @@ -131,12 +137,17 @@ define([ | ||||
|             let requestOptions = this.buildOptionsFromConfiguration(telemetryObject); | ||||
|             return this.openmct.telemetry.request(telemetryObject, requestOptions) | ||||
|                 .then(telemetryData => { | ||||
|                     //Check that telemetry object has not been removed since telemetry was requested. | ||||
|                     if (!this.telemetryObjects.includes(telemetryObject)) { | ||||
|                         return; | ||||
|                     } | ||||
|                     let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
|                     let columnMap = this.getColumnMapForObject(keyString); | ||||
|                     let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); | ||||
|  | ||||
|                     let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|                     this.boundedRows.add(telemetryRows); | ||||
|                 }).finally(() => { | ||||
|                     this.decrementOutstandingRequests(); | ||||
|                 }); | ||||
|         } | ||||
| @@ -166,6 +177,7 @@ define([ | ||||
|             if (!isTick) { | ||||
|                 this.filteredRows.clear(); | ||||
|                 this.boundedRows.clear(); | ||||
|                 this.boundedRows.sortByTimeSystem(this.openmct.time.timeSystem()); | ||||
|                 this.telemetryObjects.forEach(this.requestDataFor); | ||||
|             } | ||||
|         } | ||||
| @@ -186,6 +198,10 @@ define([ | ||||
|             let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); | ||||
|  | ||||
|             this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => { | ||||
|                 //Check that telemetry object has not been removed since telemetry was requested. | ||||
|                 if (!this.telemetryObjects.includes(telemetryObject)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|             }, subscribeOptions); | ||||
|         } | ||||
| @@ -208,11 +224,22 @@ define([ | ||||
|             delete this.subscriptions[keyString]; | ||||
|         } | ||||
|  | ||||
|         sortBy(sortOptions) { | ||||
|             this.filteredRows.sortBy(sortOptions); | ||||
|  | ||||
|             if (this.openmct.editor.isEditing()) { | ||||
|                 let configuration = this.configuration.getConfiguration(); | ||||
|                 configuration.sortOptions = sortOptions; | ||||
|                 this.configuration.updateConfiguration(configuration); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         destroy() { | ||||
|             this.boundedRows.destroy(); | ||||
|             this.filteredRows.destroy(); | ||||
|             Object.keys(this.subscriptions).forEach(this.unsubscribe, this); | ||||
|             this.openmct.time.off('bounds', this.refreshData); | ||||
|             this.openmct.time.off('timeSystem', this.refreshData); | ||||
|             if (this.filterObserver) { | ||||
|                 this.filterObserver(); | ||||
|             } | ||||
|   | ||||
| @@ -32,12 +32,20 @@ define([ | ||||
|     Vue | ||||
| ) { | ||||
|     function TelemetryTableViewProvider(openmct) { | ||||
|         function hasTelemetry(domainObject) { | ||||
|             if (!domainObject.hasOwnProperty('telemetry')) { | ||||
|                 return false; | ||||
|             } | ||||
|             let metadata = openmct.telemetry.getMetadata(domainObject); | ||||
|             return metadata.values().length > 0; | ||||
|         } | ||||
|         return { | ||||
|             key: 'table', | ||||
|             name: 'Telemetry Table', | ||||
|             cssClass: 'icon-tabular-realtime', | ||||
|             canView(domainObject) { | ||||
|                 return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry'); | ||||
|                 return domainObject.type === 'table' || | ||||
|                     hasTelemetry(domainObject) | ||||
|             }, | ||||
|             canEdit(domainObject) { | ||||
|                 return domainObject.type === 'table'; | ||||
|   | ||||
| @@ -41,7 +41,6 @@ define( | ||||
|                 this.bounds = this.bounds.bind(this) | ||||
|  | ||||
|                 this.sortByTimeSystem(openmct.time.timeSystem()); | ||||
|                 openmct.time.on('timeSystem', this.sortByTimeSystem); | ||||
|  | ||||
|                 this.lastBounds = openmct.time.bounds(); | ||||
|                 openmct.time.on('bounds', this.bounds); | ||||
| @@ -51,8 +50,8 @@ define( | ||||
|                 // Insert into either in-bounds array, or the future buffer. | ||||
|                 // Data in the future buffer will be re-evaluated for possible  | ||||
|                 // insertion on next bounds change | ||||
|                 let beforeStartOfBounds = item.datum[this.sortOptions.key] < this.lastBounds.start; | ||||
|                 let afterEndOfBounds = item.datum[this.sortOptions.key] > this.lastBounds.end; | ||||
|                 let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start; | ||||
|                 let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end; | ||||
|  | ||||
|                 if (!afterEndOfBounds && !beforeStartOfBounds) { | ||||
|                     return super.addOne(item); | ||||
| @@ -64,6 +63,12 @@ define( | ||||
|  | ||||
|             sortByTimeSystem(timeSystem) { | ||||
|                 this.sortBy({key: timeSystem.key, direction: 'asc'}); | ||||
|                 let formatter = this.openmct.telemetry.getValueFormatter({ | ||||
|                     key: timeSystem.key, | ||||
|                     source: timeSystem.key, | ||||
|                     format: timeSystem.timeFormat | ||||
|                 }); | ||||
|                 this.parseTime = formatter.parse.bind(formatter); | ||||
|                 this.futureBuffer.sortBy({key: timeSystem.key, direction: 'asc'}); | ||||
|             } | ||||
|  | ||||
| @@ -131,7 +136,6 @@ define( | ||||
|             } | ||||
|  | ||||
|             destroy() { | ||||
|                 this.openmct.time.off('timeSystem', this.sortByTimeSystem); | ||||
|                 this.openmct.time.off('bounds', this.bounds); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -127,7 +127,8 @@ define( | ||||
|                     if (testRowValue > lastValue) { | ||||
|                         return this.rows.length; | ||||
|                     } else if (testRowValue === lastValue) { | ||||
|                         return this.rows.length - 1; | ||||
|                         // Maintain stable sort | ||||
|                         return this.rows.length; | ||||
|                     } else if (testRowValue <= firstValue) { | ||||
|                         return 0; | ||||
|                     } else { | ||||
| @@ -141,7 +142,8 @@ define( | ||||
|                     } else if (testRowValue < lastValue) { | ||||
|                         return this.rows.length; | ||||
|                     } else if (testRowValue === lastValue) { | ||||
|                         return this.rows.length - 1; | ||||
|                         // Maintain stable sort | ||||
|                         return this.rows.length; | ||||
|                     } else { | ||||
|                         // Use a custom comparison function to support descending sort. | ||||
|                         return lodashFunction(rows, testRow, (thisRow) => { | ||||
|   | ||||
| @@ -34,39 +34,13 @@ | ||||
|             isSortable ? 'is-sortable' : '',  | ||||
|             isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',  | ||||
|             isSortable && sortOptions.direction].join(' ')"> | ||||
|             <div v-if="isEditing" class="c-telemetry-table__resize-hotzone c-telemetry-table__resize-hotzone--right" | ||||
|             <div class="c-telemetry-table__resize-hitarea" | ||||
|                 @mousedown="resizeColumnStart" | ||||
|             ></div> | ||||
|             <slot></slot> | ||||
|         </div> | ||||
| </th> | ||||
| </template> | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|     @import "~styles/table"; | ||||
|  | ||||
|     $hotzone-size: 6px; | ||||
|  | ||||
|     .c-telemetry-table__headers__content { | ||||
|         width: 100%; | ||||
|     } | ||||
|  | ||||
|     .c-table.c-telemetry-table { | ||||
|         .c-telemetry-table__resize-hotzone { | ||||
|             display: block; | ||||
|             position: absolute; | ||||
|             height: 100%; | ||||
|             padding: 0; | ||||
|             margin: 0; | ||||
|             width: $hotzone-size; | ||||
|             min-width: $hotzone-size; | ||||
|             cursor: col-resize; | ||||
|             border: none; | ||||
|             right: 0px; | ||||
|             margin-right: -$tabularTdPadLR - 1 - $hotzone-size / 2; | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
| <script> | ||||
| import _ from 'lodash'; | ||||
| const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex'; | ||||
|   | ||||
| @@ -23,18 +23,18 @@ | ||||
| <div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar" | ||||
|      :class="{'loading': loading}"> | ||||
|     <div class="c-table__control-bar c-control-bar"> | ||||
|         <a class="c-button icon-download labeled" | ||||
|         <button class="c-button icon-download labeled" | ||||
|            v-on:click="exportAsCSV()" | ||||
|            title="Export This View's Data"> | ||||
|             <span class="c-button__label">Export As CSV</span> | ||||
|         </a> | ||||
|         </button> | ||||
|     </div> | ||||
|     <div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div> | ||||
|     <!-- Headers table --> | ||||
|     <div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}"> | ||||
|         <table class="c-table__headers c-telemetry-table__headers"> | ||||
|             <thead> | ||||
|                 <tr class="c-telemetry-table__headers__name"> | ||||
|                 <tr class="c-telemetry-table__headers__labels"> | ||||
|                     <table-column-header | ||||
|                         v-for="(title, key, headerIndex) in headers" | ||||
|                         :key="key" | ||||
| @@ -49,7 +49,8 @@ | ||||
|                         :columnWidth="columnWidths[key]" | ||||
|                         :sortOptions="sortOptions" | ||||
|                         :isEditing="isEditing" | ||||
|                         >{{title}}</table-column-header> | ||||
|                     ><span class="c-telemetry-table__headers__label">{{title}}</span> | ||||
|                     </table-column-header> | ||||
|                 </tr> | ||||
|                 <tr class="c-telemetry-table__headers__filter"> | ||||
|                     <table-column-header | ||||
| @@ -142,6 +143,7 @@ | ||||
|             // Wraps __headers table | ||||
|             flex: 0 0 auto; | ||||
|             overflow: hidden; | ||||
|             background: $colorTabHeaderBg; | ||||
|         } | ||||
|  | ||||
|         /******************************* TABLES */ | ||||
| @@ -158,6 +160,32 @@ | ||||
|             thead { | ||||
|                 display: block; | ||||
|             } | ||||
|  | ||||
|             &__labels { | ||||
|                 // Top row, has labels | ||||
|                 .c-telemetry-table__headers__content { | ||||
|                     // Holds __label, sort indicator and resize-hitarea | ||||
|                     display: flex; | ||||
|                     align-items: center; | ||||
|                     justify-content: center; | ||||
|                     width: 100%; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__headers__label { | ||||
|             overflow: hidden; | ||||
|             flex: 0 1 auto; | ||||
|         } | ||||
|  | ||||
|         &__resize-hitarea { | ||||
|             // In table-column-header.vue | ||||
|             @include abs(); | ||||
|             display: none; // Set to display: block in .is-editing section below | ||||
|             left: auto; right: -1 * $tabularTdPadLR; | ||||
|             width: $tableResizeColHitareaD; | ||||
|             cursor: col-resize; | ||||
|             transform: translateX(50%); // Move so this element sits over border between columns | ||||
|         } | ||||
|  | ||||
|         /******************************* ELEMENTS */ | ||||
| @@ -172,6 +200,7 @@ | ||||
|         &__body-w { | ||||
|             // Wraps __body table provides scrolling | ||||
|             flex: 1 1 100%; | ||||
|             height: 0; // Fixes Chrome 73 overflow bug | ||||
|             overflow-x: auto; | ||||
|             overflow-y: scroll; | ||||
|         } | ||||
| @@ -219,7 +248,7 @@ | ||||
|  | ||||
|     /******************************* EDITING */ | ||||
|     .is-editing { | ||||
|         .c-telemetry-table__headers__name { | ||||
|         .c-telemetry-table__headers__labels { | ||||
|             th[draggable], | ||||
|             th[draggable] > * { | ||||
|                 cursor: move; | ||||
| @@ -231,6 +260,10 @@ | ||||
|                 > * { background: $b; } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .c-telemetry-table__resize-hitarea { | ||||
|             display: block; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /******************************* LEGACY */ | ||||
| @@ -408,7 +441,7 @@ export default { | ||||
|                     direction: 'asc' | ||||
|                 } | ||||
|             } | ||||
|             this.table.filteredRows.sortBy(this.sortOptions); | ||||
|             this.table.sortBy(this.sortOptions); | ||||
|         }, | ||||
|         scroll() { | ||||
|             if (!this.processingScroll) { | ||||
| @@ -555,13 +588,18 @@ export default { | ||||
|             let el = this.$el; | ||||
|             let width = el.clientWidth; | ||||
|             let height = el.clientHeight; | ||||
|             let scrollTop = this.scrollable.scrollTop; | ||||
|  | ||||
|             this.resizePollHandle = setInterval(() => { | ||||
|                 if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) { | ||||
|                     this.calculateTableSize(); | ||||
|                     // On some resize events scrollTop is reset to 0. Possibly due to a transition we're using? | ||||
|                     // Need to preserve scroll position in this case. | ||||
|                     this.scrollable.scrollTop = scrollTop; | ||||
|                     width = el.clientWidth; | ||||
|                     height = el.clientHeight; | ||||
|                 } | ||||
|                 scrollTop = this.scrollable.scrollTop; | ||||
|             }, RESIZE_POLL_INTERVAL); | ||||
|         }, | ||||
|  | ||||
|   | ||||
| @@ -23,73 +23,76 @@ | ||||
|     <div class="c-conductor" | ||||
|          :class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"> | ||||
|         <form class="u-contents" ref="conductorForm" @submit.prevent="updateTimeFromConductor"> | ||||
|             <button class="c-input--submit" type="submit" ref="submitButton"></button> | ||||
|             <ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon> | ||||
|             <div class="c-conductor__time-bounds"> | ||||
|                 <button class="c-input--submit" type="submit" ref="submitButton"></button> | ||||
|                 <ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon> | ||||
|  | ||||
|             <div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed" | ||||
|                  v-if="isFixed"> | ||||
|                 <!-- Fixed start --> | ||||
|                 <div class="c-conductor__start-fixed__label">Start</div> | ||||
|                 <input class="c-input--datetime" | ||||
|                        type="text" autocorrect="off" spellcheck="false" | ||||
|                        ref="startDate" | ||||
|                        v-model="formattedBounds.start" | ||||
|                        @change="validateAllBounds(); submitForm()" /> | ||||
|                 <date-picker | ||||
|                         v-if="isFixed && isUTCBased" | ||||
|                         :default-date-time="formattedBounds.start" | ||||
|                         :formatter="timeFormatter" | ||||
|                         @date-selected="startDateSelected"></date-picker> | ||||
|             </div> | ||||
|  | ||||
|             <div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta" | ||||
|                  v-if="!isFixed"> | ||||
|                 <!-- RT start --> | ||||
|                 <div class="c-direction-indicator icon-minus"></div> | ||||
|                 <input class="c-input--hrs-min-sec" | ||||
|                        type="text" autocorrect="off" | ||||
|                        ref="startOffset" | ||||
|                        spellcheck="false" | ||||
|                        v-model="offsets.start" | ||||
|                        @change="validateAllOffsets(); submitForm()"> | ||||
|             </div> | ||||
|  | ||||
|             <div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed"> | ||||
|                 <!-- Fixed end and RT 'last update' display --> | ||||
|                 <div class="c-conductor__end-fixed__label"> | ||||
|                     {{ isFixed ? 'End' : 'Updated' }} | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed" | ||||
|                      v-if="isFixed"> | ||||
|                     <!-- Fixed start --> | ||||
|                     <div class="c-conductor__start-fixed__label">Start</div> | ||||
|                     <input class="c-input--datetime" | ||||
|                            type="text" autocorrect="off" spellcheck="false" | ||||
|                            ref="startDate" | ||||
|                            v-model="formattedBounds.start" | ||||
|                            @change="validateAllBounds(); submitForm()" /> | ||||
|                     <date-picker | ||||
|                             v-if="isFixed && isUTCBased" | ||||
|                             :default-date-time="formattedBounds.start" | ||||
|                             :formatter="timeFormatter" | ||||
|                             @date-selected="startDateSelected"></date-picker> | ||||
|                 </div> | ||||
|                 <input class="c-input--datetime" | ||||
|                        type="text" autocorrect="off" spellcheck="false" | ||||
|                        v-model="formattedBounds.end" | ||||
|                        :disabled="!isFixed" | ||||
|                        ref="endDate" | ||||
|                        @change="validateAllBounds(); submitForm()"> | ||||
|                 <date-picker | ||||
|                         v-if="isFixed && isUTCBased" | ||||
|                         class="c-ctrl-wrapper--menus-left" | ||||
|                         :default-date-time="formattedBounds.end" | ||||
|                         :formatter="timeFormatter" | ||||
|                         @date-selected="endDateSelected"></date-picker> | ||||
|             </div> | ||||
|  | ||||
|             <div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta" | ||||
|                  v-if="!isFixed"> | ||||
|                 <!-- RT end --> | ||||
|                 <div class="c-direction-indicator icon-plus"></div> | ||||
|                 <input class="c-input--hrs-min-sec" | ||||
|                        type="text" | ||||
|                        autocorrect="off" | ||||
|                        spellcheck="false" | ||||
|                        ref="endOffset" | ||||
|                        v-model="offsets.end" | ||||
|                        @change="validateAllOffsets(); submitForm()"> | ||||
|             </div> | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta" | ||||
|                      v-if="!isFixed"> | ||||
|                     <!-- RT start --> | ||||
|                     <div class="c-direction-indicator icon-minus"></div> | ||||
|                     <input class="c-input--hrs-min-sec" | ||||
|                            type="text" autocorrect="off" | ||||
|                            ref="startOffset" | ||||
|                            spellcheck="false" | ||||
|                            v-model="offsets.start" | ||||
|                            @change="validateAllOffsets(); submitForm()"> | ||||
|                 </div> | ||||
|  | ||||
|             <conductor-axis | ||||
|                     class="c-conductor__ticks" | ||||
|                     :bounds="rawBounds" | ||||
|                     @panAxis="setViewFromBounds"></conductor-axis> | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed"> | ||||
|                     <!-- Fixed end and RT 'last update' display --> | ||||
|                     <div class="c-conductor__end-fixed__label"> | ||||
|                         {{ isFixed ? 'End' : 'Updated' }} | ||||
|                     </div> | ||||
|                     <input class="c-input--datetime" | ||||
|                            type="text" autocorrect="off" spellcheck="false" | ||||
|                            v-model="formattedBounds.end" | ||||
|                            :disabled="!isFixed" | ||||
|                            ref="endDate" | ||||
|                            @change="validateAllBounds(); submitForm()"> | ||||
|                     <date-picker | ||||
|                             v-if="isFixed && isUTCBased" | ||||
|                             class="c-ctrl-wrapper--menus-left" | ||||
|                             :default-date-time="formattedBounds.end" | ||||
|                             :formatter="timeFormatter" | ||||
|                             @date-selected="endDateSelected"></date-picker> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta" | ||||
|                      v-if="!isFixed"> | ||||
|                     <!-- RT end --> | ||||
|                     <div class="c-direction-indicator icon-plus"></div> | ||||
|                     <input class="c-input--hrs-min-sec" | ||||
|                            type="text" | ||||
|                            autocorrect="off" | ||||
|                            spellcheck="false" | ||||
|                            ref="endOffset" | ||||
|                            v-model="offsets.end" | ||||
|                            @change="validateAllOffsets(); submitForm()"> | ||||
|                 </div> | ||||
|  | ||||
|                 <conductor-axis | ||||
|                         class="c-conductor__ticks" | ||||
|                         :bounds="rawBounds" | ||||
|                         @panAxis="setViewFromBounds"></conductor-axis> | ||||
|  | ||||
|             </div> | ||||
|             <div class="c-conductor__controls"> | ||||
|                 <!-- Mode, time system menu buttons and duration slider --> | ||||
|                 <ConductorMode class="c-conductor__mode-select"></ConductorMode> | ||||
| @@ -113,17 +116,17 @@ | ||||
|  | ||||
|     /*********************************************** CONDUCTOR LAYOUT */ | ||||
|     .c-conductor { | ||||
|         display: grid; | ||||
|         grid-column-gap: $interiorMargin; | ||||
|         grid-row-gap: $interiorMargin; | ||||
|         align-items: center; | ||||
|         &__time-bounds { | ||||
|             display: grid; | ||||
|             grid-column-gap: $interiorMargin; | ||||
|             grid-row-gap: $interiorMargin; | ||||
|             align-items: center; | ||||
|  | ||||
|         // Default: fixed mode, desktop | ||||
|         grid-template-rows: 1fr 1fr; | ||||
|         grid-template-columns: 20px auto 1fr auto; | ||||
|         grid-template-areas: | ||||
|                 "tc-mode-icon tc-start tc-ticks tc-end" | ||||
|                 "tc-controls tc-controls tc-controls tc-controls"; | ||||
|             // Default: fixed mode, desktop | ||||
|             grid-template-rows: 1fr; | ||||
|             grid-template-columns: 20px auto 1fr auto; | ||||
|             grid-template-areas: "tc-mode-icon tc-start tc-ticks tc-end"; | ||||
|         } | ||||
|  | ||||
|         &__mode-icon { | ||||
|             grid-area: tc-mode-icon; | ||||
| @@ -163,10 +166,10 @@ | ||||
|         } | ||||
|  | ||||
|         &.is-realtime-mode { | ||||
|             grid-template-columns: 20px auto 1fr auto auto; | ||||
|             grid-template-areas: | ||||
|                     "tc-mode-icon tc-start tc-ticks tc-updated tc-end" | ||||
|                     "tc-controls tc-controls tc-controls tc-controls tc-controls"; | ||||
|             .c-conductor__time-bounds { | ||||
|                 grid-template-columns: 20px auto 1fr auto auto; | ||||
|                 grid-template-areas: "tc-mode-icon tc-start tc-ticks tc-updated tc-end"; | ||||
|             } | ||||
|  | ||||
|             .c-conductor__end-fixed { | ||||
|                 grid-area: tc-updated; | ||||
| @@ -174,9 +177,15 @@ | ||||
|         } | ||||
|  | ||||
|         body.phone.portrait & { | ||||
|             grid-row-gap: $interiorMargin; | ||||
|             grid-template-rows: auto auto auto; | ||||
|             grid-template-columns: 20px auto auto; | ||||
|             .c-conductor__time-bounds { | ||||
|                 grid-row-gap: $interiorMargin; | ||||
|                 grid-template-rows: auto auto; | ||||
|                 grid-template-columns: 20px auto auto; | ||||
|             } | ||||
|  | ||||
|             .c-conductor__controls { | ||||
|                 padding-left: 25px; // Line up visually with other controls | ||||
|             } | ||||
|  | ||||
|             &__mode-icon { | ||||
|                 grid-row: 1; | ||||
| @@ -200,17 +209,19 @@ | ||||
|                     justify-content: flex-start; | ||||
|                 } | ||||
|  | ||||
|                 grid-template-areas: | ||||
|                 .c-conductor__time-bounds { | ||||
|                     grid-template-areas: | ||||
|                         "tc-mode-icon tc-start tc-start" | ||||
|                         "tc-mode-icon tc-end tc-end" | ||||
|                         "tc-mode-icon tc-controls tc-controls"; | ||||
|             } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|             &.is-realtime-mode { | ||||
|                 grid-template-areas: | ||||
|                 .c-conductor__time-bounds { | ||||
|                     grid-template-areas: | ||||
|                         "tc-mode-icon tc-start tc-updated" | ||||
|                         "tc-mode-icon tc-end tc-end" | ||||
|                         "tc-mode-icon tc-controls tc-controls"; | ||||
|                         "tc-mode-icon tc-end tc-end"; | ||||
|                 } | ||||
|  | ||||
|                 .c-conductor__end-fixed { | ||||
|                     justify-content: flex-end; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper" ref="calendarHolder"> | ||||
|         <a class="c-click-icon icon-calendar" | ||||
|         <a class="c-icon-button icon-calendar" | ||||
|            @click="toggle"></a> | ||||
|         <div class="c-menu c-menu--mobile-modal c-datetime-picker" | ||||
|              v-if="open"> | ||||
| @@ -30,10 +30,10 @@ | ||||
|                         @click="toggle"></button> | ||||
|             </div> | ||||
|             <div class="c-datetime-picker__pager c-pager l-month-year-pager"> | ||||
|                 <div class="c-pager__prev c-click-icon icon-arrow-left" | ||||
|                 <div class="c-pager__prev c-icon-button icon-arrow-left" | ||||
|                    @click.stop="changeMonth(-1)"></div> | ||||
|                 <div class="c-pager__month-year">{{model.month}} {{model.year}}</div> | ||||
|                 <div class="c-pager__next c-click-icon icon-arrow-right" | ||||
|                 <div class="c-pager__next c-icon-button icon-arrow-right" | ||||
|                    @click.stop="changeMonth(1)"></div> | ||||
|             </div> | ||||
|             <div class="c-datetime-picker__calendar c-calendar"> | ||||
| @@ -91,7 +91,7 @@ | ||||
|         grid-template-columns: auto 1fr auto; | ||||
|         align-items: center; | ||||
|  | ||||
|         .c-click-icon { | ||||
|         .c-icon-button { | ||||
|             font-size: 0.8em; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,15 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['EventEmitter'], function (EventEmitter) { | ||||
| define( | ||||
|     [ | ||||
|         'EventEmitter', | ||||
|         'lodash' | ||||
|     ], | ||||
|     function ( | ||||
|         EventEmitter, | ||||
|         _ | ||||
| ) { | ||||
|  | ||||
|     /** | ||||
|      * Manages selection state for Open MCT | ||||
| @@ -47,21 +55,87 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|      * Selects the selectable object and emits the 'change' event. | ||||
|      * | ||||
|      * @param {object} selectable an object with element and context properties | ||||
|      * @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.select = function (selectable) { | ||||
|     Selection.prototype.select = function (selectable, isMultiSelectEvent) { | ||||
|         if (!Array.isArray(selectable)) { | ||||
|             selectable = [selectable]; | ||||
|         } | ||||
|  | ||||
|         if (this.selected[0] && this.selected[0].element) { | ||||
|             this.selected[0].element.removeAttribute('s-selected'); | ||||
|         let multiSelect = isMultiSelectEvent && | ||||
|             this.parentSupportsMultiSelect(selectable) && | ||||
|             this.isPeer(selectable) && | ||||
|             !this.selectionContainsParent(selectable); | ||||
|  | ||||
|         if (multiSelect) { | ||||
|             this.handleMultiSelect(selectable); | ||||
|         } else { | ||||
|             this.setSelectionStyles(selectable); | ||||
|             this.selected = [selectable]; | ||||
|         } | ||||
|  | ||||
|         if (this.selected[1] && this.selected[1].element) { | ||||
|             this.selected[1].element.removeAttribute('s-selected-parent'); | ||||
|         this.emit('change', this.selected); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.handleMultiSelect = function (selectable) { | ||||
|         if (this.elementSelected(selectable)) { | ||||
|             this.remove(selectable); | ||||
|         } else { | ||||
|             this.addSelectionAttributes(selectable); | ||||
|             this.selected.push(selectable); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.elementSelected = function (selectable) { | ||||
|         return this.selected.some(selectionPath => _.isEqual(selectionPath, selectable)); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.remove = function (selectable) { | ||||
|         this.selected = this.selected.filter(selectionPath => !_.isEqual(selectionPath, selectable)); | ||||
|  | ||||
|         if (this.selected.length === 0) { | ||||
|             this.removeSelectionAttributes(selectable); | ||||
|             selectable[1].element.click(); // Select the parent if there is no selection. | ||||
|         } else { | ||||
|             this.removeSelectionAttributes(selectable, true); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|      Selection.prototype.setSelectionStyles = function (selectable) { | ||||
|         this.selected.map(selectionPath => { | ||||
|             this.removeSelectionAttributes(selectionPath); | ||||
|         }); | ||||
|         this.addSelectionAttributes(selectable); | ||||
|      }; | ||||
|  | ||||
|     Selection.prototype.removeSelectionAttributes = function (selectionPath, keepParentStyle) { | ||||
|         if (selectionPath[0] && selectionPath[0].element) { | ||||
|             selectionPath[0].element.removeAttribute('s-selected'); | ||||
|         } | ||||
|  | ||||
|         if (selectionPath[1] && selectionPath[1].element && !keepParentStyle) { | ||||
|             selectionPath[1].element.removeAttribute('s-selected-parent'); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     /* | ||||
|      * Adds selection attributes to the selected element and its parent. | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.addSelectionAttributes = function (selectable) { | ||||
|         if (selectable[0] && selectable[0].element) { | ||||
|             selectable[0].element.setAttribute('s-selected', ""); | ||||
|         } | ||||
| @@ -69,16 +143,36 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|         if (selectable[1] && selectable[1].element) { | ||||
|             selectable[1].element.setAttribute('s-selected-parent', ""); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|         this.selected = selectable; | ||||
|         this.emit('change', this.selected); | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.parentSupportsMultiSelect = function (selectable) { | ||||
|         return selectable[1] && selectable[1].context.supportsMultiSelect; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.selectionContainsParent = function (selectable) { | ||||
|         return this.selected.some(selectionPath => _.isEqual(selectionPath[0], selectable[1])); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.isPeer = function (selectable) { | ||||
|         return this.selected.some(selectionPath => _.isEqual(selectionPath[1], selectable[1])); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.capture = function (selectable) { | ||||
|         if (!this.capturing) { | ||||
|         let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable); | ||||
|  | ||||
|         if (!this.capturing || capturingContainsSelectable) { | ||||
|             this.capturing = []; | ||||
|         } | ||||
|  | ||||
| @@ -88,13 +182,14 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     Selection.prototype.selectCapture = function (selectable) { | ||||
|     Selection.prototype.selectCapture = function (selectable, event) { | ||||
|         if (!this.capturing) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.select(this.capturing.reverse()); | ||||
|         let reversedCapturing = this.capturing.reverse(); | ||||
|         delete this.capturing; | ||||
|         this.select(reversedCapturing, event.shiftKey); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
| @@ -112,7 +207,7 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|      * @public | ||||
|      */ | ||||
|     Selection.prototype.selectable = function (element, context, select) { | ||||
|         var selectable = { | ||||
|         let selectable = { | ||||
|             context: context, | ||||
|             element: element | ||||
|         }; | ||||
|   | ||||
| @@ -21,6 +21,46 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| // Used by About screen, licenses, etc. | ||||
| .c-splash-image { | ||||
|     background-position: center; | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: cover; | ||||
|     background-image: url('../ui/layout/assets/images/bg-splash.jpg'); | ||||
|  | ||||
|     &:before, | ||||
|     &:after { | ||||
|         background-position: center; | ||||
|         background-repeat: no-repeat; | ||||
|         position: absolute; | ||||
|         background-image: url('../ui/layout/assets/images/logo-app-shdw.svg'); | ||||
|         background-size: contain; | ||||
|         content: ''; | ||||
|     } | ||||
|  | ||||
|     &:before { | ||||
|         // NASA logo, dude | ||||
|         $w: 5%; | ||||
|         $m: 10px; | ||||
|         background-image: url('../ui/layout/assets/images/logo-nasa.svg'); | ||||
|         top: $m; | ||||
|         right: auto; | ||||
|         bottom: auto; | ||||
|         left: $m; | ||||
|         height: auto; | ||||
|         width: $w * 2; | ||||
|         padding-bottom: $w; | ||||
|         padding-top: $w; | ||||
|     } | ||||
|  | ||||
|     &:after { | ||||
|         // App logo | ||||
|         top: 0; | ||||
|         right: 15%; | ||||
|         bottom: 0; | ||||
|         left: 15%; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-about { | ||||
|     &--splash { | ||||
|         // Large initial image after click on app logo with text beneath | ||||
| @@ -37,48 +77,6 @@ | ||||
|     &__text { | ||||
|         height: 50%; | ||||
|         flex: 1 1 0; | ||||
|         position: relative; | ||||
|     } | ||||
|  | ||||
|     &__image { | ||||
|         background-position: center; | ||||
|         background-repeat: no-repeat; | ||||
|         background-size: cover; | ||||
|         background-image: url('../ui/layout/assets/images/bg-splash.jpg'); | ||||
|         position: relative; | ||||
|  | ||||
|         &:before, | ||||
|         &:after { | ||||
|             background-position: center; | ||||
|             background-repeat: no-repeat; | ||||
|             position: absolute; | ||||
|             background-image: url('../ui/layout/assets/images/logo-app-shdw.svg'); | ||||
|             background-size: contain; | ||||
|             content: ''; | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             // NASA logo, dude | ||||
|             $w: 5%; | ||||
|             $m: 10px; | ||||
|             background-image: url('../ui/layout/assets/images/logo-nasa.svg'); | ||||
|             top: $m; | ||||
|             right: auto; | ||||
|             bottom: auto; | ||||
|             left: $m; | ||||
|             height: auto; | ||||
|             width: $w * 2; | ||||
|             padding-bottom: $w; | ||||
|             padding-top: $w; | ||||
|         } | ||||
|  | ||||
|         &:after { | ||||
|             // App logo | ||||
|             top: 0; | ||||
|             right: 15%; | ||||
|             bottom: 0; | ||||
|             left: 15%; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__text { | ||||
|   | ||||
| @@ -79,10 +79,13 @@ $colorKeyHov: #26d8ff; | ||||
| $colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%); | ||||
| $colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%); | ||||
| $colorKeySelectedBg: $colorKey; | ||||
| $uiColor: #00b2ff; // Resize bars, splitter bars, etc. | ||||
| $uiColor: #0093ff; // Resize bars, splitter bars, etc. | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #ccc; | ||||
| $colorAHov: #fff; | ||||
| $filterHov: brightness(1.3); // Tree, location items | ||||
| $colorSelectedBg: pushBack($colorKey, 10%); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| // Layout | ||||
| $shellMainPad: 4px 0; | ||||
| @@ -98,6 +101,20 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningHiFg: #FF9594; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorWarningLoFg: #523400; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorDiagnosticFg: #39461A; | ||||
| $colorCommand: #3693bd; | ||||
| $colorCommandFg: #fff; | ||||
| $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -126,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); | ||||
|  | ||||
| /************************************************** EDITING */ | ||||
| $editUIColor: $uiColor; // Base color | ||||
| $editUIColorBg: $editUIColor; | ||||
| $editUIColorFg: #fff; | ||||
| $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color | ||||
| $editUIBaseColor: #344b8d; // Base color, toolbar bg | ||||
| $editUIBaseColorHov: pullForward($editUIBaseColor, 20%); | ||||
| @@ -142,8 +161,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames | ||||
| $editFrameColorSelected: #ccc; // Border of selected frames | ||||
| $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout | ||||
| $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color | ||||
| $editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px; | ||||
| $editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element | ||||
| $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px; | ||||
| $editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element | ||||
| $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color | ||||
| $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%);  // Grippy lines, container size text | ||||
| $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style | ||||
| @@ -151,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%); | ||||
| $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style | ||||
| $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); | ||||
| $editFrameMovebarH: 10px; // Height of move bar in layout frame | ||||
| $editMarqueeBorder: 1px dashed $editFrameColorSelected; | ||||
|  | ||||
| // Icons | ||||
| $colorIconAlias: #4af6f3; | ||||
| @@ -173,9 +193,13 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%); | ||||
| $colorBtnCautionBg: #f16f6f; | ||||
| $colorBtnCautionBgHov: #f1504e; | ||||
| $colorBtnCautionFg: $colorBtnFg; | ||||
| $colorClickIcon: $colorKey; | ||||
| $colorClickIconBgHov: rgba($colorKey, 0.6); | ||||
| $colorClickIconFgHov: $colorKeyHov; | ||||
| $colorBtnActiveBg: $colorOk; | ||||
| $colorBtnActiveFg: $colorOkFg; | ||||
| $colorBtnSelectedBg: $colorSelectedBg; | ||||
| $colorBtnSelectedFg: $colorSelectedFg; | ||||
| $colorClickIconButton: $colorKey; | ||||
| $colorClickIconButtonBgHov: rgba($colorKey, 0.6); | ||||
| $colorClickIconButtonFgHov: $colorKeyHov; | ||||
| $colorDropHint: $colorKey; | ||||
| $colorDropHintBg: pushBack($colorDropHint, 10%); | ||||
| $colorDropHintBgHov: $colorDropHint; | ||||
| @@ -183,6 +207,9 @@ $colorDropHintFg: pullForward($colorDropHint, 40%); | ||||
| $colorDisclosureCtrl: rgba($colorBodyFg, 0.5); | ||||
| $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7); | ||||
| $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(white, 0.6); | ||||
| $shdwCursorGuide: rgba(black, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyBg, 0.8); | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pullForward($colorBodyBg, 15%); | ||||
| @@ -200,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25); | ||||
| $paletteItemBorderOuterColorSelected: black; | ||||
| $paletteItemBorderInnerColorSelected: white; | ||||
| $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); | ||||
| $mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed | ||||
| $mixedSettingBgSize: 5px; | ||||
|  | ||||
| // Forms | ||||
| $colorCheck: $colorKey; | ||||
| @@ -241,7 +270,7 @@ $overlayColorFg: $colorMenuFg; | ||||
| $colorOvrBtnBg: pullForward($overlayColorBg, 20%); | ||||
| $colorOvrBtnFg: #fff; | ||||
| $overlayCr: $interiorMarginLg; | ||||
| $overlayBrightnessAdjust: brightness(1.3); | ||||
| $overlayBrightnessAdjust: brightness(1.3); // Applied in a filter: property | ||||
|  | ||||
| // Indicator colors | ||||
| $colorIndicatorAvailable: $colorKey; | ||||
| @@ -262,19 +291,9 @@ $colorLimitRedBg: #940000; | ||||
| $colorLimitRedFg: #ffa489; | ||||
| $colorLimitRedIc: #ff4222; | ||||
|  | ||||
| // Status | ||||
| $colorAlert: #ff3c00; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorCommand: #3693bd; | ||||
| $colorInfo: #2294a2; | ||||
| $colorOk: #33cc33; | ||||
|  | ||||
| // Bubble colors | ||||
| $colorInfoBubbleBg: #dddddd; | ||||
| $colorInfoBubbleFg: #666; | ||||
| $colorInfoBubbleFg: #666; | ||||
| $colorThumbsBubbleFg: pullForward($colorBodyFg, 10%); | ||||
| $colorThumbsBubbleBg: pullForward($colorBodyBg, 10%); | ||||
|  | ||||
| @@ -296,6 +315,9 @@ $colorTabHeaderFg: $colorBodyFg; | ||||
| $colorTabHeaderBorder: $colorBodyBg; | ||||
| $colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%); | ||||
| $colorTabGroupHeaderFg: pushBack($colorTabHeaderFg, 10%); | ||||
| $colorSummaryBg: #2c2c2c; | ||||
| $colorSummaryFg: rgba($colorBodyFg, 0.7); | ||||
| $colorSummaryFgEm: $colorBodyFg; | ||||
|  | ||||
| // Plot | ||||
| $colorPlotBg: rgba(black, 0.05); | ||||
| @@ -314,7 +336,7 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%); | ||||
| $colorItemTreeIcon: $colorKey; // Used | ||||
| $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: pushBack($colorKey, 15%); | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| @@ -368,8 +390,10 @@ $colorLoadingFg: #776ba2; | ||||
| $colorLoadingBg: rgba($colorLoadingFg, 0.1); | ||||
|  | ||||
| // Transitions | ||||
| $transIn: all 50ms ease-in-out; | ||||
| $transOut: all 250ms ease-in-out; | ||||
| $transInTime: 50ms; | ||||
| $transOutTime: 250ms; | ||||
| $transIn: all $transInTime ease-in-out; | ||||
| $transOut: all $transOutTime ease-in-out; | ||||
| $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); | ||||
| $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); | ||||
|  | ||||
|   | ||||
| @@ -87,6 +87,9 @@ $uiColor: #00b2ff; // Resize bars, splitter bars, etc. | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #ccc; | ||||
| $colorAHov: #fff; | ||||
| $filterHov: brightness(1.3); // Tree, location items | ||||
| $colorSelectedBg: pushBack($colorKey, 10%); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| // Layout | ||||
| $shellMainPad: 4px 0; | ||||
| @@ -102,6 +105,20 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningHiFg: #FF9594; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorWarningLoFg: #523400; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorDiagnosticFg: #39461A; | ||||
| $colorCommand: #3693bd; | ||||
| $colorCommandFg: #fff; | ||||
| $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -130,6 +147,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); | ||||
|  | ||||
| /************************************************** EDITING */ | ||||
| $editUIColor: $uiColor; // Base color | ||||
| $editUIColorBg: $editUIColor; | ||||
| $editUIColorFg: #fff; | ||||
| $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color | ||||
| $editUIBaseColor: #344b8d; // Base color, toolbar bg | ||||
| $editUIBaseColorHov: pullForward($editUIBaseColor, 20%); | ||||
| @@ -146,8 +165,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames | ||||
| $editFrameColorSelected: #ccc; // Border of selected frames | ||||
| $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout | ||||
| $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color | ||||
| $editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px; | ||||
| $editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element | ||||
| $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px; | ||||
| $editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element | ||||
| $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color | ||||
| $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%);  // Grippy lines, container size text | ||||
| $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style | ||||
| @@ -155,6 +174,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%); | ||||
| $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style | ||||
| $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); | ||||
| $editFrameMovebarH: 10px; // Height of move bar in layout frame | ||||
| $editMarqueeBorder: 1px dashed $editFrameColorSelected; | ||||
|  | ||||
| // Icons | ||||
| $colorIconAlias: #4af6f3; | ||||
| @@ -177,9 +197,13 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%); | ||||
| $colorBtnCautionBg: #f16f6f; | ||||
| $colorBtnCautionBgHov: #f1504e; | ||||
| $colorBtnCautionFg: $colorBtnFg; | ||||
| $colorClickIcon: $colorKey; | ||||
| $colorClickIconBgHov: rgba($colorKey, 0.6); | ||||
| $colorClickIconFgHov: $colorKeyHov; | ||||
| $colorBtnActiveBg: $colorOk; | ||||
| $colorBtnActiveFg: $colorOkFg; | ||||
| $colorBtnSelectedBg: $colorSelectedBg; | ||||
| $colorBtnSelectedFg: $colorSelectedFg; | ||||
| $colorClickIconButton: $colorKey; | ||||
| $colorClickIconButtonBgHov: rgba($colorKey, 0.6); | ||||
| $colorClickIconButtonFgHov: $colorKeyHov; | ||||
| $colorDropHint: $colorKey; | ||||
| $colorDropHintBg: pushBack($colorDropHint, 10%); | ||||
| $colorDropHintBgHov: $colorDropHint; | ||||
| @@ -187,6 +211,9 @@ $colorDropHintFg: pullForward($colorDropHint, 40%); | ||||
| $colorDisclosureCtrl: rgba($colorBodyFg, 0.5); | ||||
| $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7); | ||||
| $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(white, 0.6); | ||||
| $shdwCursorGuide: rgba(black, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyBg, 0.8); | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pullForward($colorBodyBg, 15%); | ||||
| @@ -204,6 +231,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25); | ||||
| $paletteItemBorderOuterColorSelected: black; | ||||
| $paletteItemBorderInnerColorSelected: white; | ||||
| $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); | ||||
| $mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed | ||||
| $mixedSettingBgSize: 5px; | ||||
|  | ||||
| // Forms | ||||
| $colorCheck: $colorKey; | ||||
| @@ -245,7 +274,7 @@ $overlayColorFg: $colorMenuFg; | ||||
| $colorOvrBtnBg: pullForward($overlayColorBg, 20%); | ||||
| $colorOvrBtnFg: #fff; | ||||
| $overlayCr: $interiorMarginLg; | ||||
| $overlayBrightnessAdjust: brightness(1.3); | ||||
| $overlayBrightnessAdjust: brightness(1.3); // Applied in a filter: property | ||||
|  | ||||
| // Indicator colors | ||||
| $colorIndicatorAvailable: $colorKey; | ||||
| @@ -266,19 +295,9 @@ $colorLimitRedBg: #940000; | ||||
| $colorLimitRedFg: #ffa489; | ||||
| $colorLimitRedIc: #ff4222; | ||||
|  | ||||
| // Status | ||||
| $colorAlert: #ff3c00; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorCommand: #3693bd; | ||||
| $colorInfo: #2294a2; | ||||
| $colorOk: #33cc33; | ||||
|  | ||||
| // Bubble colors | ||||
| $colorInfoBubbleBg: #dddddd; | ||||
| $colorInfoBubbleFg: #666; | ||||
| $colorInfoBubbleFg: #666; | ||||
| $colorThumbsBubbleFg: pullForward($colorBodyFg, 10%); | ||||
| $colorThumbsBubbleBg: pullForward($colorBodyBg, 10%); | ||||
|  | ||||
| @@ -300,6 +319,9 @@ $colorTabHeaderFg: $colorBodyFg; | ||||
| $colorTabHeaderBorder: $colorBodyBg; | ||||
| $colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%); | ||||
| $colorTabGroupHeaderFg: pushBack($colorTabHeaderFg, 10%); | ||||
| $colorSummaryBg: #2c2c2c; | ||||
| $colorSummaryFg: rgba($colorBodyFg, 0.7); | ||||
| $colorSummaryFgEm: $colorBodyFg; | ||||
|  | ||||
| // Plot | ||||
| $colorPlotBg: rgba(black, 0.05); | ||||
| @@ -318,7 +340,7 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%); | ||||
| $colorItemTreeIcon: $colorKey; // Used | ||||
| $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: pushBack($colorKey, 15%); | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| @@ -365,15 +387,17 @@ $colorMobilePaneLeftTreeItemFg: $colorItemTreeFg; | ||||
| $colorMobileSelectListTreeItemBg: rgba(#000, 0.05); | ||||
|  | ||||
| // About Screen | ||||
| $colorAboutLink: $colorKeySubtle; | ||||
| $colorAboutLink: #9bb5ff; | ||||
|  | ||||
| // Loading | ||||
| $colorLoadingFg: #776ba2; | ||||
| $colorLoadingBg: rgba($colorLoadingFg, 0.1); | ||||
|  | ||||
| // Transitions | ||||
| $transIn: all 50ms ease-in-out; | ||||
| $transOut: all 250ms ease-in-out; | ||||
| $transInTime: 50ms; | ||||
| $transOutTime: 250ms; | ||||
| $transIn: all $transInTime ease-in-out; | ||||
| $transOut: all $transOutTime ease-in-out; | ||||
| $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); | ||||
| $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); | ||||
|  | ||||
|   | ||||
| @@ -27,8 +27,8 @@ $mobileListIconSize: 30px; | ||||
| $mobileTitleDescH: 35px; | ||||
| $mobileOverlayMargin: 20px; | ||||
| $mobileMenuIconD: 34px; | ||||
| $phoneItemH: floor($ueBrowseGridItemLg/4); | ||||
| $tabletItemH: floor($ueBrowseGridItemLg/3); | ||||
| $phoneItemH: floor($gridItemMobile / 4); | ||||
| $tabletItemH: floor($gridItemMobile / 3); | ||||
|  | ||||
| /************************** MOBILE TREE MENU DIMENSIONS */ | ||||
| $mobileTreeItemH: 35px; | ||||
|   | ||||
| @@ -83,6 +83,9 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc. | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #999; | ||||
| $colorAHov: $colorKey; | ||||
| $filterHov: brightness(1.3); // Tree, location items | ||||
| $colorSelectedBg: pushBack($colorKey, 40%); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 10%); | ||||
|  | ||||
| // Layout | ||||
| $shellMainPad: 4px 0; | ||||
| @@ -98,6 +101,20 @@ $colorStatusAlertFilter: invert(89%) sepia(26%) saturate(5035%) hue-rotate(316de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningHiFg: #FF9594; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorWarningLoFg: #523400; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorDiagnosticFg: #39461A; | ||||
| $colorCommand: #3693bd; | ||||
| $colorCommandFg: #fff; | ||||
| $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -126,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); | ||||
|  | ||||
| /************************************************** EDITING */ | ||||
| $editUIColor: $uiColor; // Base color | ||||
| $editUIColorBg: $editUIColor; | ||||
| $editUIColorFg: #fff; | ||||
| $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color | ||||
| $editUIBaseColor: #cae1ff; // Base color, toolbar bg | ||||
| $editUIBaseColorHov: pushBack($editUIBaseColor, 20%); | ||||
| @@ -142,7 +161,7 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames | ||||
| $editFrameColorSelected: #333; // Border of selected frames | ||||
| $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout | ||||
| $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color | ||||
| $editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px; | ||||
| $editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px; | ||||
| $editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element | ||||
| $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color | ||||
| $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%);  // Grippy lines, container size text | ||||
| @@ -151,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%); | ||||
| $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style | ||||
| $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); | ||||
| $editFrameMovebarH: 10px; // Height of move bar in layout frame | ||||
| $editMarqueeBorder: 1px dashed $editFrameColorSelected; | ||||
|  | ||||
| // Icons | ||||
| $colorIconAlias: #4af6f3; | ||||
| @@ -173,9 +193,13 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%); | ||||
| $colorBtnCautionBg: #f16f6f; | ||||
| $colorBtnCautionBgHov: #f1504e; | ||||
| $colorBtnCautionFg: $colorBtnFg; | ||||
| $colorClickIcon: $colorKey; | ||||
| $colorClickIconBgHov: rgba($colorKey, 0.2); | ||||
| $colorClickIconFgHov: $colorKeyHov; | ||||
| $colorBtnActiveBg: $colorOk; | ||||
| $colorBtnActiveFg: $colorOkFg; | ||||
| $colorBtnSelectedBg: $colorBtnMajorBg; | ||||
| $colorBtnSelectedFg: $colorBtnMajorFg; | ||||
| $colorClickIconButton: $colorKey; | ||||
| $colorClickIconButtonBgHov: rgba($colorKey, 0.2); | ||||
| $colorClickIconButtonFgHov: $colorKeyHov; | ||||
| $colorDropHint: $colorKey; | ||||
| $colorDropHintBg: pushBack($colorDropHint, 10%); | ||||
| $colorDropHintBgHov: pushBack($colorDropHint, 40%); | ||||
| @@ -183,6 +207,9 @@ $colorDropHintFg: pushBack($colorDropHint, 0); | ||||
| $colorDisclosureCtrl: rgba($colorBodyFg, 0.5); | ||||
| $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7); | ||||
| $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(black, 0.6); | ||||
| $shdwCursorGuide: rgba(white, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyFg, 0.8); | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pushBack($colorBodyBg, 10%); | ||||
| @@ -200,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25); | ||||
| $paletteItemBorderOuterColorSelected: black; | ||||
| $paletteItemBorderInnerColorSelected: white; | ||||
| $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); | ||||
| $mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.9)); // Used in .c-click-icon--mixed | ||||
| $mixedSettingBgSize: 10px; | ||||
|  | ||||
| // Forms | ||||
| $colorCheck: $colorKey; | ||||
| @@ -218,8 +247,8 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%); | ||||
| $colorFormText: pushBack($colorBodyFg, 10%); | ||||
| $colorInputIcon: pushBack($colorBodyFg, 25%); | ||||
| $colorFieldHint: pullForward($colorBodyFg, 40%); | ||||
| $shdwInput: inset rgba(black, 0.7) 0 0 1px; | ||||
| $shdwInputHov: inset rgba(black, 0.7) 0 0 2px; | ||||
| $shdwInput: inset rgba(black, 0.5) 0 0 2px; | ||||
| $shdwInputHov: inset rgba(black, 0.8) 0 0 2px; | ||||
| $shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px; | ||||
| $formTBPad: $interiorMargin; | ||||
| $formLRPad: $interiorMargin; | ||||
| @@ -241,7 +270,7 @@ $overlayColorFg: $colorMenuFg; | ||||
| $colorOvrBtnBg: pullForward($overlayColorBg, 40%); | ||||
| $colorOvrBtnFg: #fff; | ||||
| $overlayCr: $interiorMarginLg; | ||||
| $overlayBrightnessAdjust: brightness(1.3); | ||||
| $overlayBrightnessAdjust: brightness(1); // Applied in a filter: property | ||||
|  | ||||
| // Indicator colors | ||||
| $colorIndicatorAvailable: $colorKey; | ||||
| @@ -262,15 +291,6 @@ $colorLimitRedBg: #ff0000; | ||||
| $colorLimitRedFg: #fff; | ||||
| $colorLimitRedIc: #ffa99a; | ||||
|  | ||||
| // Status | ||||
| $colorAlert: #ff3c00; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorDiagnostic: #a4b442; | ||||
| $colorCommand: #3693bd; | ||||
| $colorInfo: #2294a2; | ||||
| $colorOk: #33cc33; | ||||
|  | ||||
| // Bubble colors | ||||
| $colorInfoBubbleBg: $colorMenuBg; | ||||
| $colorInfoBubbleFg: #666; | ||||
| @@ -295,6 +315,9 @@ $colorTabHeaderFg: pullForward($colorBodyFg, 20%); | ||||
| $colorTabHeaderBorder: $colorBodyBg; | ||||
| $colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%); | ||||
| $colorTabGroupHeaderFg: pullForward($colorTabGroupHeaderBg, 40%); | ||||
| $colorSummaryBg: #999; | ||||
| $colorSummaryFg: rgba($colorBodyBg, 0.7); | ||||
| $colorSummaryFgEm: white; | ||||
|  | ||||
| // Plot | ||||
| $colorPlotBg: rgba(black, 0.05); | ||||
| @@ -313,8 +336,8 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 10%); | ||||
| $colorItemTreeIcon: $colorKey; // Used | ||||
| $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: pushBack($colorKey, 15%); | ||||
| $colorItemTreeSelectedFg: $colorBodyBg; | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| $colorItemTreeEditingFg: $editUIColor; | ||||
| @@ -337,6 +360,7 @@ $scrollbarThumbColorMenuHov: darken($scrollbarThumbColorMenu, 2%); | ||||
|  | ||||
| // Splitter | ||||
| $splitterHandleD: 2px; | ||||
| $splitterD: $splitterHandleD; | ||||
| $splitterHandleHitMargin: 4px; | ||||
| $colorSplitterBaseBg: $colorBodyBg; | ||||
| $colorSplitterBg: pullForward($colorSplitterBaseBg, 20%); | ||||
| @@ -366,8 +390,10 @@ $colorLoadingFg: #776ba2; | ||||
| $colorLoadingBg: rgba($colorLoadingFg, 0.1); | ||||
|  | ||||
| // Transitions | ||||
| $transIn: all 50ms ease-in-out; | ||||
| $transOut: all 250ms ease-in-out; | ||||
| $transInTime: 50ms; | ||||
| $transOutTime: 250ms; | ||||
| $transIn: all $transInTime ease-in-out; | ||||
| $transOut: all $transOutTime ease-in-out; | ||||
| $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); | ||||
| $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user