diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index fd8339d7..8779b264 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -58,6 +58,7 @@ interface Editor { ydoc: any hocuspocusProvider: any, isCollabEnabledOnThisOrg: boolean + userRandomColor: string setContent: (content: string) => void } @@ -142,7 +143,7 @@ function Editor(props: Editor) { provider: props.hocuspocusProvider, user: { name: props.session.user.first_name + ' ' + props.session.user.last_name, - color: randomColor({ luminosity: 'light' }), + color: props.userRandomColor , }, }), ] : []), diff --git a/apps/web/components/Objects/Editor/EditorWrapper.tsx b/apps/web/components/Objects/Editor/EditorWrapper.tsx index 8883969a..e910f76b 100644 --- a/apps/web/components/Objects/Editor/EditorWrapper.tsx +++ b/apps/web/components/Objects/Editor/EditorWrapper.tsx @@ -11,7 +11,10 @@ import { useSession } from '@components/Contexts/SessionContext' import { HocuspocusProvider } from '@hocuspocus/provider' import * as Y from 'yjs' import { IndexeddbPersistence } from 'y-indexeddb' -import { LEARNHOUSE_COLLABORATION_WS_URL, getCollaborationServerUrl } from '@services/config/config' +import { getCollaborationServerUrl } from '@services/config/config' +import randomColor from 'randomcolor' +import MouseMovements from './MouseMovements' +import { v4 as uuidv4 } from 'uuid'; interface EditorWrapperProps { content: string @@ -24,16 +27,43 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { const session = useSession() as any // Define provider in the state const [provider, setProvider] = React.useState(null); + const [thisPageColor, setThisPageColor] = useState(randomColor({ luminosity: 'light' }) as string) + let uuid = uuidv4(); + const [onlinePageInstanceID, setOnlinePageInstanceID] = useState(uuid as string) /* Collaboration Features */ const collab = getCollaborationServerUrl() const isCollabEnabledOnThisOrg = props.org.config.config.GeneralConfig.collaboration && collab const doc = new Y.Doc() + // mouse movement + const [mouseMovements, setMouseMovements] = useState({} as any); + const timeoutRef = useRef(null); + + const debouncedSetMouseMovements = (newMovements: any) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(() => { + setMouseMovements(newMovements); + }, 10); + }; // Store the Y document in the browser new IndexeddbPersistence(props.activity.activity_uuid, doc) + document.addEventListener("mousemove", (event) => { + // Share any information you like + provider?.setAwarenessField("userMouseMovement", { + user: session.user, + mouseX: event.clientX, + mouseY: event.clientY, + color: thisPageColor, + onlineInstanceID: onlinePageInstanceID + }); + }); + async function setContent(content: any) { let activity = props.activity @@ -57,6 +87,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { + // Create a ref to store the last save timestamp of each user const lastSaveTimestampRef = useRef({}) as any; @@ -71,8 +102,30 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { // TODO(alpha code): This whole block of code should be improved to something more efficient and less hacky onAwarenessUpdate: ({ states }) => { const usersStates = states; + /* Showing user mouse movement */ + usersStates.forEach((userState: any) => { + if (userState.userMouseMovement) { + const userMouseMovement = userState.userMouseMovement; - // Check if a user has saved the document + // Update the mouse movements state + debouncedSetMouseMovements((prevMovements: any) => { + return { + ...prevMovements, + [userMouseMovement.user.user_uuid]: { + user: userMouseMovement.user, + mouseX: userMouseMovement.mouseX, + mouseY: userMouseMovement.mouseY, + color: userMouseMovement.color, + onlinePageInstanceID: userMouseMovement.onlineInstanceID + }, + }; + } + ); + } + }); + + + /* Notifiying if a user has saved course content */ usersStates.forEach((userState: any) => { if (userState.savings_states) { const savingsState = userState.savings_states @@ -109,6 +162,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { return ( <> + {!session.isLoading && ()} @@ -127,4 +182,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element { } } + + export default EditorWrapper + diff --git a/apps/web/components/Objects/Editor/MouseMovements.tsx b/apps/web/components/Objects/Editor/MouseMovements.tsx new file mode 100644 index 00000000..8c0b7070 --- /dev/null +++ b/apps/web/components/Objects/Editor/MouseMovements.tsx @@ -0,0 +1,67 @@ +import { m, motion } from "framer-motion"; +import React from 'react' + +interface User { + user_uuid: string; + first_name: string; + last_name: string; +} + +interface Movement { + user: User; + mouseX: number; + mouseY: number; + color: string; + onlinePageInstanceID: string; +} + +interface MouseMovementsProps { + movements: Record; + onlinePageInstanceID: string; +} + +function MouseMovements({ movements, onlinePageInstanceID }: MouseMovementsProps): JSX.Element { + return ( +
+ {Object.keys(movements).map((key) => ( + movements[key].onlinePageInstanceID !== onlinePageInstanceID && ( + +
{movements[key].user.first_name} {movements[key].user.last_name}
+
) + + ))} +
+ ); +} + +function CursorSvg({ color }: { color: string }) { + return ( + + + + ); +} + +export default MouseMovements; \ No newline at end of file