HomeDashboard/.venv/lib/python3.12/site-packages/nicegui/elements/interactive_image.js
2026-01-03 14:54:18 +01:00

149 lines
4.9 KiB
JavaScript

export default {
template: `
<div :style="{ position: 'relative', aspectRatio: size ? size[0] / size[1] : undefined }">
<img
ref="img"
:src="computed_src"
:style="{ width: '100%', height: '100%', opacity: src ? 1 : 0 }"
@load="onImageLoaded"
v-on="onCrossEvents"
v-on="onUserEvents"
draggable="false"
/>
<svg ref="svg" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none" :viewBox="viewBox" preserveAspectRatio="none">
<g :style="{ display: showCross ? 'block' : 'none' }">
<line v-if="cross" :x1="x" y1="0" :x2="x" y2="100%" :stroke="cross === true ? 'black' : cross" />
<line v-if="cross" x1="0" :y1="y" x2="100%" :y2="y" :stroke="cross === true ? 'black' : cross" />
<slot name="cross" :x="x" :y="y"></slot>
</g>
<g v-html="content"></g>
</svg>
<slot></slot>
</div>
`,
data() {
return {
viewBox: "0 0 0 0",
loaded_image_width: 0,
loaded_image_height: 0,
x: 100,
y: 100,
showCross: false,
computed_src: undefined,
waiting_source: undefined,
loading: false,
};
},
mounted() {
setTimeout(() => this.compute_src(), 0); // NOTE: wait for window.path_prefix to be set in app.mounted()
const handle_completion = () => {
if (this.waiting_source) {
this.computed_src = this.waiting_source;
this.waiting_source = undefined;
} else {
this.loading = false;
}
};
this.$refs.img.addEventListener("load", handle_completion);
this.$refs.img.addEventListener("error", handle_completion);
for (const event of [
"pointermove",
"pointerdown",
"pointerup",
"pointerover",
"pointerout",
"pointerenter",
"pointerleave",
"pointercancel",
]) {
this.$refs.svg.addEventListener(event, (e) => this.onPointerEvent(event, e));
}
},
updated() {
this.compute_src();
},
methods: {
compute_src() {
const suffix = this.t ? (this.src.includes("?") ? "&" : "?") + "_nicegui_t=" + this.t : "";
const new_src = (this.src.startsWith("/") ? window.path_prefix : "") + this.src + suffix;
if (new_src == this.computed_src) {
return;
}
if (this.loading) {
this.waiting_source = new_src;
} else {
this.computed_src = new_src;
this.loading = true;
}
if (!this.src && this.size) {
this.updateViewbox(this.size[0], this.size[1]);
}
},
updateCrossHair(e) {
const width = this.src ? this.loaded_image_width : this.size ? this.size[0] : 1;
const height = this.src ? this.loaded_image_height : this.size ? this.size[1] : 1;
this.x = (e.offsetX * width) / e.target.clientWidth;
this.y = (e.offsetY * height) / e.target.clientHeight;
},
onImageLoaded(e) {
this.loaded_image_width = e.target.naturalWidth;
this.loaded_image_height = e.target.naturalHeight;
this.updateViewbox(this.loaded_image_width, this.loaded_image_height);
this.$emit("loaded", { width: this.loaded_image_width, height: this.loaded_image_height, source: e.target.src });
},
onMouseEvent(type, e) {
const imageWidth = this.src ? this.loaded_image_width : this.size ? this.size[0] : 1;
const imageHeight = this.src ? this.loaded_image_height : this.size ? this.size[1] : 1;
this.$emit("mouse", {
mouse_event_type: type,
image_x: (e.offsetX * imageWidth) / this.$refs.img.clientWidth,
image_y: (e.offsetY * imageHeight) / this.$refs.img.clientHeight,
button: e.button,
buttons: e.buttons,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
});
},
onPointerEvent(type, e) {
const imageWidth = this.src ? this.loaded_image_width : this.size ? this.size[0] : 1;
const imageHeight = this.src ? this.loaded_image_height : this.size ? this.size[1] : 1;
this.$emit(`svg:${type}`, {
type: type,
element_id: e.target.id,
image_x: (e.offsetX * imageWidth) / this.$refs.svg.clientWidth,
image_y: (e.offsetY * imageHeight) / this.$refs.svg.clientHeight,
});
},
updateViewbox(width, height) {
this.viewBox = `0 0 ${width} ${height}`;
},
},
computed: {
onCrossEvents() {
if (!this.cross && !this.$slots.cross) return {};
return {
mouseenter: () => (this.showCross = true),
mouseleave: () => (this.showCross = false),
mousemove: (event) => this.updateCrossHair(event),
};
},
onUserEvents() {
const events = {};
for (const type of this.events || []) {
events[type] = (event) => this.onMouseEvent(type, event);
}
return events;
},
},
props: {
src: String,
content: String,
size: Object,
events: Array,
cross: Boolean,
t: String,
},
};