@tldraw/editor 4.3.0 → 4.4.0-canary.09e80a09d230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist-cjs/index.d.ts +180 -11
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/LiveCollaborators.js +14 -24
- package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +201 -0
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +30 -16
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +3 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +13 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +58 -6
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +13 -21
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +378 -89
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js +144 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js.map +7 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +180 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +8 -3
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +29 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +29 -0
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/options.js +1 -0
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/collaboratorState.js +42 -0
- package/dist-cjs/lib/utils/collaboratorState.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +180 -11
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/LiveCollaborators.mjs +17 -24
- package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +181 -0
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +30 -16
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +3 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +13 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +58 -6
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +13 -21
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +378 -89
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs +114 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +160 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -3
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +29 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +33 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +1 -0
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/collaboratorState.mjs +22 -0
- package/dist-esm/lib/utils/collaboratorState.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +6 -0
- package/package.json +10 -8
- package/src/index.ts +3 -0
- package/src/lib/components/LiveCollaborators.tsx +26 -37
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +244 -0
- package/src/lib/components/default-components/DefaultCanvas.tsx +16 -6
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -1
- package/src/lib/components/default-components/DefaultShapeIndicators.tsx +16 -1
- package/src/lib/config/TLUserPreferences.test.ts +1 -0
- package/src/lib/config/TLUserPreferences.ts +8 -0
- package/src/lib/editor/Editor.ts +84 -6
- package/src/lib/editor/derivations/notVisibleShapes.ts +15 -41
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +491 -106
- package/src/lib/editor/managers/SpatialIndexManager/RBushIndex.ts +144 -0
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +214 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +24 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +44 -0
- package/src/lib/hooks/usePeerIds.ts +46 -1
- package/src/lib/options.ts +7 -0
- package/src/lib/utils/collaboratorState.ts +54 -0
- package/src/version.ts +3 -3
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -621
|
@@ -13,18 +13,111 @@ export interface ScribbleItem {
|
|
|
13
13
|
next: null | VecModel
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** @public */
|
|
17
|
+
export interface ScribbleSessionOptions {
|
|
18
|
+
/** Session id. Auto-generated if not provided. */
|
|
19
|
+
id?: string
|
|
20
|
+
/**
|
|
21
|
+
* Whether scribbles self-consume (shrink from start) while drawing.
|
|
22
|
+
* - true: scribbles eat their own tail as you draw (default, used for eraser/select)
|
|
23
|
+
* - false: scribbles persist until session stops (used for laser)
|
|
24
|
+
*/
|
|
25
|
+
selfConsume?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* How long to wait after last activity before auto-stopping the session.
|
|
28
|
+
* Only applies when selfConsume is false.
|
|
29
|
+
*/
|
|
30
|
+
idleTimeoutMs?: number
|
|
31
|
+
/**
|
|
32
|
+
* How scribbles fade when stopping.
|
|
33
|
+
* - 'individual': each scribble fades on its own (default)
|
|
34
|
+
* - 'grouped': all scribbles fade together as one sequence
|
|
35
|
+
*/
|
|
36
|
+
fadeMode?: 'individual' | 'grouped'
|
|
37
|
+
/**
|
|
38
|
+
* Easing for grouped fade.
|
|
39
|
+
*/
|
|
40
|
+
fadeEasing?: 'linear' | 'ease-in'
|
|
41
|
+
/**
|
|
42
|
+
* Duration of the fade in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
fadeDurationMs?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Internal session state (not exported)
|
|
48
|
+
interface Session {
|
|
49
|
+
id: string
|
|
50
|
+
items: ScribbleItem[]
|
|
51
|
+
state: 'active' | 'stopping' | 'complete'
|
|
52
|
+
options: Required<Omit<ScribbleSessionOptions, 'id'>>
|
|
53
|
+
idleTimeoutHandle?: number
|
|
54
|
+
fadeElapsed: number
|
|
55
|
+
totalPointsAtFadeStart: number
|
|
56
|
+
}
|
|
57
|
+
|
|
16
58
|
/** @public */
|
|
17
59
|
export class ScribbleManager {
|
|
18
|
-
|
|
19
|
-
state = 'paused' as 'paused' | 'running'
|
|
60
|
+
private sessions = new Map<string, Session>()
|
|
20
61
|
|
|
21
62
|
constructor(private editor: Editor) {}
|
|
22
63
|
|
|
23
|
-
|
|
24
|
-
|
|
64
|
+
// ==================== SESSION API ====================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Start a new session for grouping scribbles.
|
|
68
|
+
* Returns a session ID that can be used with other session methods.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Session configuration
|
|
71
|
+
* @returns Session ID
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
startSession(options: ScribbleSessionOptions = {}): string {
|
|
75
|
+
const id = options.id ?? uniqueId()
|
|
76
|
+
const session: Session = {
|
|
25
77
|
id,
|
|
78
|
+
items: [],
|
|
79
|
+
state: 'active',
|
|
80
|
+
options: {
|
|
81
|
+
selfConsume: options.selfConsume ?? true,
|
|
82
|
+
idleTimeoutMs: options.idleTimeoutMs ?? 0,
|
|
83
|
+
fadeMode: options.fadeMode ?? 'individual',
|
|
84
|
+
fadeEasing: options.fadeEasing ?? (options.fadeMode === 'grouped' ? 'ease-in' : 'linear'),
|
|
85
|
+
fadeDurationMs: options.fadeDurationMs ?? this.editor.options.laserFadeoutMs,
|
|
86
|
+
},
|
|
87
|
+
fadeElapsed: 0,
|
|
88
|
+
totalPointsAtFadeStart: 0,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.sessions.set(id, session)
|
|
92
|
+
|
|
93
|
+
// Set up idle timeout if configured
|
|
94
|
+
if (session.options.idleTimeoutMs > 0) {
|
|
95
|
+
this.resetIdleTimeout(session)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return id
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Add a scribble to a session.
|
|
103
|
+
*
|
|
104
|
+
* @param sessionId - The session ID
|
|
105
|
+
* @param scribble - Partial scribble properties
|
|
106
|
+
* @param scribbleId - Optional scribble ID
|
|
107
|
+
* @public
|
|
108
|
+
*/
|
|
109
|
+
addScribbleToSession(
|
|
110
|
+
sessionId: string,
|
|
111
|
+
scribble: Partial<TLScribble>,
|
|
112
|
+
scribbleId = uniqueId()
|
|
113
|
+
): ScribbleItem {
|
|
114
|
+
const session = this.sessions.get(sessionId)
|
|
115
|
+
if (!session) throw Error(`Session ${sessionId} not found`)
|
|
116
|
+
|
|
117
|
+
const item: ScribbleItem = {
|
|
118
|
+
id: scribbleId,
|
|
26
119
|
scribble: {
|
|
27
|
-
id,
|
|
120
|
+
id: scribbleId,
|
|
28
121
|
size: 20,
|
|
29
122
|
color: 'accent',
|
|
30
123
|
opacity: 0.8,
|
|
@@ -40,46 +133,217 @@ export class ScribbleManager {
|
|
|
40
133
|
prev: null,
|
|
41
134
|
next: null,
|
|
42
135
|
}
|
|
43
|
-
|
|
136
|
+
|
|
137
|
+
session.items.push(item)
|
|
138
|
+
|
|
139
|
+
// Reset idle timeout on activity
|
|
140
|
+
if (session.options.idleTimeoutMs > 0) {
|
|
141
|
+
this.resetIdleTimeout(session)
|
|
142
|
+
}
|
|
143
|
+
|
|
44
144
|
return item
|
|
45
145
|
}
|
|
46
146
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Add a point to a scribble in a session.
|
|
149
|
+
*
|
|
150
|
+
* @param sessionId - The session ID
|
|
151
|
+
* @param scribbleId - The scribble ID
|
|
152
|
+
* @param x - X coordinate
|
|
153
|
+
* @param y - Y coordinate
|
|
154
|
+
* @param z - Z coordinate (pressure)
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
addPointToSession(
|
|
158
|
+
sessionId: string,
|
|
159
|
+
scribbleId: string,
|
|
160
|
+
x: number,
|
|
161
|
+
y: number,
|
|
162
|
+
z = 0.5
|
|
163
|
+
): ScribbleItem {
|
|
164
|
+
const session = this.sessions.get(sessionId)
|
|
165
|
+
if (!session) throw Error(`Session ${sessionId} not found`)
|
|
166
|
+
|
|
167
|
+
const item = session.items.find((i) => i.id === scribbleId)
|
|
168
|
+
if (!item) throw Error(`Scribble ${scribbleId} not found in session ${sessionId}`)
|
|
169
|
+
|
|
170
|
+
const point = { x, y, z }
|
|
171
|
+
if (!item.prev || Vec.Dist(item.prev, point) >= 1) {
|
|
172
|
+
item.next = point
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Reset idle timeout on activity
|
|
176
|
+
if (session.options.idleTimeoutMs > 0) {
|
|
177
|
+
this.resetIdleTimeout(session)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return item
|
|
50
181
|
}
|
|
51
182
|
|
|
52
183
|
/**
|
|
53
|
-
*
|
|
184
|
+
* Extend a session, resetting its idle timeout.
|
|
54
185
|
*
|
|
186
|
+
* @param sessionId - The session ID
|
|
55
187
|
* @public
|
|
56
188
|
*/
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
if (!
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
189
|
+
extendSession(sessionId: string): void {
|
|
190
|
+
const session = this.sessions.get(sessionId)
|
|
191
|
+
if (!session) return
|
|
192
|
+
|
|
193
|
+
if (session.options.idleTimeoutMs > 0) {
|
|
194
|
+
this.resetIdleTimeout(session)
|
|
195
|
+
}
|
|
63
196
|
}
|
|
64
197
|
|
|
65
198
|
/**
|
|
66
|
-
*
|
|
199
|
+
* Stop a session, triggering fade-out.
|
|
67
200
|
*
|
|
68
|
-
* @param
|
|
69
|
-
* @param x - The x coordinate of the point.
|
|
70
|
-
* @param y - The y coordinate of the point.
|
|
71
|
-
* @param z - The z coordinate of the point.
|
|
201
|
+
* @param sessionId - The session ID
|
|
72
202
|
* @public
|
|
73
203
|
*/
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
if (!
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
204
|
+
stopSession(sessionId: string): void {
|
|
205
|
+
const session = this.sessions.get(sessionId)
|
|
206
|
+
if (!session || session.state !== 'active') return
|
|
207
|
+
|
|
208
|
+
this.clearIdleTimeout(session)
|
|
209
|
+
session.state = 'stopping'
|
|
210
|
+
|
|
211
|
+
if (session.options.fadeMode === 'grouped') {
|
|
212
|
+
session.totalPointsAtFadeStart = session.items.reduce(
|
|
213
|
+
(sum, item) => sum + item.scribble.points.length,
|
|
214
|
+
0
|
|
215
|
+
)
|
|
216
|
+
session.fadeElapsed = 0
|
|
217
|
+
for (const item of session.items) {
|
|
218
|
+
item.scribble.state = 'stopping'
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
for (const item of session.items) {
|
|
222
|
+
item.delayRemaining = Math.min(item.delayRemaining, 200)
|
|
223
|
+
item.scribble.state = 'stopping'
|
|
224
|
+
}
|
|
81
225
|
}
|
|
82
|
-
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Clear all scribbles in a session immediately.
|
|
230
|
+
*
|
|
231
|
+
* @param sessionId - The session ID
|
|
232
|
+
* @public
|
|
233
|
+
*/
|
|
234
|
+
clearSession(sessionId: string): void {
|
|
235
|
+
const session = this.sessions.get(sessionId)
|
|
236
|
+
if (!session) return
|
|
237
|
+
|
|
238
|
+
this.clearIdleTimeout(session)
|
|
239
|
+
for (const item of session.items) {
|
|
240
|
+
item.scribble.points.length = 0
|
|
241
|
+
}
|
|
242
|
+
session.state = 'complete'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if a session is active.
|
|
247
|
+
*
|
|
248
|
+
* @param sessionId - The session ID
|
|
249
|
+
* @public
|
|
250
|
+
*/
|
|
251
|
+
isSessionActive(sessionId: string): boolean {
|
|
252
|
+
const session = this.sessions.get(sessionId)
|
|
253
|
+
return session?.state === 'active'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ==================== SIMPLE API (for eraser, select, etc.) ====================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Add a scribble using the default self-consuming behavior.
|
|
260
|
+
* Creates an implicit session for the scribble.
|
|
261
|
+
*
|
|
262
|
+
* @param scribble - Partial scribble properties
|
|
263
|
+
* @param id - Optional scribble id
|
|
264
|
+
* @returns The created scribble item
|
|
265
|
+
* @public
|
|
266
|
+
*/
|
|
267
|
+
addScribble(scribble: Partial<TLScribble>, id = uniqueId()): ScribbleItem {
|
|
268
|
+
const sessionId = this.startSession()
|
|
269
|
+
return this.addScribbleToSession(sessionId, scribble, id)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Add a point to a scribble. Searches all sessions.
|
|
274
|
+
*
|
|
275
|
+
* @param id - The scribble id
|
|
276
|
+
* @param x - X coordinate
|
|
277
|
+
* @param y - Y coordinate
|
|
278
|
+
* @param z - Z coordinate (pressure)
|
|
279
|
+
* @public
|
|
280
|
+
*/
|
|
281
|
+
addPoint(id: string, x: number, y: number, z = 0.5): ScribbleItem {
|
|
282
|
+
for (const session of this.sessions.values()) {
|
|
283
|
+
const item = session.items.find((i) => i.id === id)
|
|
284
|
+
if (item) {
|
|
285
|
+
const point = { x, y, z }
|
|
286
|
+
if (!item.prev || Vec.Dist(item.prev, point) >= 1) {
|
|
287
|
+
item.next = point
|
|
288
|
+
}
|
|
289
|
+
if (session.options.idleTimeoutMs > 0) {
|
|
290
|
+
this.resetIdleTimeout(session)
|
|
291
|
+
}
|
|
292
|
+
return item
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
throw Error(`Scribble with id ${id} not found`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Mark a scribble as complete (done being drawn but not yet fading).
|
|
300
|
+
* Searches all sessions.
|
|
301
|
+
*
|
|
302
|
+
* @param id - The scribble id
|
|
303
|
+
* @public
|
|
304
|
+
*/
|
|
305
|
+
complete(id: string): ScribbleItem {
|
|
306
|
+
for (const session of this.sessions.values()) {
|
|
307
|
+
const item = session.items.find((i) => i.id === id)
|
|
308
|
+
if (item) {
|
|
309
|
+
if (item.scribble.state === 'starting' || item.scribble.state === 'active') {
|
|
310
|
+
item.scribble.state = 'complete'
|
|
311
|
+
}
|
|
312
|
+
return item
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
throw Error(`Scribble with id ${id} not found`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Stop a scribble. Searches all sessions.
|
|
320
|
+
*
|
|
321
|
+
* @param id - The scribble id
|
|
322
|
+
* @public
|
|
323
|
+
*/
|
|
324
|
+
stop(id: string): ScribbleItem {
|
|
325
|
+
for (const session of this.sessions.values()) {
|
|
326
|
+
const item = session.items.find((i) => i.id === id)
|
|
327
|
+
if (item) {
|
|
328
|
+
item.delayRemaining = Math.min(item.delayRemaining, 200)
|
|
329
|
+
item.scribble.state = 'stopping'
|
|
330
|
+
return item
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
throw Error(`Scribble with id ${id} not found`)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Stop and remove all sessions.
|
|
338
|
+
*
|
|
339
|
+
* @public
|
|
340
|
+
*/
|
|
341
|
+
reset(): void {
|
|
342
|
+
for (const session of this.sessions.values()) {
|
|
343
|
+
this.clearIdleTimeout(session)
|
|
344
|
+
}
|
|
345
|
+
this.sessions.clear()
|
|
346
|
+
this.editor.updateInstanceState({ scribbles: [] })
|
|
83
347
|
}
|
|
84
348
|
|
|
85
349
|
/**
|
|
@@ -88,98 +352,219 @@ export class ScribbleManager {
|
|
|
88
352
|
* @param elapsed - The number of milliseconds since the last tick.
|
|
89
353
|
* @public
|
|
90
354
|
*/
|
|
91
|
-
tick(elapsed: number) {
|
|
92
|
-
|
|
355
|
+
tick(elapsed: number): void {
|
|
356
|
+
const currentScribbles = this.editor.getInstanceState().scribbles
|
|
357
|
+
if (this.sessions.size === 0 && currentScribbles.length === 0) return
|
|
358
|
+
|
|
93
359
|
this.editor.run(() => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const { next, prev } = item
|
|
99
|
-
if (next && next !== prev) {
|
|
100
|
-
item.prev = next
|
|
101
|
-
item.scribble.points.push(next)
|
|
102
|
-
}
|
|
360
|
+
// Tick all sessions
|
|
361
|
+
for (const session of this.sessions.values()) {
|
|
362
|
+
this.tickSession(session, elapsed)
|
|
363
|
+
}
|
|
103
364
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
365
|
+
// Remove completed sessions
|
|
366
|
+
for (const [id, session] of this.sessions) {
|
|
367
|
+
if (session.state === 'complete') {
|
|
368
|
+
this.clearIdleTimeout(session)
|
|
369
|
+
this.sessions.delete(id)
|
|
108
370
|
}
|
|
371
|
+
}
|
|
109
372
|
|
|
110
|
-
|
|
111
|
-
|
|
373
|
+
// Collect scribbles from all sessions
|
|
374
|
+
const scribbles: TLScribble[] = []
|
|
375
|
+
for (const session of this.sessions.values()) {
|
|
376
|
+
for (const item of session.items) {
|
|
377
|
+
if (item.scribble.points.length > 0) {
|
|
378
|
+
scribbles.push({
|
|
379
|
+
...item.scribble,
|
|
380
|
+
points: [...item.scribble.points],
|
|
381
|
+
})
|
|
382
|
+
}
|
|
112
383
|
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.editor.updateInstanceState({ scribbles })
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ==================== PRIVATE HELPERS ====================
|
|
113
391
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
392
|
+
private resetIdleTimeout(session: Session): void {
|
|
393
|
+
this.clearIdleTimeout(session)
|
|
394
|
+
session.idleTimeoutHandle = this.editor.timers.setTimeout(() => {
|
|
395
|
+
this.stopSession(session.id)
|
|
396
|
+
}, session.options.idleTimeoutMs)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private clearIdleTimeout(session: Session): void {
|
|
400
|
+
if (session.idleTimeoutHandle !== undefined) {
|
|
401
|
+
clearTimeout(session.idleTimeoutHandle)
|
|
402
|
+
session.idleTimeoutHandle = undefined
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private tickSession(session: Session, elapsed: number): void {
|
|
407
|
+
if (session.state === 'complete') return
|
|
408
|
+
|
|
409
|
+
if (session.state === 'stopping' && session.options.fadeMode === 'grouped') {
|
|
410
|
+
this.tickGroupedFade(session, elapsed)
|
|
411
|
+
} else {
|
|
412
|
+
this.tickSessionItems(session, elapsed)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Check if session is complete
|
|
416
|
+
const hasContent = session.items.some((item) => item.scribble.points.length > 0)
|
|
417
|
+
if (!hasContent && (session.state === 'stopping' || session.items.length === 0)) {
|
|
418
|
+
session.state = 'complete'
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private tickSessionItems(session: Session, elapsed: number): void {
|
|
423
|
+
for (const item of session.items) {
|
|
424
|
+
const shouldSelfConsume =
|
|
425
|
+
session.options.selfConsume ||
|
|
426
|
+
session.state === 'stopping' ||
|
|
427
|
+
item.scribble.state === 'stopping'
|
|
428
|
+
|
|
429
|
+
if (shouldSelfConsume) {
|
|
430
|
+
this.tickSelfConsumingItem(item, elapsed)
|
|
431
|
+
} else {
|
|
432
|
+
this.tickPersistentItem(item)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Remove completed items in individual fade mode
|
|
437
|
+
if (session.options.fadeMode === 'individual') {
|
|
438
|
+
for (let i = session.items.length - 1; i >= 0; i--) {
|
|
439
|
+
if (session.items[i].scribble.points.length === 0) {
|
|
440
|
+
session.items.splice(i, 1)
|
|
117
441
|
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private tickPersistentItem(item: ScribbleItem): void {
|
|
447
|
+
const { scribble } = item
|
|
448
|
+
|
|
449
|
+
if (scribble.state === 'starting') {
|
|
450
|
+
const { next, prev } = item
|
|
451
|
+
if (next && next !== prev) {
|
|
452
|
+
item.prev = next
|
|
453
|
+
scribble.points.push(next)
|
|
454
|
+
}
|
|
455
|
+
if (scribble.points.length > 8) {
|
|
456
|
+
scribble.state = 'active'
|
|
457
|
+
}
|
|
458
|
+
return
|
|
459
|
+
}
|
|
118
460
|
|
|
119
|
-
|
|
461
|
+
if (scribble.state === 'active') {
|
|
462
|
+
const { next, prev } = item
|
|
463
|
+
if (next && next !== prev) {
|
|
464
|
+
item.prev = next
|
|
465
|
+
scribble.points.push(next)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
120
469
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (next && next !== prev) {
|
|
124
|
-
item.prev = next
|
|
125
|
-
scribble.points.push(next)
|
|
470
|
+
private tickSelfConsumingItem(item: ScribbleItem, elapsed: number): void {
|
|
471
|
+
const { scribble } = item
|
|
126
472
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
473
|
+
if (scribble.state === 'starting') {
|
|
474
|
+
const { next, prev } = item
|
|
475
|
+
if (next && next !== prev) {
|
|
476
|
+
item.prev = next
|
|
477
|
+
scribble.points.push(next)
|
|
478
|
+
}
|
|
479
|
+
if (scribble.points.length > 8) {
|
|
480
|
+
scribble.state = 'active'
|
|
481
|
+
}
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (item.delayRemaining > 0) {
|
|
486
|
+
item.delayRemaining = Math.max(0, item.delayRemaining - elapsed)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
item.timeoutMs += elapsed
|
|
490
|
+
if (item.timeoutMs >= 16) {
|
|
491
|
+
item.timeoutMs = 0
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const { delayRemaining, timeoutMs, prev, next } = item
|
|
495
|
+
|
|
496
|
+
switch (scribble.state) {
|
|
497
|
+
case 'active': {
|
|
498
|
+
if (next && next !== prev) {
|
|
499
|
+
item.prev = next
|
|
500
|
+
scribble.points.push(next)
|
|
501
|
+
if (delayRemaining === 0 && scribble.points.length > 8) {
|
|
502
|
+
scribble.points.shift()
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
if (timeoutMs === 0) {
|
|
506
|
+
if (scribble.points.length > 1) {
|
|
507
|
+
scribble.points.shift()
|
|
133
508
|
} else {
|
|
134
|
-
|
|
135
|
-
if (timeoutMs === 0) {
|
|
136
|
-
if (scribble.points.length > 1) {
|
|
137
|
-
scribble.points.shift()
|
|
138
|
-
} else {
|
|
139
|
-
// Reset the item's delay
|
|
140
|
-
item.delayRemaining = scribble.delay
|
|
141
|
-
}
|
|
142
|
-
}
|
|
509
|
+
item.delayRemaining = scribble.delay
|
|
143
510
|
}
|
|
144
|
-
break
|
|
145
511
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (scribble.shrink) {
|
|
156
|
-
// Drop the scribble's size as it shrinks
|
|
157
|
-
scribble.size = Math.max(1, scribble.size * (1 - scribble.shrink))
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Drop the scribble's first point (its tail)
|
|
161
|
-
scribble.points.shift()
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
break
|
|
512
|
+
}
|
|
513
|
+
break
|
|
514
|
+
}
|
|
515
|
+
case 'stopping': {
|
|
516
|
+
if (delayRemaining === 0 && timeoutMs === 0) {
|
|
517
|
+
if (scribble.points.length <= 1) {
|
|
518
|
+
scribble.points.length = 0
|
|
519
|
+
return
|
|
165
520
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
break
|
|
521
|
+
if (scribble.shrink) {
|
|
522
|
+
scribble.size = Math.max(1, scribble.size * (1 - scribble.shrink))
|
|
169
523
|
}
|
|
524
|
+
scribble.points.shift()
|
|
170
525
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
526
|
+
break
|
|
527
|
+
}
|
|
528
|
+
case 'paused': {
|
|
529
|
+
break
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private tickGroupedFade(session: Session, elapsed: number): void {
|
|
535
|
+
session.fadeElapsed += elapsed
|
|
536
|
+
|
|
537
|
+
let remainingPoints = 0
|
|
538
|
+
for (const item of session.items) {
|
|
539
|
+
remainingPoints += item.scribble.points.length
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (remainingPoints === 0) return
|
|
543
|
+
|
|
544
|
+
if (session.fadeElapsed >= session.options.fadeDurationMs) {
|
|
545
|
+
for (const item of session.items) {
|
|
546
|
+
item.scribble.points.length = 0
|
|
547
|
+
}
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const progress = session.fadeElapsed / session.options.fadeDurationMs
|
|
552
|
+
const easedProgress = session.options.fadeEasing === 'ease-in' ? progress * progress : progress
|
|
553
|
+
|
|
554
|
+
const targetRemoved = Math.floor(easedProgress * session.totalPointsAtFadeStart)
|
|
555
|
+
const actuallyRemoved = session.totalPointsAtFadeStart - remainingPoints
|
|
556
|
+
const pointsToRemove = Math.max(1, targetRemoved - actuallyRemoved)
|
|
557
|
+
|
|
558
|
+
let removed = 0
|
|
559
|
+
let itemIndex = 0
|
|
560
|
+
while (removed < pointsToRemove && itemIndex < session.items.length) {
|
|
561
|
+
const item = session.items[itemIndex]
|
|
562
|
+
if (item.scribble.points.length > 0) {
|
|
563
|
+
item.scribble.points.shift()
|
|
564
|
+
removed++
|
|
565
|
+
} else {
|
|
566
|
+
itemIndex++
|
|
567
|
+
}
|
|
568
|
+
}
|
|
184
569
|
}
|
|
185
570
|
}
|