Merge pull request #1659 from pierotofy/cluster

Pan support, mobile support for zoom/pan
pull/1662/head
Piero Toffanin 2025-04-22 04:13:26 -04:00 zatwierdzone przez GitHub
commit 7e5e5c988b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
2 zmienionych plików z 110 dodań i 8 usunięć

Wyświetl plik

@ -20,7 +20,8 @@ class ImagePopup extends React.Component {
translateX: 0,
translateY: 0,
scale: 1
scale: 1,
dragging: false
}
}
@ -37,6 +38,13 @@ class ImagePopup extends React.Component {
if (this.image){
this.image.addEventListener("fullscreenchange", this.onFullscreenChange);
this.image.addEventListener("mousewheel", this.onMouseWheel);
this.image.addEventListener("mousedown", this.onMouseDown);
this.image.addEventListener("mousemove", this.onMouseMove);
this.image.addEventListener("mouseup", this.onMouseUp);
this.image.addEventListener("touchstart", this.onTouchStart);
this.image.addEventListener("touchmove", this.onTouchMove);
this.image.addEventListener("touchend", this.onTouchEnd);
}
}
@ -44,6 +52,12 @@ class ImagePopup extends React.Component {
if (this.image){
this.image.removeEventListener("fullscreenchange", this.onFullscreenChange);
this.image.removeEventListener("mousewheel", this.onMouseWheel);
this.image.removeEventListener("mousedown", this.onMouseDown);
this.image.removeEventListener("mousemove", this.onMouseMove);
this.image.removeEventListener("mouseup", this.onMouseUp);
this.image.removeEventListener("touchstart", this.onTouchStart);
this.image.removeEventListener("touchmove", this.onTouchMove);
this.image.removeEventListener("touchend", this.onTouchEnd);
}
}
@ -61,6 +75,88 @@ class ImagePopup extends React.Component {
this.setState({loading: false});
}
onMouseDown = (e) => {
const { translateX, translateY } = this.state;
this.dragging = true;
this.dragged = false;
this.startMouseX = e.clientX;
this.startTranslateX = translateX;
this.startMouseY = e.clientY;
this.startTranslateY = translateY;
}
onMouseUp = () => {
if (this.dragging){
this.startMouseX = this.startMouseY = 0;
this.setState({dragging: false});
}
this.dragging = false;
}
onMouseMove = (e) => {
if (this.dragging){
const dx = e.clientX - this.startMouseX;
const dy = e.clientY - this.startMouseY;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5){
this.dragged = true;
this.setState({
dragging: true,
translateX: dx + this.startTranslateX,
translateY: dy + this.startTranslateY
});
}
}
}
touchDistance = e => {
if (e.touches && e.touches.length === 2){
const [t1, t2] = e.touches;
const dx = t1.clientX - t2.clientX;
const dy = t1.clientY - t2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
return 0;
}
onTouchStart = e => {
if (e.touches.length === 2){
this.lastTouchDist = this.touchDistance(e);
}else if (e.touches.length === 1){
this.lastTouchDist = 0;
this.onMouseDown({
clientX: e.touches[0].clientX,
clientY: e.touches[0].clientY
});
}
}
onTouchMove = e => {
if (e.touches.length === 2 && this.lastTouchDist > 0){
const [t1, t2] = e.touches;
const curDist = this.touchDistance(e);
const delta = 1.5 * (curDist - this.lastTouchDist);
if (Math.abs(delta) > 0.05){
this.lastTouchDist = curDist;
this.onMouseWheel({
clientX: (t1.clientX + t2.clientX) / 2,
clientY: (t1.clientY + t2.clientY) / 2,
deltaY: -delta
});
}
}else if (e.touches.length === 1){
this.onMouseMove({
clientX: e.touches[0].clientX,
clientY: e.touches[0].clientY
});
}
}
onTouchEnd = () => {
this.lastTouchDist = 0;
this.onMouseUp();
}
onMouseWheel = e => {
if (!this.image || !this.state.expandThumb) return;
@ -74,8 +170,8 @@ class ImagePopup extends React.Component {
const mouseY = e.clientY;
const delta = -e.deltaY || e.wheelDelta || -e.detail;
const zoomFactor = 1.2;
const newScale = Math.max(1, delta > 0 ? scale * zoomFactor : scale / zoomFactor);
const zoomFactor = 1.0 + (2.0 * delta / Math.max(window.innerHeight, window.innerWidth));
const newScale = Math.max(1, scale * zoomFactor);
if (newScale > maxScale) return;
@ -101,14 +197,14 @@ class ImagePopup extends React.Component {
if (!expandThumb){
this.image.requestFullscreen();
this.setState({ loading: true, expandThumb: true, translateX: 0, translateY: 0, scale: 1});
}else{
}else if (!this.dragged){
document.exitFullscreen();
this.setState({ expandThumb: false, translateX: 0, translateY: 0, scale: 1 });
}
}
render(){
const { error, loading, expandThumb, translateX, translateY, scale } = this.state;
const { error, loading, expandThumb, dragging, translateX, translateY, scale } = this.state;
const { feature, task } = this.props;
const downloadImageLink = `/api/projects/${task.project}/tasks/${task.id}/images/download/${feature.properties.filename}`;
@ -122,11 +218,11 @@ class ImagePopup extends React.Component {
: ""}
{error !== "" ? <div style={{marginTop: "8px"}}>{error}</div>
: [
<div key="image" className={`image ${expandThumb ? "fullscreen" : ""}`}
<div key="image" className={`image ${expandThumb ? "fullscreen" : ""} ${dragging ? "dragging" : ""}`}
style={{marginTop: "8px"}}
ref={(domNode) => { this.image = domNode;}}>
{loading && expandThumb ? <div><i className="fa fa-circle-notch fa-spin fa-fw"></i></div> : ""}
<a onClick={this.onImgClick} href="javascript:void(0);" title={feature.properties.filename}><img style={{borderRadius: "4px", transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`}} src={imageUrl} onLoad={this.imageOnLoad} onError={this.imageOnError} /></a>
<a draggable="false" onClick={this.onImgClick} href="javascript:void(0);" title={feature.properties.filename}><img draggable="false" style={{borderRadius: "4px", transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`}} src={imageUrl} onLoad={this.imageOnLoad} onError={this.imageOnError} /></a>
</div>,
<div key="download-image">
<a href={downloadImageLink}><i className="fa fa-image"></i> {_("Download Image")}</a>

Wyświetl plik

@ -29,7 +29,7 @@
top: 45%;
}
a{
cursor: zoom-in;
cursor: default;
}
img{
image-rendering: pixelated;
@ -38,5 +38,11 @@
transform-origin: 0 0;
}
}
&.dragging{
a{
cursor: move;
}
}
}
}