mirror of
https://github.com/nasa/openmct.git
synced 2024-12-21 22:17:49 +00:00
feat(imagery): show viewable area when zoomed (#5877)
* feat: viewable area * chore: add test * fix: get image ref when real-time Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
parent
a79646a915
commit
1ddf5e5137
@ -31,21 +31,32 @@
|
||||
:title="image.formattedTime"
|
||||
>
|
||||
<a
|
||||
class="c-thumb__image-wrapper"
|
||||
href=""
|
||||
:download="image.imageDownloadName"
|
||||
@click.prevent
|
||||
>
|
||||
<img
|
||||
ref="img"
|
||||
class="c-thumb__image"
|
||||
:src="image.url"
|
||||
fetchpriority="low"
|
||||
@load="imageLoadCompleted"
|
||||
>
|
||||
</a>
|
||||
<div
|
||||
v-if="viewableArea"
|
||||
class="c-thumb__viewable-area"
|
||||
:style="viewableAreaStyle"
|
||||
></div>
|
||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const THUMB_PADDING = 4;
|
||||
const BORDER_WIDTH = 2;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
image: {
|
||||
@ -63,6 +74,77 @@ export default {
|
||||
realTime: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
viewableArea: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgWidth: 0,
|
||||
imgHeight: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
viewableAreaStyle() {
|
||||
if (!this.viewableArea || !this.imgWidth || !this.imgHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { widthRatio, heightRatio, xOffsetRatio, yOffsetRatio } = this.viewableArea;
|
||||
const imgWidth = this.imgWidth;
|
||||
const imgHeight = this.imgHeight;
|
||||
|
||||
let translateX = imgWidth * xOffsetRatio;
|
||||
let translateY = imgHeight * yOffsetRatio;
|
||||
let width = imgWidth * widthRatio;
|
||||
let height = imgHeight * heightRatio;
|
||||
|
||||
if (translateX < 0) {
|
||||
width += translateX;
|
||||
translateX = 0;
|
||||
}
|
||||
|
||||
if (translateX + width > imgWidth) {
|
||||
width = imgWidth - translateX;
|
||||
}
|
||||
|
||||
if (translateX + 2 * BORDER_WIDTH > imgWidth) {
|
||||
translateX = imgWidth - 2 * BORDER_WIDTH;
|
||||
}
|
||||
|
||||
if (translateY < 0) {
|
||||
height += translateY;
|
||||
translateY = 0;
|
||||
}
|
||||
|
||||
if (translateY + height > imgHeight) {
|
||||
height = imgHeight - translateY;
|
||||
}
|
||||
|
||||
if (translateY + 2 * BORDER_WIDTH > imgHeight) {
|
||||
translateY = imgHeight - 2 * BORDER_WIDTH;
|
||||
}
|
||||
|
||||
return {
|
||||
'transform': `translate(${translateX + THUMB_PADDING}px, ${translateY + THUMB_PADDING}px)`,
|
||||
'width': `${width}px`,
|
||||
'height': `${height}px`
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
imageLoadCompleted() {
|
||||
if (!this.$refs.img) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width: imgWidth, height: imgHeight } = this.$refs.img;
|
||||
this.imgWidth = imgWidth;
|
||||
this.imgHeight = imgHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -174,6 +174,7 @@
|
||||
:active="focusedImageIndex === index"
|
||||
:selected="focusedImageIndex === index && isPaused"
|
||||
:real-time="!isFixed"
|
||||
:viewable-area="focusedImageIndex === index ? viewableArea : null"
|
||||
@click.native="thumbnailClicked(index)"
|
||||
/>
|
||||
</div>
|
||||
@ -219,6 +220,8 @@ const ZOOM_SCALE_DEFAULT = 1;
|
||||
const SHOW_THUMBS_THRESHOLD_HEIGHT = 200;
|
||||
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
|
||||
|
||||
const IMAGE_CONTAINER_BORDER_WIDTH = 1;
|
||||
|
||||
export default {
|
||||
name: 'ImageryView',
|
||||
components: {
|
||||
@ -281,6 +284,8 @@ export default {
|
||||
},
|
||||
imageTranslateX: 0,
|
||||
imageTranslateY: 0,
|
||||
imageViewportWidth: 0,
|
||||
imageViewportHeight: 0,
|
||||
pan: undefined,
|
||||
animateZoom: true,
|
||||
imagePanned: false,
|
||||
@ -516,6 +521,23 @@ export default {
|
||||
}
|
||||
|
||||
return 'Alt drag to pan';
|
||||
},
|
||||
viewableArea() {
|
||||
if (this.zoomFactor === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const imageWidth = this.sizedImageWidth * this.zoomFactor;
|
||||
const imageHeight = this.sizedImageHeight * this.zoomFactor;
|
||||
const xOffset = (imageWidth - this.imageViewportWidth) / 2;
|
||||
const yOffset = (imageHeight - this.imageViewportHeight) / 2;
|
||||
|
||||
return {
|
||||
widthRatio: this.imageViewportWidth / imageWidth,
|
||||
heightRatio: this.imageViewportHeight / imageHeight,
|
||||
xOffsetRatio: (xOffset - this.imageTranslateX * this.zoomFactor) / imageWidth,
|
||||
yOffsetRatio: (yOffset - this.imageTranslateY * this.zoomFactor) / imageHeight
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -1063,12 +1085,12 @@ export default {
|
||||
}
|
||||
|
||||
this.setSizedImageDimensions();
|
||||
this.setImageViewport();
|
||||
this.calculateViewHeight();
|
||||
this.scrollToFocused();
|
||||
},
|
||||
setSizedImageDimensions() {
|
||||
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
||||
|
||||
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
|
||||
// container is wider than image
|
||||
this.sizedImageWidth = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
|
||||
@ -1079,6 +1101,17 @@ export default {
|
||||
this.sizedImageHeight = this.imageContainerWidth / this.focusedImageNaturalAspectRatio;
|
||||
}
|
||||
},
|
||||
setImageViewport() {
|
||||
if (this.imageContainerHeight > this.sizedImageHeight + IMAGE_CONTAINER_BORDER_WIDTH) {
|
||||
// container is taller than wrapper
|
||||
this.imageViewportWidth = this.sizedImageWidth;
|
||||
this.imageViewportHeight = this.sizedImageHeight;
|
||||
} else {
|
||||
// container is wider than wrapper
|
||||
this.imageViewportWidth = this.imageContainerWidth;
|
||||
this.imageViewportHeight = this.imageContainerHeight;
|
||||
}
|
||||
},
|
||||
handleThumbWindowResizeStart() {
|
||||
if (!this.autoScroll) {
|
||||
return;
|
||||
|
@ -285,6 +285,13 @@
|
||||
flex: 0 0 auto;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
&__viewable-area {
|
||||
position: absolute;
|
||||
border: 2px yellow solid;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.is-small-thumbs {
|
||||
|
@ -529,6 +529,19 @@ describe("The Imagery View Layouts", () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should display the viewable area when zoom factor is greater than 1', async () => {
|
||||
await Vue.nextTick();
|
||||
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(0);
|
||||
|
||||
parent.querySelector('.t-btn-zoom-in').click();
|
||||
await Vue.nextTick();
|
||||
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(1);
|
||||
|
||||
parent.querySelector('.t-btn-zoom-reset').click();
|
||||
await Vue.nextTick();
|
||||
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should reset the brightness and contrast when clicking the reset button', async () => {
|
||||
const viewInstance = imageryView._getInstance();
|
||||
await Vue.nextTick();
|
||||
|
Loading…
Reference in New Issue
Block a user