Compare commits

...

9 Commits

Author SHA1 Message Date
370fdc6466 Preserve the previousFocusedImage for subscription updates, bound change 2021-12-14 16:30:43 -06:00
44cbd3fc50 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

Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov>
2021-12-07 14:54:31 -08:00
858de567ae Fixes LAD rows for string telemetry (#4508)
* Fixes LAD rows for string telemetry
* saving the object if it was missing (#4471)
2021-11-29 09:29:47 -08:00
ad22661029 [LAD Tables] Use RAF for updating template (#4500) 2021-11-24 15:23:47 -08:00
26aa49d9a8 [Tabs] Sizing of offscreen tabs causing issues (#4444)
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-11-24 11:22:54 -08:00
cb55500cba adding RAF to display layout alphanumerics (#4486) 2021-11-24 10:38:47 -08:00
340246ab26 Changed descending to ascending in sort order method (#4480) 2021-11-18 13:18:37 -08:00
9cb743fb48 Bugfix/create tree node (#4472)
* Transaction fix (#4421)

* When transaction is active, objects.get should search in dirty object first.

Co-authored-by: Andrew Henry <akhenry@gmail.com>

* 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"

This reverts commit 93bee13915.

* add item to correct location

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-11-18 12:30:42 -08:00
9c52ec7233 Transaction fix (#4421) (#4461)
* When transaction is active, objects.get should search in dirty object first.
2021-11-15 14:27:23 -08:00
15 changed files with 311 additions and 94 deletions

View File

@ -82,6 +82,7 @@
);
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.Generator());

View File

@ -184,6 +184,15 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
}
identifier = utils.parseKeyString(identifier);
let dirtyObject;
if (this.isTransactionActive()) {
dirtyObject = this.transaction.getDirtyObject(keystring);
}
if (dirtyObject) {
return Promise.resolve(dirtyObject);
}
const provider = this.getProvider(identifier);
if (!provider) {

View File

@ -55,6 +55,17 @@ export default class Transaction {
});
}
getDirtyObject(keystring) {
let dirtyObject;
this.dirtyObjects.forEach(object => {
if (this.objectAPI.makeKeyString(object.identifier) === keystring) {
dirtyObject = object;
}
});
return dirtyObject;
}
start() {
this.dirtyObjects = new Set();
}

View File

@ -48,6 +48,7 @@ const CONTEXT_MENU_ACTIONS = [
'viewHistoricalData',
'remove'
];
const BLANK_VALUE = '---';
export default {
inject: ['openmct', 'currentView'],
@ -67,15 +68,43 @@ export default {
},
data() {
return {
datum: undefined,
timestamp: undefined,
value: '---',
valueClass: '',
timestampKey: undefined,
unit: ''
};
},
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() {
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() {
return [this.domainObject, ...this.pathToTable];
@ -96,15 +125,19 @@ export default {
this.timestampKey = this.openmct.time.timeSystem().key;
this.valueMetadata = this.metadata ? this
.metadata
.valuesForHints(['range'])[0] : undefined;
this.valueMetadata = undefined;
if (this.metadata) {
this.valueMetadata = this
.metadata
.valuesForHints(['range'])[0] || this.firstNonDomainAttribute(this.metadata);
}
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
this.unsubscribe = this.openmct
.telemetry
.subscribe(this.domainObject, this.updateValues);
.subscribe(this.domainObject, this.setLatestValues);
this.requestHistory();
@ -118,29 +151,29 @@ export default {
this.openmct.time.off('bounds', this.updateBounds);
},
methods: {
updateValues(datum) {
let newTimestamp = this.getParsedTimestamp(datum);
let limit;
updateView() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(() => {
let newTimestamp = this.getParsedTimestamp(this.latestDatum);
if (this.shouldUpdate(newTimestamp)) {
this.datum = datum;
this.timestamp = newTimestamp;
this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
if (this.shouldUpdate(newTimestamp)) {
this.timestamp = newTimestamp;
this.datum = this.latestDatum;
}
this.updatingView = false;
});
}
},
shouldUpdate(newTimestamp) {
let newTimestampInBounds = this.inBounds(newTimestamp);
let noExistingTimestamp = this.timestamp === undefined;
let newTimestampIsLatest = newTimestamp > this.timestamp;
setLatestValues(datum) {
this.latestDatum = datum;
return newTimestampInBounds
&& (noExistingTimestamp || newTimestampIsLatest);
this.updateView();
},
shouldUpdate(newTimestamp) {
return this.inBounds(newTimestamp)
&& (this.timestamp === undefined || newTimestamp > this.timestamp);
},
requestHistory() {
this.openmct
@ -151,7 +184,7 @@ export default {
size: 1,
strategy: 'latest'
})
.then((array) => this.updateValues(array[array.length - 1]))
.then((array) => this.setLatestValues(array[array.length - 1]))
.catch((error) => {
console.warn('Error fetching data', error);
});
@ -189,31 +222,21 @@ export default {
}
},
resetValues() {
this.value = '---';
this.timestamp = undefined;
this.valueClass = '';
this.datum = undefined;
},
getParsedTimestamp(timestamp) {
if (this.timeSystemFormat()) {
return this.formats[this.timestampKey].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;
if (this.timeSystemFormat) {
return this.timeSystemFormat.parse(timestamp);
}
},
setUnit() {
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,
getMockTelemetry,
getLatestTelemetry,
spyOnBuiltins,
resetApplicationState
} from 'utils/testing';
@ -160,6 +161,11 @@ describe("The LAD Table", () => {
anotherTelemetryObjectResolve = resolve;
});
spyOnBuiltins(['requestAnimationFrame']);
window.requestAnimationFrame.and.callFake((callBack) => {
callBack();
});
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);

View File

@ -263,7 +263,8 @@ export default {
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
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
&& (this.openmct.time.bounds().end >= datumTimeStamp))
) {
this.updateView(datum);
this.latestDatum = datum;
this.updateView();
}
}.bind(this));
},
updateView(datum) {
this.datum = datum;
updateView() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(() => {
this.datum = this.latestDatum;
this.updatingView = false;
});
}
},
removeSubscription() {
if (this.subscription) {
@ -290,7 +298,8 @@ export default {
},
refreshData(bounds, isTick) {
if (!isTick) {
this.datum = undefined;
this.latestDatum = undefined;
this.updateView();
this.requestHistoricalData(this.domainObject);
}
},

View File

@ -414,9 +414,11 @@ export default {
if (this.indexForFocusedImage !== undefined) {
imageIndex = this.initFocusedImageIndex;
} else {
imageIndex = newSize - 1;
imageIndex = newSize > 0 ? newSize - 1 : undefined;
}
// handle subscription update
this.previousFocusedImage = this.focusedImage ? JSON.parse(JSON.stringify(this.focusedImage)) : undefined;
this.setFocusedImage(imageIndex, false);
this.scrollToRight();
},
@ -670,24 +672,49 @@ export default {
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) {
let focusedIndex = index;
if (!(Number.isInteger(index) && index > -1)) {
return;
}
if (this.previousFocusedImage) {
console.log('previousImageUrl', this.previousFocusedImage.url)
// 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;
console.log('new focused image', focusedIndex, this.imageHistory[focusedIndex].url)
}
if (thumbnailClick) {
//We use the props till the user changes what they want to see
this.initFocusedImageIndex = undefined;
}
this.focusedImageIndex = focusedIndex;
delete this.previousFocusedImage;
if (this.isPaused && !thumbnailClick && this.initFocusedImageIndex === undefined) {
this.nextImageIndex = index;
this.nextImageIndex = focusedIndex;
//this could happen if bounds changes
if (this.focusedImageIndex > this.imageHistory.length - 1) {
this.focusedImageIndex = index;
this.focusedImageIndex = focusedIndex;
}
return;
}
this.focusedImageIndex = index;
if (thumbnailClick && !this.isPaused) {
this.paused(true);
}

View File

@ -122,14 +122,46 @@ export default {
return this.timeFormatter.parse(datum);
},
boundsChange(bounds, isTick) {
if (!isTick) {
this.requestHistory();
if (isTick) {
return;
}
// the focusedImageIndex is calculated twice; once before and after request history arrives
const focusedImage = this.imageHistory[this.focusedImageIndex];
const previousFocusedImage = focusedImage ? JSON.parse(JSON.stringify(focusedImage)) : undefined;
this.updateFocusedImageIndex(previousFocusedImage, this.imageHistory);
return this.requestHistory();
},
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
&& x.utc === previous.utc
));
},
updateFocusedImageIndex(previous, imageHistory) {
if (!previous) {
return;
}
const matchIndex = this.matchIndexOfPreviousImage(
previous,
imageHistory
);
this.focusedImageIndex = matchIndex > -1 ? matchIndex : this.imageHistory.length - 1;
},
async requestHistory() {
let bounds = this.timeContext.bounds();
this.requestCount++;
const requestId = this.requestCount;
// maintain previous focused image value to discern new index
const focusedImage = this.imageHistory[this.focusedImageIndex];
const previousFocusedImage = focusedImage ? JSON.parse(JSON.stringify(focusedImage)) : undefined;
this.imageHistory = [];
let data = await this.openmct.telemetry
@ -143,7 +175,8 @@ export default {
imagery.push(image);
}
});
//this is to optimize anything that reacts to imageHistory length
this.updateFocusedImageIndex(previousFocusedImage, imagery);
this.imageHistory = imagery;
}
},

View File

@ -4,7 +4,7 @@ import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vu
import SnapshotContainer from './snapshot-container';
import monkeyPatchObjectAPIForNotebooks from './monkeyPatchObjectAPIForNotebooks.js';
import { notebookImageMigration } from '../notebook/utils/notebook-migration';
import { notebookImageMigration, IMAGE_MIGRATION_VER } from '../notebook/utils/notebook-migration';
import { NOTEBOOK_TYPE } from './notebook-constants';
import Vue from 'vue';
@ -28,6 +28,7 @@ export default function NotebookPlugin() {
domainObject.configuration = {
defaultSort: 'oldest',
entries: {},
imageMigrationVer: IMAGE_MIGRATION_VER,
pageTitle: 'Page',
sections: [],
sectionTitle: 'Section',

View File

@ -1,7 +1,7 @@
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl, saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from './notebook-image';
import { mutateObject } from './notebook-entries';
const IMAGE_MIGRATION_VER = "v1";
export const IMAGE_MIGRATION_VER = "v1";
export function notebookImageMigration(openmct, domainObject) {
const configuration = domainObject.configuration;

View File

@ -108,7 +108,7 @@ describe('the plugin', () => {
expect(result).toBeTrue();
});
it('updates an object', async () => {
xit('updates an object', async () => {
const result = await openmct.objects.save(mockDomainObject);
expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled();

View File

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

View File

@ -1,6 +1,10 @@
<template>
<div class="c-tabs-view">
<div
ref="tabs"
class="c-tabs-view"
>
<div
ref="tabsHolder"
class="c-tabs-view__tabs-holder c-tabs"
:class="{
'is-dragging': isDragging && allowEditing,
@ -28,8 +32,10 @@
}"
@click="showTab(tab, index)"
>
<div class="c-tabs-view__tab__label c-object-label"
:class="[tab.status ? `is-status--${tab.status}` : '']"
<div
ref="tabsLabel"
class="c-tabs-view__tab__label c-object-label"
:class="[tab.status ? `is-status--${tab.status}` : '']"
>
<div class="c-object-label__type-icon"
:class="tab.type.definition.cssClass"
@ -49,11 +55,12 @@
<div
v-for="tab in tabsList"
:key="tab.keyString"
:style="getTabStyles(tab)"
class="c-tabs-view__object-holder"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"
>
<object-view
v-if="isTabLoaded(tab)"
v-if="shouldLoadTab(tab)"
class="c-tabs-view__object"
:default-object="tab.domainObject"
:object-path="tab.objectPath"
@ -65,6 +72,7 @@
<script>
import ObjectView from '../../../ui/components/ObjectView.vue';
import RemoveAction from '../../remove/RemoveAction.js';
import _ from 'lodash';
const unknownObjectType = {
definition: {
@ -88,6 +96,8 @@ export default {
let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return {
tabWidth: undefined,
tabHeight: undefined,
internalDomainObject: this.domainObject,
currentTab: {},
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.openmct.router.on('change:params', this.updateCurrentTab.bind(this));
@ -138,6 +152,8 @@ export default {
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder);
this.tabsViewResizeObserver.disconnect();
this.tabsList.forEach(tab => {
tab.statusUnsubscribe();
});
@ -158,12 +174,28 @@ export default {
this.loadedTabs[tab.keyString] = true;
},
getTabStyles(tab) {
let styles = {};
if (!this.isCurrent(tab)) {
styles = {
height: this.tabHeight,
width: this.tabWidth
};
}
return styles;
},
setCurrentTabByIndex(index) {
if (this.tabsList[index]) {
this.showTab(this.tabsList[index]);
}
},
showTab(tab, index) {
if (!tab) {
return;
}
if (index !== undefined) {
this.storeCurrentTabIndexInURL(index);
}
@ -171,6 +203,13 @@ export default {
this.currentTab = 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) {
if (!this.tabsList[index]) {
return;
@ -325,6 +364,14 @@ export default {
this.currentTabIndex = 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.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing';
import {
createOpenMct,
resetApplicationState,
spyOnBuiltins
} from 'utils/testing';
import TabsLayout from './plugin';
import Vue from "vue";
import EventEmitter from "EventEmitter";
@ -63,13 +67,13 @@ describe('the plugin', function () {
'phase': 5,
'randomness': 0
},
'name': 'Sine Wave Generator',
'type': 'generator',
'modified': 1592851063871,
'location': 'mine',
'persisted': 1592851063871
};
let telemetryItem1 = Object.assign({}, telemetryItemTemplate, {
'name': 'Sine Wave Generator 1',
'id': '55122607-e65e-44d5-9c9d-9c31a914ca89',
'identifier': {
'namespace': '',
@ -77,6 +81,7 @@ describe('the plugin', function () {
}
});
let telemetryItem2 = Object.assign({}, telemetryItemTemplate, {
'name': 'Sine Wave Generator 2',
'id': '55122607-e65e-44d5-9c9d-9c31a914ca90',
'identifier': {
'namespace': '',
@ -91,6 +96,9 @@ describe('the plugin', function () {
element = document.createElement('div');
child = document.createElement('div');
child.style.display = 'block';
child.style.width = '1920px';
child.style.height = '1080px';
element.appendChild(child);
openmct.on('start', done);
@ -150,8 +158,17 @@ describe('the plugin', function () {
let tabsLayoutViewProvider;
let mockComposition;
let count = 0;
let resizeCallback;
beforeEach(() => {
class mockResizeObserver {
constructor(cb) {
resizeCallback = cb;
}
observe() { }
disconnect() { }
}
mockComposition = new EventEmitter();
mockComposition.load = () => {
if (count === 0) {
@ -165,6 +182,9 @@ describe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
spyOnBuiltins(['ResizeObserver']);
window.ResizeObserver.and.callFake(mockResizeObserver);
const applicableViews = openmct.objectViews.get(testViewObject, []);
tabsLayoutViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'tabs');
let view = tabsLayoutViewProvider.view(testViewObject, []);
@ -175,6 +195,7 @@ describe('the plugin', function () {
afterEach(() => {
count = 0;
testViewObject.keep_alive = true;
});
it ('renders a tab for each item', () => {
@ -185,10 +206,22 @@ describe('the plugin', function () {
describe('with domainObject.keep_alive set to', () => {
it ('true, will keep all views loaded, regardless of current tab view', () => {
let tabViewEls = element.querySelectorAll('.c-tabs-view__object');
it ('true, will keep all views loaded, regardless of current tab view', (done) => {
resizeCallback();
expect(tabViewEls.length).toEqual(2);
// 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');
expect(tabViewEls.length).toEqual(2);
}).finally(() => {
done();
});
});
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 LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
const RETURN_ALL_DESCENDANTS = true;
const SORT_MY_ITEMS_ALPH_ASC = true;
const TREE_ITEM_INDENT_PX = 18;
@ -432,7 +431,7 @@ export default {
return scrollTopAmount >= treeStart && scrollTopAmount < treeEnd;
},
sortNameDescending(a, b) {
sortNameAscending(a, b) {
// sorting tree children items
if (!(a.name && b.name)) {
if (a.object.name > b.object.name) {
@ -455,15 +454,16 @@ export default {
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) {
let collection = this.openmct.composition.get(domainObject);
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) {
const sortedComposition = composition.sort(this.sortNameDescending);
if (SORT_MY_ITEMS_ALPH_ASC && this.isSortable(parentObjectPath)) {
const sortedComposition = composition.slice().sort(this.sortNameAscending);
composition = sortedComposition;
}
@ -509,17 +509,35 @@ export default {
},
compositionAddHandler(navigationPath) {
return (domainObject) => {
let parentItem = this.getTreeItemByPath(navigationPath);
let newItem = this.buildTreeItem(domainObject, parentItem.objectPath, true);
let allDescendants = this.getChildrenInTreeFor(parentItem, RETURN_ALL_DESCENDANTS);
let afterItem = allDescendants.length ? allDescendants.pop() : parentItem;
const parentItem = this.getTreeItemByPath(navigationPath);
const newItem = this.buildTreeItem(domainObject, parentItem.objectPath, true);
const descendants = this.getChildrenInTreeFor(parentItem, true);
const directDescendants = this.getChildrenInTreeFor(parentItem);
this.addItemToTreeAfter(newItem, afterItem);
const isNestedInMyItems = Boolean(parentItem.objectPath && parentItem.objectPath.find(path => path.identifier.key === 'mine'));
if (directDescendants.length === 0) {
this.addItemToTreeAfter(newItem, parentItem);
if (SORT_MY_ITEMS_ALPH_ASC && isNestedInMyItems) {
this.sortTreeComposition(this.sortNameDescending, navigationPath);
return;
}
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) {
@ -551,17 +569,18 @@ export default {
const removeIndex = this.getTreeItemIndex(item.navigationPath);
this.treeItems.splice(removeIndex, 1);
},
sortTreeComposition(algorithem, parentPath) {
const parentIndex = this.getTreeItemIndex(parentPath);
const parentItem = this.treeItems[parentIndex];
addItemToTreeBefore(addItem, beforeItem) {
const addIndex = this.getTreeItemIndex(beforeItem.navigationPath);
const allDescendants = this.getChildrenInTreeFor(parentItem);
const sortedChildren = allDescendants.sort(algorithem);
this.treeItems.splice(parentIndex + 1, allDescendants.length, ...sortedChildren);
this.addItemToTree(addItem, addIndex);
},
addItemToTreeAfter(addItem, afterItem) {
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)) {
this.openTreeItem(addItem);