Skip to content

Commit

Permalink
Collaborators from the DB (#4946)
Browse files Browse the repository at this point in the history
* wip

* if not using the bff, get collabs from liveblocks

* refresh collaborators

* remove old cruft

* revert

* split calls

* remove liveblocks client

* tests

* readd deprecated

* collaborator import

* clean up

* more cleanup

* add missing field

* fix local port

* restore ordering

* handle response

* fix circular dependency

* remove remnant

* multiplayer substate
  • Loading branch information
ruggi authored Feb 26, 2024
1 parent 32c0338 commit 7adcafa
Show file tree
Hide file tree
Showing 31 changed files with 394 additions and 303 deletions.
35 changes: 11 additions & 24 deletions editor/liveblocks.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
type WindowPoint,
} from './src/core/shared/math-utils'
import type { RemixPresence } from './src/core/shared/multiplayer'
import { projectIdToRoomId } from './src/core/shared/multiplayer'
import { HEADERS } from './src/common/server'
import urljoin from 'url-join'
import { getCollaborators } from './src/components/editor/server'

export const liveblocksThrottle = 100 // ms

Expand Down Expand Up @@ -66,7 +66,7 @@ type SceneIdToRouteMapping = LiveObject<{ [sceneId: string]: string }>
export type Storage = {
// author: LiveObject<{ firstName: string, lastName: string }>,
// ...
collaborators: LiveObject<{ [userId: string]: User }> // this is an object (and not a list) so we can quickly check if a user is a collaborator, but later we can extend the information by storing something more than a boolean (e.g. a permission level)
collaborators: LiveObject<{ [userId: string]: User }> // TODO remove collaborators when the BFF is on
userReadStatusesByThread: LiveObject<{ [threadId: string]: UserReadStatuses }>
remixSceneRoutes: LiveObject<{ [userId: string]: SceneIdToRouteMapping }>
connections: LiveObject<{ [userId: string]: ConnectionInfo[] }>
Expand All @@ -79,7 +79,7 @@ export type UserReadStatuses = LiveObject<UserReadStatusesMeta>

export function initialStorage(): Storage {
return {
collaborators: new LiveObject(),
collaborators: new LiveObject(), // TODO remove this when the BFF is on
userReadStatusesByThread: new LiveObject(),
remixSceneRoutes: new LiveObject(),
connections: new LiveObject(),
Expand Down Expand Up @@ -164,26 +164,28 @@ export const {
// Used only for Comments. Return a list of user information retrieved
// from `userIds`. This info is used in comments, mentions etc.

// This should be provided by the Utopia backend, but as a quick hack I store the user data in the room storage.
// This means we need the room id to get the users, which is not provided to this function, but fortunately we can
// recreate that from the project id.
const projectId = getProjectID()
if (projectId == null) {
return []
}

const users = await getAllUsersFromRoom(projectIdToRoomId(projectId))
const users = await getCollaborators(projectId)
return users.filter((u) => userIds.includes(u.id))
},
async resolveMentionSuggestions({ text, roomId }) {
async resolveMentionSuggestions({ text }) {
// Used only for Comments. Return a list of userIds where the name matches `text`.
// These userIds are used to create a mention list when typing in the
// composer.
//
// For example when you type "@jo", `text` will be `"jo"`, and
// you should to return an array with John and Joanna's userIds.

const users = await getAllUsersFromRoom(roomId)
const projectId = getProjectID()
if (projectId == null) {
return []
}

const users = await getCollaborators(projectId)

if (text == null) {
return users.map((u) => u.id)
Expand All @@ -200,18 +202,3 @@ export const {
.map((u) => u.id)
},
})

async function getAllUsersFromRoom(roomId: string) {
const room = liveblocksClient.getRoom(roomId)
if (room == null) {
return []
}

const storage = await room.getStorage()

const collabs = storage.root.get('collaborators') as LiveObject<{ [userId: string]: User }>
if (collabs == null) {
return []
}
return Object.values(collabs.toObject()).map((u) => u.toObject())
}
24 changes: 7 additions & 17 deletions editor/src/components/canvas/controls/comment-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,22 @@ import { AnimatePresence, motion, useAnimate } from 'framer-motion'
import type { CSSProperties } from 'react'
import React from 'react'
import type { ThreadMetadata, UserMeta } from '../../../../liveblocks.config'
import { useEditThreadMetadata, useStorage } from '../../../../liveblocks.config'
import { useEditThreadMetadata } from '../../../../liveblocks.config'
import {
getCollaboratorById,
getThreadLocationOnCanvas,
useActiveThreads,
useCanvasCommentThreadAndLocation,
useCanvasLocationOfThread,
useCollaborators,
useMyThreadReadStatus,
} from '../../../core/commenting/comment-hooks'
import type {
CanvasPoint,
CanvasRectangle,
LocalPoint,
MaybeInfinityCanvasRectangle,
} from '../../../core/shared/math-utils'
import type { CanvasPoint } from '../../../core/shared/math-utils'
import {
canvasPoint,
distance,
getLocalPointInNewParentContext,
isNotNullFiniteRectangle,
localPoint,
nullIfInfinity,
offsetPoint,
pointDifference,
windowPoint,
Expand Down Expand Up @@ -65,11 +59,7 @@ import { useRefAtom } from '../../editor/hook-utils'
import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template'
import * as PP from '../../../core/shared/property-path'
import { CanvasOffsetWrapper } from './canvas-offset-wrapper'
import {
canvasThreadMetadata,
liveblocksThreadMetadataToUtopia,
utopiaThreadMetadataToLiveblocksPartial,
} from '../../../core/commenting/comment-types'
import { utopiaThreadMetadataToLiveblocksPartial } from '../../../core/commenting/comment-types'

export const CommentIndicators = React.memo(() => {
const projectId = useEditorState(
Expand Down Expand Up @@ -118,12 +108,12 @@ function useCommentBeingComposed(): TemporaryCommentIndicatorProps | null {
commentBeingComposed ?? { type: 'existing', threadId: 'dummy-thread-id' }, // this is as a placeholder for nulls
)

const collabs = useStorage((storage) => storage.collaborators)
const collabs = useCollaborators()

const myUserId = useMyUserId()

const collaboratorInfo = React.useMemo(() => {
const collaborator = optionalMap((id) => collabs[id], myUserId)
const collaborator = optionalMap((id) => getCollaboratorById(collabs, id), myUserId)
if (collaborator == null) {
return {
initials: 'AN',
Expand Down Expand Up @@ -257,7 +247,7 @@ interface CommentIndicatorProps {
const CommentIndicator = React.memo(({ thread }: CommentIndicatorProps) => {
const dispatch = useDispatch()

const collabs = useStorage((storage) => storage.collaborators)
const collabs = useCollaborators()

const firstComment = React.useMemo(() => {
return thread.comments[0]
Expand Down
36 changes: 21 additions & 15 deletions editor/src/components/canvas/multiplayer-presence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ import {
useOthersListener,
useRoom,
useSelf,
useStorage,
useUpdateMyPresence,
} from '../../../liveblocks.config'
import {
getCollaborator,
getConnectionById,
useAddMyselfToCollaborators,
useCanComment,
useMyUserAndPresence,
useConnections,
useCollaborators,
getCollaboratorById,
useAddMyselfToCollaborators_DEPRECATED,
} from '../../core/commenting/comment-hooks'
import { MetadataUtils } from '../../core/model/element-metadata-utils'
import { mapDropNulls } from '../../core/shared/array-utils'
Expand Down Expand Up @@ -65,6 +66,7 @@ import {
useMyUserId,
useRemixPresence,
useMonitorConnection,
useLoadCollaborators,
} from '../../core/shared/multiplayer-hooks'
import { CanvasOffsetWrapper } from './controls/canvas-offset-wrapper'
import { when } from '../../utils/react-conditionals'
Expand Down Expand Up @@ -113,7 +115,9 @@ export const MultiplayerPresence = React.memo(() => {
'MultiplayerPresence mode',
)

useAddMyselfToCollaborators()
useAddMyselfToCollaborators_DEPRECATED()
useLoadCollaborators()

useStoreConnection()
useMonitorConnection()

Expand All @@ -124,8 +128,8 @@ export const MultiplayerPresence = React.memo(() => {
return
}
updateMyPresence({
canvasScale,
canvasOffset,
canvasScale: canvasScale,
canvasOffset: canvasOffset,
following: isFollowMode(mode) ? mode.playerId : null,
remix: remixPresence,
})
Expand Down Expand Up @@ -175,7 +179,7 @@ MultiplayerPresence.displayName = 'MultiplayerPresence'

const MultiplayerCursors = React.memo(() => {
const me = useSelf()
const collabs = useStorage((store) => store.collaborators)
const collabs = useCollaborators()
const others = useOthers((list) => {
const presences = excludeMyConnection(me.id, me.connectionId, list)
return presences.map((p) => ({
Expand Down Expand Up @@ -369,13 +373,15 @@ const FollowingOverlay = React.memo(() => {
dispatch([switchEditorMode(EditorModes.selectMode(null, false, 'none'))])
}, [dispatch])

const collabs = useCollaborators()

const followed = React.useMemo(() => {
return room.getOthers().find(isFollowTarget) ?? null
}, [room, isFollowTarget])

const followedUser = useStorage((store) =>
followed != null ? store.collaborators[followed.id] : null,
)
const followedUser = React.useMemo(() => {
return followed != null ? getCollaboratorById(collabs, followed.id) : null
}, [followed, collabs])

const remixPresence = useRemixPresence()

Expand Down Expand Up @@ -454,8 +460,6 @@ const FollowingOverlay = React.memo(() => {
}
}, [connections, followed])

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

const { user: myUser, presence: myPresence } = useMyUserAndPresence()
const others = useOthers((list) =>
list
Expand Down Expand Up @@ -568,15 +572,15 @@ const MultiplayerShadows = React.memo(() => {
const myUserId = useMyUserId()
const updateMyPresence = useUpdateMyPresence()

const collabs = useStorage((store) => store.collaborators)
const collabs = useCollaborators()
const others = useOthers((list) => {
if (myUserId == null) {
return []
}
const presences = normalizeOthersList(myUserId, list)
return presences.map((p) => ({
presenceInfo: p,
userInfo: collabs[p.id],
userInfo: getCollaboratorById(collabs, p.id),
}))
})

Expand All @@ -588,8 +592,10 @@ const MultiplayerShadows = React.memo(() => {
other.presenceInfo.presence.activeFrames?.map((activeFrame) => ({
activeFrame: activeFrame,
colorIndex:
getConnectionById(connections, other.userInfo.id, other.presenceInfo.connectionId)
?.colorIndex ?? null,
other.userInfo != null
? getConnectionById(connections, other.userInfo.id, other.presenceInfo.connectionId)
?.colorIndex ?? null
: null,
})) ?? [],
)
}, [connections, others])
Expand Down
7 changes: 7 additions & 0 deletions editor/src/components/editor/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import type { ProjectServerState } from './store/project-server-state'
import type { SetHuggingParentToFixed } from '../canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy'
import type { MapLike } from 'typescript'
import type { CommentFilterMode } from '../inspector/sections/comment-section'
import type { Collaborator } from '../../core/shared/multiplayer'
export { isLoggedIn, loggedInUser, notLoggedIn } from '../../common/user'
export type { LoginState, UserDetails } from '../../common/user'

Expand Down Expand Up @@ -1082,6 +1083,11 @@ export interface SetCommentFilterMode {
commentFilterMode: CommentFilterMode
}

export interface SetCollaborators {
action: 'SET_COLLABORATORS'
collaborators: Collaborator[]
}

export type EditorAction =
| ClearSelection
| InsertJSXElement
Expand Down Expand Up @@ -1257,6 +1263,7 @@ export type EditorAction =
| UpdateCodeFromCollaborationUpdate
| SetCommentFilterMode
| SetForking
| SetCollaborators

export type DispatchPriority =
| 'everyone'
Expand Down
9 changes: 9 additions & 0 deletions editor/src/components/editor/actions/action-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ import type {
UpdateCodeFromCollaborationUpdate,
SetCommentFilterMode,
SetForking,
SetCollaborators,
} from '../action-types'
import type { InsertionSubjectWrapper, Mode } from '../editor-modes'
import { EditorModes, insertionSubject } from '../editor-modes'
Expand Down Expand Up @@ -246,6 +247,7 @@ import type { PostActionChoice } from '../../canvas/canvas-strategies/post-actio
import type { ProjectServerState } from '../store/project-server-state'
import type { SetHuggingParentToFixed } from '../../canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy'
import type { CommentFilterMode } from '../../inspector/sections/comment-section'
import type { Collaborator } from '../../../core/shared/multiplayer'

export function clearSelection(): EditorAction {
return {
Expand Down Expand Up @@ -1727,3 +1729,10 @@ export function setCommentFilterMode(commentFilterMode: CommentFilterMode): SetC
commentFilterMode: commentFilterMode,
}
}

export function setCollaborators(collaborators: Collaborator[]): SetCollaborators {
return {
action: 'SET_COLLABORATORS',
collaborators: collaborators,
}
}
1 change: 1 addition & 0 deletions editor/src/components/editor/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export function isTransientAction(action: EditorAction): boolean {
case 'UPDATE_PROJECT_SERVER_STATE':
case 'SET_COMMENT_FILTER_MODE':
case 'SET_FORKING':
case 'SET_COLLABORATORS':
return true

case 'TRUE_UP_ELEMENTS':
Expand Down
13 changes: 9 additions & 4 deletions editor/src/components/editor/actions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ import type {
UpdateCodeFromCollaborationUpdate,
SetCommentFilterMode,
SetForking,
SetCollaborators,
} from '../action-types'
import { isLoggedIn } from '../action-types'
import type { Mode } from '../editor-modes'
Expand Down Expand Up @@ -434,10 +435,7 @@ import { addButtonPressed, removeButtonPressed } from '../../../utils/mouse'
import { stripLeadingSlash } from '../../../utils/path-utils'
import { pickCanvasStateFromEditorState } from '../../canvas/canvas-strategies/canvas-strategies'
import { getEscapeHatchCommands } from '../../canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy'
import {
canCopyElement,
isAllowedToReparent,
} from '../../canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers'
import { canCopyElement } from '../../canvas/canvas-strategies/strategies/reparent-helpers/reparent-helpers'
import {
getReparentOutcome,
pathToReparent,
Expand Down Expand Up @@ -914,6 +912,7 @@ export function restoreEditorState(
activeFrames: currentEditor.activeFrames,
commentFilterMode: currentEditor.commentFilterMode,
forking: currentEditor.forking,
collaborators: currentEditor.collaborators,
}
}

Expand Down Expand Up @@ -2082,6 +2081,12 @@ export const UPDATE_FNS = {
forking: action.forking,
}
},
SET_COLLABORATORS: (action: SetCollaborators, editor: EditorModel): EditorModel => {
return {
...editor,
collaborators: action.collaborators,
}
},
UPDATE_GITHUB_OPERATIONS: (action: UpdateGithubOperations, editor: EditorModel): EditorModel => {
const operations = [...editor.githubOperations]
switch (action.type) {
Expand Down
2 changes: 1 addition & 1 deletion editor/src/components/editor/editor-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ import { generateUUID } from '../../utils/utils'
import { isLiveblocksEnabled } from './liveblocks-utils'
import type { Storage, Presence, RoomEvent, UserMeta } from '../../../liveblocks.config'
import LiveblocksProvider from '@liveblocks/yjs'
import { isRoomId, projectIdToRoomId } from '../../core/shared/multiplayer'
import { EditorModes } from './editor-modes'
import { useDataThemeAttributeOnBody } from '../../core/commenting/comment-hooks'
import { CollaborationStateUpdater } from './store/collaboration-state'
Expand All @@ -74,6 +73,7 @@ import { getPermissions } from './store/permissions'
import { CommentMaintainer } from '../../core/commenting/comment-maintainer'
import { useIsLoggedIn, useLiveblocksConnectionListener } from '../../core/shared/multiplayer-hooks'
import { ForkSearchParamKey, ProjectForkFlow } from './project-fork-flow'
import { isRoomId, projectIdToRoomId } from '../../utils/room-id'

const liveModeToastId = 'play-mode-toast'

Expand Down
Loading

0 comments on commit 7adcafa

Please sign in to comment.