mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
212 lines
6.7 KiB
TypeScript
212 lines
6.7 KiB
TypeScript
'use client'
|
|
import { default as React, useEffect, useRef, useState } from 'react'
|
|
import Editor from './Editor'
|
|
import { updateActivity } from '@services/courses/activities'
|
|
import { toast } from 'react-hot-toast'
|
|
import Toast from '@components/StyledElements/Toast/Toast'
|
|
import { OrgProvider } from '@components/Contexts/OrgContext'
|
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
|
|
|
// Collaboration
|
|
import { HocuspocusProvider } from '@hocuspocus/provider'
|
|
import * as Y from 'yjs'
|
|
import { IndexeddbPersistence } from 'y-indexeddb'
|
|
import { getCollaborationServerUrl } from '@services/config/config'
|
|
import randomColor from 'randomcolor'
|
|
import MouseMovements from './MouseMovements'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { debounce } from '@/lib/utils'
|
|
|
|
interface EditorWrapperProps {
|
|
content: string
|
|
activity: any
|
|
course: any
|
|
org: any
|
|
}
|
|
|
|
function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
|
const session = useLHSession() as any
|
|
const access_token = session?.data?.tokens?.access_token
|
|
// Define provider in the state
|
|
const [provider, setProvider] = React.useState<HocuspocusProvider | null>(
|
|
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.features.collaboration.enabled && collab
|
|
const doc = new Y.Doc()
|
|
// mouse movement
|
|
const [mouseMovements, setMouseMovements] = useState({} as any)
|
|
const timeoutRef = useRef<NodeJS.Timeout | null>(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',
|
|
debounce((event: MouseEvent) => {
|
|
provider?.setAwarenessField('userMouseMovement', {
|
|
user: session.data.user,
|
|
mouseX: event.clientX,
|
|
mouseY: event.clientY,
|
|
color: thisPageColor,
|
|
onlineInstanceID: onlinePageInstanceID,
|
|
})
|
|
}, 300)
|
|
)
|
|
|
|
async function setContent(content: any) {
|
|
let activity = props.activity
|
|
activity.content = content
|
|
|
|
provider?.setAwarenessField('savings_states', {
|
|
[session.data.user.user_uuid]: {
|
|
status: 'action_save',
|
|
timestamp: new Date().toISOString(),
|
|
user: session.data.user,
|
|
},
|
|
})
|
|
|
|
toast.promise(
|
|
updateActivity(activity, activity.activity_uuid, access_token),
|
|
{
|
|
loading: 'Saving...',
|
|
success: <b>Activity saved!</b>,
|
|
error: <b>Could not save.</b>,
|
|
}
|
|
)
|
|
}
|
|
|
|
// Create a ref to store the last save timestamp of each user
|
|
const lastSaveTimestampRef = useRef({}) as any
|
|
|
|
useEffect(() => {
|
|
// Check if provider is not already set
|
|
if (!provider) {
|
|
const newProvider = new HocuspocusProvider({
|
|
url: collab,
|
|
name: props.activity.activity_uuid,
|
|
document: doc,
|
|
|
|
// TODO(alpha code): This whole block of code should be improved to something more efficient and less hacky
|
|
onConnect: () => {
|
|
// Set the online page instance ID
|
|
setOnlinePageInstanceID(uuidv4())
|
|
|
|
// Set the user color
|
|
setThisPageColor(randomColor({ luminosity: 'light' }) as string)
|
|
},
|
|
|
|
onAwarenessUpdate: ({ states }) => {
|
|
const usersStates = states
|
|
/* Showing user mouse movement */
|
|
usersStates.forEach((userState: any) => {
|
|
if (userState.userMouseMovement) {
|
|
const userMouseMovement = userState.userMouseMovement
|
|
|
|
// 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
|
|
|
|
// Check if a user has saved the document
|
|
Object.keys(savingsState).forEach((user) => {
|
|
const userObj = savingsState[user].user
|
|
const status = savingsState[user].status
|
|
const timestamp = savingsState[user].timestamp
|
|
|
|
// Get the current timestamp
|
|
const currentTimestamp = new Date().getTime()
|
|
|
|
// If the user has saved the document and the timestamp is close to the current timestamp, show the toast
|
|
if (
|
|
status === 'action_save' &&
|
|
Math.abs(currentTimestamp - new Date(timestamp).getTime()) <
|
|
10
|
|
) {
|
|
// 5000 milliseconds = 5 seconds
|
|
// Update the last save timestamp for this user
|
|
lastSaveTimestampRef.current[user] = timestamp
|
|
|
|
toast.success(
|
|
`${userObj.first_name} ${userObj.last_name} has saved the document`
|
|
)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
// Set the new provider
|
|
setProvider(newProvider)
|
|
}
|
|
}, [])
|
|
|
|
{
|
|
return (
|
|
<>
|
|
<Toast></Toast>
|
|
<MouseMovements
|
|
org={props.org}
|
|
movements={mouseMovements}
|
|
onlinePageInstanceID={onlinePageInstanceID}
|
|
/>
|
|
<OrgProvider orgslug={props.org.slug}>
|
|
{!session.isLoading && (
|
|
<Editor
|
|
org={props.org}
|
|
course={props.course}
|
|
activity={props.activity}
|
|
content={props.content}
|
|
setContent={setContent}
|
|
session={session}
|
|
ydoc={doc}
|
|
hocuspocusProvider={provider}
|
|
isCollabEnabledOnThisOrg={isCollabEnabledOnThisOrg}
|
|
userRandomColor={thisPageColor}
|
|
mouseMovements={mouseMovements}
|
|
></Editor>
|
|
)}
|
|
</OrgProvider>
|
|
</>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default EditorWrapper
|