From 4801dc4f327d446eebee2b8e134e3d0bfc9108d3 Mon Sep 17 00:00:00 2001
From: Jamie V <jamie.j.vigliotta@nasa.gov>
Date: Mon, 24 Aug 2020 13:47:56 -0700
Subject: [PATCH] New tree refactor (#3098)

* revised new tree refactor, moved most of the logic to mct-tree instead of tree-item

* scrollTo set for sync, bug fixes, window resize handling

* removing console logs

* checking domainobject composition for length to verify children instead of composition object itself

* added scrollTo on load if in viewed objects directory

* loading, sync bug, search issues, opitmization

* initial PR review updates

* modified so search now uses the same container and virtual scroll

* eslint fix

* Adding new glyphs

- Multiple new glyphs cherrypicked from branch `add-new-glyphs-062320`;

* Styling for new-tree-refactor WIP

- WIP!
- New glyphs, markup changes in BrowseBar.vue;
- Refinements to tree items, WIP;
- TODO: move hard-coded CSS values into _constants, make
theme-compatible;

* Styling for new-tree-refactor WIP

- WIP!
- Added new `c-click-link` CSS class;
- Move tree sync button into tree pane area;
- Added named "controls" slot to pane.vue;
- _up and _down arrows now use visibility instead of opacity to prevent
accidental clicks;

* Styling for new-tree-refactor WIP

- WIP!
- Significant mods and simplification in pane.vue and assoc CSS for
expand/collapse functionality;
- Wait spinner when in tree: cleanups, simplification;

* More new glyphs, updated art

- New glyphs: icon-unlocked and icon-target;
- Updated art for icon-lock glyph;

* remove arrows for search results, hightlight "my items" correctly, added empty folder notic

* Styling for new-tree-refactor WIP

- WIP!
- Refinements to "empty" object element;
- Changed sync-tree icon glyph;

* Styling for new-tree-refactor WIP

- Nav up arrows now left-align properly;

* Styling for new-tree-refactor

- Significant consolidation and cleanups in mct-tree.scss;
- Normalize base and hover styles across new tree, legacy tree,
list-items (used in Notebook) and Folder List View;
- Class naming normalization, change `c-list-item__name-value` to
`c-list-item__name`;
- Add styling to override and remove `<a> outline: dotted` coming from
normalize-min;
- Removed too-broad `<a>` coloring in tables;

* Styling for new-tree-refactor

- Fix styles for Snow theme;
- Sync Maelstrom and Espresso themes;
- Remove too-broad `<a>` hover styling from global.scss;
- Disallow pointer-events on `is-navigated` object's label (click on
c-nav__down element still allowed);

* Styling for new-tree-refactor

- Normalizing status area expand/collapse iconography to match new
approach in panes;

* Adding new glyphs

- Added `icon-items-collapse` and `icon-items-expand`;

* Styling for new-tree-refactor

- Using new glyphs for items expand/collapse in Status area;

* dynamic item height for desktop and mobile views

* lint fixes

* updated addChild and removeChild functions as they were not working at all

* some PR comment updates!;

* Remove unneeded hard-coded CSS color property

* fixed issues when multiple root children exist, added plugin to change the name of the default root object

* removing "my other items" testing references

* linting fixes

* updating karma timeouts for testing purposes

* eslint fixes

* WIP: fixing linting issues

* updating for testing

* set root object provider to update root registry if called more than once

* tweaking tests so that it passes both locally and on the serve tests

* removing old css code preventing context clicks on active menu items

* fixing testing errors

* backwards compatible storage fix

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
---
 karma.conf.js                                 |   2 +-
 src/api/objects/ObjectAPI.js                  |   2 +-
 src/api/objects/RootObjectProvider.js         |  56 +-
 .../objects/test/RootObjectProviderSpec.js    |  51 +-
 src/plugins/defaultRootName/plugin.js         |  29 +
 src/plugins/defaultRootName/pluginSpec.js     |  99 +++
 .../folderView/components/list-view.scss      |   2 +-
 src/plugins/plugins.js                        |   7 +-
 src/styles/_animations.scss                   |   4 +-
 src/styles/_constants-espresso.scss           |  11 +-
 src/styles/_constants-maelstrom.scss          |  13 +-
 src/styles/_constants-snow.scss               |   9 +-
 src/styles/_constants.scss                    |   3 +-
 src/styles/_controls.scss                     |  12 +-
 src/styles/_global.scss                       |  17 +-
 src/ui/components/viewControl.vue             |  14 +-
 src/ui/layout/BrowseBar.vue                   |   3 +-
 src/ui/layout/Layout.vue                      |  23 +-
 src/ui/layout/layout.scss                     |  57 +-
 src/ui/layout/mct-tree.scss                   | 178 ++++-
 src/ui/layout/mct-tree.vue                    | 639 ++++++++++++++++--
 src/ui/layout/pane.scss                       |  78 +--
 src/ui/layout/pane.vue                        |   9 +-
 src/ui/layout/tree-item.vue                   | 185 ++---
 24 files changed, 1141 insertions(+), 362 deletions(-)
 create mode 100644 src/plugins/defaultRootName/plugin.js
 create mode 100644 src/plugins/defaultRootName/pluginSpec.js

