diff --git a/src/plugins/imagery/ImageryViewProvider.js b/src/plugins/imagery/ImageryViewProvider.js index 1419990027..2fbc57f365 100644 --- a/src/plugins/imagery/ImageryViewProvider.js +++ b/src/plugins/imagery/ImageryViewProvider.js @@ -62,6 +62,9 @@ export default function ImageryViewProvider(openmct) { destroy: function () { component.$destroy(); component = undefined; + }, + _getInstance: function () { + return component; } }; } diff --git a/src/plugins/imagery/components/ImageryViewLayout.vue b/src/plugins/imagery/components/ImageryViewLayout.vue index e480824c14..b39f3dbd2a 100644 --- a/src/plugins/imagery/components/ImageryViewLayout.vue +++ b/src/plugins/imagery/components/ImageryViewLayout.vue @@ -124,27 +124,40 @@
-
- - - -
{{ image.formattedTime }}
+ + +
{{ image.formattedTime }}
+
+ + @@ -171,6 +184,8 @@ const TWENTYFOUR_HOURS = EIGHT_HOURS * 3; const ARROW_RIGHT = 39; const ARROW_LEFT = 37; +const SCROLL_LATENCY = 250; + export default { components: { Compass @@ -204,7 +219,8 @@ export default { focusedImageNaturalAspectRatio: undefined, imageContainerWidth: undefined, imageContainerHeight: undefined, - lockCompass: true + lockCompass: true, + resizingWindow: false }; }, computed: { @@ -380,9 +396,13 @@ export default { this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer); this.imageContainerResizeObserver.observe(this.$refs.focusedImage); - }, - updated() { - this.scrollToRight(); + + // For adjusting scroll bar size and position when resizing thumbs wrapper + this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY); + this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY); + + this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart); + this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper); }, beforeDestroy() { if (this.unsubscribe) { @@ -394,6 +414,10 @@ export default { this.imageContainerResizeObserver.disconnect(); } + if (this.thumbWrapperResizeObserver) { + this.thumbWrapperResizeObserver.disconnect(); + } + if (this.relatedTelemetry.hasRelatedTelemetry) { this.relatedTelemetry.destroy(); } @@ -561,17 +585,15 @@ export default { }, handleScroll() { const thumbsWrapper = this.$refs.thumbsWrapper; - if (!thumbsWrapper) { + if (!thumbsWrapper || this.resizingWindow) { return; } - const { scrollLeft, scrollWidth, clientWidth, scrollTop, scrollHeight, clientHeight } = thumbsWrapper; - const disableScroll = (scrollWidth - scrollLeft) > 2 * clientWidth - || (scrollHeight - scrollTop) > 2 * clientHeight; + const { scrollLeft, scrollWidth, clientWidth } = thumbsWrapper; + const disableScroll = scrollWidth > Math.ceil(scrollLeft + clientWidth); this.autoScroll = !disableScroll; }, paused(state, type) { - this.isPaused = state; if (type === 'button') { @@ -584,6 +606,7 @@ export default { } this.autoScroll = true; + this.scrollToRight(); }, scrollToFocused() { const thumbsWrapper = this.$refs.thumbsWrapper; @@ -600,8 +623,8 @@ export default { }); } }, - scrollToRight() { - if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) { + scrollToRight(type) { + if (type !== 'reset' && (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll)) { return; } @@ -610,7 +633,9 @@ export default { return; } - setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0); + this.$nextTick(() => { + this.$refs.thumbsWrapper.scrollLeft = scrollWidth; + }); }, setFocusedImage(index, thumbnailClick = false) { if (this.isPaused && !thumbnailClick) { @@ -678,9 +703,9 @@ export default { image.imageDownloadName = this.getImageDownloadName(datum); this.imageHistory.push(image); - if (setFocused) { this.setFocusedImage(this.imageHistory.length - 1); + this.scrollToRight(); } }, getFormatter(key) { @@ -816,6 +841,24 @@ export default { this.imageContainerHeight = this.$refs.focusedImage.clientHeight; } }, + handleThumbWindowResizeStart() { + if (!this.autoScroll) { + return; + } + + // To hide resume button while scrolling + this.resizingWindow = true; + this.handleThumbWindowResizeEnded(); + }, + handleThumbWindowResizeEnded() { + if (!this.isPaused) { + this.scrollToRight('reset'); + } + + this.$nextTick(() => { + this.resizingWindow = false; + }); + }, toggleLockCompass() { this.lockCompass = !this.lockCompass; } diff --git a/src/plugins/imagery/components/imagery-view-layout.scss b/src/plugins/imagery/components/imagery-view-layout.scss index 8f6fec9dc1..7e45f2bd6d 100644 --- a/src/plugins/imagery/components/imagery-view-layout.scss +++ b/src/plugins/imagery/components/imagery-view-layout.scss @@ -93,24 +93,43 @@ } &__thumbs-wrapper { - flex: 0 0 auto; + display: flex; // Uses row layout + + &.is-autoscroll-off { + background: $colorInteriorBorder; + [class*='__auto-scroll-resume-button'] { + display: block; + } + } + + &.is-paused { + background: rgba($colorPausedBg, 0.4); + } + } + + &__thumbs-scroll-area { + flex: 0 1 auto; display: flex; flex-direction: row; height: 135px; overflow-x: auto; overflow-y: hidden; + margin-bottom: 1px; padding-bottom: $interiorMarginSm; - &.is-paused { - background: rgba($colorPausedBg, 0.4); - } - .c-thumb:last-child { // Hilite the lastest thumb background: $colorBodyFg; color: $colorBodyBg; } } + + &__auto-scroll-resume-button { + display: none; // Set to block when __thumbs-wrapper has .is-autoscroll-off + flex: 0 0 auto; + font-size: 0.8em; + margin: $interiorMarginSm; + } } /*************************************** THUMBS */ @@ -142,7 +161,7 @@ .l-layout, .c-fl { - .c-imagery__thumbs-wrapper { + .c-imagery__thumbs-scroll-area { // When Imagery is in a layout, hide the thumbs area display: none; } diff --git a/src/plugins/imagery/pluginSpec.js b/src/plugins/imagery/pluginSpec.js index 9179692d81..9a73c9ebdb 100644 --- a/src/plugins/imagery/pluginSpec.js +++ b/src/plugins/imagery/pluginSpec.js @@ -92,6 +92,7 @@ describe("The Imagery View Layout", () => { let resolveFunction; let openmct; + let appHolder; let parent; let child; let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT); @@ -195,7 +196,7 @@ describe("The Imagery View Layout", () => { // this setups up the app beforeEach((done) => { - const appHolder = document.createElement('div'); + appHolder = document.createElement('div'); appHolder.style.width = '640px'; appHolder.style.height = '480px'; @@ -209,6 +210,8 @@ describe("The Imagery View Layout", () => { child = document.createElement('div'); parent.appendChild(child); + // document.querySelector('body').append(parent); + spyOn(window, 'ResizeObserver').and.returnValue({ observe() {}, disconnect() {} @@ -362,5 +365,21 @@ describe("The Imagery View Layout", () => { done(); }); }); + it ('shows an auto scroll button when scroll to left', async () => { + // to mock what a scroll would do + imageryView._getInstance().$refs.ImageryLayout.autoScroll = false; + await Vue.nextTick(); + let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button'); + expect(autoScrollButton).toBeTruthy(); + }); + it ('scrollToRight is called when clicking on auto scroll button', async () => { + // use spyon to spy the scroll function + spyOn(imageryView._getInstance().$refs.ImageryLayout, 'scrollToRight'); + imageryView._getInstance().$refs.ImageryLayout.autoScroll = false; + await Vue.nextTick(); + parent.querySelector('.c-imagery__auto-scroll-resume-button').click(); + expect(imageryView._getInstance().$refs.ImageryLayout.scrollToRight).toHaveBeenCalledWith('reset'); + + }); }); });