@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
8 lines (7 loc) • 9.15 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/lib/components/LiveCollaborators.tsx"],
"sourcesContent": ["import { track } from '@tldraw/state-react'\nimport { TLInstancePresence } from '@tldraw/tlschema'\nimport { useEffect, useRef, useState } from 'react'\nimport { Editor } from '../editor/Editor'\nimport { useEditor } from '../hooks/useEditor'\nimport { useEditorComponents } from '../hooks/useEditorComponents'\nimport { usePeerIds } from '../hooks/usePeerIds'\nimport { usePresence } from '../hooks/usePresence'\n\nexport const LiveCollaborators = track(function Collaborators() {\n\tconst peerIds = usePeerIds()\n\treturn peerIds.map((id) => <CollaboratorGuard key={id} collaboratorId={id} />)\n})\n\nconst CollaboratorGuard = track(function CollaboratorGuard({\n\tcollaboratorId,\n}: {\n\tcollaboratorId: string\n}) {\n\tconst editor = useEditor()\n\tconst presence = usePresence(collaboratorId)\n\tconst collaboratorState = useCollaboratorState(editor, presence)\n\n\tif (!(presence && presence.currentPageId === editor.getCurrentPageId())) {\n\t\t// No need to render if we don't have a presence or if they're on a different page\n\t\treturn null\n\t}\n\n\tswitch (collaboratorState) {\n\t\tcase 'inactive': {\n\t\t\tconst { followingUserId, highlightedUserIds } = editor.getInstanceState()\n\t\t\t// If they're inactive and unless we're following them or they're highlighted, hide them\n\t\t\tif (!(followingUserId === presence.userId || highlightedUserIds.includes(presence.userId))) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tcase 'idle': {\n\t\t\tconst { highlightedUserIds } = editor.getInstanceState()\n\t\t\t// If they're idle and following us and unless they have a chat message or are highlighted, hide them\n\t\t\tif (\n\t\t\t\tpresence.followingUserId === editor.user.getId() &&\n\t\t\t\t!(presence.chatMessage || highlightedUserIds.includes(presence.userId))\n\t\t\t) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tcase 'active': {\n\t\t\t// If they're active, show them\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn <Collaborator latestPresence={presence} />\n})\n\nconst Collaborator = track(function Collaborator({\n\tlatestPresence,\n}: {\n\tlatestPresence: TLInstancePresence\n}) {\n\tconst editor = useEditor()\n\n\tconst {\n\t\tCollaboratorBrush,\n\t\tCollaboratorScribble,\n\t\tCollaboratorCursor,\n\t\tCollaboratorHint,\n\t\tCollaboratorShapeIndicator,\n\t} = useEditorComponents()\n\n\tconst zoomLevel = editor.getZoomLevel()\n\tconst viewportPageBounds = editor.getViewportPageBounds()\n\tconst { userId, chatMessage, brush, scribbles, selectedShapeIds, userName, cursor, color } =\n\t\tlatestPresence\n\n\tif (!cursor) return null\n\n\t// Add a little padding to the top-left of the viewport\n\t// so that the cursor doesn't get cut off\n\tconst isCursorInViewport = !(\n\t\tcursor.x < viewportPageBounds.minX - 12 / zoomLevel ||\n\t\tcursor.y < viewportPageBounds.minY - 16 / zoomLevel ||\n\t\tcursor.x > viewportPageBounds.maxX - 12 / zoomLevel ||\n\t\tcursor.y > viewportPageBounds.maxY - 16 / zoomLevel\n\t)\n\n\treturn (\n\t\t<>\n\t\t\t{brush && CollaboratorBrush ? (\n\t\t\t\t<CollaboratorBrush\n\t\t\t\t\tclassName=\"tl-collaborator__brush\"\n\t\t\t\t\tkey={userId + '_brush'}\n\t\t\t\t\tuserId={userId}\n\t\t\t\t\tbrush={brush}\n\t\t\t\t\tcolor={color}\n\t\t\t\t\topacity={0.1}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t{isCursorInViewport && CollaboratorCursor ? (\n\t\t\t\t<CollaboratorCursor\n\t\t\t\t\tclassName=\"tl-collaborator__cursor\"\n\t\t\t\t\tkey={userId + '_cursor'}\n\t\t\t\t\tuserId={userId}\n\t\t\t\t\tpoint={cursor}\n\t\t\t\t\tcolor={color}\n\t\t\t\t\tzoom={zoomLevel}\n\t\t\t\t\tname={userName !== 'New User' ? userName : null}\n\t\t\t\t\tchatMessage={chatMessage ?? ''}\n\t\t\t\t/>\n\t\t\t) : CollaboratorHint ? (\n\t\t\t\t<CollaboratorHint\n\t\t\t\t\tclassName=\"tl-collaborator__cursor-hint\"\n\t\t\t\t\tkey={userId + '_cursor_hint'}\n\t\t\t\t\tuserId={userId}\n\t\t\t\t\tpoint={cursor}\n\t\t\t\t\tcolor={color}\n\t\t\t\t\tzoom={zoomLevel}\n\t\t\t\t\tviewport={viewportPageBounds}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t{CollaboratorScribble && scribbles.length ? (\n\t\t\t\t<>\n\t\t\t\t\t{scribbles.map((scribble) => (\n\t\t\t\t\t\t<CollaboratorScribble\n\t\t\t\t\t\t\tkey={userId + '_scribble_' + scribble.id}\n\t\t\t\t\t\t\tclassName=\"tl-collaborator__scribble\"\n\t\t\t\t\t\t\tuserId={userId}\n\t\t\t\t\t\t\tscribble={scribble}\n\t\t\t\t\t\t\tcolor={color}\n\t\t\t\t\t\t\tzoom={zoomLevel}\n\t\t\t\t\t\t\topacity={scribble.color === 'laser' ? 0.5 : 0.1}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</>\n\t\t\t) : null}\n\t\t\t{CollaboratorShapeIndicator &&\n\t\t\t\tselectedShapeIds\n\t\t\t\t\t.filter((id) => !editor.isShapeHidden(id))\n\t\t\t\t\t.map((shapeId) => (\n\t\t\t\t\t\t<CollaboratorShapeIndicator\n\t\t\t\t\t\t\tclassName=\"tl-collaborator__shape-indicator\"\n\t\t\t\t\t\t\tkey={userId + '_' + shapeId}\n\t\t\t\t\t\t\tuserId={userId}\n\t\t\t\t\t\t\tshapeId={shapeId}\n\t\t\t\t\t\t\tcolor={color}\n\t\t\t\t\t\t\topacity={0.5}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t</>\n\t)\n})\n\nfunction getStateFromElapsedTime(editor: Editor, elapsed: number) {\n\treturn elapsed > editor.options.collaboratorInactiveTimeoutMs\n\t\t? 'inactive'\n\t\t: elapsed > editor.options.collaboratorIdleTimeoutMs\n\t\t\t? 'idle'\n\t\t\t: 'active'\n}\n\nfunction useCollaboratorState(editor: Editor, latestPresence: TLInstancePresence | null) {\n\tconst rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1)\n\n\tconst [state, setState] = useState<'active' | 'idle' | 'inactive'>(() =>\n\t\tgetStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)\n\t)\n\n\tuseEffect(() => {\n\t\tconst interval = editor.timers.setInterval(() => {\n\t\t\tsetState(getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current))\n\t\t}, editor.options.collaboratorCheckIntervalMs)\n\n\t\treturn () => clearInterval(interval)\n\t}, [editor])\n\n\tif (latestPresence) {\n\t\t// We can do this on every render, it's free and cheaper than an effect\n\t\t// remember, there can be lots and lots of cursors moving around all the time\n\t\trLastActivityTimestamp.current = latestPresence.lastActivityTimestamp ?? Infinity\n\t}\n\n\treturn state\n}\n"],
"mappings": "AAW4B,SAgHxB,UAhHwB,KA8E1B,YA9E0B;AAX5B,SAAS,aAAa;AAEtB,SAAS,WAAW,QAAQ,gBAAgB;AAE5C,SAAS,iBAAiB;AAC1B,SAAS,2BAA2B;AACpC,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAErB,MAAM,oBAAoB,MAAM,SAAS,gBAAgB;AAC/D,QAAM,UAAU,WAAW;AAC3B,SAAO,QAAQ,IAAI,CAAC,OAAO,oBAAC,qBAA2B,gBAAgB,MAApB,EAAwB,CAAE;AAC9E,CAAC;AAED,MAAM,oBAAoB,MAAM,SAASA,mBAAkB;AAAA,EAC1D;AACD,GAEG;AACF,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY,cAAc;AAC3C,QAAM,oBAAoB,qBAAqB,QAAQ,QAAQ;AAE/D,MAAI,EAAE,YAAY,SAAS,kBAAkB,OAAO,iBAAiB,IAAI;AAExE,WAAO;AAAA,EACR;AAEA,UAAQ,mBAAmB;AAAA,IAC1B,KAAK,YAAY;AAChB,YAAM,EAAE,iBAAiB,mBAAmB,IAAI,OAAO,iBAAiB;AAExE,UAAI,EAAE,oBAAoB,SAAS,UAAU,mBAAmB,SAAS,SAAS,MAAM,IAAI;AAC3F,eAAO;AAAA,MACR;AACA;AAAA,IACD;AAAA,IACA,KAAK,QAAQ;AACZ,YAAM,EAAE,mBAAmB,IAAI,OAAO,iBAAiB;AAEvD,UACC,SAAS,oBAAoB,OAAO,KAAK,MAAM,KAC/C,EAAE,SAAS,eAAe,mBAAmB,SAAS,SAAS,MAAM,IACpE;AACD,eAAO;AAAA,MACR;AACA;AAAA,IACD;AAAA,IACA,KAAK,UAAU;AAEd;AAAA,IACD;AAAA,EACD;AAEA,SAAO,oBAAC,gBAAa,gBAAgB,UAAU;AAChD,CAAC;AAED,MAAM,eAAe,MAAM,SAASC,cAAa;AAAA,EAChD;AACD,GAEG;AACF,QAAM,SAAS,UAAU;AAEzB,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI,oBAAoB;AAExB,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,qBAAqB,OAAO,sBAAsB;AACxD,QAAM,EAAE,QAAQ,aAAa,OAAO,WAAW,kBAAkB,UAAU,QAAQ,MAAM,IACxF;AAED,MAAI,CAAC,OAAQ,QAAO;AAIpB,QAAM,qBAAqB,EAC1B,OAAO,IAAI,mBAAmB,OAAO,KAAK,aAC1C,OAAO,IAAI,mBAAmB,OAAO,KAAK,aAC1C,OAAO,IAAI,mBAAmB,OAAO,KAAK,aAC1C,OAAO,IAAI,mBAAmB,OAAO,KAAK;AAG3C,SACC,iCACE;AAAA,aAAS,oBACT;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QAEV;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA;AAAA,MAJJ,SAAS;AAAA,IAKf,IACG;AAAA,IACH,sBAAsB,qBACtB;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QAEV;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,MAAM,aAAa,aAAa,WAAW;AAAA,QAC3C,aAAa,eAAe;AAAA;AAAA,MANvB,SAAS;AAAA,IAOf,IACG,mBACH;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QAEV;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,UAAU;AAAA;AAAA,MALL,SAAS;AAAA,IAMf,IACG;AAAA,IACH,wBAAwB,UAAU,SAClC,gCACE,oBAAU,IAAI,CAAC,aACf;AAAA,MAAC;AAAA;AAAA,QAEA,WAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,SAAS,SAAS,UAAU,UAAU,MAAM;AAAA;AAAA,MANvC,SAAS,eAAe,SAAS;AAAA,IAOvC,CACA,GACF,IACG;AAAA,IACH,8BACA,iBACE,OAAO,CAAC,OAAO,CAAC,OAAO,cAAc,EAAE,CAAC,EACxC,IAAI,CAAC,YACL;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QAEV;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA;AAAA,MAJJ,SAAS,MAAM;AAAA,IAKrB,CACA;AAAA,KACJ;AAEF,CAAC;AAED,SAAS,wBAAwB,QAAgB,SAAiB;AACjE,SAAO,UAAU,OAAO,QAAQ,gCAC7B,aACA,UAAU,OAAO,QAAQ,4BACxB,SACA;AACL;AAEA,SAAS,qBAAqB,QAAgB,gBAA2C;AACxF,QAAM,yBAAyB,OAAO,gBAAgB,yBAAyB,EAAE;AAEjF,QAAM,CAAC,OAAO,QAAQ,IAAI;AAAA,IAAyC,MAClE,wBAAwB,QAAQ,KAAK,IAAI,IAAI,uBAAuB,OAAO;AAAA,EAC5E;AAEA,YAAU,MAAM;AACf,UAAM,WAAW,OAAO,OAAO,YAAY,MAAM;AAChD,eAAS,wBAAwB,QAAQ,KAAK,IAAI,IAAI,uBAAuB,OAAO,CAAC;AAAA,IACtF,GAAG,OAAO,QAAQ,2BAA2B;AAE7C,WAAO,MAAM,cAAc,QAAQ;AAAA,EACpC,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,gBAAgB;AAGnB,2BAAuB,UAAU,eAAe,yBAAyB;AAAA,EAC1E;AAEA,SAAO;AACR;",
"names": ["CollaboratorGuard", "Collaborator"]
}