Ver código fonte

Updates cursor logic

main
Steve Ruiz 3 anos atrás
pai
commit
49baa47a0e

+ 11
- 38
components/canvas/canvas.tsx Ver arquivo

@@ -1,19 +1,18 @@
1
-import styled from 'styles'
2 1
 import { ErrorBoundary } from 'react-error-boundary'
3
-import state, { useSelector } from 'state'
4
-import React, { useRef } from 'react'
5
-import useZoomEvents from 'hooks/useZoomEvents'
6
-import useCamera from 'hooks/useCamera'
7
-import Defs from './defs'
8
-import Page from './page'
9
-import Brush from './brush'
10
-import Cursor from './cursor'
11 2
 import Bounds from './bounds/bounding-box'
12 3
 import BoundsBg from './bounds/bounds-bg'
4
+import Brush from './brush'
5
+import ContextMenu from './context-menu/context-menu'
6
+import Coop from './coop/coop'
7
+import Defs from './defs'
13 8
 import Handles from './bounds/handles'
9
+import Page from './page'
10
+import React, { useRef } from 'react'
11
+import state, { useSelector } from 'state'
12
+import styled from 'styles'
13
+import useCamera from 'hooks/useCamera'
14 14
 import useCanvasEvents from 'hooks/useCanvasEvents'
15
-import ContextMenu from './context-menu/context-menu'
16
-import { deepCompareArrays } from 'utils'
15
+import useZoomEvents from 'hooks/useZoomEvents'
17 16
 
18 17
 function resetError() {
19 18
   null
@@ -40,10 +39,10 @@ export default function Canvas(): JSX.Element {
40 39
             <g ref={rGroup} id="shapes">
41 40
               <BoundsBg />
42 41
               <Page />
42
+              <Coop />
43 43
               <Bounds />
44 44
               <Handles />
45 45
               <Brush />
46
-              <Peers />
47 46
             </g>
48 47
           )}
49 48
         </ErrorBoundary>
@@ -52,32 +51,6 @@ export default function Canvas(): JSX.Element {
52 51
   )
53 52
 }
54 53
 
