2022-04-05 22:49:13 +08:00
|
|
|
import * as React from "react";
|
2022-04-11 00:31:12 +08:00
|
|
|
import { _ } from "lodash";
|
2022-04-05 22:49:13 +08:00
|
|
|
|
2022-04-11 00:31:12 +08:00
|
|
|
function usePrevious(value) {
|
|
|
|
const ref = React.useRef();
|
|
|
|
React.useEffect(() => {
|
|
|
|
ref.current = value;
|
|
|
|
}, [value]);
|
|
|
|
return ref.current;
|
|
|
|
}
|
|
|
|
|
|
|
|
const renderCursor = (
|
|
|
|
name,
|
|
|
|
color,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
currentPoint,
|
|
|
|
pageState,
|
|
|
|
owner = false
|
|
|
|
) => {
|
2022-04-05 22:49:13 +08:00
|
|
|
const z = !owner ? 2 : 1;
|
2022-04-11 00:31:12 +08:00
|
|
|
let _x = null;
|
|
|
|
let _y = null;
|
2022-04-05 22:49:13 +08:00
|
|
|
|
2022-04-11 00:31:12 +08:00
|
|
|
if (!currentPoint) {
|
2022-05-30 00:24:44 +08:00
|
|
|
_x = (x + pageState?.camera?.point[0]) * pageState?.camera?.zoom;
|
|
|
|
_y = (y + pageState?.camera?.point[1]) * pageState?.camera?.zoom;
|
2022-04-11 00:31:12 +08:00
|
|
|
}
|
2022-04-05 22:49:13 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div
|
|
|
|
key={`${name}-${color}-${x}-${y}`}
|
|
|
|
style={{
|
|
|
|
zIndex: z,
|
|
|
|
position: "absolute",
|
2022-04-11 00:31:12 +08:00
|
|
|
left: (_x || x) - 2.5,
|
|
|
|
top: (_y || y) - 2.5,
|
2022-04-05 22:49:13 +08:00
|
|
|
width: 5,
|
|
|
|
height: 5,
|
|
|
|
borderRadius: "50%",
|
|
|
|
background: `${color}`,
|
|
|
|
pointerEvents: "none",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
zIndex: z,
|
|
|
|
position: "absolute",
|
|
|
|
pointerEvents: "none",
|
2022-04-11 00:31:12 +08:00
|
|
|
left: (_x || x) + 3.75,
|
|
|
|
top: (_y || y) + 3,
|
2022-04-05 22:49:13 +08:00
|
|
|
paddingLeft: ".25rem",
|
|
|
|
paddingRight: ".25rem",
|
|
|
|
paddingBottom: ".1rem",
|
|
|
|
lineHeight: "1rem",
|
|
|
|
borderRadius: "2px",
|
|
|
|
color: "#FFF",
|
|
|
|
backgroundColor: color,
|
|
|
|
border: `1px solid ${color}`,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{name}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const PositionLabel = (props) => {
|
|
|
|
const {
|
|
|
|
currentUser,
|
2022-04-11 00:31:12 +08:00
|
|
|
currentPoint,
|
|
|
|
pageState,
|
2022-04-05 22:49:13 +08:00
|
|
|
publishCursorUpdate,
|
2022-05-07 00:37:43 +08:00
|
|
|
whiteboardId,
|
2022-06-28 07:46:01 +08:00
|
|
|
pos,
|
2022-04-05 22:49:13 +08:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
const { name, color, userId, presenter } = currentUser;
|
2022-04-11 00:31:12 +08:00
|
|
|
const prevCurrentPoint = usePrevious(currentPoint);
|
2022-04-05 22:49:13 +08:00
|
|
|
|
|
|
|
React.useEffect(() => {
|
2022-04-11 00:31:12 +08:00
|
|
|
try {
|
2022-06-28 07:46:01 +08:00
|
|
|
const point = [pos.x, pos.y];
|
2022-05-07 00:37:43 +08:00
|
|
|
publishCursorUpdate({
|
2022-05-30 00:24:44 +08:00
|
|
|
xPercent:
|
|
|
|
point[0] / pageState?.camera?.zoom - pageState?.camera?.point[0],
|
|
|
|
yPercent:
|
|
|
|
point[1] / pageState?.camera?.zoom - pageState?.camera?.point[1],
|
|
|
|
whiteboardId,
|
2022-05-07 00:37:43 +08:00
|
|
|
});
|
2022-04-11 00:31:12 +08:00
|
|
|
} catch (e) {
|
|
|
|
console.log(e);
|
|
|
|
}
|
2022-06-28 07:46:01 +08:00
|
|
|
}, [pos?.x, pos?.y]);
|
2022-04-05 22:49:13 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div style={{ position: "absolute", height: "100%", width: "100%" }}>
|
2022-06-28 07:46:01 +08:00
|
|
|
{renderCursor(name, color, pos.x, pos.y, currentPoint, props.pageState)}
|
2022-04-05 22:49:13 +08:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default function Cursors(props) {
|
2022-05-30 00:24:44 +08:00
|
|
|
let cursorWrapper = React.useRef(null);
|
|
|
|
const [active, setActive] = React.useState(false);
|
2022-06-28 07:46:01 +08:00
|
|
|
const [pos, setPos] = React.useState({ x: 0, y: 0 });
|
2022-05-30 22:51:08 +08:00
|
|
|
const {
|
|
|
|
whiteboardId,
|
|
|
|
otherCursors,
|
|
|
|
currentUser,
|
|
|
|
tldrawAPI,
|
|
|
|
publishCursorUpdate,
|
|
|
|
children,
|
2022-06-29 04:12:14 +08:00
|
|
|
isViewersCursorLocked,
|
|
|
|
hasMultiUserAccess,
|
2022-05-30 22:51:08 +08:00
|
|
|
} = props;
|
2022-06-28 07:46:01 +08:00
|
|
|
|
|
|
|
const start = () => setActive(true);
|
|
|
|
|
|
|
|
const end = () => {
|
|
|
|
publishCursorUpdate({
|
|
|
|
xPercent: null,
|
|
|
|
yPercent: null,
|
|
|
|
whiteboardId: whiteboardId,
|
|
|
|
});
|
|
|
|
setActive(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const moved = (event) => {
|
|
|
|
const { type } = event;
|
|
|
|
const yOffset = parseFloat(document.getElementById('Navbar')?.style?.height);
|
|
|
|
const getSibling = (el) => el?.previousSibling || null;
|
|
|
|
const panel = getSibling(document.getElementById('Navbar'));
|
|
|
|
const subPanel = panel && getSibling(panel);
|
|
|
|
const xOffset = (parseFloat(panel?.style?.width) || 0) + (parseFloat(subPanel?.style?.width) || 0);
|
|
|
|
if (type === 'touchmove') {
|
|
|
|
!active && setActive(true);
|
|
|
|
return setPos({ x: event?.changedTouches[0]?.clientX - xOffset, y: event?.changedTouches[0]?.clientY - yOffset });
|
|
|
|
}
|
|
|
|
return setPos({ x: event.x - xOffset, y: event.y - yOffset });
|
|
|
|
}
|
|
|
|
|
2022-05-30 00:24:44 +08:00
|
|
|
React.useEffect(() => {
|
|
|
|
!cursorWrapper.hasOwnProperty("mouseenter") &&
|
2022-06-28 07:46:01 +08:00
|
|
|
cursorWrapper?.addEventListener("mouseenter", start);
|
|
|
|
|
2022-05-30 00:24:44 +08:00
|
|
|
!cursorWrapper.hasOwnProperty("mouseleave") &&
|
2022-06-28 07:46:01 +08:00
|
|
|
cursorWrapper?.addEventListener("mouseleave", end);
|
|
|
|
|
|
|
|
!cursorWrapper.hasOwnProperty("touchend") &&
|
|
|
|
cursorWrapper?.addEventListener("touchend", end);
|
|
|
|
|
|
|
|
!cursorWrapper.hasOwnProperty("mousemove") &&
|
|
|
|
cursorWrapper?.addEventListener("mousemove", moved);
|
|
|
|
|
|
|
|
!cursorWrapper.hasOwnProperty("touchmove") &&
|
|
|
|
cursorWrapper?.addEventListener("touchmove", moved);
|
2022-05-30 00:24:44 +08:00
|
|
|
}, [cursorWrapper]);
|
|
|
|
|
2022-06-28 07:46:01 +08:00
|
|
|
React.useEffect(() => {
|
|
|
|
return () => {
|
|
|
|
cursorWrapper.removeEventListener('mouseenter', start);
|
|
|
|
cursorWrapper.removeEventListener('mouseleave', end);
|
|
|
|
cursorWrapper.removeEventListener('mousemove', moved);
|
|
|
|
cursorWrapper.removeEventListener('touchend', end);
|
|
|
|
cursorWrapper.removeEventListener('touchmove', moved);
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2022-04-05 22:49:13 +08:00
|
|
|
return (
|
2022-05-30 00:24:44 +08:00
|
|
|
<span disabled={true} ref={(r) => (cursorWrapper = r)}>
|
2022-06-28 07:46:01 +08:00
|
|
|
<div style={{ height: "100%", cursor: "none" }}>
|
2022-05-30 00:24:44 +08:00
|
|
|
{active && (
|
|
|
|
<PositionLabel
|
2022-06-28 07:46:01 +08:00
|
|
|
pos={pos}
|
2022-05-30 22:51:08 +08:00
|
|
|
otherCursors={otherCursors}
|
|
|
|
currentUser={currentUser}
|
|
|
|
currentPoint={tldrawAPI?.currentPoint}
|
|
|
|
pageState={tldrawAPI?.getPageState()}
|
|
|
|
publishCursorUpdate={publishCursorUpdate}
|
|
|
|
whiteboardId={whiteboardId}
|
2022-05-30 00:24:44 +08:00
|
|
|
/>
|
|
|
|
)}
|
2022-05-30 22:51:08 +08:00
|
|
|
{children}
|
2022-06-28 07:46:01 +08:00
|
|
|
</div>
|
2022-05-30 22:51:08 +08:00
|
|
|
{otherCursors
|
2022-05-30 00:24:44 +08:00
|
|
|
.filter((c) => c?.xPercent && c?.yPercent)
|
2022-06-02 23:00:28 +08:00
|
|
|
.filter((c) => {
|
|
|
|
if ((isViewersCursorLocked && c?.role !== "VIEWER") || !isViewersCursorLocked || currentUser?.presenter) {
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
})
|
2022-05-30 00:24:44 +08:00
|
|
|
.map((c) => {
|
2022-06-29 04:12:14 +08:00
|
|
|
if (c && currentUser.userId !== c?.userId) {
|
|
|
|
if (c.presenter) {
|
|
|
|
return renderCursor(
|
|
|
|
c?.userName,
|
|
|
|
"#C70039",
|
|
|
|
c?.xPercent,
|
|
|
|
c?.yPercent,
|
|
|
|
null,
|
|
|
|
tldrawAPI?.getPageState(),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasMultiUserAccess(whiteboardId, c?.userId) && (
|
|
|
|
renderCursor(
|
|
|
|
c?.userName,
|
|
|
|
"#AFE1AF",
|
|
|
|
c?.xPercent,
|
|
|
|
c?.yPercent,
|
|
|
|
null,
|
|
|
|
tldrawAPI?.getPageState(),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2022-05-30 00:24:44 +08:00
|
|
|
})}
|
|
|
|
</span>
|
2022-04-05 22:49:13 +08:00
|
|
|
);
|
|
|
|
}
|