Files
kiss-translator/src/views/Action/Draggable.js
2025-10-21 02:07:33 +08:00

181 lines
4.0 KiB
JavaScript

import { useEffect, useMemo, useState } from "react";
import { limitNumber } from "../../libs/utils";
import { isMobile } from "../../libs/mobile";
import { putFab } from "../../libs/storage";
import { debounce } from "../../libs/utils";
import Paper from "@mui/material/Paper";
const getEdgePosition = ({
x: left,
y: top,
width,
height,
windowWidth,
windowHeight,
hover,
}) => {
const right = windowWidth - left - width;
const bottom = windowHeight - top - height;
const min = Math.min(left, top, right, bottom);
switch (min) {
case right:
left = hover ? windowWidth - width : windowWidth - width / 2;
break;
case left:
left = hover ? 0 : -width / 2;
break;
case bottom:
top = hover ? windowHeight - height : windowHeight - height / 2;
break;
default:
top = hover ? 0 : -height / 2;
}
return { x: left, y: top };
};
function DraggableWrapper({ children, usePaper, ...props }) {
if (usePaper) {
return (
<Paper {...props} elevation={4}>
{children}
</Paper>
);
}
return <div {...props}>{children}</div>;
}
export default function Draggable({
windowSize: { w: windowWidth, h: windowHeight },
width,
height,
left,
top,
show = true,
snapEdge,
onStart,
onMove,
handler,
children,
usePaper,
}) {
const [hover, setHover] = useState(false);
const [origin, setOrigin] = useState(null);
const [position, setPosition] = useState({ x: left, y: top });
const setFabPosition = useMemo(() => debounce(putFab, 500), []);
const handlePointerDown = (e) => {
!isMobile && e.target.setPointerCapture(e.pointerId);
onStart && onStart();
const { x, y } = position;
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
setOrigin({ x, y, clientX, clientY });
};
const handlePointerMove = (e) => {
onMove && onMove();
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
if (origin) {
const dx = clientX - origin.clientX;
const dy = clientY - origin.clientY;
let x = origin.x + dx;
let y = origin.y + dy;
x = limitNumber(x, -width / 2, windowWidth - width / 2);
y = limitNumber(y, 0, windowHeight - height / 2);
setPosition({ x, y });
}
};
const handlePointerUp = (e) => {
e.stopPropagation();
setOrigin(null);
};
const handleClick = (e) => {
e.stopPropagation();
};
const handleMouseEnter = (e) => {
e.stopPropagation();
setHover(true);
};
const handleMouseLeave = (e) => {
e.stopPropagation();
setHover(false);
};
useEffect(() => {
if (!snapEdge || !!origin) {
return;
}
setPosition((pre) => {
const edgePosition = getEdgePosition({
...pre,
width,
height,
windowWidth,
windowHeight,
hover,
});
setFabPosition(edgePosition);
return edgePosition;
});
}, [
origin,
hover,
width,
height,
windowWidth,
windowHeight,
snapEdge,
setFabPosition,
]);
const opacity = useMemo(() => {
if (snapEdge) {
return hover || origin ? 1 : 0.2;
}
return origin ? 0.8 : 1;
}, [origin, snapEdge, hover]);
const touchProps = isMobile
? {
onTouchStart: handlePointerDown,
onTouchMove: handlePointerMove,
onTouchEnd: handlePointerUp,
}
: {
onPointerDown: handlePointerDown,
onPointerMove: handlePointerMove,
onPointerUp: handlePointerUp,
};
return (
<DraggableWrapper
usePaper={usePaper}
style={{
opacity,
position: "fixed",
left: position.x,
top: position.y,
zIndex: 2147483647,
display: show ? "block" : "none",
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
>
<div
style={{
touchAction: "none",
}}
{...touchProps}
>
{handler}
</div>
<div>{children}</div>
</DraggableWrapper>
);
}