feat: add live users avatar indicators

This commit is contained in:
swve 2024-04-05 00:49:14 +02:00
parent 54718d7111
commit 6a47202c78
5 changed files with 100 additions and 15 deletions

View file

@ -0,0 +1,67 @@
import React, { useEffect, useState } from 'react'
import UserAvatar from '../UserAvatar'
import { useSession } from '@components/Contexts/SessionContext'
import { getUserAvatarMediaDirectory } from '@services/media/media';
import { getCollaborationServerUrl } from '@services/config/config';
import { useOrg } from '@components/Contexts/OrgContext';
type ActiveAvatarsProps = {
mouseMovements: any;
userRandomColor: string;
}
function ActiveAvatars(props: ActiveAvatarsProps) {
const session = useSession() as any;
const org = useOrg() as any;
const [activeUsers, setActiveUsers] = useState({} as any);
/* Collaboration Features */
const collab = getCollaborationServerUrl()
const isCollabEnabledOnThisOrg = org?.config.config.GeneralConfig.collaboration && collab
// Get users from the mouseMovements object
useEffect(() => {
const users: any = {};
Object.keys(props.mouseMovements).forEach((key) => {
users[props.mouseMovements[key].user.user_uuid] = props.mouseMovements[key].user;
});
// Remove the current user from the list
delete users[session.user.user_uuid];
setActiveUsers(users);
}
, [props.mouseMovements, session.user, org]);
return (
<div className=''>
<div className='flex -space-x-2 transition-all ease-linear'>
{isCollabEnabledOnThisOrg && Object.keys(activeUsers).map((key) => (
<div className='flex' style={{ position: 'relative' }} key={key}>
<UserAvatar
key={key}
width={40}
border="border-4"
rounded="rounded-full"
avatar_url={getUserAvatarMediaDirectory(activeUsers[key].user_uuid, activeUsers[key].avatar_image) as string}
/>
<div className="h-2 w-2 rounded-full" style={{ position: 'absolute', bottom: -5, right: 16, backgroundColor: props.mouseMovements[key].color }} />
</div>
))}
{session.isAuthenticated && (
<div className='z-50'>
<UserAvatar
width={40}
border="border-4"
rounded="rounded-full"
/>
</div>
)}
</div>
</div>
)
}
export default ActiveAvatars

View file

@ -48,6 +48,7 @@ import UserAvatar from '../UserAvatar'
import randomColor from 'randomcolor' import randomColor from 'randomcolor'
import Collaboration from '@tiptap/extension-collaboration' import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import ActiveAvatars from './ActiveAvatars'
interface Editor { interface Editor {
content: string content: string
@ -59,6 +60,7 @@ interface Editor {
hocuspocusProvider: any, hocuspocusProvider: any,
isCollabEnabledOnThisOrg: boolean isCollabEnabledOnThisOrg: boolean
userRandomColor: string userRandomColor: string
mouseMovements: any
setContent: (content: string) => void setContent: (content: string) => void
} }
@ -137,13 +139,14 @@ function Editor(props: Editor) {
...(props.isCollabEnabledOnThisOrg ? [ ...(props.isCollabEnabledOnThisOrg ? [
Collaboration.configure({ Collaboration.configure({
document: props.hocuspocusProvider?.document, document: props.hocuspocusProvider?.document,
}), }),
CollaborationCursor.configure({ CollaborationCursor.configure({
provider: props.hocuspocusProvider, provider: props.hocuspocusProvider,
user: { user: {
name: props.session.user.first_name + ' ' + props.session.user.last_name, name: props.session.user.first_name + ' ' + props.session.user.last_name,
color: props.userRandomColor , color: props.userRandomColor,
}, },
}), }),
] : []), ] : []),
@ -267,14 +270,7 @@ function Editor(props: Editor) {
/> />
<EditorUserProfileWrapper> <EditorUserProfileWrapper>
{!session.isAuthenticated && <span>Loading</span>} <ActiveAvatars userRandomColor={props.userRandomColor} mouseMovements={props.mouseMovements} />
{session.isAuthenticated && (
<UserAvatar
width={40}
border="border-4"
rounded="rounded-full"
/>
)}
</EditorUserProfileWrapper> </EditorUserProfileWrapper>
</EditorUsersSection> </EditorUsersSection>
</EditorTop> </EditorTop>

View file

@ -100,6 +100,14 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
document: doc, document: doc,
// TODO(alpha code): This whole block of code should be improved to something more efficient and less hacky // 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 }) => { onAwarenessUpdate: ({ states }) => {
const usersStates = states; const usersStates = states;
/* Showing user mouse movement */ /* Showing user mouse movement */
@ -121,6 +129,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
}; };
} }
); );
} }
}); });
@ -162,7 +171,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
return ( return (
<> <>
<Toast></Toast> <Toast></Toast>
<MouseMovements movements={mouseMovements} onlinePageInstanceID={onlinePageInstanceID} /> <MouseMovements org={props.org} movements={mouseMovements} onlinePageInstanceID={onlinePageInstanceID} />
<OrgProvider orgslug={props.org.slug}> <OrgProvider orgslug={props.org.slug}>
{!session.isLoading && (<Editor {!session.isLoading && (<Editor
org={props.org} org={props.org}
@ -175,6 +184,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
hocuspocusProvider={provider} hocuspocusProvider={provider}
isCollabEnabledOnThisOrg={isCollabEnabledOnThisOrg} isCollabEnabledOnThisOrg={isCollabEnabledOnThisOrg}
userRandomColor={thisPageColor} userRandomColor={thisPageColor}
mouseMovements={mouseMovements}
></Editor>)} ></Editor>)}
</OrgProvider> </OrgProvider>
</> </>

View file

@ -1,5 +1,6 @@
import { m, motion } from "framer-motion"; import { getCollaborationServerUrl } from "@services/config/config";
import React from 'react' import { motion } from "framer-motion";
import React, { useEffect } from 'react'
interface User { interface User {
user_uuid: string; user_uuid: string;
@ -18,12 +19,24 @@ interface Movement {
interface MouseMovementsProps { interface MouseMovementsProps {
movements: Record<string, Movement>; movements: Record<string, Movement>;
onlinePageInstanceID: string; onlinePageInstanceID: string;
org ?: any;
} }
function MouseMovements({ movements, onlinePageInstanceID }: MouseMovementsProps): JSX.Element { function MouseMovements({ movements, onlinePageInstanceID, org }: MouseMovementsProps): JSX.Element {
/* Collaboration config */
const collab = getCollaborationServerUrl()
const isCollabEnabledOnThisOrg = org?.config.config.GeneralConfig.collaboration && collab
useEffect(() => {
}
, [movements, org]);
return ( return (
<div> <div>
{Object.keys(movements).map((key) => ( {isCollabEnabledOnThisOrg && Object.keys(movements).map((key) => (
movements[key].onlinePageInstanceID !== onlinePageInstanceID && (<motion.div movements[key].onlinePageInstanceID !== onlinePageInstanceID && (<motion.div
key={key} key={key}
className="flex -space-x-2" className="flex -space-x-2"

View file

@ -38,7 +38,6 @@ function UserAvatar(props: UserAvatarProps) {
return predefinedAvatar return predefinedAvatar
} else { } else {
if (props.avatar_url) { if (props.avatar_url) {
console.log('avatar_url', props.avatar_url)
return props.avatar_url return props.avatar_url
} else { } else {
if (session.user.avatar_image) { if (session.user.avatar_image) {