Skip to content

Commit

Permalink
Cursor presence.
Browse files Browse the repository at this point in the history
  • Loading branch information
Yyassin committed Feb 23, 2024
1 parent 4d7aa6c commit 8ec82ac
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 112 deletions.
2 changes: 0 additions & 2 deletions client/src/Bootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const Bootstrap = () => {
const { setCanvasElementState } = useCanvasElementStore([
'setCanvasElementState',
]);
const { setColorMaping } = useCommentsStore(['setColorMaping']);
const [isLoaded, setIsLoaded] = useState(false);
const auth = async () => {
const token = localStorage.getItem(ACCESS_TOKEN_TAG);
Expand All @@ -38,7 +37,6 @@ const Bootstrap = () => {
setCanvases,
setBoardMeta,
setCanvasElementState,
setColorMaping,
)
)?.valueOf();
if (response.status === HTTP_STATUS.SUCCESS) {
Expand Down
7 changes: 4 additions & 3 deletions client/src/WebsocketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export default class WebsocketClient {
* Method that sends a message over the WebSocket connection.
* @param msg Object, the message object to be sent.
*/
async send(msg: object) {
async send(msg: Record<string, unknown>) {
if (!this.checkSocket()) return;
try {
await this.waitForOpenConnection();
Expand Down Expand Up @@ -216,13 +216,14 @@ export default class WebsocketClient {
| string[]
| UpdatedTimeMessage
| null
| { elemID: string; comment: Partial<Comment> },
| { elemID: string; comment: Partial<Comment> }
| Record<string, unknown>,
) {
// Msg to be changed to proper type once everything finalized
if (this.room === null) throw 'No room assigned!';
return this.send({
...this.msgTemplate,
topic: topic,
topic,
payload: msg,
room: this.room,
id: this.userId,
Expand Down
3 changes: 0 additions & 3 deletions client/src/components/lib/BoardScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export const BoardScroll = () => {
'setCanvasElementState',
]);
const { userID } = useAuthStore(['userID']);
const { setColorMaping } = useCommentsStore(['setColorMaping']);

const setCanvasState = () => {
state && setCanvasElementState(state);
Expand Down Expand Up @@ -101,8 +100,6 @@ export const BoardScroll = () => {
params: { id: board.id, userID },
});

setColorMaping(boardState.data.board.collaborators);

collabID = boardState.data.collabID;

const state = createStateWithRoughElement(
Expand Down
47 changes: 32 additions & 15 deletions client/src/components/lib/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import { normalizeAngle } from '@/lib/math';
import { useCanvasBoardStore } from '@/stores/CanavasBoardStore';
import { tenancy } from '@/api';
import { useAuthStore } from '@/stores/AuthStore';
import { useCommentsStore } from '@/stores/CommentsStore';
import CursorPresence from './CursorPresence';
import { throttle } from 'lodash';
import { idToColour } from '@/lib/userColours';

/**
* Main Canvas View
Expand Down Expand Up @@ -132,8 +134,6 @@ export default function Canvas() {
'attachedFileUrls',
]);

const { addColor } = useCommentsStore(['addColor']);

const { socket, setWebsocketAction, setRoomID, setTenants, clearTenants } =
useWebSocketStore([
'socket',
Expand Down Expand Up @@ -161,13 +161,7 @@ export default function Canvas() {
* Callbacks for active tenants in the room.
*/
useEffect(() => {
socket?.on(WS_TOPICS.NOTIFY_JOIN_ROOM, (payload) => {
initTenants();
const collabID = extractCollabID(
(payload as { payload: { id: string } }).payload.id,
);
collabID && addColor(collabID);
});
socket?.on(WS_TOPICS.NOTIFY_JOIN_ROOM, initTenants);
socket?.on(WS_TOPICS.NOTIFY_LEAVE_ROOM, initTenants);
return clearTenants;
}, [socket]);
Expand All @@ -180,19 +174,22 @@ export default function Canvas() {
*/
const initTenants = async () => {
const tenantIds = (await tenancy.get(boardMeta.roomID)) as string[];
console.log(tenantIds);
const activeTenants = tenantIds.reduce(
(acc, id) => {
const collabId = extractCollabID(id);
if (collabId === null) return acc;

const isMe = collabId === boardMeta.collabID;
// We don't want to add ourselves to the list of tenants.
id !== userEmail &&
!isMe &&
(acc[id] = {
// Temp
username: extractUsername(id) ?? id,
email: id,
initials: 'A',
avatar: 'https://github.com/shadcn.png',
outlineColor: `#${Math.floor(Math.random() * 16777215).toString(
16,
)}`,
outlineColor: idToColour(collabId),
});
return acc;
},
Expand Down Expand Up @@ -517,12 +514,28 @@ export default function Canvas() {
action !== 'writing' && setAction('none');
};

const sendCursorPositions = React.useCallback(
throttle((x: number | null, y: number | null) => {
socket?.sendMsgRoom(
'updateCursorPosition',
// make collab id
{
x,
y,
userId: userEmail,
},
);
}, 16),
[userEmail, socket],
);

// eslint-disable-next-line sonarjs/cognitive-complexity
const handleMouseMove = (e: MouseEvent<HTMLCanvasElement>) => {
const { clientX, clientY } = getMouseCoordinates(e);
sendCursorPositions(clientX, clientY);
if (e.button === PERIPHERAL_CODES.RIGHT_MOUSE) {
return;
}
const { clientX, clientY } = getMouseCoordinates(e);

if (action === 'panning') {
const deltaX = clientX - panMouseStartPosition.current.x;
Expand Down Expand Up @@ -684,6 +697,7 @@ export default function Canvas() {

return (
<>
<CursorPresence />
<canvas
id="canvas"
style={{
Expand All @@ -696,6 +710,9 @@ export default function Canvas() {
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onPointerLeave={() => {
sendCursorPositions(null, null);
}}
/>
{tool === 'select' &&
attachedFileUrls[selectedElementIds[0]] !== undefined && (
Expand Down
69 changes: 34 additions & 35 deletions client/src/components/lib/CommentsSheetContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useCanvasBoardStore } from '@/stores/CanavasBoardStore';
import { useCanvasElementStore } from '@/stores/CanvasElementsStore';
import { useWebSocketStore } from '@/stores/WebSocketStore';
import { useAppStore } from '@/stores/AppStore';
import { idToColour } from '@/lib/userColours';

/**
* Defines a CommentsSheetContent component that displays the comments of a board, and allows the user to add a comment or like existing ones.
Expand Down Expand Up @@ -136,21 +137,14 @@ const CommentsSheetContent = () => {
'angles',
'fileIds',
]);
const {
comments,
colorMaping,
updateComment,
addComment,
removeComment,
setComments,
} = useCommentsStore([
'comments',
'colorMaping',
'updateComment',
'addComment',
'removeComment',
'setComments',
]);
const { comments, updateComment, addComment, removeComment, setComments } =
useCommentsStore([
'comments',
'updateComment',
'addComment',
'removeComment',
'setComments',
]);

const { isViewingComments } = useAppStore(['isViewingComments']);

Expand Down Expand Up @@ -191,7 +185,9 @@ const CommentsSheetContent = () => {
setComments(
comments.data.comments.map((comment: Comment) => ({
...comment,
outlineColor: `${colorMaping[comment.outlineColor]}`,
collabId: comment.outlineColor,
// Outline colour is the collaborator id
outlineColor: `${idToColour(comment.outlineColor)}`,
})),
);
};
Expand Down Expand Up @@ -270,25 +266,27 @@ const CommentsSheetContent = () => {
</p>
</div>
</div>
<Button
className="p-0 h-7 w-15 bg-[#7f7dcf] hover:text-red-600 hover:bg-[#7f7dcf]"
onClick={() => {
removeComment(comment.uid);
axios.delete(REST.comment.delete, {
params: { id: comment.uid },
});
{boardMeta.collabID === comment.collabId && (
<Button
className="p-0 h-7 w-15 bg-[#7f7dcf] hover:text-red-600 hover:bg-[#7f7dcf]"
onClick={() => {
removeComment(comment.uid);
axios.delete(REST.comment.delete, {
params: { id: comment.uid },
});

setWebsocketAction(
{
comment: { uid: comment.uid },
elemID: selectedElementIds[0],
},
'removeComment',
);
}}
>
<TrashIcon className="h-5 w-5" />
</Button>
setWebsocketAction(
{
comment: { uid: comment.uid },
elemID: selectedElementIds[0],
},
'removeComment',
);
}}
>
<TrashIcon className="h-5 w-5" />
</Button>
)}
</div>
))}
</div>
Expand Down Expand Up @@ -356,7 +354,8 @@ const CommentsSheetContent = () => {
comment: comment.data.comment.comment,
likes: 0,
initials: getInitials(`${userFirstName} ${userLastName}`),
outlineColor: `${colorMaping[boardMeta.collabID]}`,
outlineColor: `${idToColour(boardMeta.collabID)}`,
collabId: boardMeta.collabID,
isLiked: false,
};

Expand Down
74 changes: 74 additions & 0 deletions client/src/components/lib/CursorPresence.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { extractCollabID, extractUsername } from '@/lib/misc';
import { idToColour } from '@/lib/userColours';
import { useWebSocketStore } from '@/stores/WebSocketStore';
import { Vector2 } from '@/types';
import { MousePointer2 } from 'lucide-react';
import React, { memo, useMemo } from 'react';

const Cursor = memo(({ userId, x, y }: Vector2 & { userId: string }) => {
const { userName, cursorColour } = useMemo(
() => ({
userName: extractUsername(userId) ?? userId,
cursorColour: idToColour(extractCollabID(userId) ?? userId),
}),
[userId],
);
return (
<foreignObject
style={{
transform: `translate(${x}px, ${y}px)`,
}}
height={50}
width={userName.length * 10 + 24}
className="relative drop-shadow-md"
>
<MousePointer2
className="h-5 w-5"
style={{
fill: cursorColour,
color: cursorColour,
}}
/>
<div
className="absolute left-5 px-1.5 py-0.5 rounded-md text-xs text-white font-semibold"
style={{ backgroundColor: cursorColour }}
>
{userName}
</div>
</foreignObject>
);
});
Cursor.displayName = 'CursorPresence';

const CursorPresence = memo(() => {
const { cursorPositions } = useWebSocketStore(['cursorPositions']);
return (
<svg
className="h-[100vh] w-[100vw]"
style={{
backgroundColor: 'red',
position: 'absolute',
zIndex: 4,
pointerEvents: 'none',
}}
>
<g>
{Object.entries(cursorPositions).map(
([userId, position]) =>
position.x !== null &&
position.y !== null && (
<Cursor
key={userId}
userId={userId}
x={position.x}
y={position.y}
/>
),
)}
</g>
</svg>
);
});

CursorPresence.displayName = 'CursorPresence';
export default CursorPresence;
4 changes: 0 additions & 4 deletions client/src/components/lib/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useAppStore } from '@/stores/AppStore';
import axios from 'axios';
import { REST } from '@/constants';
import { useAuthStore } from '@/stores/AuthStore';
import { useCommentsStore } from '@/stores/CommentsStore';

/**
* Define a react component that the top bar of the main dashboard
Expand All @@ -24,7 +23,6 @@ export const TopBar = () => {
]);
const { userID } = useAuthStore(['userID']);
const { setMode } = useAppStore(['setMode']);
const { setColorMaping } = useCommentsStore(['setColorMaping']);

return (
<div className="flex flex-col">
Expand Down Expand Up @@ -90,8 +88,6 @@ export const TopBar = () => {

addCanvas(boardData);

setColorMaping(boardData.collaborators);

setBoardMeta({
roomID: boardData.roomID,
title: boardData.title,
Expand Down
Loading

0 comments on commit 8ec82ac

Please sign in to comment.