Compare commits

...

2 Commits

Author SHA1 Message Date
55e9cafbef working pagination 2019-11-18 14:53:47 -08:00
8cd91061f7 working prototype - todo - pagination 2019-11-15 15:47:34 -08:00
3 changed files with 469 additions and 1 deletions

View File

@ -301,7 +301,7 @@
<script>
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 MctTemplate from '../legacy/mct-template.vue';
import CreateButton from './CreateButton.vue';

View 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>

View 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>