@q9labsai/chalk-whiteboard 5.0.1 → 5.2.0
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/CHANGELOG.md +28 -0
- package/dist/chunk-IVFWOYSW.js +10 -0
- package/dist/{chunk-W4FLPXAX.js → chunk-MPNIDZHM.js} +1 -1
- package/dist/{chunk-6ZV7GBAC.js → chunk-X3WDTGPQ.js} +57 -7
- package/dist/collab/engine.d.ts +41 -0
- package/dist/collab/index.js +3 -2
- package/dist/collab/ink.d.ts +5 -0
- package/dist/collab/presence.d.ts +1 -0
- package/dist/embedded/chalk-whiteboard/asset-manifest.json +2 -2
- package/dist/embedded/chalk-whiteboard/renderer.js +579 -579
- package/dist/embedded/controller.d.ts +17 -1
- package/dist/embedded/index.js +2 -0
- package/dist/{engine-BIJ57YN3.js → engine-MERF7RL3.js} +2 -1
- package/dist/index.js +3 -2
- package/dist/react/WhiteboardCanvas.d.ts +17 -0
- package/dist/react/engine-history.d.ts +7 -0
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +164 -55
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 5.2.0
|
|
4
|
+
|
|
5
|
+
### follow-view-owner
|
|
6
|
+
|
|
7
|
+
Converge Follow views on one Episode owner, recover from its canonical snapshot,
|
|
8
|
+
and preserve real camera moves during recovery and tab switches. Zoom controls
|
|
9
|
+
and keyboard moves now consistently leave Follow and lead the person's view.
|
|
10
|
+
|
|
11
|
+
Keep all 500 active people's views using internal schema-generated limits in the client and React caches. The client's public bounds, event unions and decoded-frame unions are unchanged. The client now exports `ChalkWhiteboardViewVersions`, which the React SDK uses to order view moves, and whiteboard view events now carry `ownerId`, `ownerSequence` and `originEpoch`, so both releases are minor.
|
|
12
|
+
|
|
13
|
+
## 5.1.0
|
|
14
|
+
|
|
15
|
+
- Add headless mode for hosts that draw their own board controls: `WhiteboardCanvas` accepts `headless`, `inkColor` and `onEngineReady`, and the React entry exports `WhiteboardEngineHandle` (clear, clear with undo, place math), `CANONICAL_INK`, and `observeEngineHistory` with `EngineHistoryState`.
|
|
16
|
+
- Add shared board view events, so collaborators can follow a person's view or bring everyone to theirs.
|
|
17
|
+
- Keep Clear and Undo bound to the scene they were taken from. Redo brings back undone text, and small drawings open at 100% zoom at most.
|
|
18
|
+
|
|
19
|
+
See the [project changelog](../../CHANGELOG.md) for the coordinated v5.1 release.
|
|
20
|
+
|
|
21
|
+
## 5.0.0
|
|
22
|
+
|
|
23
|
+
- Export `preloadWhiteboardStylesheet` from the React entry point.
|
|
24
|
+
- Keep element order stable across board loads and reconnects.
|
|
25
|
+
- Keep the rest of a board syncing when an element exceeds the wire size limit.
|
|
26
|
+
- BREAKING: remove the retired local image-upload queue and import path. Remote file downloads remain available.
|
|
27
|
+
|
|
28
|
+
See the [project changelog](../../CHANGELOG.md) for the coordinated v5 release.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/collab/ink.ts
|
|
2
|
+
var CANONICAL_INK = "#1b1c1f";
|
|
3
|
+
function withInk(element, from, to) {
|
|
4
|
+
return from !== to && element.strokeColor.toLowerCase() === from.toLowerCase() ? { ...element, strokeColor: to } : element;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
CANONICAL_INK,
|
|
9
|
+
withInk
|
|
10
|
+
};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CANONICAL_INK,
|
|
3
|
+
withInk
|
|
4
|
+
} from "./chunk-IVFWOYSW.js";
|
|
5
|
+
|
|
1
6
|
// src/collab/engine.ts
|
|
2
|
-
import { CaptureUpdateAction as CaptureUpdateAction2, reconcileElements, restoreElements } from "@excalidraw/excalidraw";
|
|
7
|
+
import { CaptureUpdateAction as CaptureUpdateAction2, newElementWith as newElementWith2, reconcileElements, restoreElements } from "@excalidraw/excalidraw";
|
|
3
8
|
|
|
4
9
|
// src/collab/camera.ts
|
|
5
10
|
function getWhiteboardCamera(appState) {
|
|
@@ -193,7 +198,7 @@ var toSocketId = (value) => value;
|
|
|
193
198
|
var WhiteboardPresence = class {
|
|
194
199
|
constructor(opts) {
|
|
195
200
|
this.opts = opts;
|
|
196
|
-
this.pruneInterval = setInterval(() => this.pruneStale(), 1e3);
|
|
201
|
+
if (opts.renderCursors) this.pruneInterval = setInterval(() => this.pruneStale(), 1e3);
|
|
197
202
|
}
|
|
198
203
|
opts;
|
|
199
204
|
lastCursorSend = 0;
|
|
@@ -211,6 +216,7 @@ var WhiteboardPresence = class {
|
|
|
211
216
|
this.opts.sendCursor(payload.pointer);
|
|
212
217
|
}
|
|
213
218
|
handleRemoteCursor(payload) {
|
|
219
|
+
if (!this.opts.renderCursors) return;
|
|
214
220
|
const ts = payload.timestamp instanceof Date ? payload.timestamp.getTime() : Date.now();
|
|
215
221
|
this.collaborators.set(payload.participantId, {
|
|
216
222
|
pointer: { x: payload.x, y: payload.y, tool: "pointer", renderCursor: true },
|
|
@@ -399,6 +405,8 @@ var ExcalidrawCollabEngine = class {
|
|
|
399
405
|
constructor(opts) {
|
|
400
406
|
this.opts = opts;
|
|
401
407
|
this.canDraw = opts.canDraw;
|
|
408
|
+
this.canManage = opts.canManage;
|
|
409
|
+
this.inkColor = opts.inkColor ?? CANONICAL_INK;
|
|
402
410
|
this.filesSync = new WhiteboardFilesSync({
|
|
403
411
|
excalidrawAPI: opts.excalidrawAPI,
|
|
404
412
|
fileTransfer: opts.fileTransfer,
|
|
@@ -409,6 +417,7 @@ var ExcalidrawCollabEngine = class {
|
|
|
409
417
|
this.presence = new WhiteboardPresence({
|
|
410
418
|
excalidrawAPI: opts.excalidrawAPI,
|
|
411
419
|
sendCursor: opts.sendCursor,
|
|
420
|
+
renderCursors: opts.renderCursors ?? true,
|
|
412
421
|
throttleMs: CURSOR_THROTTLE_MS,
|
|
413
422
|
staleMs: CURSOR_STALE_MS
|
|
414
423
|
});
|
|
@@ -417,8 +426,11 @@ var ExcalidrawCollabEngine = class {
|
|
|
417
426
|
}
|
|
418
427
|
opts;
|
|
419
428
|
sceneId = null;
|
|
429
|
+
sceneRevision = null;
|
|
420
430
|
sceneGeneration = null;
|
|
421
431
|
canDraw = true;
|
|
432
|
+
canManage;
|
|
433
|
+
inkColor;
|
|
422
434
|
storedImageFileIds = /* @__PURE__ */ new Set();
|
|
423
435
|
broadcastedElementVersions = /* @__PURE__ */ new Map();
|
|
424
436
|
rejectedElementVersions = /* @__PURE__ */ new Map();
|
|
@@ -437,11 +449,24 @@ var ExcalidrawCollabEngine = class {
|
|
|
437
449
|
setCanDraw(next) {
|
|
438
450
|
this.canDraw = next;
|
|
439
451
|
}
|
|
452
|
+
setCanManage(next) {
|
|
453
|
+
this.canManage = next;
|
|
454
|
+
}
|
|
455
|
+
/** Repaints Ink for a new surface without a new version, so nothing is sent. */
|
|
456
|
+
setInkColor(next) {
|
|
457
|
+
const previous = this.inkColor;
|
|
458
|
+
if (next === previous) return;
|
|
459
|
+
this.inkColor = next;
|
|
460
|
+
const api = this.opts.excalidrawAPI;
|
|
461
|
+
api.updateScene({ elements: api.getSceneElementsIncludingDeleted().map((element) => withInk(element, previous, next)), captureUpdate: CaptureUpdateAction2.NEVER });
|
|
462
|
+
api.history.clear();
|
|
463
|
+
}
|
|
440
464
|
async clear() {
|
|
441
|
-
if (!this.canDraw) throw new Error("whiteboard
|
|
465
|
+
if (!(this.canManage ?? this.canDraw)) throw new Error("whiteboard management is not permitted");
|
|
442
466
|
const commit = await this.opts.clear();
|
|
443
467
|
this.storedImageFileIds.clear();
|
|
444
468
|
this.sceneId = commit.sceneId;
|
|
469
|
+
this.sceneRevision = commit.revision;
|
|
445
470
|
this.sceneGeneration = commit.sceneGeneration ?? null;
|
|
446
471
|
this.broadcastedElementVersions.clear();
|
|
447
472
|
this.rejectedElementVersions.clear();
|
|
@@ -451,8 +476,20 @@ var ExcalidrawCollabEngine = class {
|
|
|
451
476
|
elements: [],
|
|
452
477
|
captureUpdate: CaptureUpdateAction2.NEVER
|
|
453
478
|
});
|
|
479
|
+
this.opts.excalidrawAPI.history.clear();
|
|
454
480
|
return commit;
|
|
455
481
|
}
|
|
482
|
+
/** Restore only into the exact empty scene committed by this clear. */
|
|
483
|
+
async clearWithUndo() {
|
|
484
|
+
const api = this.opts.excalidrawAPI;
|
|
485
|
+
const before = api.getSceneElements();
|
|
486
|
+
const commit = await this.clear();
|
|
487
|
+
return () => {
|
|
488
|
+
if (this.disposed || !this.canDraw || this.sceneId !== commit.sceneId || this.sceneRevision !== commit.revision || this.sceneGeneration !== (commit.sceneGeneration ?? null) || api.getSceneElementsIncludingDeleted().length > 0) return false;
|
|
489
|
+
api.updateScene({ elements: before.map((element) => newElementWith2(element, {})), captureUpdate: CaptureUpdateAction2.IMMEDIATELY });
|
|
490
|
+
return true;
|
|
491
|
+
};
|
|
492
|
+
}
|
|
456
493
|
dispose() {
|
|
457
494
|
if (this.disposed) return;
|
|
458
495
|
const hasPendingChange = this.changeDebounce !== null || this.dirtyDuringSubmission || this.submissionRetryTimer !== null;
|
|
@@ -497,6 +534,7 @@ var ExcalidrawCollabEngine = class {
|
|
|
497
534
|
case "snapshot":
|
|
498
535
|
this.handleRemoteSnapshot({
|
|
499
536
|
sceneId: event.sceneId,
|
|
537
|
+
revision: event.revision,
|
|
500
538
|
...event.sceneGeneration ? { sceneGeneration: event.sceneGeneration } : {},
|
|
501
539
|
elements: event.elements,
|
|
502
540
|
appState: event.appState
|
|
@@ -505,6 +543,7 @@ var ExcalidrawCollabEngine = class {
|
|
|
505
543
|
case "update":
|
|
506
544
|
this.handleRemoteData({
|
|
507
545
|
sceneId: event.sceneId,
|
|
546
|
+
revision: event.revision,
|
|
508
547
|
...event.sceneGeneration ? { sceneGeneration: event.sceneGeneration } : {},
|
|
509
548
|
syncAll: false,
|
|
510
549
|
elements: event.elements
|
|
@@ -519,25 +558,30 @@ var ExcalidrawCollabEngine = class {
|
|
|
519
558
|
timestamp: new Date(event.occurredAt)
|
|
520
559
|
});
|
|
521
560
|
return;
|
|
561
|
+
case "view":
|
|
562
|
+
return;
|
|
522
563
|
case "reset_required":
|
|
564
|
+
this.sceneRevision = null;
|
|
523
565
|
this.requestSnapshot();
|
|
524
566
|
}
|
|
525
567
|
}
|
|
526
568
|
handleRemoteData(payload) {
|
|
527
569
|
this.applyRemoteElements({
|
|
528
570
|
sceneId: payload.sceneId,
|
|
571
|
+
revision: payload.revision,
|
|
529
572
|
...payload.sceneGeneration ? { sceneGeneration: payload.sceneGeneration } : {},
|
|
530
573
|
syncAll: payload.syncAll,
|
|
531
|
-
remoteElements: payload.elements.map(fromWireElement),
|
|
574
|
+
remoteElements: payload.elements.map((element) => withInk(fromWireElement(element), CANONICAL_INK, this.inkColor)),
|
|
532
575
|
isSnapshot: false
|
|
533
576
|
});
|
|
534
577
|
}
|
|
535
578
|
handleRemoteSnapshot(payload) {
|
|
536
579
|
this.applyRemoteElements({
|
|
537
580
|
sceneId: payload.sceneId,
|
|
581
|
+
revision: payload.revision,
|
|
538
582
|
...payload.sceneGeneration ? { sceneGeneration: payload.sceneGeneration } : {},
|
|
539
583
|
syncAll: true,
|
|
540
|
-
remoteElements: payload.elements.map(fromWireElement),
|
|
584
|
+
remoteElements: payload.elements.map((element) => withInk(fromWireElement(element), CANONICAL_INK, this.inkColor)),
|
|
541
585
|
appState: payload.appState,
|
|
542
586
|
isSnapshot: true
|
|
543
587
|
});
|
|
@@ -645,6 +689,7 @@ var ExcalidrawCollabEngine = class {
|
|
|
645
689
|
this.requestSnapshot();
|
|
646
690
|
return;
|
|
647
691
|
}
|
|
692
|
+
this.sceneRevision = commit.revision;
|
|
648
693
|
for (const element of submittedElements) {
|
|
649
694
|
const previous = this.broadcastedElementVersions.get(element.id);
|
|
650
695
|
const submittedVersion = { version: element.version, versionNonce: element.version_nonce };
|
|
@@ -685,7 +730,7 @@ var ExcalidrawCollabEngine = class {
|
|
|
685
730
|
const submitted = [];
|
|
686
731
|
for (const element of elements) {
|
|
687
732
|
try {
|
|
688
|
-
submitted.push(toFittedWireElement(element));
|
|
733
|
+
submitted.push(toFittedWireElement(withInk(element, this.inkColor, CANONICAL_INK)));
|
|
689
734
|
this.rejectedElementVersions.delete(element.id);
|
|
690
735
|
} catch (cause) {
|
|
691
736
|
const reported = this.rejectedElementVersions.has(element.id);
|
|
@@ -723,12 +768,17 @@ var ExcalidrawCollabEngine = class {
|
|
|
723
768
|
this.deferredRemoteElements.clear();
|
|
724
769
|
this.resetSubmissionRetries();
|
|
725
770
|
}
|
|
771
|
+
this.sceneRevision = args.revision ?? null;
|
|
726
772
|
if (args.sceneGeneration) this.sceneGeneration = args.sceneGeneration;
|
|
727
773
|
const excalidrawAPI = this.opts.excalidrawAPI;
|
|
728
774
|
if (replacingScene) excalidrawAPI.history.clear();
|
|
729
775
|
const local = replacingScene ? [] : excalidrawAPI.getSceneElementsIncludingDeleted();
|
|
730
776
|
const remoteElements = [...args.remoteElements].sort(compareElementOrder);
|
|
731
|
-
const
|
|
777
|
+
const received = new Map(remoteElements.map((element) => [element.id, element]));
|
|
778
|
+
const restoredRemote = restoreElements(remoteElements, null).map((element) => {
|
|
779
|
+
const remote = received.get(element.id);
|
|
780
|
+
return remote && !remote.isDeleted && element.isDeleted ? { ...element, isDeleted: false, version: remote.version, versionNonce: remote.versionNonce } : element;
|
|
781
|
+
});
|
|
732
782
|
this.storedImageFileIds = /* @__PURE__ */ new Set([...this.storedImageFileIds, ...remoteImageFileIds(restoredRemote)]);
|
|
733
783
|
const remoteForReconcile = toReconcileRemoteElements(restoredRemote);
|
|
734
784
|
const appState = excalidrawAPI.getAppState();
|
package/dist/collab/engine.d.ts
CHANGED
|
@@ -22,7 +22,31 @@ export type WhiteboardCollaborationEvent = {
|
|
|
22
22
|
readonly x: number;
|
|
23
23
|
readonly y: number;
|
|
24
24
|
readonly occurredAt: string;
|
|
25
|
+
} | ({
|
|
26
|
+
readonly type: "view";
|
|
27
|
+
readonly participantId: string;
|
|
28
|
+
readonly displayName: string;
|
|
29
|
+
readonly x: number;
|
|
30
|
+
readonly y: number;
|
|
31
|
+
readonly width: number;
|
|
32
|
+
readonly height: number;
|
|
33
|
+
readonly bring: boolean;
|
|
34
|
+
readonly occurredAt: string;
|
|
35
|
+
} & ({
|
|
36
|
+
readonly originParticipantId: string;
|
|
37
|
+
readonly originSequence: number;
|
|
38
|
+
readonly originEpoch?: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly originParticipantId?: never;
|
|
41
|
+
readonly originSequence?: never;
|
|
42
|
+
readonly originEpoch?: never;
|
|
43
|
+
}) & ({
|
|
44
|
+
readonly ownerId: string;
|
|
45
|
+
readonly ownerSequence: number;
|
|
25
46
|
} | {
|
|
47
|
+
readonly ownerId?: never;
|
|
48
|
+
readonly ownerSequence?: never;
|
|
49
|
+
})) | {
|
|
26
50
|
readonly type: "reset_required";
|
|
27
51
|
readonly sceneId: string;
|
|
28
52
|
readonly reason: "scene_changed" | "cursor_expired" | "gap";
|
|
@@ -30,8 +54,11 @@ export type WhiteboardCollaborationEvent = {
|
|
|
30
54
|
export interface ExcalidrawCollabEngineOptions extends WhiteboardFileTransferOptions {
|
|
31
55
|
excalidrawAPI: ExcalidrawImperativeAPI;
|
|
32
56
|
canDraw: boolean;
|
|
57
|
+
/** Explicit management authority; omitted hosts keep the original draw-based behavior. */
|
|
58
|
+
canManage?: boolean;
|
|
33
59
|
submitUpdate: (payload: {
|
|
34
60
|
sceneId: string;
|
|
61
|
+
revision?: string;
|
|
35
62
|
sceneGeneration?: string;
|
|
36
63
|
syncAll: boolean;
|
|
37
64
|
elements: readonly WhiteboardWireElement[];
|
|
@@ -45,12 +72,19 @@ export interface ExcalidrawCollabEngineOptions extends WhiteboardFileTransferOpt
|
|
|
45
72
|
subscribe: (listener: (event: WhiteboardCollaborationEvent) => void) => () => void;
|
|
46
73
|
onFileSyncStateChange?: (state: WhiteboardFileSyncState) => void;
|
|
47
74
|
onSubmissionError?: (error: unknown) => void;
|
|
75
|
+
/** False when the host draws its own remote cursors. */
|
|
76
|
+
renderCursors?: boolean;
|
|
77
|
+
/** The colour this viewer sees for Ink, which travels as one canonical colour. */
|
|
78
|
+
inkColor?: string;
|
|
48
79
|
}
|
|
49
80
|
export declare class ExcalidrawCollabEngine {
|
|
50
81
|
private readonly opts;
|
|
51
82
|
private sceneId;
|
|
83
|
+
private sceneRevision;
|
|
52
84
|
private sceneGeneration;
|
|
53
85
|
private canDraw;
|
|
86
|
+
private canManage;
|
|
87
|
+
private inkColor;
|
|
54
88
|
private storedImageFileIds;
|
|
55
89
|
private broadcastedElementVersions;
|
|
56
90
|
private rejectedElementVersions;
|
|
@@ -68,7 +102,12 @@ export declare class ExcalidrawCollabEngine {
|
|
|
68
102
|
private readonly unsubscribe;
|
|
69
103
|
constructor(opts: ExcalidrawCollabEngineOptions);
|
|
70
104
|
setCanDraw(next: boolean): void;
|
|
105
|
+
setCanManage(next: boolean | undefined): void;
|
|
106
|
+
/** Repaints Ink for a new surface without a new version, so nothing is sent. */
|
|
107
|
+
setInkColor(next: string): void;
|
|
71
108
|
clear(): Promise<WhiteboardCommit>;
|
|
109
|
+
/** Restore only into the exact empty scene committed by this clear. */
|
|
110
|
+
clearWithUndo(): Promise<() => boolean>;
|
|
72
111
|
dispose(): void;
|
|
73
112
|
handleChange(_elements: readonly OrderedExcalidrawElement[], _appState: AppState, _files: BinaryFiles): void;
|
|
74
113
|
handlePointerUpdate(payload: {
|
|
@@ -87,12 +126,14 @@ export declare class ExcalidrawCollabEngine {
|
|
|
87
126
|
handleRemoteEvent(event: WhiteboardCollaborationEvent): void;
|
|
88
127
|
handleRemoteData(payload: {
|
|
89
128
|
sceneId: string;
|
|
129
|
+
revision?: string;
|
|
90
130
|
sceneGeneration?: string;
|
|
91
131
|
syncAll: boolean;
|
|
92
132
|
elements: readonly WhiteboardWireElement[];
|
|
93
133
|
}): void;
|
|
94
134
|
handleRemoteSnapshot(payload: {
|
|
95
135
|
sceneId: string;
|
|
136
|
+
revision?: string;
|
|
96
137
|
sceneGeneration?: string;
|
|
97
138
|
elements: readonly WhiteboardWireElement[];
|
|
98
139
|
appState?: SharedWhiteboardAppState;
|
package/dist/collab/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mergeWhiteboardElements
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MPNIDZHM.js";
|
|
4
4
|
import {
|
|
5
5
|
ExcalidrawCollabEngine,
|
|
6
6
|
fromWireElement,
|
|
7
7
|
isWireElement,
|
|
8
8
|
toWireElement
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-X3WDTGPQ.js";
|
|
10
|
+
import "../chunk-IVFWOYSW.js";
|
|
10
11
|
export {
|
|
11
12
|
ExcalidrawCollabEngine,
|
|
12
13
|
fromWireElement,
|
|
@@ -1216,8 +1216,8 @@
|
|
|
1216
1216
|
},
|
|
1217
1217
|
{
|
|
1218
1218
|
"path": "renderer.js",
|
|
1219
|
-
"bytes":
|
|
1220
|
-
"sha256": "
|
|
1219
|
+
"bytes": 8502340,
|
|
1220
|
+
"sha256": "82afcc03b426364bcf0e83064452972ec6fc5b74f766fd024948ae866893ba68"
|
|
1221
1221
|
},
|
|
1222
1222
|
{
|
|
1223
1223
|
"path": "THIRD_PARTY_NOTICES.json",
|