diff --git a/karma.conf.js b/karma.conf.js
index 8875b78284..daf2a9d873 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -100,6 +100,6 @@ module.exports = (config) => {
         },
         concurrency: 1,
         singleRun: true,
-        browserNoActivityTimeout: 90000
+        browserNoActivityTimeout: 400000
     });
 };
diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js
index ab85477ee5..de753dac9c 100644
--- a/src/api/objects/ObjectAPI.js
+++ b/src/api/objects/ObjectAPI.js
@@ -46,7 +46,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);
     }
 
     /**
diff --git a/src/api/objects/RootObjectProvider.js b/src/api/objects/RootObjectProvider.js
index a15b4c41f7..00c43a215b 100644
--- a/src/api/objects/RootObjectProvider.js
+++ b/src/api/objects/RootObjectProvider.js
@@ -20,28 +20,42 @@
  * at runtime from the About dialog for additional information.
  *****************************************************************************/
 
-define([
-], function (
-) {
+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;
+        } else {
+            // if called twice, update instance rootRegistry
+            RootObjectProvider.instance.rootRegistry = rootRegistry;
+        }
 
-    function RootObjectProvider(rootRegistry) {
-        this.rootRegistry = rootRegistry;
+        return RootObjectProvider.instance; // eslint-disable-line no-constructor-return
     }
 
-    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;
+    }
+}
+
+function instance(rootRegistry) {
+    return new RootObjectProvider(rootRegistry);
+}
+
+export default instance;
diff --git a/src/api/objects/test/RootObjectProviderSpec.js b/src/api/objects/test/RootObjectProviderSpec.js
index 5cc83a4c2f..9536f845d5 100644
--- a/src/api/objects/test/RootObjectProviderSpec.js
+++ b/src/api/objects/test/RootObjectProviderSpec.js
@@ -19,34 +19,33 @@
  * this source code distribution or the Licensing information page available
  * at runtime from the About dialog for additional information.
  *****************************************************************************/
-define([
-    '../RootObjectProvider'
-], function (
-    RootObjectProvider
-) {
-    describe('RootObjectProvider', function () {
-        let rootRegistry;
-        let rootObjectProvider;
+import RootObjectProvider from '../RootObjectProvider';
 
-        beforeEach(function () {
-            rootRegistry = jasmine.createSpyObj('rootRegistry', ['getRoots']);
-            rootRegistry.getRoots.and.returnValue(Promise.resolve(['some root']));
-            rootObjectProvider = new RootObjectProvider(rootRegistry);
-        });
+describe('RootObjectProvider', function () {
+    // let rootRegistry;
+    let rootObjectProvider;
+    let roots = ['some root'];
+    let rootRegistry = {
+        getRoots: () => {
+            return Promise.resolve(roots);
+        }
+    };
 
-        it('supports fetching root', function () {
-            return rootObjectProvider.get()
-                .then(function (root) {
-                    expect(root).toEqual({
-                        identifier: {
-                            key: "ROOT",
-                            namespace: ""
-                        },
-                        name: 'The root object',
-                        type: 'root',
-                        composition: ['some root']
-                    });
-                });
+    beforeEach(function () {
+        rootObjectProvider = new RootObjectProvider(rootRegistry);
+    });
+
+    it('supports fetching root', async () => {
+        let root = await rootObjectProvider.get();
+
+        expect(root).toEqual({
+            identifier: {
+                key: "ROOT",
+                namespace: ""
+            },
+            name: 'The root object',
+            type: 'root',
+            composition: ['some root']
         });
     });
 });
diff --git a/src/plugins/defaultRootName/plugin.js b/src/plugins/defaultRootName/plugin.js
new file mode 100644
index 0000000000..a8edf2eeb4
--- /dev/null
+++ b/src/plugins/defaultRootName/plugin.js
@@ -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);
+    };
+}
diff --git a/src/plugins/defaultRootName/pluginSpec.js b/src/plugins/defaultRootName/pluginSpec.js
new file mode 100644
index 0000000000..00c88a81b3
--- /dev/null
+++ b/src/plugins/defaultRootName/pluginSpec.js
@@ -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;
+    let compositionAPI;
+    let newFolderAction;
+    let mockObjectPath;
+    let mockDialogService;
+    let mockComposition;
+    let mockPromise;
+    let 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();
+        });
+    });
+});
diff --git a/src/plugins/folderView/components/list-view.scss b/src/plugins/folderView/components/list-view.scss
index bb4ba0ace8..21aced062d 100644
--- a/src/plugins/folderView/components/list-view.scss
+++ b/src/plugins/folderView/components/list-view.scss
@@ -13,7 +13,7 @@
             cursor: pointer;
 
             &:hover {
-                background: $colorItemTreeHoverBg;
+                background: $colorListItemBgHov;
                 filter: $filterHov;
                 transition: $transIn;
             }
diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js
index 8dc1e82777..d03612e2c4 100644
--- a/src/plugins/plugins.js
+++ b/src/plugins/plugins.js
@@ -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
 ) {
     const 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;
 });
diff --git a/src/styles/_animations.scss b/src/styles/_animations.scss
index 1d06cf7f9e..f95e73560c 100644
--- a/src/styles/_animations.scss
+++ b/src/styles/_animations.scss
@@ -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 {
diff --git a/src/styles/_constants-espresso.scss b/src/styles/_constants-espresso.scss
index 4098bd20a0..96b41a62f8 100644
--- a/src/styles/_constants-espresso.scss
+++ b/src/styles/_constants-espresso.scss
@@ -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;
 
diff --git a/src/styles/_constants-maelstrom.scss b/src/styles/_constants-maelstrom.scss
index 9c6593acc3..e4d1992612 100644
--- a/src/styles/_constants-maelstrom.scss
+++ b/src/styles/_constants-maelstrom.scss
@@ -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;
 
diff --git a/src/styles/_constants-snow.scss b/src/styles/_constants-snow.scss
index d3d1e0908e..aa6541064f 100644
--- a/src/styles/_constants-snow.scss
+++ b/src/styles/_constants-snow.scss
@@ -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;
diff --git a/src/styles/_constants.scss b/src/styles/_constants.scss
index ce5954741b..5a5c20a4a9 100755
--- a/src/styles/_constants.scss
+++ b/src/styles/_constants.scss
@@ -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;
diff --git a/src/styles/_controls.scss b/src/styles/_controls.scss
index bb47dcb6fb..dccff7b0dc 100644
--- a/src/styles/_controls.scss
+++ b/src/styles/_controls.scss
@@ -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;
     }
 
diff --git a/src/styles/_global.scss b/src/styles/_global.scss
index 72fbb53d53..b786bbfcac 100644
--- a/src/styles/_global.scss
+++ b/src/styles/_global.scss
@@ -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;
diff --git a/src/ui/components/viewControl.vue b/src/ui/components/viewControl.vue
index c32693a809..2a54a0bb5b 100644
--- a/src/ui/components/viewControl.vue
+++ b/src/ui/components/viewControl.vue
@@ -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: {
diff --git a/src/ui/layout/BrowseBar.vue b/src/ui/layout/BrowseBar.vue
index 87912e1b0b..8ef379ad75 100644
--- a/src/ui/layout/BrowseBar.vue
+++ b/src/ui/layout/BrowseBar.vue
@@ -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
diff --git a/src/ui/layout/Layout.vue b/src/ui/layout/Layout.vue
index 3884570cd7..75e4b4c96f 100644
--- a/src/ui/layout/Layout.vue
+++ b/src/ui/layout/Layout.vue
@@ -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"
@@ -126,7 +139,8 @@ export default {
             conductorComponent: undefined,
             isEditing: false,
             hasToolbar: false,
-            headExpanded
+            headExpanded,
+            triggerSync: false
         };
     },
     computed: {
@@ -200,6 +214,9 @@ export default {
             }
 
             this.hasToolbar = structure.length > 0;
+        },
+        handleSyncTreeNavigation() {
+            this.triggerSync = !this.triggerSync;
         }
     }
 };
