mirror of
https://github.com/nasa/openmct.git
synced 2025-01-03 03:46:42 +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"
|
:title="image.formattedTime"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
class="c-thumb__image-wrapper"
|
||||||
href=""
|
href=""
|
||||||
:download="image.imageDownloadName"
|
:download="image.imageDownloadName"
|
||||||
@click.prevent
|
@click.prevent
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
ref="img"
|
||||||
class="c-thumb__image"
|
class="c-thumb__image"
|
||||||
:src="image.url"
|
:src="image.url"
|
||||||
fetchpriority="low"
|
fetchpriority="low"
|
||||||
|
@load="imageLoadCompleted"
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
|
<div
|
||||||
|
v-if="viewableArea"
|
||||||
|
class="c-thumb__viewable-area"
|
||||||
|
:style="viewableAreaStyle"
|
||||||
|
></div>
|
||||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const THUMB_PADDING = 4;
|
||||||
|
const BORDER_WIDTH = 2;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
image: {
|
image: {
|
||||||
@ -63,6 +74,77 @@ export default {
|
|||||||
realTime: {
|
realTime: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
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"
|
:active="focusedImageIndex === index"
|
||||||
:selected="focusedImageIndex === index && isPaused"
|
:selected="focusedImageIndex === index && isPaused"
|
||||||
:real-time="!isFixed"
|
:real-time="!isFixed"
|
||||||
|
:viewable-area="focusedImageIndex === index ? viewableArea : null"
|
||||||
@click.native="thumbnailClicked(index)"
|
@click.native="thumbnailClicked(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -219,6 +220,8 @@ const ZOOM_SCALE_DEFAULT = 1;
|
|||||||
const SHOW_THUMBS_THRESHOLD_HEIGHT = 200;
|
const SHOW_THUMBS_THRESHOLD_HEIGHT = 200;
|
||||||
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
|
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
|
||||||
|
|
||||||
|
const IMAGE_CONTAINER_BORDER_WIDTH = 1;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ImageryView',
|
name: 'ImageryView',
|
||||||
components: {
|
components: {
|
||||||
@ -281,6 +284,8 @@ export default {
|
|||||||
},
|
},
|
||||||
imageTranslateX: 0,
|
imageTranslateX: 0,
|
||||||
imageTranslateY: 0,
|
imageTranslateY: 0,
|
||||||
|
imageViewportWidth: 0,
|
||||||
|
imageViewportHeight: 0,
|
||||||
pan: undefined,
|
pan: undefined,
|
||||||
animateZoom: true,
|
animateZoom: true,
|
||||||
imagePanned: false,
|
imagePanned: false,
|
||||||
@ -516,6 +521,23 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 'Alt drag to pan';
|
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: {
|
watch: {
|
||||||
@ -1063,12 +1085,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setSizedImageDimensions();
|
this.setSizedImageDimensions();
|
||||||
|
this.setImageViewport();
|
||||||
this.calculateViewHeight();
|
this.calculateViewHeight();
|
||||||
this.scrollToFocused();
|
this.scrollToFocused();
|
||||||
},
|
},
|
||||||
setSizedImageDimensions() {
|
setSizedImageDimensions() {
|
||||||
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
||||||
|
|
||||||
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
|
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
|
||||||
// container is wider than image
|
// container is wider than image
|
||||||
this.sizedImageWidth = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
|
this.sizedImageWidth = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
|
||||||
@ -1079,6 +1101,17 @@ export default {
|
|||||||
this.sizedImageHeight = this.imageContainerWidth / this.focusedImageNaturalAspectRatio;
|
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() {
|
handleThumbWindowResizeStart() {
|
||||||
if (!this.autoScroll) {
|
if (!this.autoScroll) {
|
||||||
return;
|
return;
|
||||||
|
@ -285,6 +285,13 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding: 2px 3px;
|
padding: 2px 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__viewable-area {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px yellow solid;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-small-thumbs {
|
.is-small-thumbs {
|
||||||
|
@ -529,6 +529,19 @@ describe("The Imagery View Layouts", () => {
|
|||||||
done();
|
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 () => {
|
it('should reset the brightness and contrast when clicking the reset button', async () => {
|
||||||
const viewInstance = imageryView._getInstance();
|
const viewInstance = imageryView._getInstance();
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
Loading…
Reference in New Issue
Block a user