Compare commits
	
		
			44 Commits
		
	
	
		
			mct4463
			...
			imagery-ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2d8afa1456 | ||
|   | f4ef82ef74 | ||
|   | f43121a38a | ||
|   | 9f7d788c86 | ||
|   | d2374920ff | ||
|   | a6b9ba184c | ||
|   | c00ab72d04 | ||
|   | 98fb507f01 | ||
|   | 5258227d87 | ||
|   | f43ba8f8f4 | ||
|   | 0ff9821091 | ||
|   | 353bdb5ca5 | ||
|   | 28a93fb262 | ||
|   | 8882e5f3b3 | ||
|   | 9cfabe0054 | ||
|   | 7975cf89ef | ||
|   | a9c0e5a139 | ||
|   | 92a7ac18a2 | ||
|   | 240bc9e713 | ||
|   | d09013ba28 | ||
|   | d99d414c84 | ||
|   | 2638302dab | ||
|   | 8241ed8bb8 | ||
|   | 23dffe33a9 | ||
|   | 92e08f19bd | ||
|   | c644e5266a | ||
|   | 7fa93c18f3 | ||
|   | 5b6e61d95a | ||
|   | 474b1ed2bf | ||
|   | 0d0de1ed64 | ||
|   | 85ab5cb319 | ||
|   | 299005982f | ||
|   | 8d3b277ef4 | ||
|   | 8eae707833 | ||
|   | f62b39054a | ||
|   | 7515ce504e | ||
|   | 0b5ca621ec | ||
|   | 0e0644cd1f | ||
|   | 6089ae3531 | ||
|   | 876eb59787 | ||
|   | 17be0d7132 | ||
|   | ace880dd41 | ||
|   | dc3781c8e5 | ||
|   | b76b4e4098 | 
| @@ -96,6 +96,6 @@ module.exports = (config) => { | ||||
|             logLevel: 'warn' | ||||
|         }, | ||||
|         singleRun: true, | ||||
|         browserNoActivityTimeout: 90000 | ||||
|         browserNoActivityTimeout: 400000 | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -47,7 +47,7 @@ define([ | ||||
|         this.eventEmitter = new EventEmitter(); | ||||
|         this.providers = {}; | ||||
|         this.rootRegistry = new RootRegistry(); | ||||
|         this.rootProvider = new RootObjectProvider(this.rootRegistry); | ||||
|         this.rootProvider = new RootObjectProvider.default(this.rootRegistry); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -20,28 +20,37 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
| ], function ( | ||||
| ) { | ||||
|  | ||||
|     function RootObjectProvider(rootRegistry) { | ||||
|         this.rootRegistry = rootRegistry; | ||||
| class RootObjectProvider { | ||||
|     constructor(rootRegistry) { | ||||
|         if(!RootObjectProvider.instance) { | ||||
|             this.rootRegistry = rootRegistry; | ||||
|             this.rootObject = { | ||||
|                 identifier: { | ||||
|                     key: "ROOT", | ||||
|                     namespace: "" | ||||
|                 }, | ||||
|                 name: 'The root object', | ||||
|                 type: 'root', | ||||
|                 composition: [] | ||||
|             }; | ||||
|             RootObjectProvider.instance = this; | ||||
|         } | ||||
|         return RootObjectProvider.instance; | ||||
|     } | ||||
|  | ||||
|     RootObjectProvider.prototype.get = function () { | ||||
|         return this.rootRegistry.getRoots() | ||||
|             .then(function (roots) { | ||||
|                 return { | ||||
|                     identifier: { | ||||
|                         key: "ROOT", | ||||
|                         namespace: "" | ||||
|                     }, | ||||
|                     name: 'The root object', | ||||
|                     type: 'root', | ||||
|                     composition: roots | ||||
|                 }; | ||||
|             }); | ||||
|     }; | ||||
|     updateName(name) { | ||||
|         this.rootObject.name = name | ||||
|     } | ||||
|  | ||||
|     return RootObjectProvider; | ||||
| }); | ||||
|     async get() { | ||||
|         let roots = await this.rootRegistry.getRoots(); | ||||
|         this.rootObject.composition = roots; | ||||
|         return this.rootObject; | ||||
|     } | ||||
| } | ||||
|  | ||||
| const instance = function (rootRegistry) { | ||||
|     return new RootObjectProvider(rootRegistry); | ||||
| } | ||||
|  | ||||
| export default instance; | ||||
|   | ||||
| @@ -31,7 +31,7 @@ define([ | ||||
|         beforeEach(function () { | ||||
|             rootRegistry = jasmine.createSpyObj('rootRegistry', ['getRoots']); | ||||
|             rootRegistry.getRoots.and.returnValue(Promise.resolve(['some root'])); | ||||
|             rootObjectProvider = new RootObjectProvider(rootRegistry); | ||||
|             rootObjectProvider = new RootObjectProvider.default(rootRegistry); | ||||
|         }); | ||||
|  | ||||
|         it('supports fetching root', function () { | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/plugins/defaultRootName/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/plugins/defaultRootName/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
| import RootObjectProvider from '../../api/objects/RootObjectProvider.js'; | ||||
|  | ||||
| export default function (name) { | ||||
|     return function (openmct) { | ||||
|         let rootObjectProvider = new RootObjectProvider(); | ||||
|         rootObjectProvider.updateName(name); | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/plugins/defaultRootName/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/plugins/defaultRootName/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
| import { | ||||
|     createOpenMct, | ||||
|     resetApplicationState | ||||
| } from 'utils/testing'; | ||||
|  | ||||
| xdescribe("the plugin", () => { | ||||
|     let openmct, | ||||
|         compositionAPI, | ||||
|         newFolderAction, | ||||
|         mockObjectPath, | ||||
|         mockDialogService, | ||||
|         mockComposition, | ||||
|         mockPromise, | ||||
|         newFolderName = 'New Folder'; | ||||
|  | ||||
|     beforeEach((done) => { | ||||
|         openmct = createOpenMct(); | ||||
|  | ||||
|         openmct.on('start', done); | ||||
|         openmct.startHeadless(); | ||||
|  | ||||
|         newFolderAction = openmct.contextMenu._allActions.filter(action => { | ||||
|             return action.key === 'newFolder'; | ||||
|         })[0]; | ||||
|     }); | ||||
|  | ||||
|     afterEach(() => { | ||||
|         resetApplicationState(openmct); | ||||
|     }); | ||||
|  | ||||
|     it('installs the new folder action', () => { | ||||
|         expect(newFolderAction).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     describe('when invoked', () => { | ||||
|  | ||||
|         beforeEach((done) => { | ||||
|             compositionAPI = openmct.composition; | ||||
|             mockObjectPath = [{ | ||||
|                 name: 'mock folder', | ||||
|                 type: 'folder', | ||||
|                 identifier: { | ||||
|                     key: 'mock-folder', | ||||
|                     namespace: '' | ||||
|                 } | ||||
|             }]; | ||||
|             mockPromise = { | ||||
|                 then: (callback) => { | ||||
|                     callback({name: newFolderName}); | ||||
|                     done(); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']); | ||||
|             mockComposition = jasmine.createSpyObj('composition', ['add']); | ||||
|  | ||||
|             mockDialogService.getUserInput.and.returnValue(mockPromise); | ||||
|  | ||||
|             spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService); | ||||
|             spyOn(compositionAPI, 'get').and.returnValue(mockComposition); | ||||
|             spyOn(openmct.objects, 'mutate'); | ||||
|  | ||||
|             newFolderAction.invoke(mockObjectPath); | ||||
|         }); | ||||
|  | ||||
|         it('gets user input for folder name', () => { | ||||
|             expect(mockDialogService.getUserInput).toHaveBeenCalled(); | ||||
|         }); | ||||
|  | ||||
|         it('creates a new folder object', () => { | ||||
|             expect(openmct.objects.mutate).toHaveBeenCalled(); | ||||
|         }); | ||||
|  | ||||
|         it('adds new folder object to parent composition', () => { | ||||
|             expect(mockComposition.add).toHaveBeenCalled(); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -13,7 +13,7 @@ | ||||
|             cursor: pointer; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $colorItemTreeHoverBg; | ||||
|                 background: $colorListItemBgHov; | ||||
|                 filter: $filterHov; | ||||
|                 transition: $transIn; | ||||
|             } | ||||
|   | ||||
| @@ -228,7 +228,7 @@ export default { | ||||
|         subscribe() { | ||||
|             this.unsubscribe = this.openmct.telemetry | ||||
|                 .subscribe(this.domainObject, (datum) => { | ||||
|                     let parsedTimestamp = this.timeFormat.parse(datum[this.timeKey]), | ||||
|                     let parsedTimestamp = this.timeFormat.parse(datum), | ||||
|                         bounds = this.openmct.time.bounds(); | ||||
|  | ||||
|                     if(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) { | ||||
|   | ||||
| @@ -54,7 +54,8 @@ define([ | ||||
|     './themes/snow', | ||||
|     './URLTimeSettingsSynchronizer/plugin', | ||||
|     './notificationIndicator/plugin', | ||||
|     './newFolderAction/plugin' | ||||
|     './newFolderAction/plugin', | ||||
|     './defaultRootName/plugin' | ||||
| ], function ( | ||||
|     _, | ||||
|     UTCTimeSystem, | ||||
| @@ -89,7 +90,8 @@ define([ | ||||
|     Snow, | ||||
|     URLTimeSettingsSynchronizer, | ||||
|     NotificationIndicator, | ||||
|     NewFolderAction | ||||
|     NewFolderAction, | ||||
|     DefaultRootName | ||||
| ) { | ||||
|     var bundleMap = { | ||||
|         LocalStorage: 'platform/persistence/local', | ||||
| @@ -201,6 +203,7 @@ define([ | ||||
|     plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default; | ||||
|     plugins.NotificationIndicator = NotificationIndicator.default; | ||||
|     plugins.NewFolderAction = NewFolderAction.default; | ||||
|     plugins.DefaultRootName = DefaultRootName.default; | ||||
|  | ||||
|     return plugins; | ||||
| }); | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
| } | ||||
|  | ||||
| @keyframes rotation-centered { | ||||
|     0%   { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|     100% { transform: translate(-50%, -50%) rotate(360deg); } | ||||
|     0%   { transform: rotate(0deg); } | ||||
|     100% { transform: rotate(360deg); } | ||||
| } | ||||
|  | ||||
| @keyframes clock-hands { | ||||
|   | ||||
| @@ -80,8 +80,8 @@ $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%); | ||||
| $filterHov: brightness(1.3) contrast(1.5); // Tree, location items | ||||
| $colorSelectedBg: rgba($colorKey, 0.3); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| // Object labels | ||||
| @@ -361,13 +361,14 @@ $legendTableHeadBg: $colorTabHeaderBg; | ||||
|  | ||||
| // Tree | ||||
| $colorTreeBg: transparent; | ||||
| $colorItemTreeHoverBg: rgba(white, 0.07); | ||||
| $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%); | ||||
| $colorItemTreeHoverBg: rgba(#fff, 0.03); | ||||
| $colorItemTreeHoverFg: #fff; | ||||
| $colorItemTreeIcon: $colorKey; // Used | ||||
| $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $filterItemTreeSelected: $filterHov; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| $colorItemTreeEditingFg: $editUIColor; | ||||
| @@ -402,7 +403,7 @@ $splitterBtnColorBg: $colorBtnBg; | ||||
| $splitterBtnColorFg: #999; | ||||
| $splitterBtnLabelColorFg: #666; | ||||
| $splitterCollapsedBtnColorBg: #222; | ||||
| $splitterCollapsedBtnColorFg: #666; | ||||
| $splitterCollapsedBtnColorFg: #555; | ||||
| $splitterCollapsedBtnColorBgHov: $colorKey; | ||||
| $splitterCollapsedBtnColorFgHov: $colorKeyFg; | ||||
|  | ||||
|   | ||||
| @@ -80,12 +80,12 @@ $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%); | ||||
| $filterHov: brightness(1.3) contrast(1.5); // Tree, location items | ||||
| $colorSelectedBg: rgba($colorKey, 0.3); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| // Object labels | ||||
| @@ -365,13 +365,14 @@ $legendTableHeadBg: rgba($colorBodyFg, 0.15); | ||||
|  | ||||
| // Tree | ||||
| $colorTreeBg: transparent; | ||||
| $colorItemTreeHoverBg: rgba(white, 0.07); | ||||
| $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%); | ||||
| $colorItemTreeHoverBg: rgba(#fff, 0.03); | ||||
| $colorItemTreeHoverFg: #fff; | ||||
| $colorItemTreeIcon: $colorKey; // Used | ||||
| $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $filterItemTreeSelected: $filterHov; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| $colorItemTreeEditingFg: $editUIColor; | ||||
| @@ -406,7 +407,7 @@ $splitterBtnColorBg: $colorBtnBg; | ||||
| $splitterBtnColorFg: #999; | ||||
| $splitterBtnLabelColorFg: #666; | ||||
| $splitterCollapsedBtnColorBg: #222; | ||||
| $splitterCollapsedBtnColorFg: #666; | ||||
| $splitterCollapsedBtnColorFg: #555; | ||||
| $splitterCollapsedBtnColorBgHov: $colorKey; | ||||
| $splitterCollapsedBtnColorFgHov: $colorKeyFg; | ||||
|  | ||||
|   | ||||
| @@ -78,10 +78,10 @@ $colorKeyFilterHov: invert(69%) sepia(87%) saturate(3243%) hue-rotate(151deg) br | ||||
| $colorKeySelectedBg: $colorKey; | ||||
| $uiColor: #289fec; // Resize bars, splitter bars, etc. | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #999; | ||||
| $colorA: $colorBodyFg; | ||||
| $colorAHov: $colorKey; | ||||
| $filterHov: brightness(0.8) contrast(2); // Tree, location items | ||||
| $colorSelectedBg: pushBack($colorKey, 40%); | ||||
| $colorSelectedBg: rgba($colorKey, 0.2); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 10%); | ||||
|  | ||||
| // Object labels | ||||
| @@ -94,7 +94,7 @@ $shellPanePad: $interiorMargin, 7px; | ||||
| $drawerBg: darken($colorBodyBg, 5%); | ||||
| $drawerFg: darken($colorBodyFg, 5%); | ||||
| $sideBarBg: $drawerBg; | ||||
| $sideBarHeaderBg: rgba(black, 0.25); | ||||
| $sideBarHeaderBg: rgba(black, 0.1); | ||||
| $sideBarHeaderFg: rgba($colorBodyFg, 0.7); | ||||
|  | ||||
| // Status colors, mainly used for messaging and item ancillary symbols | ||||
| @@ -368,7 +368,8 @@ $colorItemTreeIconHover: $colorItemTreeIcon; // Used | ||||
| $colorItemTreeFg: $colorBodyFg; | ||||
| $colorItemTreeSelectedBg: $colorSelectedBg; | ||||
| $colorItemTreeSelectedFg: $colorItemTreeHoverFg; | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeSelectedFg; | ||||
| $filterItemTreeSelected: contrast(1.4); | ||||
| $colorItemTreeSelectedIcon: $colorItemTreeIcon; | ||||
| $colorItemTreeEditingBg: pushBack($editUIColor, 20%); | ||||
| $colorItemTreeEditingFg: $editUIColor; | ||||
| $colorItemTreeEditingIcon: $editUIColor; | ||||
|   | ||||
| @@ -44,6 +44,7 @@ $overlayOuterMarginFullscreen: 0%; | ||||
| $overlayOuterMarginDialog: 20%; | ||||
| $overlayInnerMargin: 25px; | ||||
| $mainViewPad: 0px; | ||||
| $treeNavArrowD: 20px; | ||||
| /*************** Items */ | ||||
| $itemPadLR: 5px; | ||||
| $gridItemDesk: 175px; | ||||
| @@ -81,8 +82,8 @@ $formLabelMinW: 120px; | ||||
| $formLabelW: 30%; | ||||
| /*************** Wait Spinner */ | ||||
| $waitSpinnerD: 32px; | ||||
| $waitSpinnerTreeD: 20px; | ||||
| $waitSpinnerBorderW: 5px; | ||||
| $waitSpinnerTreeD: 20px; | ||||
| $waitSpinnerTreeBorderW: 3px; | ||||
| /*************** Messages */ | ||||
| $messageIconD: 80px; | ||||
|   | ||||
| @@ -97,7 +97,8 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-click-link { | ||||
| .c-click-link, | ||||
| .c-icon-link { | ||||
|     // A clickable element, typically inline, with an icon and label | ||||
|     @include cControl(); | ||||
|     cursor: pointer; | ||||
| @@ -112,8 +113,15 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-icon-link { | ||||
|     &:before { | ||||
|         // Icon | ||||
|         //color: $colorBtnMajorBg; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-icon-button { | ||||
|     .c-icon-button__label { | ||||
|     &__label { | ||||
|         margin-left: $interiorMargin; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -101,8 +101,9 @@ a { | ||||
|     color: $colorA; | ||||
|     cursor: pointer; | ||||
|     text-decoration: none; | ||||
|     &:hover { | ||||
|         color: $colorAHov; | ||||
|  | ||||
|     &:focus { | ||||
|         outline: none !important; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -280,19 +281,23 @@ body.desktop .has-local-controls { | ||||
|  | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         padding-left: $spinnerL + $d/2 + $interiorMargin; | ||||
|         background: $colorLoadingBg; | ||||
|         margin-left: $treeNavArrowD + $interiorMargin; | ||||
|         min-height: 5px + $d; | ||||
|  | ||||
|         .c-tree__item__label { | ||||
|             font-style: italic; | ||||
|             margin-left: $interiorMargin; | ||||
|             opacity: 0.6; | ||||
|         } | ||||
|         &:before { | ||||
|             left: auto; | ||||
|             top: auto; | ||||
|             transform: translate(0); | ||||
|             height: $d; | ||||
|             width: $d; | ||||
|             border-width: 4px; | ||||
|             left: $spinnerL; | ||||
|             border-width: 3px; | ||||
|             //left: $spinnerL; | ||||
|             position: relative; | ||||
|         } | ||||
|         &:after { | ||||
|             display: none; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <template> | ||||
| <span | ||||
|     class="c-disclosure-triangle" | ||||
|     :class="{ | ||||
|         'c-disclosure-triangle--expanded' : value, | ||||
|         'is-enabled' : enabled | ||||
|     }" | ||||
|     :class="[ | ||||
|         controlClass, | ||||
|         { 'c-disclosure-triangle--expanded' : value }, | ||||
|         {'is-enabled' : enabled } | ||||
|     ]" | ||||
|     @click="handleClick" | ||||
| ></span> | ||||
| </template> | ||||
| @@ -25,6 +25,10 @@ export default { | ||||
|         propagate: { | ||||
|             type: Boolean, | ||||
|             default: true | ||||
|         }, | ||||
|         controlClass: { | ||||
|             type: String, | ||||
|             default: 'c-disclosure-triangle' | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|   | ||||
| @@ -3,7 +3,8 @@ | ||||
|     <div class="l-browse-bar__start"> | ||||
|         <button | ||||
|             v-if="hasParent" | ||||
|             class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left" | ||||
|             class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-arrow-nav-to-parent" | ||||
|             title="Navigate up to parent" | ||||
|             @click="goToParent" | ||||
|         ></button> | ||||
|         <div | ||||
|   | ||||
| @@ -15,7 +15,9 @@ | ||||
|         <CreateButton class="l-shell__create-button" /> | ||||
|         <indicators class="l-shell__head-section l-shell__indicators" /> | ||||
|         <button | ||||
|             class="l-shell__head__collapse-button c-button" | ||||
|             class="l-shell__head__collapse-button c-icon-button" | ||||
|             :class="headExpanded ? 'l-shell__head__collapse-button--collapse' : 'l-shell__head__collapse-button--expand'" | ||||
|             :title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`" | ||||
|             @click="toggleShellHead" | ||||
|         ></button> | ||||
|         <notification-banner /> | ||||
| @@ -47,12 +49,23 @@ | ||||
|             label="Browse" | ||||
|             collapsable | ||||
|         > | ||||
|             <mct-tree class="l-shell__tree" /> | ||||
|             <button | ||||
|                 slot="controls" | ||||
|                 class="c-icon-button l-shell__sync-tree-button icon-target" | ||||
|                 title="Show selected item in tree" | ||||
|                 @click="handleSyncTreeNavigation" | ||||
|             > | ||||
|             </button> | ||||
|             <mct-tree | ||||
|                 :sync-tree-navigation="triggerSync" | ||||
|                 class="l-shell__tree" | ||||
|             /> | ||||
|         </pane> | ||||
|         <pane class="l-shell__pane-main"> | ||||
|             <browse-bar | ||||
|                 ref="browseBar" | ||||
|                 class="l-shell__main-view-browse-bar" | ||||
|                 @sync-tree-navigation="handleSyncTreeNavigation" | ||||
|             /> | ||||
|             <toolbar | ||||
|                 v-if="toolbar" | ||||
| @@ -154,7 +167,8 @@ export default { | ||||
|             conductorComponent: undefined, | ||||
|             isEditing: false, | ||||
|             hasToolbar: false, | ||||
|             headExpanded | ||||
|             headExpanded, | ||||
|             triggerSync: false | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -204,6 +218,9 @@ export default { | ||||
|             } | ||||
|  | ||||
|             this.hasToolbar = structure.length > 0; | ||||
|         }, | ||||
|         handleSyncTreeNavigation() { | ||||
|             this.triggerSync = !this.triggerSync; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
|                 color: $colorKey !important; | ||||
|                 position: absolute; | ||||
|                 right: -18px; | ||||
|                 top: 0; | ||||
|                 top: $interiorMarginSm; | ||||
|                 transform: translateX(100%); | ||||
|                 width: $mobileMenuIconD; | ||||
|                 z-index: 2; | ||||
| @@ -100,6 +100,11 @@ | ||||
|         &__pane-tree { | ||||
|             background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3)); | ||||
|  | ||||
|             [class*="expand-button"], | ||||
|             [class*="sync-tree-button"] { | ||||
|                 display: none; | ||||
|             } | ||||
|  | ||||
|             &[class*="--collapsed"] { | ||||
|                 [class*="collapse-button"] { | ||||
|                     right: -8px; | ||||
| @@ -153,7 +158,7 @@ | ||||
|     } | ||||
|  | ||||
|     &__head { | ||||
|         align-items: stretch; | ||||
|         align-items: center; | ||||
|         background: $colorHeadBg; | ||||
|         justify-content: space-between; | ||||
|         padding: $interiorMargin $interiorMargin + 2; | ||||
| @@ -162,14 +167,21 @@ | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
|  | ||||
|         [class*='__head__collapse-button'] { | ||||
|             align-self: start; | ||||
|         .l-shell__head__collapse-button { | ||||
|             color: $colorBtnMajorBg; | ||||
|             flex: 0 0 auto; | ||||
|             margin-top: 6px; | ||||
|             font-size: 0.9em; | ||||
|  | ||||
|             &:before { | ||||
|                 content: $glyph-icon-arrow-down; | ||||
|                 font-size: 1.1em; | ||||
|             &--collapse { | ||||
|                 &:before { | ||||
|                     content: $glyph-icon-items-collapse; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             &--expand { | ||||
|                 &:before { | ||||
|                     content: $glyph-icon-items-expand; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -184,12 +196,6 @@ | ||||
|             .c-indicator__label { | ||||
|                 transition: none !important; | ||||
|             } | ||||
|  | ||||
|             [class*='__head__collapse-button'] { | ||||
|                 &:before { | ||||
|                     transform: rotate(180deg); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -304,6 +310,10 @@ | ||||
|         display: inline-flex; | ||||
|     } | ||||
|  | ||||
|     > * + * { | ||||
|         margin-left: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     &__start, | ||||
|     &__end, | ||||
|     &__actions { | ||||
| @@ -327,8 +337,12 @@ | ||||
|  | ||||
|     &__start { | ||||
|         flex: 1 1 auto; | ||||
|         margin-right: $interiorMargin; | ||||
|         //margin-right: $interiorMargin; | ||||
|         min-width: 0; // Forces interior to compress when pushed on | ||||
|  | ||||
|         [class*='button'] { | ||||
|             flex: 0 0 auto; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__end { | ||||
| @@ -337,15 +351,15 @@ | ||||
|  | ||||
|     &__nav-to-parent-button, | ||||
|     &__disclosure-button { | ||||
|         flex: 0 0 auto; | ||||
|         //flex: 0 0 auto; | ||||
|     } | ||||
|  | ||||
|     &__nav-to-parent-button { | ||||
|         // This is an icon-button | ||||
|         $p: $interiorMargin; | ||||
|         margin-right: $interiorMargin; | ||||
|         padding-left: $p; | ||||
|         padding-right: $p; | ||||
|         //$p: $interiorMargin; | ||||
|         //margin-right: $interiorMargin; | ||||
|         //padding-left: $p; | ||||
|         //padding-right: $p; | ||||
|  | ||||
|         .is-editing & { | ||||
|             display: none; | ||||
| @@ -362,7 +376,8 @@ | ||||
|     } | ||||
|  | ||||
|     &__object-name--w { | ||||
|         @include headerFont(1.4em); | ||||
|         @include headerFont(1.5em); | ||||
|         margin-left: $interiorMarginLg; | ||||
|         min-width: 0; | ||||
|  | ||||
|         .is-missing__indicator { | ||||
|   | ||||
| @@ -12,10 +12,6 @@ | ||||
|         flex: 0 0 auto; | ||||
|     } | ||||
|  | ||||
|     &__loading { | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
|     &__no-results { | ||||
|         font-style: italic; | ||||
|         opacity: 0.6; | ||||
| @@ -26,6 +22,33 @@ | ||||
|         height: 0; // Chrome 73 overflow bug fix | ||||
|         padding-right: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     .c-tree { | ||||
|         flex: 1 1 auto; | ||||
|         overflow: hidden; | ||||
|         transition: all; | ||||
|  | ||||
|         .scrollable-children { | ||||
|             .c-tree__item-h { | ||||
|                 width: 100%; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__item--empty { | ||||
|             // Styling for empty tree items | ||||
|             // Indent should allow for c-nav view-control width and icon spacing | ||||
|             font-style: italic; | ||||
|             padding: $interiorMarginSm * 2 1px; | ||||
|             opacity: 0.7; | ||||
|             pointer-events: none; | ||||
|  | ||||
|             &:before { | ||||
|                 content: ''; | ||||
|                 display: inline-block; | ||||
|                 width: $treeNavArrowD + $interiorMarginLg; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-tree, | ||||
| @@ -43,7 +66,6 @@ | ||||
|     } | ||||
|  | ||||
|     &__item { | ||||
|         $aPad: $interiorMarginSm; | ||||
|         border-radius: $controlCr; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
| @@ -82,22 +104,9 @@ | ||||
|             margin-left: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &.is-navigated-object, | ||||
|         &.is-selected { | ||||
|             .c-tree__item__type-icon:before { | ||||
|                 color: $colorItemTreeIconHover; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-being-edited { | ||||
|             background: $colorItemTreeEditingBg; | ||||
|             .c-tree__item__type-icon:before { | ||||
|                 color: $colorItemTreeEditingIcon; | ||||
|             } | ||||
|  | ||||
|             .c-tree__item__name { | ||||
|                 color: $colorItemTreeEditingFg; | ||||
|                 font-style: italic; | ||||
|         @include desktop { | ||||
|             &:hover { | ||||
|                 background: $colorItemTreeHoverBg; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -106,10 +115,6 @@ | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|  | ||||
|         &__name { | ||||
|             color: $colorItemTreeFg; | ||||
|         } | ||||
|  | ||||
|         &.is-alias { | ||||
|             // Object is an alias to an original. | ||||
|             [class*='__type-icon'] { | ||||
| @@ -125,6 +130,62 @@ | ||||
|                 width: ceil($mobileTreeItemH * 0.5); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-navigated-object, | ||||
|         &.is-selected { | ||||
|             background: $colorItemTreeSelectedBg; | ||||
|  | ||||
|             [class*="__label"], | ||||
|             [class*="__name"] { | ||||
|                 color: $colorItemTreeSelectedFg; | ||||
|             } | ||||
|  | ||||
|             [class*="__type-icon"]:before { | ||||
|                 color: $colorItemTreeSelectedIcon; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-navigated-object  { | ||||
|             [class*="__label"], | ||||
|             [class*="__name"] { | ||||
|                 pointer-events: none; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__item__label { | ||||
|         @include desktop { | ||||
|             &:hover { | ||||
|                 filter: $filterHov; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .is-editing .is-navigated-object { | ||||
|     a[class*="__item__label"] { | ||||
|         opacity: 0.4; | ||||
|  | ||||
|         [class*="__name"] { | ||||
|             font-style: italic; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-tree { | ||||
|     &__item { | ||||
|        body.mobile & { | ||||
|             @include button($bg: $colorMobilePaneLeftTreeItemBg, $fg: $colorMobilePaneLeftTreeItemFg); | ||||
|             height: $mobileTreeItemH; | ||||
|             margin-bottom: $interiorMarginSm; | ||||
|             [class*="view-control"] { | ||||
|                 width: ceil($mobileTreeItemH * 0.5); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-tree { | ||||
|         margin-left: $treeItemIndent; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -141,6 +202,51 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-nav { | ||||
|     $dimension: $treeNavArrowD; | ||||
|  | ||||
|     &__up, &__down { | ||||
|         flex: 0 0 auto; | ||||
|         height: $dimension; | ||||
|         width: $dimension; | ||||
|         visibility: hidden; | ||||
|         position: relative; | ||||
|         text-align: center; | ||||
|  | ||||
|         &.is-enabled { | ||||
|             visibility: visible; | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             // Nav arrow | ||||
|             $dimension: 9px; | ||||
|             $width: 3px; | ||||
|             border: solid $colorItemTreeVC; | ||||
|             border-width: 0 $width $width 0; | ||||
|             content: ''; | ||||
|             display: block; | ||||
|             position: absolute; | ||||
|             left: 50%; top: 50%; | ||||
|             height: $dimension; | ||||
|             width: $dimension; | ||||
|         } | ||||
|  | ||||
|         @include desktop { | ||||
|             &:hover:before { | ||||
|                 border-color: $colorItemTreeHoverFg; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__up:before { | ||||
|         transform: translate(-30%, -50%) rotate(135deg); | ||||
|     } | ||||
|  | ||||
|     &__down:before { | ||||
|         transform: translate(-70%, -50%) rotate(-45deg); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-selector { | ||||
|     .c-tree-and-search__tree.c-tree { | ||||
|         border: 1px solid $colorInteriorBorder; | ||||
| @@ -148,3 +254,32 @@ | ||||
|         padding: $interiorMargin; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // TRANSITIONS | ||||
| .slide-left, | ||||
| .slide-right { | ||||
|     animation-duration: 500ms; | ||||
|     animation-iteration-count: 1; | ||||
|     transition: all; | ||||
|     transition-timing-function: ease-in-out; | ||||
| } | ||||
|  | ||||
| .slide-left { | ||||
|     animation-name: animSlideLeft; | ||||
| } | ||||
|  | ||||
| .slide-right { | ||||
|     animation-name: animSlideRight; | ||||
| } | ||||
|  | ||||
| @keyframes animSlideLeft { | ||||
|     0% {opactiy: 0; transform: translateX(100%);} | ||||
|     10% {opacity: 1;} | ||||
|     100% {transform: translateX(0);} | ||||
| } | ||||
|  | ||||
| @keyframes animSlideRight { | ||||
|     0% {opactiy: 0; transform: translateX(-100%);} | ||||
|     10% {opacity: 1;} | ||||
|     100% {transform: translateX(0);} | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| <template> | ||||
| <div class="c-tree-and-search"> | ||||
| <div | ||||
|     class="c-tree-and-search" | ||||
| > | ||||
|  | ||||
|     <div class="c-tree-and-search__search"> | ||||
|         <search | ||||
|             ref="shell-search" | ||||
| @@ -10,15 +13,8 @@ | ||||
|         /> | ||||
|     </div> | ||||
|  | ||||
|     <!-- loading --> | ||||
|     <div | ||||
|         v-if="isLoading" | ||||
|         class="c-tree-and-search__loading loading" | ||||
|     ></div> | ||||
|     <!-- end loading --> | ||||
|  | ||||
|     <div | ||||
|         v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)" | ||||
|         v-if="(searchValue && allTreeItems.length === 0 && !isLoading) || (searchValue && searchResultItems.length === 0)" | ||||
|         class="c-tree-and-search__no-results" | ||||
|     > | ||||
|         No results found | ||||
| @@ -26,30 +22,72 @@ | ||||
|  | ||||
|     <!-- main tree --> | ||||
|     <ul | ||||
|         v-if="!isLoading" | ||||
|         v-show="!searchValue" | ||||
|         ref="mainTree" | ||||
|         class="c-tree-and-search__tree c-tree" | ||||
|     > | ||||
|         <tree-item | ||||
|             v-for="treeItem in allTreeItems" | ||||
|             :key="treeItem.id" | ||||
|             :node="treeItem" | ||||
|         /> | ||||
|         <!-- ancestors --> | ||||
|         <div v-if="!activeSearch"> | ||||
|             <tree-item | ||||
|                 v-for="(ancestor, index) in ancestors" | ||||
|                 :key="ancestor.id" | ||||
|                 :node="ancestor" | ||||
|                 :show-up="index < ancestors.length - 1" | ||||
|                 :show-down="false" | ||||
|                 :left-offset="index * 10 + 'px'" | ||||
|                 :emit-height="getChildHeight" | ||||
|                 @emittedHeight="setChildHeight" | ||||
|                 @resetTree="handleReset" | ||||
|             /> | ||||
|             <!-- loading --> | ||||
|             <li | ||||
|                 v-if="isLoading" | ||||
|                 class="c-tree__item c-tree-and-search__loading loading" | ||||
|             > | ||||
|                 <span class="c-tree__item__label">Loading...</span> | ||||
|             </li> | ||||
|             <!-- end loading --> | ||||
|         </div> | ||||
|         <!-- currently viewed children --> | ||||
|         <transition | ||||
|             @enter="childrenIn" | ||||
|         > | ||||
|             <li | ||||
|                 v-if="!isLoading" | ||||
|                 :class="childrenSlideClass" | ||||
|                 :style="childrenListStyles()" | ||||
|             > | ||||
|                 <ul | ||||
|                     ref="scrollable" | ||||
|                     class="scrollable-children" | ||||
|                     :style="scrollableStyles()" | ||||
|                     @scroll="scrollItems" | ||||
|                 > | ||||
|                     <div :style="{ height: childrenHeight + 'px'}"> | ||||
|                         <tree-item | ||||
|                             v-for="(treeItem, index) in visibleItems" | ||||
|                             :key="treeItem.id" | ||||
|                             :node="treeItem" | ||||
|                             :left-offset="itemLeftOffset" | ||||
|                             :item-offset="itemOffset" | ||||
|                             :item-index="index" | ||||
|                             :item-height="itemHeight" | ||||
|                             :virtual-scroll="!noScroll" | ||||
|                             :show-down="activeSearch ? false : true" | ||||
|                             @expanded="handleExpanded" | ||||
|                         /> | ||||
|                         <li | ||||
|                             v-if="visibleItems.length === 0" | ||||
|                             :style="emptyStyles()" | ||||
|                             class="c-tree__item c-tree__item--empty" | ||||
|                         > | ||||
|                             No items | ||||
|                         </li> | ||||
|                     </div> | ||||
|                 </ul> | ||||
|             </li> | ||||
|         </transition> | ||||
|     </ul> | ||||
|     <!-- end main tree --> | ||||
|  | ||||
|     <!-- search tree --> | ||||
|     <ul | ||||
|         v-if="searchValue" | ||||
|         class="c-tree-and-search__tree c-tree" | ||||
|     > | ||||
|         <tree-item | ||||
|             v-for="treeItem in filteredTreeItems" | ||||
|             :key="treeItem.id" | ||||
|             :node="treeItem" | ||||
|         /> | ||||
|     </ul> | ||||
|     <!-- end search tree --> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -57,6 +95,14 @@ | ||||
| import treeItem from './tree-item.vue' | ||||
| import search from '../components/search.vue'; | ||||
|  | ||||
| const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded'; | ||||
| const ROOT_PATH = '/browse/'; | ||||
| const ITEM_BUFFER = 5; | ||||
| const RECHECK_DELAY = 100; | ||||
| const RESIZE_FIRE_DELAY_MS = 500; | ||||
| let windowResizeId = undefined; | ||||
| let windowResizing = false; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     name: 'MctTree', | ||||
| @@ -64,70 +110,481 @@ export default { | ||||
|         search, | ||||
|         treeItem | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             searchValue: '', | ||||
|             allTreeItems: [], | ||||
|             filteredTreeItems: [], | ||||
|             isLoading: false | ||||
|     props: { | ||||
|         syncTreeNavigation: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|     data() { | ||||
|         let isMobile = this.openmct.$injector.get('agentService'); | ||||
|         return { | ||||
|             isLoading: false, | ||||
|             searchValue: '', | ||||
|             allTreeItems: [], | ||||
|             searchResultItems: [], | ||||
|             visibleItems: [], | ||||
|             ancestors: [], | ||||
|             childrenSlideClass: 'slide-left', | ||||
|             availableContainerHeight: 0, | ||||
|             noScroll: true, | ||||
|             updatingView: false, | ||||
|             itemHeight: 28, | ||||
|             itemOffset: 0, | ||||
|             childrenHeight: 0, | ||||
|             scrollable: undefined, | ||||
|             pageThreshold: 50, | ||||
|             activeSearch: false, | ||||
|             getChildHeight: false, | ||||
|             settingChildrenHeight: false, | ||||
|             isMobile: isMobile.mobileName, | ||||
|             multipleRootChildren: false | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         currentNavigatedPath() { | ||||
|             let ancestorsCopy = [...this.ancestors]; | ||||
|             if (this.multipleRootChildren) { | ||||
|                 ancestorsCopy.shift(); // remove root | ||||
|             } | ||||
|             return ancestorsCopy | ||||
|                 .map((ancestor) => ancestor.id) | ||||
|                 .join('/'); | ||||
|         }, | ||||
|         currentObjectPath() { | ||||
|             let ancestorsCopy = [...this.ancestors]; | ||||
|             return ancestorsCopy | ||||
|                 .reverse() | ||||
|                 .map((ancestor) => ancestor.object); | ||||
|         }, | ||||
|         focusedItems() { | ||||
|             return this.activeSearch ? this.searchResultItems : this.allTreeItems; | ||||
|         }, | ||||
|         itemLeftOffset() { | ||||
|             return this.activeSearch ? '0px' : this.ancestors.length * 10 + 'px'; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         syncTreeNavigation() { | ||||
|             const AND_SAVE_PATH = true; | ||||
|             let currentLocationPath = this.openmct.router.currentLocation.path; | ||||
|             let hasParent = this.currentlyViewedObjectParentPath() || (this.multipleRootChildren && !this.currentlyViewedObjectParentPath()); | ||||
|             let jumpAndScroll = currentLocationPath && | ||||
|                     hasParent && | ||||
|                     !this.currentPathIsActivePath(); | ||||
|             let justScroll = this.currentPathIsActivePath() && !this.noScroll; | ||||
|  | ||||
|             if (this.searchValue) { | ||||
|                 this.searchValue = ''; | ||||
|             } | ||||
|  | ||||
|             if (jumpAndScroll) { | ||||
|                 this.scrollTo = this.currentlyViewedObjectId(); | ||||
|                 this.allTreeItems = []; | ||||
|                 this.jumpPath = this.currentlyViewedObjectParentPath(); | ||||
|                 if (this.multipleRootChildren) { | ||||
|                     if(!this.jumpPath) { | ||||
|                         this.jumpPath = 'ROOT'; | ||||
|                         this.ancestors = []; | ||||
|                     } else { | ||||
|                         this.ancestors = [this.ancestors[0]] | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.ancestors = []; | ||||
|                 } | ||||
|                 this.jumpToPath(AND_SAVE_PATH); | ||||
|             } else if (justScroll) { | ||||
|                 this.scrollTo = this.currentlyViewedObjectId(); | ||||
|                 this.autoScroll(); | ||||
|             } | ||||
|         }, | ||||
|         searchValue() { | ||||
|             if (this.searchValue !== '' && !this.activeSearch) { | ||||
|                 this.searchActivated(); | ||||
|             } else if (this.searchValue === '') { | ||||
|                 this.searchDeactivated(); | ||||
|             } | ||||
|         }, | ||||
|         searchResultItems() { | ||||
|             this.setContainerHeight(); | ||||
|         } | ||||
|     }, | ||||
|     async mounted() { | ||||
|         let savedPath = this.getSavedNavigatedPath(); | ||||
|         this.searchService = this.openmct.$injector.get('searchService'); | ||||
|         this.getAllChildren(); | ||||
|         window.addEventListener('resize',  this.handleWindowResize); | ||||
|  | ||||
|         let root = await this.openmct.objects.get('ROOT'); | ||||
|         let rootNode = this.buildTreeItem(root); | ||||
|         // if more than one root item, set multipleRootChildren to true and add root to ancestors | ||||
|         if (root.composition && root.composition.length > 1) { | ||||
|             this.ancestors.push(rootNode); | ||||
|             this.multipleRootChildren = true; | ||||
|         } else if (!savedPath) { | ||||
|             // needed if saved path is not set, need to set it to the only root child | ||||
|             savedPath = root.composition[0].key; | ||||
|         } | ||||
|  | ||||
|         if (savedPath) { | ||||
|             let scrollIfApplicable = () => { | ||||
|                 if (this.currentPathIsActivePath()) { | ||||
|                     this.scrollTo = this.currentlyViewedObjectId(); | ||||
|                 } | ||||
|             }; | ||||
|             this.jumpPath = savedPath; | ||||
|             this.afterJump = scrollIfApplicable; | ||||
|         } | ||||
|  | ||||
|         this.getAllChildren(rootNode); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         window.removeEventListener('resize', this.handleWindowResize); | ||||
|     }, | ||||
|     methods: { | ||||
|         getAllChildren() { | ||||
|             this.isLoading = true; | ||||
|             this.openmct.objects.get('ROOT') | ||||
|                 .then(root => { | ||||
|                     return this.openmct.composition.get(root).load() | ||||
|                 }) | ||||
|                 .then(children => { | ||||
|                     this.isLoading = false; | ||||
|                     this.allTreeItems = children.map(c => { | ||||
|                         return { | ||||
|                             id: this.openmct.objects.makeKeyString(c.identifier), | ||||
|                             object: c, | ||||
|                             objectPath: [c], | ||||
|                             navigateToParent: '/browse' | ||||
|                         }; | ||||
|                     }); | ||||
|                 }); | ||||
|         updatevisibleItems() { | ||||
|             if (this.updatingView) { | ||||
|                 return; | ||||
|             } | ||||
|             this.updatingView = true; | ||||
|             requestAnimationFrame(()=> { | ||||
|                 let start = 0; | ||||
|                 let end = this.pageThreshold; | ||||
|                 let allItemsCount = this.focusedItems.length; | ||||
|  | ||||
|                 if (allItemsCount < this.pageThreshold) { | ||||
|                     end = allItemsCount; | ||||
|                 } else { | ||||
|                     let firstVisible = this.calculateFirstVisibleItem(); | ||||
|                     let lastVisible = this.calculateLastVisibleItem(); | ||||
|                     let totalVisible = lastVisible - firstVisible; | ||||
|                     let numberOffscreen = this.pageThreshold - totalVisible; | ||||
|  | ||||
|                     start = firstVisible - Math.floor(numberOffscreen / 2); | ||||
|                     end = lastVisible + Math.ceil(numberOffscreen / 2); | ||||
|  | ||||
|                     if (start < 0) { | ||||
|                         start = 0; | ||||
|                         end = Math.min(this.pageThreshold, allItemsCount); | ||||
|                     } else if (end >= allItemsCount) { | ||||
|                         end = allItemsCount; | ||||
|                         start = end - this.pageThreshold + 1; | ||||
|                     } | ||||
|                 } | ||||
|                 this.itemOffset = start; | ||||
|                 this.visibleItems = this.focusedItems.slice(start, end); | ||||
|  | ||||
|                 this.updatingView = false; | ||||
|             }); | ||||
|         }, | ||||
|         getFilteredChildren() { | ||||
|             this.searchService.query(this.searchValue).then(children => { | ||||
|                 this.filteredTreeItems = children.hits.map(child => { | ||||
|         async setContainerHeight() { | ||||
|             await this.$nextTick(); | ||||
|             let mainTree = this.$refs.mainTree; | ||||
|             let mainTreeHeight = mainTree.clientHeight; | ||||
|  | ||||
|                     let context = child.object.getCapability('context'), | ||||
|                         object = child.object.useCapability('adapter'), | ||||
|                         objectPath = [], | ||||
|                         navigateToParent; | ||||
|             if (mainTreeHeight !== 0) { | ||||
|                 this.calculateChildHeight(() => { | ||||
|                     let ancestorsHeight = this.calculateAncestorHeight(); | ||||
|                     let allChildrenHeight = this.calculateChildrenHeight(); | ||||
|  | ||||
|                     if (context) { | ||||
|                         objectPath = context.getPath().slice(1) | ||||
|                             .map(oldObject => oldObject.useCapability('adapter')) | ||||
|                             .reverse(); | ||||
|                         navigateToParent = '/browse/' + objectPath.slice(1) | ||||
|                             .map((parent) => this.openmct.objects.makeKeyString(parent.identifier)) | ||||
|                             .join('/'); | ||||
|                     if (this.activeSearch) { | ||||
|                         ancestorsHeight = 0; | ||||
|                     } | ||||
|                     this.availableContainerHeight = mainTreeHeight - ancestorsHeight; | ||||
|  | ||||
|                     return { | ||||
|                         id: this.openmct.objects.makeKeyString(object.identifier), | ||||
|                         object, | ||||
|                         objectPath, | ||||
|                         navigateToParent | ||||
|                     if (allChildrenHeight > this.availableContainerHeight) { | ||||
|                         this.setPageThreshold(); | ||||
|                         this.noScroll = false; | ||||
|                     } else { | ||||
|                         this.noScroll = true; | ||||
|                     } | ||||
|                     this.updatevisibleItems(); | ||||
|                 }); | ||||
|             } else { | ||||
|                 window.setTimeout(this.setContainerHeight, RECHECK_DELAY); | ||||
|             } | ||||
|         }, | ||||
|         calculateFirstVisibleItem() { | ||||
|             let scrollTop = this.$refs.scrollable.scrollTop; | ||||
|             return Math.floor(scrollTop / this.itemHeight); | ||||
|         }, | ||||
|         calculateLastVisibleItem() { | ||||
|             let scrollBottom = this.$refs.scrollable.scrollTop + this.$refs.scrollable.offsetHeight; | ||||
|             return Math.ceil(scrollBottom / this.itemHeight); | ||||
|         }, | ||||
|         calculateChildrenHeight() { | ||||
|             let mainTreeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop'); | ||||
|             let childrenCount = this.focusedItems.length; | ||||
|             return (this.itemHeight * childrenCount) - mainTreeTopMargin; // 5px margin | ||||
|         }, | ||||
|         setChildrenHeight() { | ||||
|             this.childrenHeight = this.calculateChildrenHeight(); | ||||
|         }, | ||||
|         calculateAncestorHeight() { | ||||
|             let ancestorCount = this.ancestors.length; | ||||
|             return this.itemHeight * ancestorCount; | ||||
|         }, | ||||
|         calculateChildHeight(callback) { | ||||
|             if (callback) { | ||||
|                 this.afterChildHeight = callback; | ||||
|             } | ||||
|             if (!this.activeSearch) { | ||||
|                 this.getChildHeight = true; | ||||
|             } else if (this.afterChildHeight) { | ||||
|                 // keep the height from before | ||||
|                 this.afterChildHeight(); | ||||
|                 delete this.afterChildHeight; | ||||
|             } | ||||
|         }, | ||||
|         async setChildHeight(item) { | ||||
|             if (!this.getChildHeight || this.settingChildrenHeight) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.settingChildrenHeight = true; | ||||
|             if (this.isMobile) { | ||||
|                 item = item.children[0]; | ||||
|             } | ||||
|             await this.$nextTick(); | ||||
|             let topMargin = this.getElementStyleValue(item, 'marginTop'); | ||||
|             let bottomMargin = this.getElementStyleValue(item, 'marginBottom'); | ||||
|             let totalVerticalMargin = topMargin + bottomMargin; | ||||
|  | ||||
|             this.itemHeight = item.clientHeight + totalVerticalMargin; | ||||
|             this.setChildrenHeight(); | ||||
|             if (this.afterChildHeight) { | ||||
|                 this.afterChildHeight(); | ||||
|                 delete this.afterChildHeight; | ||||
|             } | ||||
|             this.getChildHeight = false; | ||||
|             this.settingChildrenHeight = false; | ||||
|         }, | ||||
|         setPageThreshold() { | ||||
|             let threshold = Math.ceil(this.availableContainerHeight / this.itemHeight) + ITEM_BUFFER; | ||||
|             // all items haven't loaded yet (nextTick not working for this) | ||||
|             if (threshold === ITEM_BUFFER) { | ||||
|                 window.setTimeout(this.setPageThreshold, RECHECK_DELAY); | ||||
|             } else { | ||||
|                 this.pageThreshold = threshold; | ||||
|             } | ||||
|         }, | ||||
|         handleWindowResize() { | ||||
|             if (!windowResizing) { | ||||
|                 windowResizing = true; | ||||
|                 window.clearTimeout(windowResizeId); | ||||
|                 windowResizeId = window.setTimeout(() => { | ||||
|                     this.setContainerHeight(); | ||||
|                     windowResizing = false; | ||||
|                 }, RESIZE_FIRE_DELAY_MS); | ||||
|             } | ||||
|         }, | ||||
|         async getAllChildren(node) { | ||||
|             this.isLoading = true; | ||||
|             if (this.composition) { | ||||
|                 this.composition.off('add', this.addChild); | ||||
|                 this.composition.off('remove', this.removeChild); | ||||
|                 delete this.composition; | ||||
|             } | ||||
|             this.allTreeItems = []; | ||||
|             this.composition = this.openmct.composition.get(node.object); | ||||
|             this.composition.on('add', this.addChild); | ||||
|             this.composition.on('remove', this.removeChild); | ||||
|             await this.composition.load(); | ||||
|             this.finishLoading(); | ||||
|         }, | ||||
|         buildTreeItem(domainObject) { | ||||
|             let navToParent = ROOT_PATH + this.currentNavigatedPath; | ||||
|             if (navToParent === ROOT_PATH) { | ||||
|                 navToParent = navToParent.slice(0, -1); | ||||
|             } | ||||
|             return { | ||||
|                 id: this.openmct.objects.makeKeyString(domainObject.identifier), | ||||
|                 object: domainObject, | ||||
|                 objectPath: [domainObject].concat(this.currentObjectPath), | ||||
|                 navigateToParent: navToParent | ||||
|             }; | ||||
|         }, | ||||
|         addChild(child) { | ||||
|             let item = this.buildTreeItem(child); | ||||
|             this.allTreeItems.push(item); | ||||
|             if (!this.isLoading) { | ||||
|                 this.setContainerHeight(); | ||||
|             } | ||||
|         }, | ||||
|         removeChild(identifier) { | ||||
|             let removeId = this.openmct.objects.makeKeyString(identifier); | ||||
|             this.allTreeItems = this.allTreeItems | ||||
|                 .filter(c => c.id !== removeId); | ||||
|             this.setContainerHeight(); | ||||
|         }, | ||||
|         async finishLoading() { | ||||
|             if (this.jumpPath) { | ||||
|                 this.jumpToPath(); | ||||
|             } | ||||
|             this.autoScroll(); | ||||
|             this.isLoading = false; | ||||
|         }, | ||||
|         async jumpToPath(saveExpandedPath = false) { | ||||
|             let nodes = this.jumpPath.split('/'); | ||||
|             for(let i = 0; i < nodes.length; i++) { | ||||
|                 let currentNode = await this.openmct.objects.get(nodes[i]); | ||||
|                 let newParent = this.buildTreeItem(currentNode); | ||||
|                 this.ancestors.push(newParent); | ||||
|  | ||||
|                 if (i === nodes.length - 1) { | ||||
|                     this.jumpPath = ''; | ||||
|                     this.getAllChildren(newParent); | ||||
|                     if (this.afterJump) { | ||||
|                         await this.$nextTick() | ||||
|                         this.afterJump(); | ||||
|                         delete this.afterJump; | ||||
|                     } | ||||
|                     if (saveExpandedPath) { | ||||
|                         this.setCurrentNavigatedPath(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         async autoScroll() { | ||||
|             if (!this.scrollTo) { | ||||
|                 return; | ||||
|             } | ||||
|             if (this.$refs.scrollable) { | ||||
|                 let indexOfScroll = this.indexOfItemById(this.scrollTo); | ||||
|                 let scrollTopAmount = indexOfScroll * this.itemHeight; | ||||
|  | ||||
|                 await this.$nextTick(); | ||||
|                 this.$refs.scrollable.scrollTop = scrollTopAmount; | ||||
|                 // race condition check | ||||
|                 if(scrollTopAmount > 0 && this.$refs.scrollable.scrollTop === 0) { | ||||
|                     window.setTimeout(this.autoScroll, RECHECK_DELAY); | ||||
|                     return; | ||||
|                 } | ||||
|                 this.scrollTo = undefined; | ||||
|             } else { | ||||
|                 window.setTimeout(this.autoScroll, RECHECK_DELAY); | ||||
|             } | ||||
|         }, | ||||
|         indexOfItemById(id) { | ||||
|             for(let i = 0; i < this.allTreeItems.length; i++) { | ||||
|                 if (this.allTreeItems[i].id === id) { | ||||
|                     return i; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         async getSearchResults() { | ||||
|             let results = await this.searchService.query(this.searchValue); | ||||
|             this.searchResultItems = results.hits.map(result => { | ||||
|  | ||||
|                 let context = result.object.getCapability('context'); | ||||
|                 let object = result.object.useCapability('adapter'); | ||||
|                 let objectPath = []; | ||||
|                 let navigateToParent; | ||||
|  | ||||
|                 if (context) { | ||||
|                     objectPath = context.getPath().slice(1) | ||||
|                         .map(oldObject => oldObject.useCapability('adapter')) | ||||
|                         .reverse(); | ||||
|                     navigateToParent = objectPath.slice(1) | ||||
|                         .map((parent) => this.openmct.objects.makeKeyString(parent.identifier)); | ||||
|                     navigateToParent = ROOT_PATH + navigateToParent.reverse().join('/'); | ||||
|                 } | ||||
|  | ||||
|                 return { | ||||
|                     id: this.openmct.objects.makeKeyString(object.identifier), | ||||
|                     object, | ||||
|                     objectPath, | ||||
|                     navigateToParent | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         searchTree(value) { | ||||
|             this.searchValue = value; | ||||
|  | ||||
|             if (this.searchValue !== '') { | ||||
|                 this.getFilteredChildren(); | ||||
|                 this.getSearchResults(); | ||||
|             } | ||||
|         }, | ||||
|         searchActivated() { | ||||
|             this.activeSearch = true; | ||||
|             this.$refs.scrollable.scrollTop = 0; | ||||
|         }, | ||||
|         searchDeactivated() { | ||||
|             this.activeSearch = false; | ||||
|             this.$refs.scrollable.scrollTop = 0; | ||||
|             this.setContainerHeight(); | ||||
|         }, | ||||
|         handleReset(node) { | ||||
|             this.childrenSlideClass = 'slide-right'; | ||||
|             this.ancestors.splice(this.ancestors.indexOf(node) + 1); | ||||
|             this.getAllChildren(node); | ||||
|             this.setCurrentNavigatedPath(); | ||||
|         }, | ||||
|         handleExpanded(node) { | ||||
|             if (this.activeSearch) { | ||||
|                 return; | ||||
|             } | ||||
|             this.childrenSlideClass = 'slide-left'; | ||||
|             let newParent = this.buildTreeItem(node); | ||||
|             this.ancestors.push(newParent); | ||||
|             this.getAllChildren(newParent); | ||||
|             this.setCurrentNavigatedPath(); | ||||
|         }, | ||||
|         getSavedNavigatedPath() { | ||||
|             return JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED)); | ||||
|         }, | ||||
|         setCurrentNavigatedPath() { | ||||
|             if (!this.searchValue) { | ||||
|                 localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(this.currentNavigatedPath)); | ||||
|             } | ||||
|         }, | ||||
|         currentPathIsActivePath() { | ||||
|             return this.getSavedNavigatedPath() === this.currentlyViewedObjectParentPath(); | ||||
|         }, | ||||
|         currentlyViewedObjectId() { | ||||
|             let currentPath = this.openmct.router.currentLocation.path; | ||||
|             if (currentPath) { | ||||
|                 currentPath = currentPath.split(ROOT_PATH)[1]; | ||||
|                 return currentPath.split('/').pop(); | ||||
|             } | ||||
|         }, | ||||
|         currentlyViewedObjectParentPath() { | ||||
|             let currentPath = this.openmct.router.currentLocation.path; | ||||
|             if (currentPath) { | ||||
|                 currentPath = currentPath.split(ROOT_PATH)[1]; | ||||
|                 currentPath = currentPath.split('/'); | ||||
|                 currentPath.pop(); | ||||
|                 return currentPath.join('/'); | ||||
|             } | ||||
|         }, | ||||
|         scrollItems(event) { | ||||
|             if (!windowResizing) { | ||||
|                 this.updatevisibleItems(); | ||||
|             } | ||||
|         }, | ||||
|         childrenListStyles() { | ||||
|             return { position: 'relative' }; | ||||
|         }, | ||||
|         scrollableStyles() { | ||||
|             return { | ||||
|                 height: this.availableContainerHeight + 'px', | ||||
|                 overflow: this.noScroll ? 'hidden' : 'scroll' | ||||
|             } | ||||
|         }, | ||||
|         emptyStyles() { | ||||
|             let offset = ((this.ancestors.length + 1) * 10); | ||||
|             return { | ||||
|                 paddingLeft: offset + 'px' | ||||
|             }; | ||||
|         }, | ||||
|         childrenIn(el, done) { | ||||
|             // still needing this timeout for some reason | ||||
|             window.setTimeout(this.setContainerHeight, RECHECK_DELAY); | ||||
|             done(); | ||||
|         }, | ||||
|         getElementStyleValue(el, style) { | ||||
|             let styleString = window.getComputedStyle(el)[style]; | ||||
|             let index = styleString.indexOf('px'); | ||||
|             return Number(styleString.slice(0, index)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,10 @@ | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         @include desktop() { margin-bottom: $interiorMargin; } | ||||
|  | ||||
|         [class*="button"] { | ||||
|             color: $colorBtnMajorBg; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &--reacts { | ||||
| @@ -128,12 +132,23 @@ | ||||
|             @include userSelectNone(); | ||||
|             color: $splitterBtnLabelColorFg; | ||||
|             display: block; | ||||
|             pointer-events: none; | ||||
|             text-transform: uppercase; | ||||
|             transform-origin: top left; | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|  | ||||
|         [class*="expand-button"] { | ||||
|             display: none; // Hidden by default | ||||
|             background: $splitterCollapsedBtnColorBg; | ||||
|             color: $splitterCollapsedBtnColorFg; | ||||
|             font-size: 0.9em; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $splitterCollapsedBtnColorBgHov; | ||||
|                 color: inherit; | ||||
|                 transition: $transIn; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &--resizing { | ||||
|             // User is dragging the handle and resizing a pane | ||||
|             @include userSelectNone(); | ||||
| @@ -160,23 +175,12 @@ | ||||
|                 display: none; | ||||
|             } | ||||
|  | ||||
|             .l-pane__header { | ||||
|                 &:hover { | ||||
|                     color: $splitterCollapsedBtnColorFgHov; | ||||
|                     .l-pane__label { | ||||
|                         color: inherit; | ||||
|                     } | ||||
|                     .l-pane__collapse-button { | ||||
|                         background: $splitterCollapsedBtnColorBgHov; | ||||
|                         color: inherit; | ||||
|                         transition: $transIn; | ||||
|                     } | ||||
|                 } | ||||
|             [class*="collapse-button"] { | ||||
|                 display: none; | ||||
|             } | ||||
|  | ||||
|             .l-pane__collapse-button { | ||||
|                 background: $splitterCollapsedBtnColorBg; | ||||
|                 color: $splitterCollapsedBtnColorFg; | ||||
|             [class*="expand-button"] { | ||||
|                 display: block; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -198,36 +202,26 @@ | ||||
|  | ||||
|             .l-pane__collapse-button { | ||||
|                 &:before { | ||||
|                     content: $glyph-icon-arrow-right-equilateral; | ||||
|                     content: $glyph-icon-line-horz; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             &[class*="--collapsed"] { | ||||
|                 /************************ COLLAPSED HORIZONTAL SPLITTER, EITHER DIRECTION */ | ||||
|                 [class*="__header"] { | ||||
|                     @include abs(); | ||||
|                     margin: 0; | ||||
|                     display: none; | ||||
|                 } | ||||
|  | ||||
|                 [class*="label"] { | ||||
|                     position: absolute; | ||||
|                     transform: translate($interiorMarginLg + 1, 18px) rotate(90deg); | ||||
|                     left: 3px; | ||||
|                     top: 0; | ||||
|                     z-index: 1; | ||||
|                 } | ||||
|  | ||||
|                 .l-pane__collapse-button { | ||||
|                     border-top-left-radius: 0; | ||||
|                     border-bottom-left-radius: 0; // Only have to do this once, because of scaleX(-1) below. | ||||
|                 [class*="expand-button"] { | ||||
|                     position: absolute; | ||||
|                     top: 0; right: 0; bottom: 0; left: 0; | ||||
|                     height: auto; width: 100%; | ||||
|                     padding: 0; | ||||
|                     padding: $interiorMargin 2px; | ||||
|  | ||||
|                     &:before { | ||||
|                         position: absolute; | ||||
|                         top: 5px; | ||||
|                     [class*="label"] { | ||||
|                         text-orientation: mixed; | ||||
|                         text-transform: uppercase; | ||||
|                         writing-mode: vertical-lr; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -243,10 +237,9 @@ | ||||
|                     transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge | ||||
|                 } | ||||
|  | ||||
|                 &[class*="--collapsed"] { | ||||
|                     .l-pane__collapse-button { | ||||
|                         transform: scaleX(-1); | ||||
|                     } | ||||
|                 [class*="expand-button"] { | ||||
|                     border-top-left-radius: $controlCr; | ||||
|                     border-bottom-left-radius: $controlCr; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -261,10 +254,9 @@ | ||||
|                     transform: translateX(floor($splitterHandleD / 2)); | ||||
|                 } | ||||
|  | ||||
|                 &:not([class*="--collapsed"]) { | ||||
|                     .l-pane__collapse-button { | ||||
|                         transform: scaleX(-1); | ||||
|                     } | ||||
|                 [class*="expand-button"] { | ||||
|                     border-top-right-radius: $controlCr; | ||||
|                     border-bottom-right-radius: $controlCr; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -20,12 +20,19 @@ | ||||
|         <span v-if="label" | ||||
|               class="l-pane__label" | ||||
|         >{{ label }}</span> | ||||
|         <slot name="controls"></slot> | ||||
|         <button | ||||
|             v-if="collapsable" | ||||
|             class="l-pane__collapse-button c-button" | ||||
|             class="l-pane__collapse-button c-icon-button" | ||||
|             @click="toggleCollapse" | ||||
|         ></button> | ||||
|     </div> | ||||
|     <button | ||||
|         class="l-pane__expand-button" | ||||
|         @click="toggleCollapse" | ||||
|     > | ||||
|         <span class="l-pane__expand-button__label">{{ label }}</span> | ||||
|     </button> | ||||
|     <div class="l-pane__contents"> | ||||
|         <slot></slot> | ||||
|     </div> | ||||
|   | ||||
| @@ -1,38 +1,39 @@ | ||||
| <template> | ||||
| <li class="c-tree__item-h"> | ||||
| <li | ||||
|     ref="me" | ||||
|     :style="{ | ||||
|         'top': virtualScroll ? itemTop : 'auto', | ||||
|         'position': virtualScroll ? 'absolute' : 'relative' | ||||
|     }" | ||||
|     class="c-tree__item-h" | ||||
| > | ||||
|     <div | ||||
|         class="c-tree__item" | ||||
|         :class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }" | ||||
|         :class="{ | ||||
|             'is-alias': isAlias, | ||||
|             'is-navigated-object': navigated | ||||
|         }" | ||||
|     > | ||||
|         <view-control | ||||
|             v-model="expanded" | ||||
|             class="c-tree__item__view-control" | ||||
|             :enabled="hasChildren" | ||||
|             :control-class="'c-nav__up'" | ||||
|             :enabled="showUp" | ||||
|             @input="resetTreeHere" | ||||
|         /> | ||||
|         <object-label | ||||
|             :domain-object="node.object" | ||||
|             :object-path="node.objectPath" | ||||
|             :navigate-to-path="navigateToPath" | ||||
|             :style="{ paddingLeft: leftOffset }" | ||||
|         /> | ||||
|         <view-control | ||||
|             v-model="expanded" | ||||
|             class="c-tree__item__view-control" | ||||
|             :control-class="'c-nav__down'" | ||||
|             :enabled="hasComposition && showDown" | ||||
|         /> | ||||
|     </div> | ||||
|     <ul | ||||
|         v-if="expanded" | ||||
|         class="c-tree" | ||||
|     > | ||||
|         <li | ||||
|             v-if="isLoading && !loaded" | ||||
|             class="c-tree__item-h" | ||||
|         > | ||||
|             <div class="c-tree__item loading"> | ||||
|                 <span class="c-tree__item__label">Loading...</span> | ||||
|             </div> | ||||
|         </li> | ||||
|         <tree-item | ||||
|             v-for="child in children" | ||||
|             :key="child.id" | ||||
|             :node="child" | ||||
|         /> | ||||
|     </ul> | ||||
| </li> | ||||
| </template> | ||||
|  | ||||
| @@ -40,8 +41,6 @@ | ||||
| import viewControl from '../components/viewControl.vue'; | ||||
| import ObjectLabel from '../components/ObjectLabel.vue'; | ||||
|  | ||||
| const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded'; | ||||
|  | ||||
| export default { | ||||
|     name: 'TreeItem', | ||||
|     inject: ['openmct'], | ||||
| @@ -53,16 +52,48 @@ export default { | ||||
|         node: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         leftOffset: { | ||||
|             type: String, | ||||
|             default: '0px' | ||||
|         }, | ||||
|         showUp: { | ||||
|             type: Boolean, | ||||
|             default: false | ||||
|         }, | ||||
|         showDown: { | ||||
|             type: Boolean, | ||||
|             default: true | ||||
|         }, | ||||
|         itemIndex: { | ||||
|             type: Number, | ||||
|             required: false, | ||||
|             default: undefined | ||||
|         }, | ||||
|         itemOffset: { | ||||
|             type: Number, | ||||
|             required: false, | ||||
|             default: undefined | ||||
|         }, | ||||
|         itemHeight: { | ||||
|             type: Number, | ||||
|             required: false, | ||||
|             default: 0 | ||||
|         }, | ||||
|         virtualScroll: { | ||||
|             type: Boolean, | ||||
|             default: false | ||||
|         }, | ||||
|         emitHeight: { | ||||
|             type: Boolean, | ||||
|             default: false | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         this.navigateToPath = this.buildPathString(this.node.navigateToParent); | ||||
|         return { | ||||
|             hasChildren: false, | ||||
|             isLoading: false, | ||||
|             loaded: false, | ||||
|             hasComposition: false, | ||||
|             navigated: this.navigateToPath === this.openmct.router.currentLocation.path, | ||||
|             children: [], | ||||
|             expanded: false | ||||
|         } | ||||
|     }, | ||||
| @@ -74,30 +105,23 @@ export default { | ||||
|             } | ||||
|             let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier); | ||||
|             return parentKeyString !== this.node.object.location; | ||||
|         }, | ||||
|         itemTop() { | ||||
|             return (this.itemOffset + this.itemIndex) * this.itemHeight + 'px'; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         expanded() { | ||||
|             if (!this.hasChildren) { | ||||
|                 return; | ||||
|             } | ||||
|             if (!this.loaded && !this.isLoading) { | ||||
|                 this.composition = this.openmct.composition.get(this.domainObject); | ||||
|                 this.composition.on('add', this.addChild); | ||||
|                 this.composition.on('remove', this.removeChild); | ||||
|                 this.composition.load().then(this.finishLoading); | ||||
|                 this.isLoading = true; | ||||
|             } | ||||
|             this.setLocalStorageExpanded(this.navigateToPath); | ||||
|             this.$emit('expanded', this.domainObject); | ||||
|         }, | ||||
|         emitHeight() { | ||||
|             this.$nextTick(() => { | ||||
|                 this.$emit('emittedHeight', this.$refs.me); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         // TODO: should update on mutation. | ||||
|         // TODO: click navigation should not fubar hash quite so much. | ||||
|         // TODO: should highlight if navigated to. | ||||
|         // TODO: should have context menu. | ||||
|         // TODO: should support drag/drop composition | ||||
|         // TODO: set isAlias per tree-item | ||||
|         let objectComposition = this.openmct.composition.get(this.node.object); | ||||
|  | ||||
|         this.domainObject = this.node.object; | ||||
|         let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => { | ||||
| @@ -105,49 +129,19 @@ export default { | ||||
|         }); | ||||
|  | ||||
|         this.$once('hook:destroyed', removeListener); | ||||
|         if (this.openmct.composition.get(this.node.object)) { | ||||
|             this.hasChildren = true; | ||||
|         if (objectComposition) { | ||||
|             this.hasComposition = true; | ||||
|         } | ||||
|  | ||||
|         this.openmct.router.on('change:path', this.highlightIfNavigated); | ||||
|  | ||||
|         this.getLocalStorageExpanded(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         /**** | ||||
|             * calling this.setLocalStorageExpanded explicitly here because for whatever reason, | ||||
|             * the watcher on this.expanded is not triggering this.setLocalStorageExpanded(), | ||||
|             * even though Vue documentation states, "At this stage the instance is still fully functional." | ||||
|         *****/ | ||||
|         this.expanded = false; | ||||
|         this.setLocalStorageExpanded(); | ||||
|         if(this.emitHeight) { | ||||
|             this.$emit('emittedHeight', this.$refs.me); | ||||
|         } | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.openmct.router.off('change:path', this.highlightIfNavigated); | ||||
|         if (this.composition) { | ||||
|             this.composition.off('add', this.addChild); | ||||
|             this.composition.off('remove', this.removeChild); | ||||
|             delete this.composition; | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         addChild(child) { | ||||
|             this.children.push({ | ||||
|                 id: this.openmct.objects.makeKeyString(child.identifier), | ||||
|                 object: child, | ||||
|                 objectPath: [child].concat(this.node.objectPath), | ||||
|                 navigateToParent: this.navigateToPath | ||||
|             }); | ||||
|         }, | ||||
|         removeChild(identifier) { | ||||
|             let removeId = this.openmct.objects.makeKeyString(identifier); | ||||
|             this.children = this.children | ||||
|                 .filter(c => c.id !== removeId); | ||||
|         }, | ||||
|         finishLoading() { | ||||
|             this.isLoading = false; | ||||
|             this.loaded = true; | ||||
|         }, | ||||
|         buildPathString(parentPath) { | ||||
|             return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/'); | ||||
|         }, | ||||
| @@ -158,35 +152,8 @@ export default { | ||||
|                 this.navigated = false; | ||||
|             } | ||||
|         }, | ||||
|         getLocalStorageExpanded() { | ||||
|             let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED); | ||||
|  | ||||
|             if (expandedPaths) { | ||||
|                 expandedPaths = JSON.parse(expandedPaths); | ||||
|                 this.expanded = expandedPaths.includes(this.navigateToPath); | ||||
|             } | ||||
|         }, | ||||
|         // expanded nodes/paths are stored in local storage as an array | ||||
|         setLocalStorageExpanded() { | ||||
|             let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED); | ||||
|             expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : []; | ||||
|  | ||||
|             if (this.expanded) { | ||||
|                 if (!expandedPaths.includes(this.navigateToPath)) { | ||||
|                     expandedPaths.push(this.navigateToPath); | ||||
|                 } | ||||
|             } else { | ||||
|                 // remove this node path and all children paths from stored expanded paths | ||||
|                 expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath)); | ||||
|             } | ||||
|  | ||||
|             localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths)); | ||||
|         }, | ||||
|         removeLocalStorageExpanded() { | ||||
|             let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED); | ||||
|             expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : []; | ||||
|             expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath)); | ||||
|             localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths)); | ||||
|         resetTreeHere() { | ||||
|             this.$emit('resetTree', this.node); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user