<template>
<q-card class="q-pa-none">
<q-card-section v-if="files.length > 0" class="q-pa-xs" style="display: flex; justify-content: center; align-items: center;">
<compass v-model="files[i].compass" :id="image_id"
@update:model-value="compassChanged" />
</q-card-section>
<q-card-section class="q-pa-none img-flex-container">
<q-btn size="xl" v-if="i > 0" class="left" dense flat round icon="arrow_back" @click="backward"
@touchstart="backward" color="primary"/>
<q-btn size="xl" v-if="i < files.length - 1" class="right" dense flat round icon="arrow_forward"
@click="forward" @touchstart="forward" color="primary" />
<div @wheel="handleZoom" @touchstart.prevent="null" class="img-container">
<img ref="img" class="img" :src="src" @mousedown="handleDragStart" @mousemove="handleDragMove"
@mouseup="handleDragEnd" @selectstart="handleSelectStart" @mouseleave="handleDragEnd"
@dragstart="handleDefaultDragStart" @touchstart.prevent="onTouchStart" @touchend.prevent="onTouchEnd"
@touchmove.prevent="onTouchMove" @load="$store.working = false" />
</div>
</q-card-section>
</q-card>
</template>
<script>
import { loadComponent } from '@/common/component-loader';
/**
* Image viewer component
* @component
* @name ImageViewer
* @example
* <ImageViewer />
*/
export default {
name: "ImageViewer",
props: ["parentPopup"],
components: {
Compass: loadComponent('compass'),
},
data() {
return {
files: [],
needsAuthentication: false,
src: "",
i: 0,
img: null,
scale: 1,
prevScale: 1,
prevX: 0,
prevY: 0,
dX: 0,
dY: 0,
totaldX: 0,
totaldY: 0,
prevDist: 0,
isDragging: false,
image_id: null,
cache: false,
compassChangedFunction: null
};
},
async mounted() {
this.initializeComponent(this.parentPopup);
this.parentPopup.title = this.files && this.files.length > 0 ? this.files[this.index].name : $t('Image viewer');
this.parentPopup.buttons = [{
label: this.$t("Download"),
icon: "download",
action: this.downloadImage,
tooltip: this.$t("Download the image")
}];
// async show(files, index, needsAuthentication, cache = false) {
this.src = "";
await this.load();
},
methods: {
/**
* Downloads the image.
*/
async downloadImage() {
this.$store.working = true;
let imageUrl = "";
if (this.needsAuthentication) {
imageUrl = await this.getImage(this.files[this.i].url, this.cache);
} else {
// todo
imageUrl = this.files[this.i].url;
}
const a = document.createElement("a");
a.href = imageUrl;
a.download = this.files[this.i].name || "image.jpg";
a.click();
if (this.needsAuthentication) {
URL.revokeObjectURL(imageUrl);
}
this.$store.working = false;
},
/**
* Moves the image viewer forward to the next image.
*/
forward() {
if (this.i < this.files.length - 1) {
this.i++;
this.load();
}
},
/**
* Moves the image viewer backward to the previous image.
*/
backward() {
if (this.i > 0) {
this.i--;
this.load();
}
},
/**
* Loads the image from the server.
*/
async load() {
this.$store.working = true;
this.scale = 1;
this.dX = 0;
this.dY = 0;
this.prevX = 0;
this.prevY = 0;
this.totaldX = 0;
this.totaldY = 0;
this.isDragging = false;
this.image_id = this.files[this.i].image_id;
this.transform();
if (this.needsAuthentication) {
this.src = await this.getImage(this.files[this.i].url, this.cache);
} else {
// todo
this.src = this.files[this.i].url;
}
this.$store.working = false;
},
/**
* Handles the drag start event for the default drag behavior.
*
* @param {Event} event - The drag start event.
*/
handleDefaultDragStart(event) {
event.preventDefault();
},
/**
* Handles the select start event.
*
* @param {Event} event - The select start event.
*/
handleSelectStart(event) {
event.preventDefault();
return false;
},
reset() {
this.scale = 1;
this.dX = 0;
this.dY = 0;
this.prevX = 0;
this.prevY = 0;
this.totaldX = 0;
this.totaldY = 0;
this.isDragging = false;
this.$refs.img.style.cursor = "default";
this.transform();
},
/**
* Handles the zoom event.
*
* @param {Event} event - The zoom event.
*/
handleZoom(event) {
event.preventDefault();
const delta = event.deltaY ? event.deltaY / 1000 : event.scale - 1;
this.scale += delta;
this.scale = Math.min(Math.max(1, this.scale), 10); // Limit scale between 1 and 4
if (this.scale <= 1) {
this.reset();
} else {
this.$refs.img.style.cursor = "grab";
}
this.transform();
},
shouldChangeImage(dX) {
if (this.scale > 1) return;
if (dX < - 100) {
this.forward();
} else if (dX > 100) {
this.backward();
};
},
/**
* Handles the drag start event.
*
* @param {Event} event - The drag start event.
*/
handleDragStart(event) {
this.prevX = event.clientX;
this.prevY = event.clientY;
//if (this.scale <= 1) return;
this.$refs.img.style.cursor = "grabbing";
this.isDragging = true;
},
/**
* Handles the drag move event.
*
* @param {Event} event - The drag move event.
*/
handleDragMove(event) {
if (!this.isDragging || this.scale <= 1) return;
this.dX = event.clientX - this.prevX;
this.dY = event.clientY - this.prevY;
this.prevX = event.clientX;
this.prevY = event.clientY;
this.totaldX += this.dX;
this.totaldY += this.dY;
this.transform();
},
/**
* Handles the drag end event.
*
* @param {Event} event - The drag end event.
*/
handleDragEnd(event) {
if (this.scale <= 1 && this.isDragging) {
this.shouldChangeImage(event.clientX - this.prevX);
}
this.$refs.img.style.cursor = "default";
this.isDragging = false;
},
/**
* Sets CSS image transformation depending on scale and drag.
*/
transform() {
this.$refs.img.style.transform = `scale(${this.scale}) translate(${this.totaldX}px, ${this.totaldY}px)`;
},
onTouchStart(ev) {
if (ev.touches.length === 1) {
this.prevX = ev.touches[0].clientX
this.prevY = ev.touches[0].clientY
this.isDragging = true;
} else if (ev.touches.length === 2) {
let distX = ev.touches[0].clientX - ev.touches[1].clientX
let distY = ev.touches[0].clientY - ev.touches[1].clientY
this.prevDist = Math.sqrt(distX * distX + distY * distY)
}
},
onTouchMove(ev) {
if (ev.touches.length === 1 && this.isDragging) {
this.dX = ev.touches[0].clientX - this.prevX;
this.dY = ev.touches[0].clientY - this.prevY;
if (this.scale <= 1) return;
this.prevX = ev.touches[0].clientX;
this.prevY = ev.touches[0].clientY;
this.totaldX += this.dX;
this.totaldY += this.dY;
this.transform();
// this.onPointerMove(ev.touches[0].clientX, ev.touches[0].clientY)
} else if (ev.touches.length === 2) {
let distX = ev.touches[0].clientX - ev.touches[1].clientX;
let distY = ev.touches[0].clientY - ev.touches[1].clientY;
let dist = Math.sqrt(distX * distX + distY * distY);
this.scale *= dist / this.prevDist;
this.scale = Math.min(Math.max(1, this.scale), 10);
if (this.scale <= 1) {
this.reset();
}
this.prevDist = dist;
this.transform();
}
},
onTouchEnd(ev) {
this.isDragging = false;
if (this.scale <= 1) {
this.shouldChangeImage(this.dX);
}
},
compassChanged(val) {
this.files[this.i].compass = val;
if (this.compassChangedFunction) this.compassChangedFunction(val, this.files[this.i].image_id);
//this.$emit("compassChanged", this.files[this.i].compass, this.files[this.i].image_id);
}
}
}
</script>
<style scoped>
.left {
position: absolute;
left: 3px;
z-index: 100;
color: black
}
.right {
position: absolute;
right: 3px;
z-index: 100;
color: black
}
.img-flex-container {
height: calc(100vh - 90px); display: flex; justify-content: center; align-items: center;
}
/* .img-container {
position: relative; width: 100%; height: 100%;
} */
.img {
max-width: 100% !important;
max-height: calc(100vh - 90px) !important;
/* width: auto !important;
height: auto !important; */
z-index: 0;
object-fit: contain;
}
</style>