mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 11:09:22 +00:00
Compare commits
2 Commits
tree-refac
...
new-tree-i
Author | SHA1 | Date | |
---|---|---|---|
55e9cafbef | |||
8cd91061f7 |
@ -301,7 +301,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Inspector from '../inspector/Inspector.vue';
|
import Inspector from '../inspector/Inspector.vue';
|
||||||
import MctTree from './mct-tree.vue';
|
import MctTree from './mct-tree-prototype.vue';
|
||||||
import ObjectView from '../components/ObjectView.vue';
|
import ObjectView from '../components/ObjectView.vue';
|
||||||
import MctTemplate from '../legacy/mct-template.vue';
|
import MctTemplate from '../legacy/mct-template.vue';
|
||||||
import CreateButton from './CreateButton.vue';
|
import CreateButton from './CreateButton.vue';
|
||||||
|
374
src/ui/layout/mct-tree-prototype.vue
Normal file
374
src/ui/layout/mct-tree-prototype.vue
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-tree-and-search">
|
||||||
|
<div class="c-tree-and-search__search">
|
||||||
|
<search class="c-search" ref="shell-search"
|
||||||
|
:value="searchValue"
|
||||||
|
@input="searchTree"
|
||||||
|
@clear="searchTree">
|
||||||
|
</search>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- loading -->
|
||||||
|
<div class="c-tree-and-search__loading loading"
|
||||||
|
v-if="isLoading"></div>
|
||||||
|
<!-- end loading -->
|
||||||
|
|
||||||
|
<!-- main tree -->
|
||||||
|
<template v-if="parentNode && parentNode.id !== 'ROOT'">
|
||||||
|
<tree-item
|
||||||
|
:key="parentNode.id"
|
||||||
|
:node="parentNode"
|
||||||
|
:isExpanded="true"
|
||||||
|
@notExpanded="navigateToParent">
|
||||||
|
</tree-item>
|
||||||
|
|
||||||
|
<div style="min-width: 100%; height: 2px; background: rgb(201,201,201);"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ul class="c-tree-and-search__tree c-tree"
|
||||||
|
v-if="!isLoading"
|
||||||
|
v-show="!searchValue"
|
||||||
|
@scroll="scrollPage">
|
||||||
|
|
||||||
|
<tree-item
|
||||||
|
v-for="treeItem in pagedChildren"
|
||||||
|
:key="treeItem.id"
|
||||||
|
:node="treeItem"
|
||||||
|
@expanded="setParentAndLoadChildren">
|
||||||
|
</tree-item>
|
||||||
|
</ul>
|
||||||
|
<!-- end main tree -->
|
||||||
|
|
||||||
|
<!-- search tree -->
|
||||||
|
<ul class="c-tree-and-search__tree c-tree"
|
||||||
|
v-if="searchValue">
|
||||||
|
<tree-item v-for="treeItem in filteredTreeItems"
|
||||||
|
:key="treeItem.id"
|
||||||
|
:node="treeItem">
|
||||||
|
</tree-item>
|
||||||
|
</ul>
|
||||||
|
<!-- end search tree -->
|
||||||
|
|
||||||
|
<div class="c-tree-and-search__no-results"
|
||||||
|
v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)">
|
||||||
|
No results found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.c-tree-and-search {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-right: $interiorMarginSm;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
> * + * { margin-top: $interiorMargin; }
|
||||||
|
|
||||||
|
&__search {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__no-results {
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tree {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 0; // Chrome 73 overflow bug fix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
@include userSelectNone();
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: $interiorMargin;
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
&.c-tree__item-h { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
$aPad: $interiorMarginSm;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 110%;
|
||||||
|
padding: $interiorMargin - $aPad;
|
||||||
|
transition: background 150ms ease;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorItemTreeHoverBg;
|
||||||
|
.c-tree__item__type-icon:before {
|
||||||
|
color: $colorItemTreeIconHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree__item__name {
|
||||||
|
color: $colorItemTreeHoverFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-navigated-object,
|
||||||
|
&.is-selected {
|
||||||
|
background: $colorItemTreeSelectedBg;
|
||||||
|
.c-tree__item__type-icon:before {
|
||||||
|
color: $colorItemTreeIconHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree__item__name {
|
||||||
|
color: $colorItemTreeSelectedFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-being-edited {
|
||||||
|
background: $colorItemTreeEditingBg;
|
||||||
|
.c-tree__item__type-icon:before {
|
||||||
|
color: $colorItemTreeEditingIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree__item__name {
|
||||||
|
color: $colorItemTreeEditingFg;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object labels in trees
|
||||||
|
&__label {
|
||||||
|
// <a> tag that holds type icon and name.
|
||||||
|
// Draggable element.
|
||||||
|
/*border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: $aPad;
|
||||||
|
white-space: nowrap;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
// @include ellipsize();
|
||||||
|
// display: inline;
|
||||||
|
color: $colorItemTreeFg;
|
||||||
|
// width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
// Type icon. Must be an HTML entity to allow inclusion of alias indicator.
|
||||||
|
// display: block;
|
||||||
|
// flex: 0 0 auto;
|
||||||
|
// font-size: 1.3em;
|
||||||
|
// margin-right: $interiorMarginSm;
|
||||||
|
color: $colorItemTreeIcon;
|
||||||
|
// width: $treeTypeIconW;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-alias {
|
||||||
|
// Object is an alias to an original.
|
||||||
|
[class*='__type-icon'] {
|
||||||
|
@include isAlias();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile & {
|
||||||
|
@include button($bg: $colorMobilePaneLeftTreeItemBg, $fg: $colorMobilePaneLeftTreeItemFg);
|
||||||
|
height: $mobileTreeItemH;
|
||||||
|
margin-bottom: $interiorMarginSm;
|
||||||
|
[class*="view-control"] {
|
||||||
|
width: ceil($mobileTreeItemH * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import treeItem from './tree-item-prototype.vue'
|
||||||
|
import search from '../components/search.vue';
|
||||||
|
|
||||||
|
const PAGE_THRESHOLD = 50;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
name: 'mct-tree',
|
||||||
|
components: {
|
||||||
|
search,
|
||||||
|
treeItem
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchValue: '',
|
||||||
|
allTreeItems: [],
|
||||||
|
filteredTreeItems: [],
|
||||||
|
isLoading: false,
|
||||||
|
currentObjectPath: [],
|
||||||
|
parentNode: undefined,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pagedChildren() {
|
||||||
|
if (this.allTreeItems.length > PAGE_THRESHOLD) {
|
||||||
|
let maxIndex = this.page * PAGE_THRESHOLD,
|
||||||
|
minIndex = maxIndex - PAGE_THRESHOLD;
|
||||||
|
|
||||||
|
return this.allTreeItems.slice(minIndex, maxIndex);
|
||||||
|
} else {
|
||||||
|
return this.allTreeItems;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lastPage() {
|
||||||
|
return Math.floor(this.allTreeItems.length / PAGE_THRESHOLD);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRootChildren() {
|
||||||
|
this.openmct.objects.get('ROOT')
|
||||||
|
.then(root => {
|
||||||
|
let rootNode = this.buildTreeItems(root);
|
||||||
|
this.getAllChildren(rootNode);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAllChildren(node) {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
if (this.composition) {
|
||||||
|
this.composition.off('add', this.addChild);
|
||||||
|
this.composition.off('remove', this.removeChild);
|
||||||
|
delete this.composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parentNode = node;
|
||||||
|
this.currentObjectPath = this.parentNode.objectPath;
|
||||||
|
this.allTreeItems = [];
|
||||||
|
|
||||||
|
this.composition = this.openmct.composition.get(node.object);
|
||||||
|
this.composition.on('add', this.addChild);
|
||||||
|
this.composition.on('remove', this.removeChild);
|
||||||
|
this.composition.load().then(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildTreeItems(domainObject) {
|
||||||
|
return {
|
||||||
|
id: this.openmct.objects.makeKeyString(domainObject.identifier),
|
||||||
|
object: domainObject,
|
||||||
|
objectPath: [domainObject].concat(this.currentObjectPath),
|
||||||
|
navigateToParent: '/browse'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getFilteredChildren() {
|
||||||
|
this.searchService.query(this.searchValue).then(children => {
|
||||||
|
this.filteredTreeItems = children.hits.map(child => {
|
||||||
|
|
||||||
|
let context = child.object.getCapability('context'),
|
||||||
|
object = child.object.useCapability('adapter'),
|
||||||
|
objectPath = [],
|
||||||
|
navigateToParent;
|
||||||
|
|
||||||
|
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('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.openmct.objects.makeKeyString(object.identifier),
|
||||||
|
object,
|
||||||
|
objectPath,
|
||||||
|
navigateToParent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
searchTree(value) {
|
||||||
|
this.searchValue = value;
|
||||||
|
|
||||||
|
if (this.searchValue !== '') {
|
||||||
|
this.getFilteredChildren();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildPathString(parentPath) {
|
||||||
|
return [parentPath, this.parentNode.id].join('/');
|
||||||
|
},
|
||||||
|
addChild (child) {
|
||||||
|
this.allTreeItems.push(this.buildTreeItems(child));
|
||||||
|
},
|
||||||
|
removeChild(identifier) {
|
||||||
|
let removeId = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
this.allChildren = this.children
|
||||||
|
.filter(c => c.id !== removeId);
|
||||||
|
},
|
||||||
|
navigateToParent(node) {
|
||||||
|
let parentDomainObject = node.objectPath[1],
|
||||||
|
parentNode = {
|
||||||
|
id: this.openmct.objects.makeKeyString(parentDomainObject.identifier),
|
||||||
|
object: parentDomainObject,
|
||||||
|
objectPath: node.objectPath.slice(1),
|
||||||
|
navigateToParent: '/browse'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getAllChildren(parentNode);
|
||||||
|
},
|
||||||
|
setParentAndLoadChildren(node){
|
||||||
|
this.getAllChildren(node);
|
||||||
|
},
|
||||||
|
nextPage() {
|
||||||
|
if (this.page < this.lastPage) {
|
||||||
|
this.page += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previousPage() {
|
||||||
|
if (this.page >= 1) {
|
||||||
|
this.page -= 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollPage(event) {
|
||||||
|
let offsetHeight = event.target.offsetHeight,
|
||||||
|
scrollHeight = event.target.scrollHeight,
|
||||||
|
scrollTop = event.target.scrollTop,
|
||||||
|
changePage = true;
|
||||||
|
|
||||||
|
if (scrollTop + offsetHeight === scrollHeight) {
|
||||||
|
this.scrollLoading = window.setTimeout(() => {
|
||||||
|
if (this.page < this.lastPage) {
|
||||||
|
this.nextPage();
|
||||||
|
event.target.scrollTop = 1;
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else if (scrollTop === 0) {
|
||||||
|
this.scrollLoading = window.setTimeout(() => {
|
||||||
|
if (this.page > 1) {
|
||||||
|
this.previousPage();
|
||||||
|
event.target.scrollTop = offsetHeight - 1;
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.searchService = this.openmct.$injector.get('searchService');
|
||||||
|
this.getRootChildren();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
94
src/ui/layout/tree-item-prototype.vue
Normal file
94
src/ui/layout/tree-item-prototype.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<li class="c-tree__item-h">
|
||||||
|
<div class="c-tree__item"
|
||||||
|
:class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }">
|
||||||
|
<view-control class="c-tree__item__view-control"
|
||||||
|
:enabled="hasChildren"
|
||||||
|
v-model="expanded">
|
||||||
|
</view-control>
|
||||||
|
<object-label :domainObject="node.object"
|
||||||
|
:objectPath="node.objectPath"
|
||||||
|
:navigateToPath="navigateToPath">
|
||||||
|
</object-label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import viewControl from '../components/viewControl.vue';
|
||||||
|
import ObjectLabel from '../components/ObjectLabel.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'tree-item',
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
node: Object,
|
||||||
|
isExpanded: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
this.navigateToPath = this.buildPathString(this.node.navigateToParent)
|
||||||
|
return {
|
||||||
|
hasChildren: false,
|
||||||
|
isLoading: false,
|
||||||
|
loaded: false,
|
||||||
|
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
||||||
|
children: [],
|
||||||
|
expanded: this.isExpanded
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAlias() {
|
||||||
|
let parent = this.node.objectPath[1];
|
||||||
|
if (!parent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||||
|
return parentKeyString !== this.node.object.location;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
expanded(isExpanded) {
|
||||||
|
if (isExpanded) {
|
||||||
|
this.$emit('expanded', this.node);
|
||||||
|
} else {
|
||||||
|
this.$emit('notExpanded', this.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.domainObject = this.node.object;
|
||||||
|
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
||||||
|
this.domainObject = newObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', removeListener);
|
||||||
|
if (this.openmct.composition.get(this.node.object)) {
|
||||||
|
this.hasChildren = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.router.on('change:path', this.highlightIfNavigated);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.openmct.router.off('change:path', this.highlightIfNavigated);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buildPathString(parentPath) {
|
||||||
|
return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
|
||||||
|
},
|
||||||
|
highlightIfNavigated(newPath, oldPath){
|
||||||
|
if (newPath === this.navigateToPath) {
|
||||||
|
this.isNavigated = true;
|
||||||
|
} else if (oldPath === this.navigateToPath) {
|
||||||
|
this.isNavigated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
viewControl,
|
||||||
|
ObjectLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Reference in New Issue
Block a user