Skip to content

Commit

Permalink
feature(editor) Cursors Based On Connections Instead Of Users. (#4794)
Browse files Browse the repository at this point in the history
- Comments now lose their user specific colours as the colours are
  now attached to connections, which would result in some inconsistent
  colours against comments versus the cursor for them.
- `MultiplayerCursors` now uses `excludeMyConnection` to get everything
  but the current connection for the current user.
- When calling `multiplayerColorFromIndex`, the `getConnectionById` function
  is used to obtain the `colorIndex` for the connection if it exists.
- `MultiplayerUserBar` now uses `excludeMyConnection` to get the connections
  excluding the current connection used by the current user.
- Added some utility functions related to connections in `comment-hooks.tsx`.
- Moved `colorIndex` from `UserMeta` to `ConnectionInfo`.
  • Loading branch information
seanparsons authored Jan 25, 2024
1 parent b910367 commit 25260fa
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 61 deletions.
2 changes: 1 addition & 1 deletion editor/liveblocks.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export type ConnectionInfo = {
id: number
startedAt: number
lastSeen: number
colorIndex: number | null
}

// Optionally, UserMeta represents static/readonly metadata on each user, as
Expand All @@ -83,7 +84,6 @@ export type UserMeta = {
id: string // Accessible through `user.id`
name: string | null
avatar: string | null
colorIndex: number | null
}

// Optionally, the type of custom events broadcast and listened to in this
Expand Down
17 changes: 3 additions & 14 deletions editor/src/components/canvas/controls/comment-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ CommentIndicators.displayName = 'CommentIndicators'

interface TemporaryCommentIndicatorProps {
position: CanvasPoint
bgColor: string
fgColor: string
avatarUrl: string | null
initials: string
}
Expand Down Expand Up @@ -123,14 +121,12 @@ function useCommentBeingComposed(): TemporaryCommentIndicatorProps | null {
if (collaborator == null) {
return {
initials: 'AN',
color: multiplayerColorFromIndex(null),
avatar: null,
}
}

return {
initials: multiplayerInitialsFromName(normalizeMultiplayerName(collaborator.name)),
color: multiplayerColorFromIndex(collaborator.colorIndex),
avatar: collaborator.avatar,
}
}, [collabs, myUserId])
Expand All @@ -141,8 +137,6 @@ function useCommentBeingComposed(): TemporaryCommentIndicatorProps | null {

return {
position: location,
bgColor: collaboratorInfo.color.background,
fgColor: collaboratorInfo.color.foreground,
avatarUrl: collaboratorInfo.avatar,
initials: collaboratorInfo.initials,
}
Expand All @@ -161,8 +155,6 @@ const CommentIndicatorsInner = React.memo(() => {
<CommentIndicatorUI
position={temporaryIndicatorData.position}
resolved={false}
bgColor={temporaryIndicatorData.bgColor}
fgColor={temporaryIndicatorData.fgColor}
avatarUrl={temporaryIndicatorData.avatarUrl}
avatarInitials={temporaryIndicatorData.initials}
isActive={true}
Expand All @@ -177,8 +169,6 @@ CommentIndicatorsInner.displayName = 'CommentIndicatorInner'
interface CommentIndicatorUIProps {
position: CanvasPoint
resolved: boolean
bgColor: string
fgColor: string
avatarInitials: string
avatarUrl?: string | null
isActive: boolean
Expand Down Expand Up @@ -232,8 +222,7 @@ function useIndicatorStyle(
}

export const CommentIndicatorUI = React.memo<CommentIndicatorUIProps>((props) => {
const { position, bgColor, fgColor, avatarUrl, avatarInitials, resolved, isActive, isRead } =
props
const { position, avatarUrl, avatarInitials, resolved, isActive, isRead } = props

const style = useIndicatorStyle(position, {
isRead: isRead ?? true,
Expand All @@ -246,7 +235,7 @@ export const CommentIndicatorUI = React.memo<CommentIndicatorUIProps>((props) =>
return (
<div style={style}>
<MultiplayerAvatar
color={{ background: bgColor, foreground: fgColor }}
color={multiplayerColorFromIndex(null)}
picture={avatarUrl}
name={avatarInitials}
/>
Expand Down Expand Up @@ -626,7 +615,7 @@ const CommentIndicatorWrapper = React.memo((props: CommentIndicatorWrapper) => {
>
<MultiplayerAvatar
name={multiplayerInitialsFromName(normalizeMultiplayerName(user.name))}
color={multiplayerColorFromIndex(user.colorIndex)}
color={multiplayerColorFromIndex(null)}
picture={user.avatar}
style={{ outline: 'none' }}
/>
Expand Down
36 changes: 28 additions & 8 deletions editor/src/components/canvas/multiplayer-presence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
} from '../../../liveblocks.config'
import {
getCollaborator,
getConnectionById,
useAddMyselfToCollaborators,
useCanComment,
useMyUserAndPresence,
useConnections,
} from '../../core/commenting/comment-hooks'
import { MetadataUtils } from '../../core/model/element-metadata-utils'
import { mapDropNulls } from '../../core/shared/array-utils'
Expand All @@ -32,7 +34,9 @@ import {
windowPoint,
zeroRectIfNullOrInfinity,
} from '../../core/shared/math-utils'
import type { MultiplayerColor } from '../../core/shared/multiplayer'
import {
excludeMyConnection,
isPlayerOnAnotherRemixRoute,
multiplayerColorFromIndex,
normalizeOthersList,
Expand Down Expand Up @@ -173,13 +177,15 @@ const MultiplayerCursors = React.memo(() => {
const me = useSelf()
const collabs = useStorage((store) => store.collaborators)
const others = useOthers((list) => {
const presences = normalizeOthersList(me.id, list)
const presences = excludeMyConnection(me.id, me.connectionId, list)
return presences.map((p) => ({
presenceInfo: p,
userInfo: getCollaborator(collabs, p),
}))
})

const connections = useConnections()

return (
<div
style={{
Expand All @@ -206,7 +212,10 @@ const MultiplayerCursors = React.memo(() => {
<MultiplayerCursor
key={`cursor-${other.presenceInfo.id}`}
name={other.userInfo.name}
colorIndex={other.userInfo.colorIndex}
colorIndex={
getConnectionById(connections, other.userInfo.id, other.presenceInfo.connectionId)
?.colorIndex ?? null
}
position={position}
userId={other.userInfo.id}
/>
Expand Down Expand Up @@ -433,12 +442,19 @@ const FollowingOverlay = React.memo(() => {
dispatch([switchEditorMode(EditorModes.selectMode(null, false, 'none'))])
}, [dispatch])

const followedUserColor = React.useMemo(() => {
return multiplayerColorFromIndex(followedUser?.colorIndex ?? null)
}, [followedUser])
const connections = useConnections()

const followedUserColor: MultiplayerColor = React.useMemo(() => {
if (followed == null) {
return multiplayerColorFromIndex(null)
} else {
return multiplayerColorFromIndex(
getConnectionById(connections, followed.id, followed.connectionId)?.colorIndex ?? null,
)
}
}, [connections, followed])

const collabs = useStorage((store) => store.collaborators)
const connections = useStorage((store) => store.connections)

const { user: myUser, presence: myPresence } = useMyUserAndPresence()
const others = useOthers((list) =>
Expand Down Expand Up @@ -564,15 +580,19 @@ const MultiplayerShadows = React.memo(() => {
}))
})

const connections = useConnections()

const shadows = React.useMemo(() => {
return others.flatMap(
(other) =>
other.presenceInfo.presence.activeFrames?.map((activeFrame) => ({
activeFrame: activeFrame,
colorIndex: other.userInfo.colorIndex,
colorIndex:
getConnectionById(connections, other.userInfo.id, other.presenceInfo.connectionId)
?.colorIndex ?? null,
})) ?? [],
)
}, [others])
}, [connections, others])

const myActiveFrames = useEditorState(
Substores.restOfEditor,
Expand Down
59 changes: 37 additions & 22 deletions editor/src/components/user-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ import { jsx } from '@emotion/react'
import type { CSSProperties } from 'react'
import { useOthers, useStatus, useStorage } from '../../liveblocks.config'
import { getUserPicture, isLoggedIn } from '../common/user'
import { getCollaborator, useMyUserAndPresence } from '../core/commenting/comment-hooks'
import {
getCollaborator,
getConnectionById,
useConnections,
useGetMyConnection,
useMyUserAndPresence,
} from '../core/commenting/comment-hooks'
import type { FollowTarget, MultiplayerColor } from '../core/shared/multiplayer'
import {
canFollowTarget,
excludeMyConnection,
followTarget,
isDefaultAuth0AvatarURL,
multiplayerColorFromIndex,
Expand Down Expand Up @@ -127,24 +134,25 @@ const MultiplayerUserBar = React.memo(() => {
}, [dispatch, url])

const collabs = useStorage((store) => store.collaborators)
const connections = useStorage((store) => store.connections)

const connections = useConnections()

const { user: myUser, presence: myPresence } = useMyUserAndPresence()
const sortAvatars = useSortMultiplayerUsers()
const isBeingFollowed = useIsBeingFollowed()

const others = useOthers((list) =>
list
.filter((entry) => entry.connectionId !== myPresence.connectionId)
.map((other) => {
return {
...getCollaborator(collabs, other),
following: other.presence.following,
connectionId: other.connectionId,
connectedAt: connections?.[other.id]?.[other.connectionId]?.startedAt ?? 0,
}
}),
)
const others = useOthers((list) => {
return excludeMyConnection(myPresence.id, myPresence.connectionId, list).map((other) => {
return {
...getCollaborator(collabs, other),
following: other.presence.following,
colorIndex:
getConnectionById(connections, other.id, other.connectionId)?.colorIndex ?? null,
connectionId: other.connectionId,
connectedAt: connections?.[other.id]?.[other.connectionId]?.startedAt ?? 0,
}
})
})

const mode = useEditorState(
Substores.restOfEditor,
Expand Down Expand Up @@ -278,7 +286,6 @@ const MultiplayerUserBar = React.memo(() => {
<MultiplayerAvatar
name={multiplayerInitialsFromName(name)}
tooltip={{ text: name, colored: false }}
color={multiplayerColorFromIndex(other.colorIndex)}
picture={other.avatar}
isOwner={isOwner}
isOffline={true}
Expand Down Expand Up @@ -307,7 +314,9 @@ const MultiplayerUserBar = React.memo(() => {
>
<MultiplayerAvatar
name={multiplayerInitialsFromName(myUser.name)}
color={multiplayerColorFromIndex(myUser.colorIndex)}
color={multiplayerColorFromIndex(
getConnectionById(connections, myUser.id, myPresence.connectionId)?.colorIndex ?? null,
)}
picture={myUser.avatar}
isOwner={amIOwner}
size={AvatarSize}
Expand All @@ -322,7 +331,7 @@ MultiplayerUserBar.displayName = 'MultiplayerUserBar'

export type MultiplayerAvatarProps = {
name: string
color: MultiplayerColor
color?: MultiplayerColor
picture?: string | null
onClick?: () => void
isBeingFollowed?: boolean
Expand Down Expand Up @@ -356,15 +365,21 @@ export const MultiplayerAvatar = React.memo((props: MultiplayerAvatarProps) => {
disabled={props.tooltip == null}
title={tooltipWithLineBreak}
placement='bottom'
backgroundColor={props.tooltip?.colored === true ? props.color.background : undefined}
textColor={props.tooltip?.colored === true ? props.color.foreground : undefined}
backgroundColor={
props.tooltip?.colored === true && props.color != null ? props.color.background : undefined
}
textColor={
props.tooltip?.colored === true && props.color != null ? props.color.foreground : undefined
}
>
<div
style={{
width: props.size ?? 25.5,
height: props.size ?? 25.5,
backgroundColor: props.isOffline ? colorTheme.bg4.value : props.color.background,
color: props.isOffline ? colorTheme.fg2.value : props.color.foreground,
backgroundColor:
props.isOffline || props.color == null ? colorTheme.bg4.value : props.color.background,
color:
props.isOffline || props.color == null ? colorTheme.fg2.value : props.color.foreground,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
Expand All @@ -378,7 +393,7 @@ export const MultiplayerAvatar = React.memo((props: MultiplayerAvatarProps) => {
? `1px solid ${colorTheme.bg1.value}`
: `1px solid ${colorTheme.transparent.value}`,
boxShadow:
props.isBeingFollowed === true
props.isBeingFollowed === true && props.color != null
? `0px 0px 0px 2.5px ${props.color.background}`
: `0px 0px 0px 2.5px ${colorTheme.transparent.value}`,
...props.style,
Expand Down
Loading

0 comments on commit 25260fa

Please sign in to comment.