diff --git a/src/ui/layout/layout.scss b/src/ui/layout/layout.scss
index 4d7944838e..527de3d6c0 100644
--- a/src/ui/layout/layout.scss
+++ b/src/ui/layout/layout.scss
@@ -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 {
diff --git a/src/ui/layout/mct-tree.scss b/src/ui/layout/mct-tree.scss
index 3defe59e60..b0fb8ec02c 100644
--- a/src/ui/layout/mct-tree.scss
+++ b/src/ui/layout/mct-tree.scss
@@ -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,55 @@
                 width: ceil($mobileTreeItemH * 0.5);
             }
         }
+
+        &.is-navigated-object,
+        &.is-selected {
+            background: $colorItemTreeSelectedBg;
+
+            [class*="__label"],
+            [class*="__name"] {
+                color: $colorItemTreeSelectedFg;
+            }
+
+            [class*="__type-icon"]:before {
+                color: $colorItemTreeSelectedIcon;
+            }
+        }
+    }
+
+    &__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 +195,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 +247,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);}
+}
diff --git a/src/ui/layout/mct-tree.vue b/src/ui/layout/mct-tree.vue
index c75352e93e..f1d969ecdf 100644
--- a/src/ui/layout/mct-tree.vue
+++ b/src/ui/layout/mct-tree.vue
@@ -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,75 +110,518 @@ export default {
         search,
         treeItem
     },
+    props: {
+        syncTreeNavigation: {
+            type: Boolean,
+            required: true
+        }
+    },
     data() {
+        let isMobile = this.openmct.$injector.get('agentService');
+
         return {
+            isLoading: false,
             searchValue: '',
             allTreeItems: [],
-            filteredTreeItems: [],
-            isLoading: false
+            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
         };
     },
-    mounted() {
+    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');
+
+        if (root.identifier !== undefined) {
+            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 && root.composition[0] !== undefined) {
+                // needed if saved path is not set, need to set it to the only root child
+                savedPath = root.composition[0];
+            }
+
+            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 => {
-                    let composition = this.openmct.composition.get(root);
-                    if (composition !== undefined) {
-                        return composition.load();
-                    } else {
-                        return [];
+        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;
                     }
-                })
-                .then(children => {
-                    this.isLoading = false;
-                    this.allTreeItems = children.map(c => {
-                        return {
-                            id: this.openmct.objects.makeKeyString(c.identifier),
-                            object: c,
-                            objectPath: [c],
-                            navigateToParent: '/browse'
-                        };
-                    });
-                });
+                }
+
+                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');
-                    let object = child.object.useCapability('adapter');
-                    let objectPath = [];
-                    let 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;
                     }
 
-                    return {
-                        id: this.openmct.objects.makeKeyString(object.identifier),
-                        object,
-                        objectPath,
-                        navigateToParent
-                    };
+                    this.availableContainerHeight = mainTreeHeight - ancestorsHeight;
+
+                    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();
+        },
+        finishLoading() {
+            if (this.jumpPath) {
+                this.jumpToPath();
+            }
+
+            this.autoScroll();
+            this.isLoading = false;
+        },
+        async jumpToPath(saveExpandedPath = false) {
+            // check for older implementations of tree storage and reformat if necessary
+            if (Array.isArray(this.jumpPath)) {
+                this.jumpPath = this.jumpPath[0];
+            }
+
+            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));
         }
     }
 };
diff --git a/src/ui/layout/pane.scss b/src/ui/layout/pane.scss
index 0d988e1124..fdcb0913e3 100644
--- a/src/ui/layout/pane.scss
+++ b/src/ui/layout/pane.scss
@@ -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;
                 }
             }
         }
diff --git a/src/ui/layout/pane.vue b/src/ui/layout/pane.vue
index 1f8a2267f7..8bd178dc50 100644
--- a/src/ui/layout/pane.vue
+++ b/src/ui/layout/pane.vue
@@ -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>
diff --git a/src/ui/layout/tree-item.vue b/src/ui/layout/tree-item.vue
index 3cb3b7490d..800955b758 100644
--- a/src/ui/layout/tree-item.vue
+++ b/src/ui/layout/tree-item.vue
@@ -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,17 +52,49 @@ 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
         };
     },
@@ -77,32 +108,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) => {
@@ -110,49 +132,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('/');
         },
@@ -163,35 +155,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);
         }
     }
 };