Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collaborators from the DB #4946

Merged
merged 23 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading