From ab10f7092550768bbb1561d0ba878d38c1b57e45 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 27 Jun 2024 18:00:14 +0100 Subject: [PATCH] Implemented Document Collaborators & Added Fixes --- .../src/ui/pages/document/Document.scss | 1 + .../client/src/ui/pages/document/Document.tsx | 6 +-- .../collaborators/Collaborators.scss | 23 +++++++++ .../collaborators/Collaborators.tsx | 23 +++++++++ .../collaborators/useCollaborators.ts | 26 ++++++++++ .../workspace/components/DocumentView.tsx | 3 ++ .../components/WorkspaceContextMenu.tsx | 3 +- .../http/handlers/usersHandlers.ts | 3 +- .../http/middlewares/authMiddlewares.ts | 6 +-- .../ws/events/document/onCursorChange.ts | 19 +++++--- .../ws/events/document/onJoinDocument.ts | 24 +++++++++- .../ws/events/document/onLeaveDocument.ts | 11 ++++- .../ws/events/workspace/onJoinWorkspace.ts | 2 +- .../src/controllers/ws/initSocketEvents.ts | 10 +++- code/server/src/controllers/ws/rooms/Room.ts | 47 ++++++++++++------- .../src/controllers/ws/rooms/operations.ts | 11 +++-- code/server/src/controllers/ws/rooms/rooms.ts | 19 ++++++-- code/server/src/controllers/ws/utils.ts | 4 +- .../src/databases/memory/MemoryUsersDB.ts | 7 +-- .../databases/postgres/PostgresResourcesDB.ts | 13 ++--- .../src/databases/postgres/PostgresUsersDB.ts | 6 +-- .../postgres/PostgresWorkspacesDB.ts | 10 ++-- code/server/src/databases/types.ts | 7 +-- code/server/src/services/UsersService.ts | 6 +-- code/server/src/services/utils.ts | 10 ++-- code/server/test/users/users.test.ts | 30 ++++-------- code/server/test/utils.ts | 9 ++-- code/shared/src/users/types.ts | 6 +-- 28 files changed, 242 insertions(+), 103 deletions(-) create mode 100644 code/client/src/ui/pages/document/components/collaborators/Collaborators.scss create mode 100644 code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx create mode 100644 code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts diff --git a/code/client/src/ui/pages/document/Document.scss b/code/client/src/ui/pages/document/Document.scss index 516812a4..b708a158 100644 --- a/code/client/src/ui/pages/document/Document.scss +++ b/code/client/src/ui/pages/document/Document.scss @@ -1,2 +1,3 @@ .document { + position: relative; } diff --git a/code/client/src/ui/pages/document/Document.tsx b/code/client/src/ui/pages/document/Document.tsx index fa497194..2a45bc59 100644 --- a/code/client/src/ui/pages/document/Document.tsx +++ b/code/client/src/ui/pages/document/Document.tsx @@ -8,6 +8,7 @@ import useDocumentService from '@services/resource/useResourcesService'; import useConnectors from '@domain/editor/connectors/useConnectors'; import FloatingButtons from '@ui/pages/document/components/floating-buttons/FloatingButtons'; import { DocumentResource } from '@notespace/shared/src/workspace/types/resource'; +import Collaborators from '@ui/pages/document/components/collaborators/Collaborators'; import './Document.scss'; function Document() { @@ -43,15 +44,14 @@ function Document() { setLoaded(true); } fetchDocument(); - return () => { - socket.emit('leaveDocument'); - }; + return () => socket.emit('leaveDocument'); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); if (!loaded) return null; return (
+
diff --git a/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss b/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss new file mode 100644 index 00000000..4e554b45 --- /dev/null +++ b/code/client/src/ui/pages/document/components/collaborators/Collaborators.scss @@ -0,0 +1,23 @@ +.collaborators { + position: absolute; + top: 0; + right: 0; + + > div { + width: 4vh; + height: 4vh; + border-radius: 50%; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + padding: 1vh; + margin: 1vh; + + display: flex; + justify-content: center; + align-items: center; + + a { + text-decoration: none !important; + color: white; + } + } +} diff --git a/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx b/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx new file mode 100644 index 00000000..a19f9249 --- /dev/null +++ b/code/client/src/ui/pages/document/components/collaborators/Collaborators.tsx @@ -0,0 +1,23 @@ +import useCollaborators from '@ui/pages/document/components/collaborators/useCollaborators'; +import { Link } from 'react-router-dom'; +import './Collaborators.scss'; + +function Collaborators() { + const collaborators = useCollaborators(); + return ( +
+ {collaborators.map(collaborator => { + const nameParts = collaborator.name.split(' '); + const initials = + nameParts.length > 1 ? `${nameParts[0][0]}${nameParts[nameParts.length - 1][0]}` : nameParts[0][0]; + return ( +
+ {initials} +
+ ); + })} +
+ ); +} + +export default Collaborators; diff --git a/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts b/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts new file mode 100644 index 00000000..75303838 --- /dev/null +++ b/code/client/src/ui/pages/document/components/collaborators/useCollaborators.ts @@ -0,0 +1,26 @@ +import { useState } from 'react'; +import { Collaborator } from '@notespace/shared/src/users/types'; +import useSocketListeners from '@services/communication/socket/useSocketListeners'; +import { useCommunication } from '@/contexts/communication/useCommunication'; + +function useCollaborators() { + const [collaborators, setCollaborators] = useState([]); + const { socket } = useCommunication(); + + const onCollaboratorsJoined = (users: Collaborator[]) => { + setCollaborators(prev => [...prev, ...users]); + }; + + const onCollaboratorLeft = (id: string) => { + setCollaborators(prev => prev.filter(u => u.id !== id)); + }; + + useSocketListeners(socket, { + joinedDocument: onCollaboratorsJoined, + leftDocument: onCollaboratorLeft, + }); + + return collaborators; +} + +export default useCollaborators; diff --git a/code/client/src/ui/pages/workspace/components/DocumentView.tsx b/code/client/src/ui/pages/workspace/components/DocumentView.tsx index aab14e5a..c22a9849 100644 --- a/code/client/src/ui/pages/workspace/components/DocumentView.tsx +++ b/code/client/src/ui/pages/workspace/components/DocumentView.tsx @@ -5,6 +5,7 @@ import { DocumentResource } from '@notespace/shared/src/workspace/types/resource import { Checkbox } from '@mui/material'; import { formatDate, formatTimePassed } from '@/utils/utils'; import { useEffect, useState } from 'react'; +import useWorkspace from '@/contexts/workspace/useWorkspace'; type DocumentViewProps = { document: DocumentResource; @@ -19,6 +20,7 @@ function DocumentView({ document, onSelect, onDelete, onRename, onDuplicate, sel const { wid } = useParams(); const { component, isEditing, setIsEditing } = useEditing(document.name || 'Untitled', onRename); const [isSelected, setSelected] = useState(selected); + const { isMember } = useWorkspace(); useEffect(() => { setSelected(selected); @@ -43,6 +45,7 @@ function DocumentView({ document, onSelect, onDelete, onRename, onDuplicate, sel onOpenInNewTab={() => window.open(`/workspaces/${wid}/${document.id}`, '_blank')} onDuplicate={onDuplicate} onDelete={onDelete} + enabled={isMember} > {isEditing ? DocumentComponent : {DocumentComponent}} diff --git a/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx b/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx index cc1fe218..53a44a24 100644 --- a/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx +++ b/code/client/src/ui/pages/workspaces/components/WorkspaceContextMenu.tsx @@ -35,9 +35,8 @@ function WorkspaceContextMenu({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentUser]); - if (!isMember) return null; return ( - +