55
-function Peers(): JSX.Element {
56
-  const peerIds = useSelector((s) => {
57
-    return s.data.room ? Object.keys(s.data.room?.peers) : []
58
-  }, deepCompareArrays)
59
-
60
-  return (
61
-    <>
62
-      {peerIds.map((id) => (
63
-        <Peer key={id} id={id} />
64
-      ))}
65
-    </>
66
-  )
67
-}
68
-
69
-function Peer({ id }: { id: string }): JSX.Element {
70
-  const hasPeer = useSelector((s) => {
71
-    return s.data.room && s.data.room.peers[id] !== undefined
72
-  })
73
-
74
-  const point = useSelector(
75
-    (s) => hasPeer && s.data.room.peers[id].cursor.point
76
-  )
77
-
78
-  return <Cursor point={point} />
79
-}
80
-
81 54
 const MainSVG = styled('svg', {
82 55
   position: 'fixed',
83 56
   overflow: 'hidden',

+ 27
- 0
components/canvas/coop/coop.tsx Ver arquivo

@@ -0,0 +1,27 @@
1
+import Cursor from './cursor'
2
+import { useCoopSelector } from 'state/coop/coop-state'
3
+
4
+export default function Presence(): JSX.Element {
5
+  const others = useCoopSelector((s) => s.data.others)
6
+
7
+  return (
8
+    <>
9
+      {Object.values(others).map(({ connectionId, presence }) => {
10
+        if (presence == null) {
11
+          return null
12
+        }
13
+
14
+        return (
15
+          <Cursor
16
+            key={`cursor-${connectionId}`}
17
+            color={'red'}
18
+            duration={presence.duration}
19
+            times={presence.times}
20
+            bufferedXs={presence.bufferedXs}
21
+            bufferedYs={presence.bufferedYs}
22
+          />
23
+        )
24
+      })}
25
+    </>
26
+  )
27
+}

components/canvas/cursor.tsx → components/canvas/coop/cursor.tsx Ver arquivo

@@ -1,25 +1,45 @@
1 1
 import React from 'react'
2 2
 import styled from 'styles'
3
+import { motion } from 'framer-motion'
4
+
5
+// const transition = {
6
+//   type: 'spring',
7
+//   mass: 2,
8
+//   damping: 20,
9
+// }
3 10
 
4 11
 export default function Cursor({
5 12
   color = 'dodgerblue',
6
-  point = [0, 0],
13
+  duration = 0,
14
+  bufferedXs = [],
15
+  bufferedYs = [],
16
+  times = [],
7 17
 }: {
8
-  color?: string
9
-  point: number[]
18
+  color: string
19
+  duration: number
20
+  bufferedXs: number[]
21
+  bufferedYs: number[]
22
+  times: number[]
10 23
 }): JSX.Element {
11
-  const transform = `translate(${point[0] - 12} ${point[1] - 10})`
12
-
13 24
   return (
14 25
     <StyledCursor
15 26
       color={color}
16
-      transform={transform}
27
+      initial={false}
28
+      animate={{
29
+        x: bufferedXs,
30
+        y: bufferedYs,
31
+        transition: {
32
+          type: 'tween',
33
+          ease: 'linear',
34
+          duration,
35
+          times,
36
+        },
37
+      }}
17 38
       width="35px"
18 39
       height="35px"
19 40
       viewBox="0 0 35 35"
20 41
       version="1.1"
21 42
       pointerEvents="none"
22
-      opacity="0"
23 43
       xmlns="http://www.w3.org/2000/svg"
24 44
       xmlnsXlink="http://www.w3.org/1999/xlink"
25 45
     >
@@ -43,7 +63,7 @@ export default function Cursor({
43 63
   )
44 64
 }
45 65
 
46
-const StyledCursor = styled('g', {
66
+const StyledCursor = styled(motion.g, {
47 67
   position: 'absolute',
48 68
   zIndex: 1000,
49 69
   top: 0,

+ 5
- 1
components/status-bar.tsx Ver arquivo

@@ -1,11 +1,14 @@
1 1
 import { useStateDesigner } from '@state-designer/react'
2 2
 import state from 'state'
3
+import { useCoopSelector } from 'state/coop/coop-state'
3 4
 import styled from 'styles'
4 5
 
5 6
 const size: any = { '@sm': 'small' }
6 7
 
7 8
 export default function StatusBar(): JSX.Element {
8 9
   const local = useStateDesigner(state)
10
+  const status = useCoopSelector((s) => s.data.status)
11
+  const others = useCoopSelector((s) => s.data.others)
9 12
 
10 13
   const active = local.active.slice(1).map((s) => {
11 14
     const states = s.split('.')
@@ -17,7 +20,8 @@ export default function StatusBar(): JSX.Element {
17 20
   return (
18 21
     <StatusBarContainer size={size}>
19 22
       <Section>
20
-        {active.join(' | ')} | {log} | {local.data.room?.status}
23
+        {active.join(' | ')} | {log} | {status} (
24
+        {Object.values(others).length || 0})
21 25
       </Section>
22 26
     </StatusBarContainer>
23 27
   )

+ 3
- 13
hooks/useCanvasEvents.ts Ver arquivo

@@ -31,22 +31,12 @@ export default function useCanvasEvents(
31 31
 
32 32
     if (state.isIn('draw.editing')) {
33 33
       fastDrawUpdate(info)
34
-      return
35
-    }
36
-
37
-    if (state.isIn('brushSelecting')) {
34
+    } else if (state.isIn('brushSelecting')) {
38 35
       fastBrushSelect(info.point)
39
-      return
40
-    }
41
-
42
-    if (state.isIn('translatingSelection')) {
36
+    } else if (state.isIn('translatingSelection')) {
43 37
       fastTranslate(info)
44
-      return
45
-    }
46
-
47
-    if (state.isIn('transformingSelection')) {
38
+    } else if (state.isIn('transformingSelection')) {
48 39
       fastTransform(info)
49
-      return
50 40
     }
51 41
 
52 42
     state.send('MOVED_POINTER', info)

+ 3
- 0
package.json Ver arquivo

@@ -29,6 +29,9 @@
29 29
     ]
30 30
   },
31 31
   "dependencies": {
32
+    "@liveblocks/client": "^0.8.1",
33
+    "@liveblocks/node": "^0.3.0",
34
+    "@liveblocks/react": "^0.8.0",
32 35
     "@monaco-editor/react": "^4.2.1",
33 36
     "@radix-ui/react-checkbox": "^0.0.16",
34 37
     "@radix-ui/react-context-menu": "^0.0.22",

+ 48
- 0
pages/api/auth-liveblocks.ts Ver arquivo

@@ -0,0 +1,48 @@
1
+import { authorize } from '@liveblocks/node'
2
+import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
3
+
4
+const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY
5
+
6
+const Auth: NextApiHandler = async (
7
+  req: NextApiRequest,
8
+  res: NextApiResponse
9
+) => {
10
+  if (!API_KEY) {
11
+    return res.status(403).end()
12
+  }
13
+
14
+  const room = req.body.room
15
+
16
+  if (room === 'example-live-cursors-avatars') {
17
+    const response = await authorize({
18
+      room,
19
+      secret: API_KEY,
20
+      userInfo: {
21
+        name: NAMES[Math.floor(Math.random() * NAMES.length)],
22
+        picture: `/assets/avatars/${Math.floor(Math.random() * 10)}.png`,
23
+      },
24
+    })
25
+
26
+    return res.status(response.status).end(response.body)
27
+  }
28
+
29
+  const response = await authorize({
30
+    room,
31
+    secret: API_KEY,
32
+  })
33
+
34
+  return res.status(response.status).end(response.body)
35
+}
36
+
37
+export default Auth
38
+
39
+const NAMES = [
40
+  'Charlie Layne',
41
+  'Mislav Abha',
42
+  'Tatum Paolo',
43
+  'Anjali Wanda',
44
+  'Jody Hekla',
45
+  'Emil Joyce',
46
+  'Jory Quispe',
47
+  'Quinn Elton',
48
+]

+ 0
- 0
pages/api/nodes.md Ver arquivo


+ 8
- 0
pages/room/[id].tsx Ver arquivo

@@ -1,10 +1,18 @@
1 1
 import dynamic from 'next/dynamic'
2 2
 import { GetServerSideProps } from 'next'
3 3
 import { getSession } from 'next-auth/client'
4
+import { useEffect } from 'react'
5
+import coopState from 'state/coop/coop-state'
4 6
 
5 7
 const Editor = dynamic(() => import('components/editor'), { ssr: false })
6 8
 
7 9
 export default function Room({ id }: { id: string }): JSX.Element {
10
+  useEffect(() => {
11
+    return () => {
12
+      coopState.send('LEFT_ROOM')
13
+    }
14
+  }, [])
15
+
8 16
   return <Editor roomId={id} />
9 17
 }
10 18
 

+ 1
- 1
public/manifest.json Ver arquivo

@@ -32,4 +32,4 @@
32 32
   "start_url": "/",
33 33
   "display": "standalone",
34 34
   "orientation": "portrait"
35
-}
35
+}

+ 131
- 0
state/coop/client-liveblocks.ts Ver arquivo

@@ -0,0 +1,131 @@
1
+/* eslint-disable prefer-const */
2
+/* eslint-disable @typescript-eslint/no-unused-vars */
3
+import { Client, Room, createClient } from '@liveblocks/client'
4
+import coopState from './coop-state'
5
+import { CoopPresence } from 'types'
6
+import {
7
+  ConnectionCallback,
8
+  MyPresenceCallback,
9
+  OthersEventCallback,
10
+} from '@liveblocks/client/lib/cjs/types'
11
+import { uniqueId } from 'utils'
12
+
13
+class CoopClient {
14
+  id = uniqueId()
15
+  roomId: string
16
+  lastCursorEventTime = 0
17
+  client: Client
18
+  room: Room
19
+  bufferedXs: number[] = []
20
+  bufferedYs: number[] = []
21
+  bufferedTs: number[] = []
22
+
23
+  constructor() {
24
+    this.client = createClient({
25
+      authEndpoint: '/api/auth-liveblocks',
26
+    })
27
+  }
28
+
29
+  private handleConnectionEvent: ConnectionCallback = (status) => {
30
+    coopState.send('CHANGED_CONNECTION_STATUS', { status })
31
+  }
32
+
33
+  private handleMyPresenceEvent: MyPresenceCallback<CoopPresence> = () => {
34
+    null
35
+  }
36
+
37
+  private handleOthersEvent: OthersEventCallback<CoopPresence> = (_, event) => {
38
+    switch (event.type) {
39
+      case 'enter': {
40
+        coopState.send('OTHER_USER_ENTERED', event)
41
+        break
42
+      }
43
+      case 'leave': {
44
+        coopState.send('OTHER_USER_LEFT', event)
45
+        break
46
+      }
47
+      case 'update': {
48
+        coopState.send('OTHER_USER_UPDATED', event)
49
+        break
50
+      }
51
+      case 'reset': {
52
+        coopState.send('RESET_OTHER_USERS', event)
53
+        break
54
+      }
55
+    }
56
+  }
57
+
58
+  connect(roomId: string) {
59
+    if (this.roomId) {
60
+      this.client.leave(this.roomId)
61
+    }
62
+
63
+    this.roomId = roomId
64
+
65
+    this.room = this.client.enter(roomId, { cursor: null })
66
+    this.room.subscribe('connection', this.handleConnectionEvent)
67
+    this.room.subscribe('my-presence', this.handleMyPresenceEvent)
68
+    this.room.subscribe('others', this.handleOthersEvent)
69
+
70
+    coopState.send('JOINED_ROOM', { others: this.room.getOthers().toArray() })
71
+  }
72
+
73
+  disconnect() {
74
+    this.room.unsubscribe('connection', this.handleConnectionEvent)
75
+    this.room.unsubscribe('my-presence', this.handleMyPresenceEvent)
76
+    this.room.unsubscribe('others', this.handleOthersEvent)
77
+
78
+    this.client.leave(this.roomId)
79
+  }
80
+
81
+  reconnect() {
82
+    this.connect(this.roomId)
83
+  }
84
+
85
+  moveCursor(pageId: string, point: number[]) {
86
+    if (!this.room) return
87
+
88
+    const now = Date.now()
89
+    let elapsed = now - this.lastCursorEventTime
90
+
91
+    if (elapsed > 200) {
92
+      // The animation's total duration (in seconds)
93
+      const duration = this.bufferedTs[this.bufferedTs.length - 1]
94
+
95
+      // Normalized times (0 - 1)
96
+      const times = this.bufferedTs.map((t) => t / duration)
97
+
98
+      // Make sure the array includes both a 0 and a 1
99
+      if (times.length === 1) {
100
+        this.bufferedXs.unshift(this.bufferedXs[0])
101
+        this.bufferedYs.unshift(this.bufferedYs[0])
102
+        times.unshift(0)
103
+      }
104
+
105
+      // Send the event to the service
106
+      this.room.updatePresence<CoopPresence>({
107
+        bufferedXs: this.bufferedXs,
108
+        bufferedYs: this.bufferedYs,
109
+        times,
110
+        duration,
111
+      })
112
+
113
+      // Reset data for next update
114
+      this.lastCursorEventTime = now
115
+      this.bufferedXs = []
116
+      this.bufferedYs = []
117
+      this.bufferedTs = []
118
+      elapsed = 0
119
+    }
120
+
121
+    this.bufferedXs.push(point[0])
122
+    this.bufferedYs.push(point[1])
123
+    this.bufferedTs.push(elapsed / 1000)
124
+  }
125
+
126
+  clearCursor() {
127
+    this.room.updatePresence({ cursor: null })
128
+  }
129
+}
130
+
131
+export default new CoopClient()

+ 64
- 0
state/coop/coop-state.ts Ver arquivo

@@ -0,0 +1,64 @@
1
+import { createSelectorHook, createState } from '@state-designer/react'
2
+import { CoopPresence } from 'types'
3
+import { User } from '@liveblocks/client'
4
+import client from 'state/coop/client-liveblocks'
5
+
6
+type ConnectionState =
7
+  | 'closed'
8
+  | 'authenticating'
9
+  | 'unavailable'
10
+  | 'failed'
11
+  | 'open'
12
+  | 'connecting'
13
+
14
+const coopState = createState({
15
+  data: {
16
+    status: 'closed' as ConnectionState,
17
+    others: {} as Record<string, User<CoopPresence>>,
18
+  },
19
+  on: {
20
+    JOINED_ROOM: 'setOthers',
21
+    LEFT_ROOM: 'disconnectFromRoom',
22
+    CHANGED_CONNECTION_STATUS: 'setStatus',
23
+    OTHER_USER_ENTERED: 'addOtherUser',
24
+    OTHER_USER_LEFT: 'removeOtherUser',
25
+    OTHER_USER_UPDATED: 'updateOtherUser',
26
+    RESET_OTHER_USERS: 'resetOtherUsers',
27
+  },
28
+  actions: {
29
+    connectToRoom(data, payload: { id: string }) {
30
+      client.connect(payload.id)
31
+    },
32
+    disconnectFromRoom() {
33
+      client.disconnect()
34
+    },
35
+    setStatus(data, payload: { status: ConnectionState }) {
36
+      data.status = payload.status
37
+    },
38
+    setOthers(data, payload: { others: User<CoopPresence>[] }) {
39
+      const { others } = payload
40
+      data.others = Object.fromEntries(
41
+        others.map((user) => [user.connectionId, user])
42
+      )
43
+    },
44
+    addOtherUser(data, payload: { user: User<CoopPresence> }) {
45
+      const { user } = payload
46
+      data.others[user.connectionId] = user
47
+    },
48
+    removeOtherUser(data, payload: { user: User<CoopPresence> }) {
49
+      const { user } = payload
50
+      delete data.others[user.connectionId]
51
+    },
52
+    updateOtherUser(data, payload: { user: User<CoopPresence>; changes: any }) {
53
+      const { user } = payload
54
+      data.others[user.connectionId] = user
55
+    },
56
+    resetOtherUsers(data) {
57
+      data.others = {}
58
+    },
59
+  },
60
+})
61
+
62
+export const useCoopSelector = createSelectorHook(coopState)
63
+
64
+export default coopState

+ 3
- 0
state/hacks.ts Ver arquivo

@@ -3,6 +3,7 @@ import { deepClone, setToArray } from 'utils'
3 3
 import tld from 'utils/tld'
4 4
 import { freeze } from 'immer'
5 5
 import session from './session'
6
+import coopClient from 'state/coop/client-liveblocks'
6 7
 import state from './state'
7 8
 import vec from 'utils/vec'
8 9
 import * as Session from './sessions'
@@ -17,6 +18,8 @@ import * as Session from './sessions'
17 18
 export function fastDrawUpdate(info: PointerInfo): void {
18 19
   const data = { ...state.data }
19 20
 
21
+  coopClient.moveCursor(data.currentPageId, info.point)
22
+
20 23
   session.update<Session.DrawSession>(
21 24
     data,
22 25
     tld.screenToWorld(info.point, data),

+ 12
- 30
state/state.ts Ver arquivo

@@ -7,7 +7,7 @@ import history from './history'
7 7
 import storage from './storage'
8 8
 import clipboard from './clipboard'
9 9
 import * as Sessions from './sessions'
10
-import coopClient from './coop/client-pusher'
10
+import coopClient from './coop/client-liveblocks'
11 11
 import commands from './commands'
12 12
 import {
13 13
   getCommonBounds,
@@ -163,18 +163,17 @@ const state = createState({
163 163
       },
164 164
       on: {
165 165
         // Network-Related
166
-        // RT_LOADED_ROOM: [
167
-        //   'clearRoom',
168
-        //   { if: 'hasRoom', do: ['clearDocument', 'connectToRoom'] },
169
-        // ],
166
+        RT_LOADED_ROOM: [
167
+          'clearRoom',
168
+          { if: 'hasRoom', do: ['clearDocument', 'connectToRoom'] },
169
+        ],
170
+        RT_CHANGED_STATUS: 'setRtStatus',
171
+        MOVED_POINTER: { secretlyDo: 'sendRtCursorMove' },
170 172
         // RT_UNLOADED_ROOM: ['clearRoom', 'clearDocument'],
171 173
         // RT_DISCONNECTED_ROOM: ['clearRoom', 'clearDocument'],
172 174
         // RT_CREATED_SHAPE: 'addRtShape',
173
-        // RT_CHANGED_STATUS: 'setRtStatus',
174 175
         // RT_DELETED_SHAPE: 'deleteRtShape',
175 176
         // RT_EDITED_SHAPE: 'editRtShape',
176
-        // RT_MOVED_CURSOR: 'moveRtCursor',
177
-        // MOVED_POINTER: { secretlyDo: 'sendRtCursorMove' },
178 177
         // Client
179 178
         RESIZED_WINDOW: 'resetPageState',
180 179
         RESET_PAGE: 'resetPage',
@@ -554,7 +553,7 @@ const state = createState({
554 553
               onEnter: 'startTransformSession',
555 554
               onExit: 'completeSession',
556 555
               on: {
557
-                // MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform
556
+                // MOVED_POINTER: 'updateTransformSession', (see hacks)
558 557
                 PANNED_CAMERA: 'updateTransformSession',
559 558
                 PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
560 559
                 RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
@@ -567,7 +566,7 @@ const state = createState({
567 566
               onExit: 'completeSession',
568 567
               on: {
569 568
                 STARTED_PINCHING: { to: 'pinching' },
570
-                MOVED_POINTER: 'updateTranslateSession',
569
+                // MOVED_POINTER: 'updateTranslateSession', (see hacks)
571 570
                 PANNED_CAMERA: 'updateTranslateSession',
572 571
                 PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
573 572
                 RELEASED_SHIFT_KEY: 'keyUpdateTranslateSession',
@@ -604,7 +603,7 @@ const state = createState({
604 603
                 'startBrushSession',
605 604
               ],
606 605
               on: {
607
-                // MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect
606
+                // MOVED_POINTER: 'updateBrushSession',  (see hacks)
608 607
                 PANNED_CAMERA: 'updateBrushSession',
609 608
                 STOPPED_POINTING: { to: 'selecting' },
610 609
                 STARTED_PINCHING: { to: 'pinching' },
@@ -640,7 +639,7 @@ const state = createState({
640 639
         },
641 640
         pinching: {
642 641
           on: {
643
-            // PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera
642
+            // PINCHED: { do: 'pinchCamera' },  (see hacks)
644 643
           },
645 644
           initial: 'selectPinching',
646 645
           onExit: { secretlyDo: 'updateZoomCSS' },
@@ -701,7 +700,7 @@ const state = createState({
701 700
                     },
702 701
                     PRESSED_SHIFT: 'keyUpdateDrawSession',
703 702
                     RELEASED_SHIFT: 'keyUpdateDrawSession',
704
-                    // MOVED_POINTER: 'updateDrawSession',
703
+                    // MOVED_POINTER: 'updateDrawSession', (see hacks)
705 704
                     PANNED_CAMERA: 'updateDrawSession',
706 705
                   },
707 706
                 },
@@ -1164,23 +1163,6 @@ const state = createState({
1164 1163
       const point = tld.screenToWorld(payload.point, data)
1165 1164
       coopClient.moveCursor(data.currentPageId, point)
1166 1165
     },
1167
-    moveRtCursor(
1168
-      data,
1169
-      payload: { id: string; pageId: string; point: number[] }
1170
-    ) {
1171
-      const { room } = data
1172
-
1173
-      if (room.peers[payload.id] === undefined) {
1174
-        room.peers[payload.id] = {
1175
-          id: payload.id,
1176
-          cursor: {
1177
-            point: payload.point,
1178
-          },
1179
-        }
1180
-      }
1181
-
1182
-      room.peers[payload.id].cursor.point = payload.point
1183
-    },
1184 1166
     clearRoom(data) {
1185 1167
       data.room = undefined
1186 1168
     },

+ 6
- 5
types.ts Ver arquivo

@@ -17,7 +17,7 @@ export interface Data {
17 17
   room?: {
18 18
     id: string
19 19
     status: string
20
-    peers: Record<string, Peer>
20
+    peers: Record<string, CoopPresence>
21 21
   }
22 22
   currentStyle: ShapeStyles
23 23
   activeTool: ShapeType | 'select'
@@ -38,11 +38,12 @@ export interface Data {
38 38
 /*                      Document                      */
39 39
 /* -------------------------------------------------- */
40 40
 
41
-export interface Peer {
41
+export type CoopPresence = {
42 42
   id: string
43
-  cursor: {
44
-    point: number[]
45
-  }
43
+  bufferedXs: number[]
44
+  bufferedYs: number[]
45
+  times: number[]
46
+  duration: number
46 47
 }
47 48
 
48 49
 export interface TLDocument {

+ 2
- 3
utils/tld.ts Ver arquivo

@@ -142,6 +142,7 @@ export default class ProjectUtils {
142 142
 
143 143
   /**
144 144
    * Get the next child index above a shape.
145
+   * TODO: Make work for grouped shapes, make faster.
145 146
    * @param data
146 147
    * @param id
147 148
    */
@@ -158,9 +159,7 @@ export default class ProjectUtils {
158 159
 
159 160
     const nextSibling = siblings[index + 1]
160 161
 
161
-    if (!nextSibling) {
162
-      return shape.childIndex + 1
163
-    }
162
+    if (!nextSibling) return shape.childIndex + 1
164 163
 
165 164
     let nextIndex = (shape.childIndex + nextSibling.childIndex) / 2
166 165
 

+ 24
- 0
yarn.lock Ver arquivo

@@ -1206,6 +1206,30 @@
1206 1206
     "@types/yargs" "^16.0.0"
1207 1207
     chalk "^4.0.0"
1208 1208
 
1209
+"@liveblocks/client@0.8.0":
1210
+  version "0.8.0"
1211
+  resolved "https://registry.yarnpkg.com/@liveblocks/client/-/client-0.8.0.tgz#b2cd1cc197d1ada76f4083d3a9065ee9f8fa1dc4"
1212
+  integrity sha512-p7h7ZZpkyNjC/asdzjcZOzyTjINpQkgI5zrZGT7323VLXbn9ge/a+YL83N5sUosMtbjycfGLGHNN8fpSRgl7pA==
1213
+
1214
+"@liveblocks/client@^0.8.1":
1215
+  version "0.8.1"
1216
+  resolved "https://registry.yarnpkg.com/@liveblocks/client/-/client-0.8.1.tgz#4220542c84473d71fb4032442e6a9861a5983ba3"
1217
+  integrity sha512-+5LNtyOUA7RyxsK2uRupEZ6SzNhi1p9119fuWFrbrgP0dMabV40U7SVuvMnMxIsGzFqC+RoDQWEQ+iJFcuBVaQ==
1218
+
1219
+"@liveblocks/node@^0.3.0":
1220
+  version "0.3.0"
1221
+  resolved "https://registry.yarnpkg.com/@liveblocks/node/-/node-0.3.0.tgz#f22ff0c3415502af2baf22250431852e198b12e0"
1222
+  integrity sha512-3IJ6uN3QU71z6WXiDM97wW17fVVvrG9zMy4G4PY3zYzmeRfMnA+KBSBT1uPvlfgWm2D3d6/HNIXWxhwyv7bkfw==
1223
+  dependencies:
1224
+    node-fetch "^2.6.1"
1225
+
1226
+"@liveblocks/react@^0.8.0":
1227
+  version "0.8.0"
1228
+  resolved "https://registry.yarnpkg.com/@liveblocks/react/-/react-0.8.0.tgz#e538be3e0e3bcb5fe47e2f8ad5fcd2e9c379fc5d"
1229
+  integrity sha512-eCCVOz15ldmeDIT0AB08ExQrBROeAig6EBI0hY9tUe5iehADDDSAJmrkujg0K/lwPvtvzjjrzGq1qW79x2ggkg==
1230
+  dependencies:
1231
+    "@liveblocks/client" "0.8.0"
1232
+
1209 1233
 "@monaco-editor/loader@^1.1.1":
1210 1234
   version "1.1.1"
1211 1235
   resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.1.1.tgz#37db648c81a86946d0febd391de00df9c28a0a3d"

Carregando…
Cancelar
Salvar