From 6a7dc121626426caa979a81301bb8145c886b6ec Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Thu, 18 May 2023 11:59:46 +0100 Subject: [PATCH] Switch to new collaborators component (#1405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Followup to https://github.com/tldraw/brivate/pull/1584 - Removes the old collaborators component, replacing with the new one. - Removes the associated debug flag ### Change Type - [ ] `patch` — Bug Fix - [ ] `minor` — New Feature - [x] `major` — Breaking Change - [ ] `dependencies` — Dependency Update (publishes a `patch` release, for devDependencies use `internal`) - [ ] `documentation` — Changes to the documentation only (will not publish a new version) - [ ] `tests` — Changes to any testing-related code only (will not publish a new version) - [ ] `internal` — Any other changes that don't affect the published package (will not publish a new version) ### Test Plan Check that multiplayer presence UI renders correctly - cursors - cursor hints (when a peer's cursor goes off the screen) - selection brush box - selection/erasing brush - selected shape(s) outline ### Release Notes - [Breaking] Removes the old version of LiveCollaborators, replacing it with the new one based on `TLInstancePresence` --- packages/editor/api-report.md | 1 - packages/editor/src/lib/components/Canvas.tsx | 7 +- .../src/lib/components/LiveCollaborators.tsx | 107 ++++-------------- .../lib/components/LiveCollaboratorsNext.tsx | 84 -------------- packages/editor/src/lib/utils/debug-flags.ts | 1 - .../lib/components/NavigationZone/Minimap.tsx | 26 +---- .../NavigationZone/MinimapManager.ts | 11 +- 7 files changed, 31 insertions(+), 206 deletions(-) delete mode 100644 packages/editor/src/lib/components/LiveCollaboratorsNext.tsx diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index c26fa0e8e..3450518af 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -619,7 +619,6 @@ export const debugFlags: { peopleMenu: Atom; logMessages: Atom; resetConnectionEveryPing: Atom; - newLiveCollaborators: Atom; }; // @internal (undocumented) diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx index 63f3115cc..4cf53ce5d 100644 --- a/packages/editor/src/lib/components/Canvas.tsx +++ b/packages/editor/src/lib/components/Canvas.tsx @@ -17,7 +17,6 @@ import { useQuickReactor } from '../hooks/useQuickReactor' import { useScreenBounds } from '../hooks/useScreenBounds' import { debugFlags } from '../utils/debug-flags' import { LiveCollaborators } from './LiveCollaborators' -import { LiveCollaboratorsNext } from './LiveCollaboratorsNext' import { SelectionBg } from './SelectionBg' import { SelectionFg } from './SelectionFg' import { Shape } from './Shape' @@ -128,11 +127,7 @@ export const Canvas = track(function Canvas({ - {debugFlags.newLiveCollaborators.value ? ( - - ) : ( - - )} + diff --git a/packages/editor/src/lib/components/LiveCollaborators.tsx b/packages/editor/src/lib/components/LiveCollaborators.tsx index 9e21578b7..274d0b830 100644 --- a/packages/editor/src/lib/components/LiveCollaborators.tsx +++ b/packages/editor/src/lib/components/LiveCollaborators.tsx @@ -1,60 +1,24 @@ -import { Box2d } from '@tldraw/primitives' -import { TLUserPresence } from '@tldraw/tlschema' -import React, { useMemo } from 'react' -import { track, useAtom } from 'signia-react' +import { TLUserId } from '@tldraw/tlschema' +import { track } from 'signia-react' import { useApp } from '../hooks/useApp' import { useEditorComponents } from '../hooks/useEditorComponents' - -export const useActivePresences = () => { - const app = useApp() - const time = useAtom('time', Date.now()) - - React.useEffect(() => { - const interval = setInterval(() => time.set(Date.now()), 1000 * 5) - return () => clearInterval(interval) - }, [time]) - - return useMemo( - () => - app.store.query.records('user_presence', () => ({ - lastActivityTimestamp: { gt: time.value - COLLABORATOR_INACTIVITY_TIMEOUT }, - userId: { neq: app.userId }, - })), - [app, time] - ) -} +import { usePeerIds } from '../hooks/usePeerIds' +import { usePresence } from '../hooks/usePresence' export const LiveCollaborators = track(function Collaborators() { - const app = useApp() - const { viewportPageBounds, zoomLevel } = app - const presences = useActivePresences() - + const peerIds = usePeerIds() return ( <> - {presences.value.map((userPresence) => ( - + {peerIds.map((id) => ( + ))} ) }) -const COLLABORATOR_INACTIVITY_TIMEOUT = 1000 * 10 - -const Collaborator = track(function Collaborator({ - presence, - viewport, - zoom, -}: { - presence: TLUserPresence - viewport: Box2d - zoom: number -}) { +const Collaborator = track(function Collaborator({ userId }: { userId: TLUserId }) { const app = useApp() + const { viewportPageBounds, zoomLevel } = app const { CollaboratorBrush, @@ -64,48 +28,21 @@ const Collaborator = track(function Collaborator({ CollaboratorShapeIndicator, } = useEditorComponents() - const { userId, color, cursor, lastUsedInstanceId } = presence - - const pageState = useMemo( - () => - lastUsedInstanceId - ? app.store.query.record('instance_page_state', () => ({ - instanceId: { eq: lastUsedInstanceId }, - pageId: { eq: app.currentPageId }, - })) - : null, - [app, lastUsedInstanceId] - ) - - const user = useMemo( - () => - app.store.query.record('user', () => ({ - id: { eq: userId }, - })), - [app, userId] - ) - - if (!lastUsedInstanceId || !pageState || !user) return null - const instance = app.store.get(lastUsedInstanceId) - if (!instance) return null + const latestPresence = usePresence(userId) + if (!latestPresence) return null // if the collaborator is on another page, ignore them - if (instance.currentPageId !== app.currentPageId) return null + if (latestPresence.currentPageId !== app.currentPageId) return null - if (!pageState.value) return null - if (!user.value) return null - - const { brush, scribble } = instance - const { selectedIds } = pageState.value - const { name } = user.value + const { brush, scribble, selectedIds, userName, cursor, color } = latestPresence // Add a little padding to the top-left of the viewport // so that the cursor doesn't get cut off const isCursorInViewport = !( - cursor.x < viewport.minX - 12 / zoom || - cursor.y < viewport.minY - 16 / zoom || - cursor.x > viewport.maxX - 12 / zoom || - cursor.y > viewport.maxY - 16 / zoom + cursor.x < viewportPageBounds.minX - 12 / zoomLevel || + cursor.y < viewportPageBounds.minY - 16 / zoomLevel || + cursor.x > viewportPageBounds.maxX - 12 / zoomLevel || + cursor.y > viewportPageBounds.maxY - 16 / zoomLevel ) return ( @@ -125,8 +62,8 @@ const Collaborator = track(function Collaborator({ key={userId + '_cursor'} point={cursor} color={color} - zoom={zoom} - name={name !== 'New User' ? name : null} + zoom={zoomLevel} + name={userName !== 'New User' ? userName : null} /> ) : CollaboratorHint ? ( ) : null} {scribble && CollaboratorScribble ? ( @@ -144,7 +81,7 @@ const Collaborator = track(function Collaborator({ key={userId + '_scribble'} scribble={scribble} color={color} - zoom={zoom} + zoom={zoomLevel} opacity={0.1} /> ) : null} diff --git a/packages/editor/src/lib/components/LiveCollaboratorsNext.tsx b/packages/editor/src/lib/components/LiveCollaboratorsNext.tsx deleted file mode 100644 index 68fd2448d..000000000 --- a/packages/editor/src/lib/components/LiveCollaboratorsNext.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { TLUserId } from '@tldraw/tlschema' -import { track } from 'signia-react' -import { useApp } from '../hooks/useApp' -import { useEditorComponents } from '../hooks/useEditorComponents' -import { usePeerIds } from '../hooks/usePeerIds' -import { usePresence } from '../hooks/usePresence' - -export const LiveCollaboratorsNext = track(function Collaborators() { - const peerIds = usePeerIds() - return ( - <> - {peerIds.map((id) => ( - - ))} - - ) -}) - -const Collaborator = track(function Collaborator({ userId }: { userId: TLUserId }) { - const app = useApp() - const { viewportPageBounds, zoomLevel } = app - - const { - CollaboratorBrush, - CollaboratorScribble, - CollaboratorCursor, - CollaboratorHint, - CollaboratorShapeIndicator, - } = useEditorComponents() - - const latestPresence = usePresence(userId) - if (!latestPresence) return null - - // if the collaborator is on another page, ignore them - if (latestPresence.currentPageId !== app.currentPageId) return null - - const { brush, scribble, selectedIds, userName, cursor, color } = latestPresence - - // Add a little padding to the top-left of the viewport - // so that the cursor doesn't get cut off - const isCursorInViewport = !( - cursor.x < viewportPageBounds.minX - 12 / zoomLevel || - cursor.y < viewportPageBounds.minY - 16 / zoomLevel || - cursor.x > viewportPageBounds.maxX - 12 / zoomLevel || - cursor.y > viewportPageBounds.maxY - 16 / zoomLevel - ) - - return ( - <> - {brush && CollaboratorBrush ? ( - - ) : null} - {isCursorInViewport && CollaboratorCursor ? ( - - ) : CollaboratorHint ? ( - - ) : null} - {scribble && CollaboratorScribble ? ( - - ) : null} - {CollaboratorShapeIndicator && - selectedIds.map((shapeId) => ( - - ))} - - ) -}) diff --git a/packages/editor/src/lib/utils/debug-flags.ts b/packages/editor/src/lib/utils/debug-flags.ts index 5cecf1598..7332729e1 100644 --- a/packages/editor/src/lib/utils/debug-flags.ts +++ b/packages/editor/src/lib/utils/debug-flags.ts @@ -23,7 +23,6 @@ export const debugFlags = { peopleMenu: createDebugValue('tldrawPeopleMenu', false), logMessages: createDebugValue('tldrawUiLog', []), resetConnectionEveryPing: createDebugValue('tldrawResetConnectionEveryPing', false), - newLiveCollaborators: createDebugValue('tldrawNewLiveCollaborators', false), } declare global { diff --git a/packages/ui/src/lib/components/NavigationZone/Minimap.tsx b/packages/ui/src/lib/components/NavigationZone/Minimap.tsx index 71ef61e35..b6dc7d56f 100644 --- a/packages/ui/src/lib/components/NavigationZone/Minimap.tsx +++ b/packages/ui/src/lib/components/NavigationZone/Minimap.tsx @@ -9,7 +9,7 @@ import { } from '@tldraw/editor' import { Box2d, intersectPolygonPolygon, Vec2d } from '@tldraw/primitives' import * as React from 'react' -import { track, useAtom } from 'signia-react' +import { track } from 'signia-react' import { MinimapManager } from './MinimapManager' export interface MinimapProps { @@ -18,26 +18,6 @@ export interface MinimapProps { viewportFill: string } -const COLLABORATOR_INACTIVITY_TIMEOUT = 10000 - -export const useActivePresences = () => { - const app = useApp() - const time = useAtom('time', Date.now()) - - React.useEffect(() => { - const interval = setInterval(() => time.set(Date.now()), 1000 * 5) - return () => clearInterval(interval) - }, [time]) - return React.useMemo( - () => - app.store.query.records('user_presence', () => ({ - lastActivityTimestamp: { gt: time.value - COLLABORATOR_INACTIVITY_TIMEOUT }, - userId: { neq: app.userId }, - })), - [app, time] - ) -} - export const Minimap = track(function Minimap({ shapeFill, selectFill, @@ -196,7 +176,9 @@ export const Minimap = track(function Minimap({ [app, minimap] ) - const presences = useActivePresences() + const presences = React.useMemo(() => { + return app.store.query.records('instance_presence') + }, [app]) useQuickReactor( 'minimap render when pagebounds or collaborators changes', diff --git a/packages/ui/src/lib/components/NavigationZone/MinimapManager.ts b/packages/ui/src/lib/components/NavigationZone/MinimapManager.ts index f8ef1808f..dd32961bd 100644 --- a/packages/ui/src/lib/components/NavigationZone/MinimapManager.ts +++ b/packages/ui/src/lib/components/NavigationZone/MinimapManager.ts @@ -1,4 +1,4 @@ -import { App, TLShapeId, TLUserPresence, uniqueId } from '@tldraw/editor' +import { App, TLInstancePresence, TLShapeId, uniqueId } from '@tldraw/editor' import { Box2d, PI2, Vec2d, clamp } from '@tldraw/primitives' export class MinimapManager { @@ -13,7 +13,7 @@ export class MinimapManager { id = uniqueId() cvs: HTMLCanvasElement | null = null pageBounds: (Box2d & { id: TLShapeId })[] = [] - collaborators: TLUserPresence[] = [] + collaborators: TLInstancePresence[] = [] canvasScreenBounds = new Box2d() canvasPageBounds = new Box2d() @@ -291,13 +291,10 @@ export class MinimapManager { const { currentPageId } = app - let collaborator: TLUserPresence + let collaborator: TLInstancePresence for (let i = 0; i < this.collaborators.length; i++) { collaborator = this.collaborators[i] - const instance = collaborator.lastUsedInstanceId - ? app.store.get(collaborator.lastUsedInstanceId) - : null - if (instance?.currentPageId !== currentPageId) { + if (collaborator.currentPageId !== currentPageId) { continue }