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