From bba29b083fb65b185fc34e0dfb0df448e5c161b2 Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Mon, 13 Dec 2021 11:19:54 -0800 Subject: [PATCH] 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 --- package.json | 2 +- src/plugins/LADTable/components/LADRow.vue | 113 +++++++++++------- src/plugins/LADTable/pluginSpec.js | 6 + .../components/TelemetryView.vue | 19 ++- .../imagery/components/ImageryView.vue | 38 +++++- src/plugins/tabs/components/tabs.scss | 2 - src/plugins/tabs/components/tabs.vue | 55 ++++++++- src/plugins/tabs/pluginSpec.js | 43 ++++++- src/ui/layout/mct-tree.vue | 63 ++++++---- 9 files changed, 253 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index c4e44a1462..c9d16b3ebe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "1.8.1-SNAPSHOT", + "version": "1.8.1", "description": "The Open MCT core platform", "devDependencies": { "@braintree/sanitize-url": "^5.0.2", diff --git a/src/plugins/LADTable/components/LADRow.vue b/src/plugins/LADTable/components/LADRow.vue index c26a87e68c..1621fa86fd 100644 --- a/src/plugins/LADTable/components/LADRow.vue +++ b/src/plugins/LADTable/components/LADRow.vue @@ -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'); } } }; diff --git a/src/plugins/LADTable/pluginSpec.js b/src/plugins/LADTable/pluginSpec.js index 329eeb5452..1ca631cf24 100644 --- a/src/plugins/LADTable/pluginSpec.js +++ b/src/plugins/LADTable/pluginSpec.js @@ -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); diff --git a/src/plugins/displayLayout/components/TelemetryView.vue b/src/plugins/displayLayout/components/TelemetryView.vue index 2b99633961..3e2626a060 100644 --- a/src/plugins/displayLayout/components/TelemetryView.vue +++ b/src/plugins/displayLayout/components/TelemetryView.vue @@ -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); } }, diff --git a/src/plugins/imagery/components/ImageryView.vue b/src/plugins/imagery/components/ImageryView.vue index fac14b71d1..9742f7e424 100644 --- a/src/plugins/imagery/components/ImageryView.vue +++ b/src/plugins/imagery/components/ImageryView.vue @@ -414,7 +414,7 @@ export default { if (this.indexForFocusedImage !== undefined) { imageIndex = this.initFocusedImageIndex; } else { - imageIndex = newSize - 1; + imageIndex = newSize > 0 ? newSize - 1 : undefined; } this.setFocusedImage(imageIndex, false); @@ -510,6 +510,12 @@ export default { 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() { const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView); const visibleActions = actionCollection.getVisibleActions(); @@ -670,23 +676,47 @@ 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) { + // 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) { //We use the props till the user changes what they want to see this.initFocusedImageIndex = undefined; } 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; + this.focusedImageIndex = focusedIndex; if (thumbnailClick && !this.isPaused) { this.paused(true); diff --git a/src/plugins/tabs/components/tabs.scss b/src/plugins/tabs/components/tabs.scss index ed49817583..bb5df6144b 100644 --- a/src/plugins/tabs/components/tabs.scss +++ b/src/plugins/tabs/components/tabs.scss @@ -31,8 +31,6 @@ flex-direction: column; &--hidden { - height: 1000px; - width: 1000px; position: absolute; left: -9999px; top: -9999px; diff --git a/src/plugins/tabs/components/tabs.vue b/src/plugins/tabs/components/tabs.vue index aa0d372fbf..bc1372b20b 100644 --- a/src/plugins/tabs/components/tabs.vue +++ b/src/plugins/tabs/components/tabs.vue @@ -1,6 +1,10 @@