Merge 1.8.1 into master (#4562)

* Transaction fix (#4421) (#4461)
* When transaction is active, objects.get should search in dirty object first.
* Bugfix/create tree node (#4472)
* Transaction fix (#4421)
* When transaction is active, objects.get should search in dirty object first.
* find insert location prior to adding item to tree
* no need to resort
* add item should only add to direct descendants
* remove unused function
* copy composition before sorting
* remove unused var
* remove master pollution
* Revert "remove master pollution"
* add item to correct location
* Changed descending to ascending in sort order method (#4480)
* adding RAF to display layout alphanumerics (#4486)
* [Tabs] Sizing of offscreen tabs causing issues (#4444)
* [LAD Tables] Use RAF for updating template (#4500)
* Fixes LAD rows for string telemetry (#4508)
* Fixes LAD rows for string telemetry
* saving the object if it was missing (#4471)
* 4328 - Maintain reference to a focusedImage if the bounds change (#4545)
* WIP: adding assertions to catch negative index state
* just testing the flow
* SUpdate the image history index to previous selected image
* Cleaning up spacing and log statements
* Converted focusedImageIndex assignment to ternary and general cleanup
* imported objects are not persisting  (#4477)
* imported objects are not persisting #4470
* disabled karma spec reporter suppressErrorSummary
* update version number
* Delete importFromJsonAction directory since it was empty
This commit is contained in:
Shefali Joshi 2021-12-13 11:19:54 -08:00 committed by GitHub
parent 7c4258d720
commit bba29b083f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 253 additions and 88 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "openmct", "name": "openmct",
"version": "1.8.1-SNAPSHOT", "version": "1.8.1",
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"devDependencies": { "devDependencies": {
"@braintree/sanitize-url": "^5.0.2", "@braintree/sanitize-url": "^5.0.2",

View File

@ -48,6 +48,7 @@ const CONTEXT_MENU_ACTIONS = [
'viewHistoricalData', 'viewHistoricalData',
'remove' 'remove'
]; ];
const BLANK_VALUE = '---';
export default { export default {
inject: ['openmct', 'currentView'], inject: ['openmct', 'currentView'],
@ -67,15 +68,43 @@ export default {
}, },
data() { data() {
return { return {
datum: undefined,
timestamp: undefined, timestamp: undefined,
value: '---', timestampKey: undefined,
valueClass: '',
unit: '' unit: ''
}; };
}, },
computed: { computed: {
value() {
if (!this.datum) {
return BLANK_VALUE;
}
return this.formats[this.valueKey].format(this.datum);
},
valueClass() {
if (!this.datum) {
return '';
}
const limit = this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
return limit ? limit.cssClass : '';
},
formattedTimestamp() { formattedTimestamp() {
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---'; if (!this.timestamp) {
return BLANK_VALUE;
}
return this.timeSystemFormat.format(this.timestamp);
},
timeSystemFormat() {
if (!this.formats[this.timestampKey]) {
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
}
return this.formats[this.timestampKey];
}, },
objectPath() { objectPath() {
return [this.domainObject, ...this.pathToTable]; return [this.domainObject, ...this.pathToTable];
@ -96,15 +125,19 @@ export default {
this.timestampKey = this.openmct.time.timeSystem().key; this.timestampKey = this.openmct.time.timeSystem().key;
this.valueMetadata = this.metadata ? this this.valueMetadata = undefined;
if (this.metadata) {
this.valueMetadata = this
.metadata .metadata
.valuesForHints(['range'])[0] : undefined; .valuesForHints(['range'])[0] || this.firstNonDomainAttribute(this.metadata);
}
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined; this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
this.unsubscribe = this.openmct this.unsubscribe = this.openmct
.telemetry .telemetry
.subscribe(this.domainObject, this.updateValues); .subscribe(this.domainObject, this.setLatestValues);
this.requestHistory(); this.requestHistory();
@ -118,29 +151,29 @@ export default {
this.openmct.time.off('bounds', this.updateBounds); this.openmct.time.off('bounds', this.updateBounds);
}, },
methods: { methods: {
updateValues(datum) { updateView() {
let newTimestamp = this.getParsedTimestamp(datum); if (!this.updatingView) {
let limit; this.updatingView = true;
requestAnimationFrame(() => {
let newTimestamp = this.getParsedTimestamp(this.latestDatum);
if (this.shouldUpdate(newTimestamp)) { if (this.shouldUpdate(newTimestamp)) {
this.datum = datum;
this.timestamp = newTimestamp; this.timestamp = newTimestamp;
this.value = this.formats[this.valueKey].format(datum); this.datum = this.latestDatum;
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
} }
this.updatingView = false;
});
} }
}, },
shouldUpdate(newTimestamp) { setLatestValues(datum) {
let newTimestampInBounds = this.inBounds(newTimestamp); this.latestDatum = datum;
let noExistingTimestamp = this.timestamp === undefined;
let newTimestampIsLatest = newTimestamp > this.timestamp;
return newTimestampInBounds this.updateView();
&& (noExistingTimestamp || newTimestampIsLatest); },
shouldUpdate(newTimestamp) {
return this.inBounds(newTimestamp)
&& (this.timestamp === undefined || newTimestamp > this.timestamp);
}, },
requestHistory() { requestHistory() {
this.openmct this.openmct
@ -151,7 +184,7 @@ export default {
size: 1, size: 1,
strategy: 'latest' strategy: 'latest'
}) })
.then((array) => this.updateValues(array[array.length - 1])) .then((array) => this.setLatestValues(array[array.length - 1]))
.catch((error) => { .catch((error) => {
console.warn('Error fetching data', error); console.warn('Error fetching data', error);
}); });
@ -189,31 +222,21 @@ export default {
} }
}, },
resetValues() { resetValues() {
this.value = '---';
this.timestamp = undefined; this.timestamp = undefined;
this.valueClass = ''; this.datum = undefined;
}, },
getParsedTimestamp(timestamp) { getParsedTimestamp(timestamp) {
if (this.timeSystemFormat()) { if (this.timeSystemFormat) {
return this.formats[this.timestampKey].parse(timestamp); return this.timeSystemFormat.parse(timestamp);
}
},
getFormattedTimestamp(timestamp) {
if (this.timeSystemFormat()) {
return this.formats[this.timestampKey].format(timestamp);
}
},
timeSystemFormat() {
if (this.formats[this.timestampKey]) {
return true;
} else {
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
return false;
} }
}, },
setUnit() { setUnit() {
this.unit = this.valueMetadata.unit || ''; this.unit = this.valueMetadata.unit || '';
},
firstNonDomainAttribute(metadata) {
return metadata
.values()
.find(metadatum => metadatum.hints.domain === undefined && metadatum.key !== 'name');
} }
} }
}; };

View File

@ -26,6 +26,7 @@ import {
getMockObjects, getMockObjects,
getMockTelemetry, getMockTelemetry,
getLatestTelemetry, getLatestTelemetry,
spyOnBuiltins,
resetApplicationState resetApplicationState
} from 'utils/testing'; } from 'utils/testing';
@ -160,6 +161,11 @@ describe("The LAD Table", () => {
anotherTelemetryObjectResolve = resolve; anotherTelemetryObjectResolve = resolve;
}); });
spyOnBuiltins(['requestAnimationFrame']);
window.requestAnimationFrame.and.callFake((callBack) => {
callBack();
});
openmct.telemetry.request.and.callFake(() => { openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry); telemetryRequestResolve(mockTelemetry);

View File

@ -263,7 +263,8 @@ export default {
this.openmct.telemetry.request(this.domainObject, options) this.openmct.telemetry.request(this.domainObject, options)
.then(data => { .then(data => {
if (data.length > 0) { if (data.length > 0) {
this.updateView(data[data.length - 1]); this.latestDatum = data[data.length - 1];
this.updateView();
} }
}); });
}, },
@ -275,12 +276,19 @@ export default {
|| (datumTimeStamp || (datumTimeStamp
&& (this.openmct.time.bounds().end >= datumTimeStamp)) && (this.openmct.time.bounds().end >= datumTimeStamp))
) { ) {
this.updateView(datum); this.latestDatum = datum;
this.updateView();
} }
}.bind(this)); }.bind(this));
}, },
updateView(datum) { updateView() {
this.datum = datum; if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(() => {
this.datum = this.latestDatum;
this.updatingView = false;
});
}
}, },
removeSubscription() { removeSubscription() {
if (this.subscription) { if (this.subscription) {
@ -290,7 +298,8 @@ export default {
}, },
refreshData(bounds, isTick) { refreshData(bounds, isTick) {
if (!isTick) { if (!isTick) {
this.datum = undefined; this.latestDatum = undefined;
this.updateView();
this.requestHistoricalData(this.domainObject); this.requestHistoricalData(this.domainObject);
} }
}, },

View File

@ -414,7 +414,7 @@ export default {
if (this.indexForFocusedImage !== undefined) { if (this.indexForFocusedImage !== undefined) {
imageIndex = this.initFocusedImageIndex; imageIndex = this.initFocusedImageIndex;
} else { } else {
imageIndex = newSize - 1; imageIndex = newSize > 0 ? newSize - 1 : undefined;
} }
this.setFocusedImage(imageIndex, false); this.setFocusedImage(imageIndex, false);
@ -510,6 +510,12 @@ export default {
this.timeContext.off("timeContext", this.setTimeContext); this.timeContext.off("timeContext", this.setTimeContext);
} }
}, },
boundsChange(bounds, isTick) {
if (!isTick) {
this.previousFocusedImage = this.focusedImage ? JSON.parse(JSON.stringify(this.focusedImage)) : undefined;
this.requestHistory();
}
},
expand() { expand() {
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView); const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
const visibleActions = actionCollection.getVisibleActions(); const visibleActions = actionCollection.getVisibleActions();
@ -670,23 +676,47 @@ export default {
this.$refs.thumbsWrapper.scrollLeft = scrollWidth; this.$refs.thumbsWrapper.scrollLeft = scrollWidth;
}); });
}, },
matchIndexOfPreviousImage(previous, imageHistory) {
// match logic uses a composite of url and time to account
// for example imagery not having fully unique urls
return imageHistory.findIndex((x) => (
x.url === previous.url
&& x.time === previous.time
));
},
setFocusedImage(index, thumbnailClick = false) { setFocusedImage(index, thumbnailClick = false) {
let focusedIndex = index;
if (!(Number.isInteger(index) && index > -1)) {
return;
}
if (this.previousFocusedImage) {
// determine if the previous image exists in the new bounds of imageHistory
const matchIndex = this.matchIndexOfPreviousImage(
this.previousFocusedImage,
this.imageHistory
);
focusedIndex = matchIndex > -1 ? matchIndex : this.imageHistory.length - 1;
delete this.previousFocusedImage;
}
if (thumbnailClick) { if (thumbnailClick) {
//We use the props till the user changes what they want to see //We use the props till the user changes what they want to see
this.initFocusedImageIndex = undefined; this.initFocusedImageIndex = undefined;
} }
if (this.isPaused && !thumbnailClick && this.initFocusedImageIndex === undefined) { if (this.isPaused && !thumbnailClick && this.initFocusedImageIndex === undefined) {
this.nextImageIndex = index; this.nextImageIndex = focusedIndex;
//this could happen if bounds changes //this could happen if bounds changes
if (this.focusedImageIndex > this.imageHistory.length - 1) { if (this.focusedImageIndex > this.imageHistory.length - 1) {
this.focusedImageIndex = index; this.focusedImageIndex = focusedIndex;
} }
return; return;
} }
this.focusedImageIndex = index; this.focusedImageIndex = focusedIndex;
if (thumbnailClick && !this.isPaused) { if (thumbnailClick && !this.isPaused) {
this.paused(true); this.paused(true);

View File

@ -31,8 +31,6 @@
flex-direction: column; flex-direction: column;
&--hidden { &--hidden {
height: 1000px;
width: 1000px;
position: absolute; position: absolute;
left: -9999px; left: -9999px;
top: -9999px; top: -9999px;

View File

@ -1,6 +1,10 @@
<template> <template>
<div class="c-tabs-view"> <div
ref="tabs"
class="c-tabs-view"
>
<div <div
ref="tabsHolder"
class="c-tabs-view__tabs-holder c-tabs" class="c-tabs-view__tabs-holder c-tabs"
:class="{ :class="{
'is-dragging': isDragging && allowEditing, 'is-dragging': isDragging && allowEditing,
@ -28,7 +32,9 @@
}" }"
@click="showTab(tab, index)" @click="showTab(tab, index)"
> >
<div class="c-tabs-view__tab__label c-object-label" <div
ref="tabsLabel"
class="c-tabs-view__tab__label c-object-label"
:class="[tab.status ? `is-status--${tab.status}` : '']" :class="[tab.status ? `is-status--${tab.status}` : '']"
> >
<div class="c-object-label__type-icon" <div class="c-object-label__type-icon"
@ -49,11 +55,12 @@
<div <div
v-for="tab in tabsList" v-for="tab in tabsList"
:key="tab.keyString" :key="tab.keyString"
:style="getTabStyles(tab)"
class="c-tabs-view__object-holder" class="c-tabs-view__object-holder"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}" :class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"
> >
<object-view <object-view
v-if="isTabLoaded(tab)" v-if="shouldLoadTab(tab)"
class="c-tabs-view__object" class="c-tabs-view__object"
:default-object="tab.domainObject" :default-object="tab.domainObject"
:object-path="tab.objectPath" :object-path="tab.objectPath"
@ -65,6 +72,7 @@
<script> <script>
import ObjectView from '../../../ui/components/ObjectView.vue'; import ObjectView from '../../../ui/components/ObjectView.vue';
import RemoveAction from '../../remove/RemoveAction.js'; import RemoveAction from '../../remove/RemoveAction.js';
import _ from 'lodash';
const unknownObjectType = { const unknownObjectType = {
definition: { definition: {
@ -88,6 +96,8 @@ export default {
let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return { return {
tabWidth: undefined,
tabHeight: undefined,
internalDomainObject: this.domainObject, internalDomainObject: this.domainObject,
currentTab: {}, currentTab: {},
currentTabIndex: undefined, currentTabIndex: undefined,
@ -122,6 +132,10 @@ export default {
}); });
} }
this.handleWindowResize = _.debounce(this.handleWindowResize, 500);
this.tabsViewResizeObserver = new ResizeObserver(this.handleWindowResize);
this.tabsViewResizeObserver.observe(this.$refs.tabs);
this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject); this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
this.openmct.router.on('change:params', this.updateCurrentTab.bind(this)); this.openmct.router.on('change:params', this.updateCurrentTab.bind(this));
@ -138,6 +152,8 @@ export default {
this.composition.off('remove', this.removeItem); this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder); this.composition.off('reorder', this.onReorder);
this.tabsViewResizeObserver.disconnect();
this.tabsList.forEach(tab => { this.tabsList.forEach(tab => {
tab.statusUnsubscribe(); tab.statusUnsubscribe();
}); });
@ -158,12 +174,28 @@ export default {
this.loadedTabs[tab.keyString] = true; this.loadedTabs[tab.keyString] = true;
}, },
getTabStyles(tab) {
let styles = {};
if (!this.isCurrent(tab)) {
styles = {
height: this.tabHeight,
width: this.tabWidth
};
}
return styles;
},
setCurrentTabByIndex(index) { setCurrentTabByIndex(index) {
if (this.tabsList[index]) { if (this.tabsList[index]) {
this.showTab(this.tabsList[index]); this.showTab(this.tabsList[index]);
} }
}, },
showTab(tab, index) { showTab(tab, index) {
if (!tab) {
return;
}
if (index !== undefined) { if (index !== undefined) {
this.storeCurrentTabIndexInURL(index); this.storeCurrentTabIndexInURL(index);
} }
@ -171,6 +203,13 @@ export default {
this.currentTab = tab; this.currentTab = tab;
this.addTabToLoaded(tab); this.addTabToLoaded(tab);
}, },
shouldLoadTab(tab) {
const isLoaded = this.isTabLoaded(tab);
const isCurrent = this.isCurrent(tab);
const tabElLoaded = this.tabWidth !== undefined && this.tabHeight !== undefined;
return (isLoaded && isCurrent) || ((isLoaded && !isCurrent) && tabElLoaded);
},
showRemoveDialog(index) { showRemoveDialog(index) {
if (!this.tabsList[index]) { if (!this.tabsList[index]) {
return; return;
@ -325,6 +364,14 @@ export default {
this.currentTabIndex = tabIndex; this.currentTabIndex = tabIndex;
this.currentTab = this.tabsList[tabIndex]; this.currentTab = this.tabsList[tabIndex];
},
handleWindowResize() {
if (!this.$refs.tabs || !this.$refs.tabsHolder) {
return;
}
this.tabWidth = this.$refs.tabs.offsetWidth + 'px';
this.tabHeight = this.$refs.tabsHolder.offsetHeight - this.$refs.tabs.offsetHeight + 'px';
} }
} }
}; };

View File

@ -20,7 +20,11 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing'; import {
createOpenMct,
resetApplicationState,
spyOnBuiltins
} from 'utils/testing';
import TabsLayout from './plugin'; import TabsLayout from './plugin';
import Vue from "vue"; import Vue from "vue";
import EventEmitter from "EventEmitter"; import EventEmitter from "EventEmitter";
@ -63,13 +67,13 @@ describe('the plugin', function () {
'phase': 5, 'phase': 5,
'randomness': 0 'randomness': 0
}, },
'name': 'Sine Wave Generator',
'type': 'generator', 'type': 'generator',
'modified': 1592851063871, 'modified': 1592851063871,
'location': 'mine', 'location': 'mine',
'persisted': 1592851063871 'persisted': 1592851063871
}; };
let telemetryItem1 = Object.assign({}, telemetryItemTemplate, { let telemetryItem1 = Object.assign({}, telemetryItemTemplate, {
'name': 'Sine Wave Generator 1',
'id': '55122607-e65e-44d5-9c9d-9c31a914ca89', 'id': '55122607-e65e-44d5-9c9d-9c31a914ca89',
'identifier': { 'identifier': {
'namespace': '', 'namespace': '',
@ -77,6 +81,7 @@ describe('the plugin', function () {
} }
}); });
let telemetryItem2 = Object.assign({}, telemetryItemTemplate, { let telemetryItem2 = Object.assign({}, telemetryItemTemplate, {
'name': 'Sine Wave Generator 2',
'id': '55122607-e65e-44d5-9c9d-9c31a914ca90', 'id': '55122607-e65e-44d5-9c9d-9c31a914ca90',
'identifier': { 'identifier': {
'namespace': '', 'namespace': '',
@ -91,6 +96,9 @@ describe('the plugin', function () {
element = document.createElement('div'); element = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');
child.style.display = 'block';
child.style.width = '1920px';
child.style.height = '1080px';
element.appendChild(child); element.appendChild(child);
openmct.on('start', done); openmct.on('start', done);
@ -150,8 +158,17 @@ describe('the plugin', function () {
let tabsLayoutViewProvider; let tabsLayoutViewProvider;
let mockComposition; let mockComposition;
let count = 0; let count = 0;
let resizeCallback;
beforeEach(() => { beforeEach(() => {
class mockResizeObserver {
constructor(cb) {
resizeCallback = cb;
}
observe() { }
disconnect() { }
}
mockComposition = new EventEmitter(); mockComposition = new EventEmitter();
mockComposition.load = () => { mockComposition.load = () => {
if (count === 0) { if (count === 0) {
@ -165,6 +182,9 @@ describe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition); spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
spyOnBuiltins(['ResizeObserver']);
window.ResizeObserver.and.callFake(mockResizeObserver);
const applicableViews = openmct.objectViews.get(testViewObject, []); const applicableViews = openmct.objectViews.get(testViewObject, []);
tabsLayoutViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'tabs'); tabsLayoutViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'tabs');
let view = tabsLayoutViewProvider.view(testViewObject, []); let view = tabsLayoutViewProvider.view(testViewObject, []);
@ -175,6 +195,7 @@ describe('the plugin', function () {
afterEach(() => { afterEach(() => {
count = 0; count = 0;
testViewObject.keep_alive = true;
}); });
it ('renders a tab for each item', () => { it ('renders a tab for each item', () => {
@ -185,10 +206,22 @@ describe('the plugin', function () {
describe('with domainObject.keep_alive set to', () => { describe('with domainObject.keep_alive set to', () => {
it ('true, will keep all views loaded, regardless of current tab view', () => { it ('true, will keep all views loaded, regardless of current tab view', (done) => {
resizeCallback();
// the function called by the resize observer is debounced 500ms,
// this is to account for that
let promise = new Promise((resolve, reject) => {
setTimeout(resolve, 501);
});
Promise.all([Vue.nextTick(), promise]).then(() => {
let tabViewEls = element.querySelectorAll('.c-tabs-view__object'); let tabViewEls = element.querySelectorAll('.c-tabs-view__object');
expect(tabViewEls.length).toEqual(2); expect(tabViewEls.length).toEqual(2);
}).finally(() => {
done();
});
}); });
it ('false, will only keep the current tab view loaded', async () => { it ('false, will only keep the current tab view loaded', async () => {

View File

@ -113,7 +113,6 @@ import search from '../components/search.vue';
const ITEM_BUFFER = 25; const ITEM_BUFFER = 25;
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded'; const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
const RETURN_ALL_DESCENDANTS = true;
const SORT_MY_ITEMS_ALPH_ASC = true; const SORT_MY_ITEMS_ALPH_ASC = true;
const TREE_ITEM_INDENT_PX = 18; const TREE_ITEM_INDENT_PX = 18;
@ -432,7 +431,7 @@ export default {
return scrollTopAmount >= treeStart && scrollTopAmount < treeEnd; return scrollTopAmount >= treeStart && scrollTopAmount < treeEnd;
}, },
sortNameDescending(a, b) { sortNameAscending(a, b) {
// sorting tree children items // sorting tree children items
if (!(a.name && b.name)) { if (!(a.name && b.name)) {
if (a.object.name.toLowerCase() if (a.object.name.toLowerCase()
@ -459,15 +458,16 @@ export default {
return 0; return 0;
}, },
isSortable(parentObjectPath) {
// determine if any part of the parent's path includes a key value of mine; aka My Items
return Boolean(parentObjectPath.find(path => path.identifier.key === 'mine'));
},
async loadAndBuildTreeItemsFor(domainObject, parentObjectPath, abortSignal) { async loadAndBuildTreeItemsFor(domainObject, parentObjectPath, abortSignal) {
let collection = this.openmct.composition.get(domainObject); let collection = this.openmct.composition.get(domainObject);
let composition = await collection.load(abortSignal); let composition = await collection.load(abortSignal);
// determine if any part of the parent's path includes a key value of mine; aka My Items
const isNestedInMyItems = Boolean(parentObjectPath.find(path => path.identifier.key === 'mine'));
if (SORT_MY_ITEMS_ALPH_ASC && isNestedInMyItems) { if (SORT_MY_ITEMS_ALPH_ASC && this.isSortable(parentObjectPath)) {
const sortedComposition = composition.sort(this.sortNameDescending); const sortedComposition = composition.slice().sort(this.sortNameAscending);
composition = sortedComposition; composition = sortedComposition;
} }
@ -513,17 +513,35 @@ export default {
}, },
compositionAddHandler(navigationPath) { compositionAddHandler(navigationPath) {
return (domainObject) => { return (domainObject) => {
let parentItem = this.getTreeItemByPath(navigationPath); const parentItem = this.getTreeItemByPath(navigationPath);
let newItem = this.buildTreeItem(domainObject, parentItem.objectPath, true); const newItem = this.buildTreeItem(domainObject, parentItem.objectPath, true);
let allDescendants = this.getChildrenInTreeFor(parentItem, RETURN_ALL_DESCENDANTS); const descendants = this.getChildrenInTreeFor(parentItem, true);
let afterItem = allDescendants.length ? allDescendants.pop() : parentItem; const directDescendants = this.getChildrenInTreeFor(parentItem);
this.addItemToTreeAfter(newItem, afterItem); if (directDescendants.length === 0) {
const isNestedInMyItems = Boolean(parentItem.objectPath && parentItem.objectPath.find(path => path.identifier.key === 'mine')); this.addItemToTreeAfter(newItem, parentItem);
if (SORT_MY_ITEMS_ALPH_ASC && isNestedInMyItems) { return;
this.sortTreeComposition(this.sortNameDescending, navigationPath);
} }
if (SORT_MY_ITEMS_ALPH_ASC && this.isSortable(parentItem.objectPath)) {
const newItemIndex = directDescendants
.findIndex(descendant => this.sortNameAscending(descendant, newItem) > 0);
const shouldInsertFirst = newItemIndex === 0;
const shouldInsertLast = newItemIndex === -1;
if (shouldInsertFirst) {
this.addItemToTreeAfter(newItem, parentItem);
} else if (shouldInsertLast) {
this.addItemToTreeAfter(newItem, descendants.pop());
} else {
this.addItemToTreeBefore(newItem, directDescendants[newItemIndex]);
}
return;
}
this.addItemToTreeAfter(newItem, descendants.pop());
}; };
}, },
compositionRemoveHandler(navigationPath) { compositionRemoveHandler(navigationPath) {
@ -555,17 +573,18 @@ export default {
const removeIndex = this.getTreeItemIndex(item.navigationPath); const removeIndex = this.getTreeItemIndex(item.navigationPath);
this.treeItems.splice(removeIndex, 1); this.treeItems.splice(removeIndex, 1);
}, },
sortTreeComposition(algorithem, parentPath) { addItemToTreeBefore(addItem, beforeItem) {
const parentIndex = this.getTreeItemIndex(parentPath); const addIndex = this.getTreeItemIndex(beforeItem.navigationPath);
const parentItem = this.treeItems[parentIndex];
const allDescendants = this.getChildrenInTreeFor(parentItem); this.addItemToTree(addItem, addIndex);
const sortedChildren = allDescendants.sort(algorithem);
this.treeItems.splice(parentIndex + 1, allDescendants.length, ...sortedChildren);
}, },
addItemToTreeAfter(addItem, afterItem) { addItemToTreeAfter(addItem, afterItem) {
const addIndex = this.getTreeItemIndex(afterItem.navigationPath); const addIndex = this.getTreeItemIndex(afterItem.navigationPath);
this.treeItems.splice(addIndex + 1, 0, addItem);
this.addItemToTree(addItem, addIndex + 1);
},
addItemToTree(addItem, index) {
this.treeItems.splice(index, 0, addItem);
if (this.isTreeItemOpen(addItem)) { if (this.isTreeItemOpen(addItem)) {
this.openTreeItem(addItem); this.openTreeItem(addItem);