DocSpace-buildtools/packages/components/viewer/sub-components/viewer-image.js

488 lines
13 KiB
JavaScript

import * as React from "react";
import classnames from "classnames";
import ViewerLoading from "./viewer-loading";
import { useSwipeable } from "../../react-swipeable";
import { isMobileOnly } from "react-device-detect";
import MobileViewer from "./mobile-viewer";
export default function ViewerImage(props) {
const {
dispatch,
createAction,
actionType,
playlist,
playlistPos,
containerSize,
setPanelVisible,
} = props;
const isMouseDown = React.useRef(false);
const isZoomingRef = React.useRef(true);
const imgRef = React.useRef(null);
const dirRef = React.useRef("");
const swipedRef = React.useRef(false);
const unMountedRef = React.useRef(false);
const startPostionRef = React.useRef({ x: 0, y: 0 });
const isDoubleTapRef = React.useRef(false);
React.useEffect(() => {
unMountedRef.current = false;
return () => (unMountedRef.current = true);
}, []);
const prePosition = React.useRef({
x: 0,
y: 0,
});
const [position, setPosition] = React.useState({
x: 0,
y: 0,
});
const CompareTo = (a, b) => {
return Math.trunc(a) > Math.trunc(b);
};
const maybeAdjustImage = (point) => {
const imageBounds = imgRef.current.getBoundingClientRect();
const containerBounds = imgRef.current.parentNode.getBoundingClientRect();
const originalWidth = imgRef.current.clientWidth;
const widthOverhang = (imageBounds.width - originalWidth) / 2;
const originalHeight = imgRef.current.clientHeight;
const heightOverhang = (imageBounds.height - originalHeight) / 2;
const isWidthOutContainer = imageBounds.width >= containerBounds.width;
const isHeightOutContainer = imageBounds.height >= containerBounds.height;
if (
CompareTo(imageBounds.left, containerBounds.left) &&
isWidthOutContainer
) {
point.x = widthOverhang;
} else if (
CompareTo(containerBounds.right, imageBounds.right) &&
isWidthOutContainer
) {
point.x = -(imageBounds.width - containerBounds.width) + widthOverhang;
} else if (!isWidthOutContainer) {
point.x = (containerBounds.width - imageBounds.width) / 2 + widthOverhang;
}
if (
CompareTo(imageBounds.top, containerBounds.top) &&
isHeightOutContainer
) {
point.y = heightOverhang;
} else if (
CompareTo(containerBounds.bottom, imageBounds.bottom) &&
isHeightOutContainer
) {
point.y = -(imageBounds.height - containerBounds.height) + heightOverhang;
} else if (!isHeightOutContainer) {
point.y =
(containerBounds.height - imageBounds.height) / 2 + heightOverhang;
}
return point;
};
const handlers = useSwipeable({
onSwipeStart: (e) => {
dirRef.current = e.dir;
swipedRef.current = false;
startPostionRef.current = {
x: props.left,
y: props.top,
};
},
onSwiping: (e) => {
if (
e.piching ||
!isZoomingRef.current ||
unMountedRef.current ||
!imgRef.current ||
isDoubleTapRef.current
)
return;
let newPoint = {
x: 0,
y: 0,
};
const isFistImage = playlistPos === 0;
const isLastImage = playlistPos === playlist.length - 1;
const containerBounds = imgRef.current.parentNode.getBoundingClientRect();
const imageBounds = imgRef.current.getBoundingClientRect();
const deltaWidth = (containerBounds.width - imageBounds.width) / 2;
const deltaHeight = (containerBounds.height - imageBounds.height) / 2;
const originalWidth = imgRef.current.clientWidth;
const widthOverhang = (imageBounds.width - originalWidth) / 2;
const originalHeight = imgRef.current.clientHeight;
const heightOverhang = (imageBounds.height - originalHeight) / 2;
if (props.scaleX * props.scaleY <= 1) {
switch (dirRef.current) {
case "Down":
newPoint.x = props.left;
newPoint.y = e.deltaY + deltaHeight + heightOverhang;
break;
case "Left":
newPoint.x = isLastImage
? props.left
: e.deltaX + deltaWidth + widthOverhang;
newPoint.y = props.top;
break;
case "Right":
newPoint.x = isFistImage
? props.left
: e.deltaX + deltaWidth + widthOverhang;
newPoint.y = props.top;
break;
default:
newPoint.x = props.left;
newPoint.y = props.top;
break;
}
} else {
const isWidthOutContainer = imageBounds.width >= containerBounds.width;
const isHeightOutContainer =
imageBounds.height >= containerBounds.height;
const [vx, vy] = e.vxvy;
const absVx = Math.abs(vx) > 0 ? Math.abs(vx) + 1 : 0;
const absVy = Math.abs(vy) > 0 ? Math.abs(vy) + 1 : 0;
const isImageHeightInsideContainer =
imageBounds.top > containerBounds.top &&
containerBounds.bottom > imageBounds.bottom;
const isImageWidhtInsideContainer =
imageBounds.left > containerBounds.left &&
containerBounds.right > imageBounds.right;
const left = imageBounds.left + e.deltaX * absVx;
const right = imageBounds.right + e.deltaX * absVx;
if (isWidthOutContainer) {
if (left > containerBounds.left) {
newPoint.x = widthOverhang;
} else if (right < containerBounds.right) {
newPoint.x =
-(imageBounds.width - containerBounds.width) + widthOverhang;
} else {
newPoint.x = e.deltaX * absVx + startPostionRef.current.x;
}
} else if (isImageWidhtInsideContainer) {
newPoint.x = props.left;
} else {
newPoint.x = e.deltaX * absVx + startPostionRef.current.x;
}
const top = imageBounds.top + e.deltaY * absVy;
const bottom = imageBounds.bottom + e.deltaY * absVy;
if (isHeightOutContainer) {
if (top > containerBounds.top) {
newPoint.y = heightOverhang;
} else if (bottom < containerBounds.bottom) {
newPoint.y =
-(imageBounds.height - containerBounds.height) + heightOverhang;
} else {
newPoint.y = e.deltaY * absVy + startPostionRef.current.y;
}
} else if (isImageHeightInsideContainer) {
newPoint.y = props.top;
} else {
newPoint.y = e.deltaY * absVy + startPostionRef.current.y;
}
}
const opacity =
props.scaleX !== 1 && props.scaleY !== 1
? 1
: dirRef.current === "Down"
? 2 -
(imageBounds.height / 2 + props.top) / (containerBounds.height / 2)
: 1;
const direction =
Math.abs(e.deltaX) > Math.abs(e.deltaY) ? "horizontal" : "vertical";
return dispatch(
createAction(actionType.update, {
left: newPoint.x,
top: newPoint.y,
opacity: direction === "vertical" && e.deltaY > 0 ? opacity : 1,
deltaX: 0,
deltaY: 0,
})
);
},
onSwipedLeft: (e) => {
if (
(props.scaleX !== 1 && props.scaleY !== 1) ||
e.piching ||
!isZoomingRef.current
)
return;
if (e.deltaX <= -100 && playlistPos !== playlist.length - 1) {
swipedRef.current = true;
props.onNextClick();
}
},
onSwipedRight: (e) => {
if (
(props.scaleX !== 1 && props.scaleY !== 1) ||
e.piching ||
!isZoomingRef.current
)
return;
if (e.deltaX >= 100 && playlistPos !== 0) {
swipedRef.current = true;
props.onPrevClick();
}
},
onSwipedDown: (e) => {
if (unMountedRef.current) return;
if (e.deltaY > 200 && props.scaleX * props.scaleY === 1) {
swipedRef.current = true;
props.onMaskClick();
}
},
onTap: (e) => {
setPanelVisible((visible) => !visible);
},
onSwiped: (e) => {
if (unMountedRef.current || isDoubleTapRef.current) return;
console.log("onSwiped");
let Point = {
x: props.left,
y: props.top,
};
dirRef.current = "";
setTimeout(() => {
if (unMountedRef.current || swipedRef.current) return;
const newPoint = maybeAdjustImage(Point);
return dispatch(
createAction(actionType.update, {
left: newPoint.x,
top: newPoint.y,
deltaX: 0,
deltaY: 0,
opacity: 1,
})
);
}, 200);
},
onTouchEndOrOnMouseUp: () => {
dirRef.current = "";
isDoubleTapRef.current = false;
},
});
React.useEffect(() => {
return () => {
bindEvent(true);
bindWindowResizeEvent(true);
};
}, []);
React.useEffect(() => {
bindWindowResizeEvent();
return () => {
bindWindowResizeEvent(true);
};
});
React.useEffect(() => {
if (props.visible && props.drag) {
bindEvent();
}
if (!props.visible && props.drag) {
handleMouseUp({});
}
return () => {
bindEvent(true);
};
}, [props.drag, props.visible]);
React.useEffect(() => {
let diffX = position.x - prePosition.current.x;
let diffY = position.y - prePosition.current.y;
prePosition.current = {
x: position.x,
y: position.y,
};
props.onChangeImgState(
props.width,
props.height,
props.top + diffY,
props.left + diffX
);
}, [position]);
function handleResize(e) {
props.onResize();
}
function handleMouseDown(e) {
if (e.button !== 0) {
return;
}
if (!props.visible || !props.drag) {
return;
}
e.preventDefault();
e.stopPropagation();
isMouseDown.current = true;
prePosition.current = {
x: e.nativeEvent.clientX,
y: e.nativeEvent.clientY,
};
}
const handleMouseMove = (e) => {
if (isMouseDown.current) {
setPosition({
x: e.clientX,
y: e.clientY,
});
}
};
function handleResize(e) {
props.onResize();
}
function handleMouseUp(e) {
isMouseDown.current = false;
}
function onClose(e) {
if (e.target === imgRef.current || e.nativeEvent.pointerType === "touch")
return;
props.onMaskClick();
}
function bindWindowResizeEvent(remove) {
let funcName = "addEventListener";
if (remove) {
funcName = "removeEventListener";
}
window[funcName]("resize", handleResize, false);
}
function bindEvent(remove) {
let funcName = "addEventListener";
if (remove) {
funcName = "removeEventListener";
}
document[funcName]("click", handleMouseUp, false);
document[funcName]("mousemove", handleMouseMove, false);
}
let imgStyle = {
width: `${props.width}px`,
height: `${props.height}px`,
opacity: `${props.opacity}`,
transition: `${props.withTransition ? "all .26s ease-out" : "none"}`,
transform: `
translateX(${props.left !== null ? props.left + "px" : "auto"}) translateY(${
props.top
}px)
rotate(${props.rotate}deg) scaleX(${props.scaleX}) scaleY(${props.scaleY})`,
};
const imgClass = classnames(`${props.prefixCls}-image`, {
drag: props.drag,
[`${props.prefixCls}-image-transition`]: !isMouseDown.current,
});
let styleIndex = {
zIndex: props.zIndex,
};
let imgNode = null;
if (props.imgSrc !== "") {
imgNode = isMobileOnly ? (
<MobileViewer
className={imgClass}
src={props.imgSrc}
width={props.width}
height={props.height}
left={props.left}
top={props.top}
onPrev={props.onPrevClick}
onNext={props.onNextClick}
onMask={props.onMaskClick}
isFistImage={playlistPos === 0}
isLastImage={playlistPos === playlist.length - 1}
setPanelVisible={setPanelVisible}
/>
) : (
<img
className={imgClass}
src={props.imgSrc}
style={imgStyle}
ref={imgRef}
onMouseDown={handleMouseDown}
/>
);
}
if (props.loading) {
imgNode = (
<div
style={{
display: "flex",
height: `${window.innerHeight}px`,
justifyContent: "center",
alignItems: "center",
}}
>
<ViewerLoading />
</div>
);
}
if (isMobileOnly) {
return (
<div
className={`${props.prefixCls}-canvas`}
onClick={onClose}
style={styleIndex}
>
{imgNode}
</div>
);
}
return (
<div
className={`${props.prefixCls}-canvas`}
onClick={onClose}
style={styleIndex}
{...handlers}
>
{imgNode}
</div>
);
}