@fieldnotes/core 0.64.0 → 0.65.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/dist/index.cjs +819 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +430 -2
- package/dist/index.d.ts +430 -2
- package/dist/index.js +809 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12100,6 +12100,800 @@ var RemoteFocusReceiver = class {
|
|
|
12100
12100
|
}
|
|
12101
12101
|
};
|
|
12102
12102
|
|
|
12103
|
+
// src/canvas/awareness-presence.ts
|
|
12104
|
+
var AWARENESS_PRESENCE_KIND = "awareness";
|
|
12105
|
+
var AWARENESS_MAX_SELECTION = 256;
|
|
12106
|
+
var MAX_ID_LENGTH = 128;
|
|
12107
|
+
var MAX_NAME_LENGTH = 64;
|
|
12108
|
+
var MAX_COLOR_LENGTH2 = 64;
|
|
12109
|
+
var MAX_ROLE_LENGTH = 32;
|
|
12110
|
+
var MAX_TOOL_LENGTH = 64;
|
|
12111
|
+
function isBoundedString(value, max) {
|
|
12112
|
+
return typeof value === "string" && value.length <= max;
|
|
12113
|
+
}
|
|
12114
|
+
function isOptionalBoundedString(value, max) {
|
|
12115
|
+
return value === void 0 || isBoundedString(value, max);
|
|
12116
|
+
}
|
|
12117
|
+
function isFinitePoint4(value) {
|
|
12118
|
+
if (typeof value !== "object" || value === null) return false;
|
|
12119
|
+
const point = value;
|
|
12120
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
12121
|
+
}
|
|
12122
|
+
function isAwarenessPresence(data) {
|
|
12123
|
+
if (typeof data !== "object" || data === null) return false;
|
|
12124
|
+
const payload = data;
|
|
12125
|
+
if (payload.kind !== AWARENESS_PRESENCE_KIND) return false;
|
|
12126
|
+
if (!isBoundedString(payload.id, MAX_ID_LENGTH) || payload.id.length === 0) return false;
|
|
12127
|
+
if ("cleared" in payload) return payload.cleared === true;
|
|
12128
|
+
if (!isOptionalBoundedString(payload.name, MAX_NAME_LENGTH)) return false;
|
|
12129
|
+
if (!isOptionalBoundedString(payload.color, MAX_COLOR_LENGTH2)) return false;
|
|
12130
|
+
if (!isOptionalBoundedString(payload.role, MAX_ROLE_LENGTH)) return false;
|
|
12131
|
+
if (!isOptionalBoundedString(payload.tool, MAX_TOOL_LENGTH)) return false;
|
|
12132
|
+
if (payload.cursor !== void 0 && !isFinitePoint4(payload.cursor)) return false;
|
|
12133
|
+
if (payload.selection !== void 0) {
|
|
12134
|
+
if (!Array.isArray(payload.selection)) return false;
|
|
12135
|
+
if (payload.selection.length > AWARENESS_MAX_SELECTION) return false;
|
|
12136
|
+
for (const id of payload.selection) {
|
|
12137
|
+
if (!isBoundedString(id, MAX_ID_LENGTH) || id.length === 0) return false;
|
|
12138
|
+
}
|
|
12139
|
+
}
|
|
12140
|
+
return true;
|
|
12141
|
+
}
|
|
12142
|
+
|
|
12143
|
+
// src/canvas/awareness-roster.ts
|
|
12144
|
+
var DEFAULT_STALE_MS = 45e3;
|
|
12145
|
+
var EMPTY_PEERS = Object.freeze([]);
|
|
12146
|
+
var EMPTY_SELECTION = Object.freeze([]);
|
|
12147
|
+
function sameSelection(a, b) {
|
|
12148
|
+
if (a.length !== b.length) return false;
|
|
12149
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
12150
|
+
return true;
|
|
12151
|
+
}
|
|
12152
|
+
function samePoint2(a, b) {
|
|
12153
|
+
if (a === null || b === null) return a === b;
|
|
12154
|
+
return a.x === b.x && a.y === b.y;
|
|
12155
|
+
}
|
|
12156
|
+
function toPeer(from, data, prev) {
|
|
12157
|
+
const cursor = data.cursor ? { x: data.cursor.x, y: data.cursor.y } : null;
|
|
12158
|
+
const incoming = data.selection ?? EMPTY_SELECTION;
|
|
12159
|
+
const selection = prev && sameSelection(prev.selection, incoming) ? prev.selection : incoming.length === 0 ? EMPTY_SELECTION : Object.freeze([...incoming]);
|
|
12160
|
+
const tool = data.tool ?? null;
|
|
12161
|
+
if (prev && prev.id === data.id && prev.name === data.name && prev.color === data.color && prev.role === data.role && prev.tool === tool && prev.selection === selection && samePoint2(prev.cursor, cursor)) {
|
|
12162
|
+
return prev;
|
|
12163
|
+
}
|
|
12164
|
+
const peer = {
|
|
12165
|
+
from,
|
|
12166
|
+
id: data.id,
|
|
12167
|
+
...data.name === void 0 ? {} : { name: data.name },
|
|
12168
|
+
...data.color === void 0 ? {} : { color: data.color },
|
|
12169
|
+
...data.role === void 0 ? {} : { role: data.role },
|
|
12170
|
+
cursor,
|
|
12171
|
+
selection,
|
|
12172
|
+
tool
|
|
12173
|
+
};
|
|
12174
|
+
return peer;
|
|
12175
|
+
}
|
|
12176
|
+
var PeerRoster = class {
|
|
12177
|
+
staleMs;
|
|
12178
|
+
now;
|
|
12179
|
+
rows = /* @__PURE__ */ new Map();
|
|
12180
|
+
discovered = /* @__PURE__ */ new Map();
|
|
12181
|
+
changeListeners = /* @__PURE__ */ new Set();
|
|
12182
|
+
discoverListeners = /* @__PURE__ */ new Set();
|
|
12183
|
+
leaveListeners = /* @__PURE__ */ new Set();
|
|
12184
|
+
snapshot = EMPTY_PEERS;
|
|
12185
|
+
snapshotDirty = false;
|
|
12186
|
+
staleTimer = null;
|
|
12187
|
+
isDisposed = false;
|
|
12188
|
+
constructor(options = {}) {
|
|
12189
|
+
this.staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
12190
|
+
this.now = options.now ?? (() => Date.now());
|
|
12191
|
+
}
|
|
12192
|
+
get disposed() {
|
|
12193
|
+
return this.isDisposed;
|
|
12194
|
+
}
|
|
12195
|
+
/**
|
|
12196
|
+
* Applies a presence payload from `from`. Non-awareness or malformed payloads
|
|
12197
|
+
* return `false` untouched, so hosts can feed every presence frame through.
|
|
12198
|
+
*/
|
|
12199
|
+
apply(from, data) {
|
|
12200
|
+
if (this.isDisposed || !isAwarenessPresence(data)) return false;
|
|
12201
|
+
const isNew = !this.discovered.has(from);
|
|
12202
|
+
this.discovered.set(from, this.now());
|
|
12203
|
+
if ("cleared" in data) {
|
|
12204
|
+
this.dropRow(from, "cleared");
|
|
12205
|
+
} else {
|
|
12206
|
+
const prev = this.rows.get(from);
|
|
12207
|
+
const next = toPeer(from, data, prev);
|
|
12208
|
+
if (next !== prev) {
|
|
12209
|
+
this.rows.set(from, next);
|
|
12210
|
+
this.changed();
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
this.armStaleTimer();
|
|
12214
|
+
if (isNew) this.emit(this.discoverListeners, (l) => l(from));
|
|
12215
|
+
return true;
|
|
12216
|
+
}
|
|
12217
|
+
/** Server-authored presence-leave: drops the row AND the discovery entry. */
|
|
12218
|
+
remove(from) {
|
|
12219
|
+
if (this.isDisposed) return;
|
|
12220
|
+
const hadEntry = this.discovered.delete(from);
|
|
12221
|
+
this.dropRow(from, "left");
|
|
12222
|
+
if (hadEntry) this.armStaleTimer();
|
|
12223
|
+
}
|
|
12224
|
+
getPeers() {
|
|
12225
|
+
if (this.snapshotDirty) {
|
|
12226
|
+
this.snapshot = this.rows.size === 0 ? EMPTY_PEERS : Object.freeze([...this.rows.values()]);
|
|
12227
|
+
this.snapshotDirty = false;
|
|
12228
|
+
}
|
|
12229
|
+
return this.snapshot;
|
|
12230
|
+
}
|
|
12231
|
+
getPeer(from) {
|
|
12232
|
+
return this.rows.get(from);
|
|
12233
|
+
}
|
|
12234
|
+
/** Fires only when `getPeers()` would return a new reference. */
|
|
12235
|
+
onChange(listener) {
|
|
12236
|
+
this.changeListeners.add(listener);
|
|
12237
|
+
return () => this.changeListeners.delete(listener);
|
|
12238
|
+
}
|
|
12239
|
+
/** First valid frame from a sender since its discovery entry was last dropped. */
|
|
12240
|
+
onDiscover(listener) {
|
|
12241
|
+
this.discoverListeners.add(listener);
|
|
12242
|
+
return () => this.discoverListeners.delete(listener);
|
|
12243
|
+
}
|
|
12244
|
+
onLeave(listener) {
|
|
12245
|
+
this.leaveListeners.add(listener);
|
|
12246
|
+
return () => this.leaveListeners.delete(listener);
|
|
12247
|
+
}
|
|
12248
|
+
dispose() {
|
|
12249
|
+
if (this.isDisposed) return;
|
|
12250
|
+
this.isDisposed = true;
|
|
12251
|
+
if (this.staleTimer !== null) clearTimeout(this.staleTimer);
|
|
12252
|
+
this.staleTimer = null;
|
|
12253
|
+
this.rows.clear();
|
|
12254
|
+
this.discovered.clear();
|
|
12255
|
+
this.snapshot = EMPTY_PEERS;
|
|
12256
|
+
this.snapshotDirty = false;
|
|
12257
|
+
this.changeListeners.clear();
|
|
12258
|
+
this.discoverListeners.clear();
|
|
12259
|
+
this.leaveListeners.clear();
|
|
12260
|
+
}
|
|
12261
|
+
dropRow(from, reason) {
|
|
12262
|
+
const row = this.rows.get(from);
|
|
12263
|
+
if (!row) return;
|
|
12264
|
+
this.rows.delete(from);
|
|
12265
|
+
this.changed();
|
|
12266
|
+
this.emit(this.leaveListeners, (l) => l(row, reason));
|
|
12267
|
+
}
|
|
12268
|
+
changed() {
|
|
12269
|
+
this.snapshotDirty = true;
|
|
12270
|
+
this.emit(this.changeListeners, (l) => l());
|
|
12271
|
+
}
|
|
12272
|
+
emit(listeners, call) {
|
|
12273
|
+
for (const listener of [...listeners]) {
|
|
12274
|
+
try {
|
|
12275
|
+
call(listener);
|
|
12276
|
+
} catch {
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
}
|
|
12280
|
+
armStaleTimer() {
|
|
12281
|
+
if (this.staleTimer !== null) clearTimeout(this.staleTimer);
|
|
12282
|
+
this.staleTimer = null;
|
|
12283
|
+
if (!Number.isFinite(this.staleMs) || this.staleMs <= 0 || this.isDisposed || this.discovered.size === 0) {
|
|
12284
|
+
return;
|
|
12285
|
+
}
|
|
12286
|
+
let earliest = Infinity;
|
|
12287
|
+
for (const seen of this.discovered.values()) if (seen < earliest) earliest = seen;
|
|
12288
|
+
const delay = Math.min(Math.max(0, earliest + this.staleMs - this.now()), 2 ** 31 - 1);
|
|
12289
|
+
this.staleTimer = setTimeout(() => {
|
|
12290
|
+
this.staleTimer = null;
|
|
12291
|
+
this.expireStale();
|
|
12292
|
+
}, delay);
|
|
12293
|
+
}
|
|
12294
|
+
expireStale() {
|
|
12295
|
+
const t = this.now();
|
|
12296
|
+
for (const from of [...this.discovered.keys()]) {
|
|
12297
|
+
const seen = this.discovered.get(from);
|
|
12298
|
+
if (seen === void 0 || t - seen < this.staleMs) continue;
|
|
12299
|
+
this.discovered.delete(from);
|
|
12300
|
+
this.dropRow(from, "stale");
|
|
12301
|
+
}
|
|
12302
|
+
this.armStaleTimer();
|
|
12303
|
+
}
|
|
12304
|
+
};
|
|
12305
|
+
|
|
12306
|
+
// src/canvas/awareness-publisher.ts
|
|
12307
|
+
var DEFAULT_FIELDS = Object.freeze({
|
|
12308
|
+
cursor: true,
|
|
12309
|
+
selection: false,
|
|
12310
|
+
tool: true
|
|
12311
|
+
});
|
|
12312
|
+
var DEFAULT_INTERVAL_MS = 50;
|
|
12313
|
+
var DEFAULT_HEARTBEAT_MS = 15e3;
|
|
12314
|
+
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
12315
|
+
var MAX_IDENTITY_ID_LENGTH = 128;
|
|
12316
|
+
var MAX_IDENTITY_NAME_LENGTH = 64;
|
|
12317
|
+
var MAX_IDENTITY_COLOR_LENGTH = 64;
|
|
12318
|
+
var MAX_IDENTITY_ROLE_LENGTH = 32;
|
|
12319
|
+
var MAX_TOOL_LENGTH2 = 64;
|
|
12320
|
+
var MAX_SELECTION_ID_LENGTH = 128;
|
|
12321
|
+
function normalizeIntervalMs(value) {
|
|
12322
|
+
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
12323
|
+
}
|
|
12324
|
+
function normalizeHeartbeatMs(value) {
|
|
12325
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
12326
|
+
}
|
|
12327
|
+
var LocalAwareness = class {
|
|
12328
|
+
host;
|
|
12329
|
+
element;
|
|
12330
|
+
send;
|
|
12331
|
+
selectionFilter;
|
|
12332
|
+
onError;
|
|
12333
|
+
intervalMs;
|
|
12334
|
+
heartbeatMs;
|
|
12335
|
+
identity;
|
|
12336
|
+
fields;
|
|
12337
|
+
lastPointer = null;
|
|
12338
|
+
selection = [];
|
|
12339
|
+
selectionFailed = false;
|
|
12340
|
+
tool;
|
|
12341
|
+
dirty = false;
|
|
12342
|
+
lastSentAt = null;
|
|
12343
|
+
throttleTimer = null;
|
|
12344
|
+
heartbeatTimer = null;
|
|
12345
|
+
unsubscribers = [];
|
|
12346
|
+
isDisposed = false;
|
|
12347
|
+
handlePointerMove = (e) => this.onPointerMove(e);
|
|
12348
|
+
handlePointerEnd = (e) => this.onPointerEnd(e);
|
|
12349
|
+
constructor(host, options) {
|
|
12350
|
+
const element = options.element ?? host.domLayer.parentElement;
|
|
12351
|
+
if (!element) throw new Error("LocalAwareness: the viewport wrapper is not mounted");
|
|
12352
|
+
this.host = host;
|
|
12353
|
+
this.element = element;
|
|
12354
|
+
this.send = options.send;
|
|
12355
|
+
this.selectionFilter = options.selectionFilter;
|
|
12356
|
+
this.onError = options.onError;
|
|
12357
|
+
this.intervalMs = normalizeIntervalMs(options.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
12358
|
+
this.heartbeatMs = normalizeHeartbeatMs(options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS);
|
|
12359
|
+
this.identity = this.normalizeIdentity(options.identity);
|
|
12360
|
+
this.fields = mergeFields(DEFAULT_FIELDS, options.fields ?? {});
|
|
12361
|
+
this.tool = host.toolManager.activeTool?.name ?? null;
|
|
12362
|
+
if (this.fields.selection) this.refreshSelection();
|
|
12363
|
+
const opts = { passive: true };
|
|
12364
|
+
element.addEventListener("pointermove", this.handlePointerMove, opts);
|
|
12365
|
+
element.addEventListener("pointerleave", this.handlePointerEnd, opts);
|
|
12366
|
+
element.addEventListener("pointercancel", this.handlePointerEnd, opts);
|
|
12367
|
+
this.unsubscribers.push(
|
|
12368
|
+
host.onSelectionChange(() => {
|
|
12369
|
+
if (this.fields.selection) this.schedule();
|
|
12370
|
+
}),
|
|
12371
|
+
host.toolManager.onChange((name) => {
|
|
12372
|
+
this.tool = name;
|
|
12373
|
+
if (this.fields.tool) this.schedule();
|
|
12374
|
+
})
|
|
12375
|
+
);
|
|
12376
|
+
this.armHeartbeat();
|
|
12377
|
+
}
|
|
12378
|
+
get disposed() {
|
|
12379
|
+
return this.isDisposed;
|
|
12380
|
+
}
|
|
12381
|
+
getFields() {
|
|
12382
|
+
return this.fields;
|
|
12383
|
+
}
|
|
12384
|
+
setIdentity(identity) {
|
|
12385
|
+
this.identity = this.normalizeIdentity(identity);
|
|
12386
|
+
this.schedule();
|
|
12387
|
+
}
|
|
12388
|
+
/** Merges the given flags into the current policy; `undefined` keys are ignored. */
|
|
12389
|
+
setFields(fields) {
|
|
12390
|
+
this.fields = mergeFields(this.fields, fields);
|
|
12391
|
+
this.schedule();
|
|
12392
|
+
}
|
|
12393
|
+
/**
|
|
12394
|
+
* Requests a full frame: immediate when idle, otherwise folded into the
|
|
12395
|
+
* pending trailing frame (so N simultaneous requests cost one frame). Hosts
|
|
12396
|
+
* call it when the connection becomes live or reconnects.
|
|
12397
|
+
*/
|
|
12398
|
+
announce() {
|
|
12399
|
+
this.schedule();
|
|
12400
|
+
}
|
|
12401
|
+
/**
|
|
12402
|
+
* The complete state a frame carries right now. Side-effecting when
|
|
12403
|
+
* selection publishing is on: re-reads `getSelectedIds()`, re-runs
|
|
12404
|
+
* `selectionFilter`, updates the fail-closed selection state, and may call
|
|
12405
|
+
* `onError`. A no-op with respect to selection while publishing is off.
|
|
12406
|
+
*/
|
|
12407
|
+
getState() {
|
|
12408
|
+
const frame = { kind: AWARENESS_PRESENCE_KIND, id: this.identity.id };
|
|
12409
|
+
if (this.identity.name !== void 0) frame.name = this.identity.name;
|
|
12410
|
+
if (this.identity.color !== void 0) frame.color = this.identity.color;
|
|
12411
|
+
if (this.identity.role !== void 0) frame.role = this.identity.role;
|
|
12412
|
+
if (this.fields.cursor && this.lastPointer !== null) {
|
|
12413
|
+
frame.cursor = { x: this.lastPointer.x, y: this.lastPointer.y };
|
|
12414
|
+
}
|
|
12415
|
+
if (this.fields.selection) {
|
|
12416
|
+
this.refreshSelection();
|
|
12417
|
+
if (!this.selectionFailed && this.selection.length > 0) {
|
|
12418
|
+
frame.selection = [...this.selection];
|
|
12419
|
+
}
|
|
12420
|
+
}
|
|
12421
|
+
if (this.fields.tool && this.tool !== null && this.tool.length <= MAX_TOOL_LENGTH2) {
|
|
12422
|
+
frame.tool = this.tool;
|
|
12423
|
+
}
|
|
12424
|
+
return frame;
|
|
12425
|
+
}
|
|
12426
|
+
dispose() {
|
|
12427
|
+
if (this.isDisposed) return;
|
|
12428
|
+
this.isDisposed = true;
|
|
12429
|
+
if (this.throttleTimer !== null) clearTimeout(this.throttleTimer);
|
|
12430
|
+
this.throttleTimer = null;
|
|
12431
|
+
if (this.heartbeatTimer !== null) clearTimeout(this.heartbeatTimer);
|
|
12432
|
+
this.heartbeatTimer = null;
|
|
12433
|
+
this.element.removeEventListener("pointermove", this.handlePointerMove);
|
|
12434
|
+
this.element.removeEventListener("pointerleave", this.handlePointerEnd);
|
|
12435
|
+
this.element.removeEventListener("pointercancel", this.handlePointerEnd);
|
|
12436
|
+
for (const unsub of this.unsubscribers) unsub();
|
|
12437
|
+
this.unsubscribers.length = 0;
|
|
12438
|
+
this.safeSend({ kind: AWARENESS_PRESENCE_KIND, id: this.identity.id, cleared: true });
|
|
12439
|
+
}
|
|
12440
|
+
now() {
|
|
12441
|
+
return Date.now();
|
|
12442
|
+
}
|
|
12443
|
+
onPointerMove(e) {
|
|
12444
|
+
if (!e.isPrimary) return;
|
|
12445
|
+
const rect = this.element.getBoundingClientRect();
|
|
12446
|
+
const world = this.host.camera.screenToWorld({
|
|
12447
|
+
x: e.clientX - rect.left,
|
|
12448
|
+
y: e.clientY - rect.top
|
|
12449
|
+
});
|
|
12450
|
+
this.lastPointer = Number.isFinite(world.x) && Number.isFinite(world.y) ? { x: world.x, y: world.y } : null;
|
|
12451
|
+
if (this.fields.cursor) this.schedule();
|
|
12452
|
+
}
|
|
12453
|
+
onPointerEnd(e) {
|
|
12454
|
+
if (!e.isPrimary || this.lastPointer === null) return;
|
|
12455
|
+
this.lastPointer = null;
|
|
12456
|
+
if (this.fields.cursor) this.schedule();
|
|
12457
|
+
}
|
|
12458
|
+
refreshSelection() {
|
|
12459
|
+
try {
|
|
12460
|
+
const raw = this.host.getSelectedIds();
|
|
12461
|
+
const ids = this.selectionFilter ? this.selectionFilter(raw) : raw;
|
|
12462
|
+
if (!Array.isArray(ids)) throw new TypeError("selectionFilter must return an array");
|
|
12463
|
+
for (const id of ids) {
|
|
12464
|
+
if (typeof id !== "string") throw new TypeError("selectionFilter must return strings");
|
|
12465
|
+
if (id.length === 0 || id.length > MAX_SELECTION_ID_LENGTH) {
|
|
12466
|
+
throw new TypeError("selectionFilter must return ids of 1..128 characters");
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12469
|
+
this.selection = ids.slice(0, AWARENESS_MAX_SELECTION);
|
|
12470
|
+
this.selectionFailed = false;
|
|
12471
|
+
} catch (error) {
|
|
12472
|
+
this.selection = [];
|
|
12473
|
+
this.selectionFailed = true;
|
|
12474
|
+
this.report(error);
|
|
12475
|
+
}
|
|
12476
|
+
}
|
|
12477
|
+
schedule() {
|
|
12478
|
+
if (this.isDisposed) return;
|
|
12479
|
+
this.dirty = true;
|
|
12480
|
+
if (this.throttleTimer !== null) return;
|
|
12481
|
+
const elapsed = this.lastSentAt === null ? Infinity : this.now() - this.lastSentAt;
|
|
12482
|
+
if (elapsed >= this.intervalMs) {
|
|
12483
|
+
this.flush();
|
|
12484
|
+
return;
|
|
12485
|
+
}
|
|
12486
|
+
this.throttleTimer = setTimeout(
|
|
12487
|
+
() => {
|
|
12488
|
+
this.throttleTimer = null;
|
|
12489
|
+
if (this.dirty) this.flush();
|
|
12490
|
+
},
|
|
12491
|
+
Math.min(this.intervalMs - elapsed, MAX_TIMER_DELAY_MS)
|
|
12492
|
+
);
|
|
12493
|
+
}
|
|
12494
|
+
flush() {
|
|
12495
|
+
this.dirty = false;
|
|
12496
|
+
this.lastSentAt = this.now();
|
|
12497
|
+
this.safeSend(this.getState());
|
|
12498
|
+
this.armHeartbeat();
|
|
12499
|
+
}
|
|
12500
|
+
armHeartbeat() {
|
|
12501
|
+
if (this.heartbeatTimer !== null) clearTimeout(this.heartbeatTimer);
|
|
12502
|
+
this.heartbeatTimer = null;
|
|
12503
|
+
if (this.heartbeatMs <= 0 || this.isDisposed) return;
|
|
12504
|
+
this.heartbeatTimer = setTimeout(
|
|
12505
|
+
() => {
|
|
12506
|
+
this.heartbeatTimer = null;
|
|
12507
|
+
this.flush();
|
|
12508
|
+
},
|
|
12509
|
+
Math.min(this.heartbeatMs, MAX_TIMER_DELAY_MS)
|
|
12510
|
+
);
|
|
12511
|
+
}
|
|
12512
|
+
safeSend(frame) {
|
|
12513
|
+
try {
|
|
12514
|
+
this.send(frame);
|
|
12515
|
+
} catch (error) {
|
|
12516
|
+
this.report(error);
|
|
12517
|
+
}
|
|
12518
|
+
}
|
|
12519
|
+
report(error) {
|
|
12520
|
+
try {
|
|
12521
|
+
this.onError?.(error);
|
|
12522
|
+
} catch {
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12525
|
+
/**
|
|
12526
|
+
* Truncates identity strings to the wire caps and rejects an invalid id, so a
|
|
12527
|
+
* sender can never publish a frame that the wire guard would drop outright.
|
|
12528
|
+
* A truncated field is reported through `onError` (a `RangeError`) rather
|
|
12529
|
+
* than silently shortened, so a caller passing an over-long name finds out.
|
|
12530
|
+
*/
|
|
12531
|
+
normalizeIdentity(identity) {
|
|
12532
|
+
if (identity.id.length === 0 || identity.id.length > MAX_IDENTITY_ID_LENGTH) {
|
|
12533
|
+
throw new RangeError("LocalAwareness: identity.id must be 1..128 characters");
|
|
12534
|
+
}
|
|
12535
|
+
const normalized = {
|
|
12536
|
+
id: identity.id
|
|
12537
|
+
};
|
|
12538
|
+
if (identity.name !== void 0) {
|
|
12539
|
+
normalized.name = identity.name.slice(0, MAX_IDENTITY_NAME_LENGTH);
|
|
12540
|
+
if (identity.name.length > MAX_IDENTITY_NAME_LENGTH) {
|
|
12541
|
+
this.report(
|
|
12542
|
+
new RangeError(
|
|
12543
|
+
`LocalAwareness: identity.name truncated to ${MAX_IDENTITY_NAME_LENGTH} characters`
|
|
12544
|
+
)
|
|
12545
|
+
);
|
|
12546
|
+
}
|
|
12547
|
+
}
|
|
12548
|
+
if (identity.color !== void 0) {
|
|
12549
|
+
normalized.color = identity.color.slice(0, MAX_IDENTITY_COLOR_LENGTH);
|
|
12550
|
+
if (identity.color.length > MAX_IDENTITY_COLOR_LENGTH) {
|
|
12551
|
+
this.report(
|
|
12552
|
+
new RangeError(
|
|
12553
|
+
`LocalAwareness: identity.color truncated to ${MAX_IDENTITY_COLOR_LENGTH} characters`
|
|
12554
|
+
)
|
|
12555
|
+
);
|
|
12556
|
+
}
|
|
12557
|
+
}
|
|
12558
|
+
if (identity.role !== void 0) {
|
|
12559
|
+
normalized.role = identity.role.slice(0, MAX_IDENTITY_ROLE_LENGTH);
|
|
12560
|
+
if (identity.role.length > MAX_IDENTITY_ROLE_LENGTH) {
|
|
12561
|
+
this.report(
|
|
12562
|
+
new RangeError(
|
|
12563
|
+
`LocalAwareness: identity.role truncated to ${MAX_IDENTITY_ROLE_LENGTH} characters`
|
|
12564
|
+
)
|
|
12565
|
+
);
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
return normalized;
|
|
12569
|
+
}
|
|
12570
|
+
};
|
|
12571
|
+
function mergeFields(current, patch) {
|
|
12572
|
+
return Object.freeze({
|
|
12573
|
+
cursor: patch.cursor ?? current.cursor,
|
|
12574
|
+
selection: patch.selection ?? current.selection,
|
|
12575
|
+
tool: patch.tool ?? current.tool
|
|
12576
|
+
});
|
|
12577
|
+
}
|
|
12578
|
+
|
|
12579
|
+
// src/canvas/remote-cursor-overlay.ts
|
|
12580
|
+
var PEER_COLORS = Object.freeze([
|
|
12581
|
+
"#e11d48",
|
|
12582
|
+
"#ea580c",
|
|
12583
|
+
"#ca8a04",
|
|
12584
|
+
"#16a34a",
|
|
12585
|
+
"#0d9488",
|
|
12586
|
+
"#0284c7",
|
|
12587
|
+
"#2563eb",
|
|
12588
|
+
"#7c3aed",
|
|
12589
|
+
"#c026d3",
|
|
12590
|
+
"#db2777",
|
|
12591
|
+
"#4d7c0f",
|
|
12592
|
+
"#b45309"
|
|
12593
|
+
]);
|
|
12594
|
+
function defaultPeerColor(seed) {
|
|
12595
|
+
if (seed.length === 0) return PEER_COLORS[0] ?? "#2563eb";
|
|
12596
|
+
let hash = 2166136261;
|
|
12597
|
+
for (let i = 0; i < seed.length; i++) {
|
|
12598
|
+
hash ^= seed.charCodeAt(i);
|
|
12599
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
12600
|
+
}
|
|
12601
|
+
return PEER_COLORS[hash % PEER_COLORS.length] ?? "#2563eb";
|
|
12602
|
+
}
|
|
12603
|
+
var DEFAULT_LABEL_FONT = "12px sans-serif";
|
|
12604
|
+
var LABEL_PAD_X = 6;
|
|
12605
|
+
var LABEL_PAD_Y = 3;
|
|
12606
|
+
var LABEL_HEIGHT = 16;
|
|
12607
|
+
var LABEL_OFFSET = 14;
|
|
12608
|
+
var MAX_LABEL_WIDTH_CACHE = 64;
|
|
12609
|
+
var RemoteCursorOverlay = class {
|
|
12610
|
+
host;
|
|
12611
|
+
roster;
|
|
12612
|
+
colorFor;
|
|
12613
|
+
showLabels;
|
|
12614
|
+
labelFont;
|
|
12615
|
+
labelWidths = /* @__PURE__ */ new Map();
|
|
12616
|
+
unregister;
|
|
12617
|
+
unsubscribe;
|
|
12618
|
+
isDisposed = false;
|
|
12619
|
+
constructor(host, roster, options = {}) {
|
|
12620
|
+
this.host = host;
|
|
12621
|
+
this.roster = roster;
|
|
12622
|
+
this.colorFor = options.colorFor;
|
|
12623
|
+
this.showLabels = options.showLabels ?? true;
|
|
12624
|
+
this.labelFont = options.labelFont ?? DEFAULT_LABEL_FONT;
|
|
12625
|
+
this.unregister = host.registerOverlay((ctx) => this.render(ctx));
|
|
12626
|
+
this.unsubscribe = roster.onChange(() => {
|
|
12627
|
+
if (this.labelWidths.size > MAX_LABEL_WIDTH_CACHE) this.labelWidths.clear();
|
|
12628
|
+
host.requestRender();
|
|
12629
|
+
});
|
|
12630
|
+
}
|
|
12631
|
+
get disposed() {
|
|
12632
|
+
return this.isDisposed;
|
|
12633
|
+
}
|
|
12634
|
+
resolveColor(peer) {
|
|
12635
|
+
return this.colorFor?.(peer) ?? peer.color ?? defaultPeerColor(peer.id);
|
|
12636
|
+
}
|
|
12637
|
+
dispose() {
|
|
12638
|
+
if (this.isDisposed) return;
|
|
12639
|
+
this.isDisposed = true;
|
|
12640
|
+
this.unsubscribe?.();
|
|
12641
|
+
this.unsubscribe = null;
|
|
12642
|
+
this.unregister?.();
|
|
12643
|
+
this.unregister = null;
|
|
12644
|
+
this.labelWidths.clear();
|
|
12645
|
+
this.host.requestRender();
|
|
12646
|
+
}
|
|
12647
|
+
render(ctx) {
|
|
12648
|
+
if (this.isDisposed) return;
|
|
12649
|
+
const zoom = this.host.camera.zoom;
|
|
12650
|
+
const inv = zoom > 0 && Number.isFinite(zoom) ? 1 / zoom : 1;
|
|
12651
|
+
for (const peer of this.roster.getPeers()) {
|
|
12652
|
+
if (peer.cursor === null) continue;
|
|
12653
|
+
const color = this.resolveColor(peer);
|
|
12654
|
+
ctx.save();
|
|
12655
|
+
ctx.translate(peer.cursor.x, peer.cursor.y);
|
|
12656
|
+
ctx.scale(inv, inv);
|
|
12657
|
+
ctx.beginPath();
|
|
12658
|
+
ctx.moveTo(0, 0);
|
|
12659
|
+
ctx.lineTo(0, 16);
|
|
12660
|
+
ctx.lineTo(4.5, 12.5);
|
|
12661
|
+
ctx.lineTo(11, 12.5);
|
|
12662
|
+
ctx.closePath();
|
|
12663
|
+
ctx.fillStyle = color;
|
|
12664
|
+
ctx.fill();
|
|
12665
|
+
ctx.strokeStyle = "#ffffff";
|
|
12666
|
+
ctx.lineWidth = 1;
|
|
12667
|
+
ctx.stroke();
|
|
12668
|
+
if (this.showLabels && peer.name !== void 0 && peer.name.length > 0) {
|
|
12669
|
+
this.drawLabel(ctx, peer.name, color);
|
|
12670
|
+
}
|
|
12671
|
+
ctx.restore();
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12674
|
+
drawLabel(ctx, name, color) {
|
|
12675
|
+
ctx.font = this.labelFont;
|
|
12676
|
+
const key = `${this.labelFont} ${name}`;
|
|
12677
|
+
let width = this.labelWidths.get(key);
|
|
12678
|
+
if (width === void 0) {
|
|
12679
|
+
width = ctx.measureText(name).width;
|
|
12680
|
+
this.labelWidths.set(key, width);
|
|
12681
|
+
}
|
|
12682
|
+
const w = width + LABEL_PAD_X * 2;
|
|
12683
|
+
ctx.fillStyle = color;
|
|
12684
|
+
ctx.beginPath();
|
|
12685
|
+
ctx.roundRect(LABEL_OFFSET, LABEL_OFFSET, w, LABEL_HEIGHT + LABEL_PAD_Y, 4);
|
|
12686
|
+
ctx.fill();
|
|
12687
|
+
ctx.fillStyle = "#ffffff";
|
|
12688
|
+
ctx.textAlign = "left";
|
|
12689
|
+
ctx.textBaseline = "middle";
|
|
12690
|
+
ctx.fillText(name, LABEL_OFFSET + LABEL_PAD_X, LABEL_OFFSET + (LABEL_HEIGHT + LABEL_PAD_Y) / 2);
|
|
12691
|
+
}
|
|
12692
|
+
};
|
|
12693
|
+
|
|
12694
|
+
// src/canvas/remote-selection-overlay.ts
|
|
12695
|
+
var DEFAULT_ALPHA = 0.6;
|
|
12696
|
+
var DEFAULT_LINE_WIDTH_PX = 2;
|
|
12697
|
+
var RemoteSelectionOverlay = class {
|
|
12698
|
+
host;
|
|
12699
|
+
roster;
|
|
12700
|
+
colorFor;
|
|
12701
|
+
alpha;
|
|
12702
|
+
lineWidthPx;
|
|
12703
|
+
signatures = [];
|
|
12704
|
+
outlines = [];
|
|
12705
|
+
storeDirty = true;
|
|
12706
|
+
unregister;
|
|
12707
|
+
unsubscribers = [];
|
|
12708
|
+
isDisposed = false;
|
|
12709
|
+
constructor(host, roster, options = {}) {
|
|
12710
|
+
this.host = host;
|
|
12711
|
+
this.roster = roster;
|
|
12712
|
+
this.colorFor = options.colorFor;
|
|
12713
|
+
this.alpha = options.alpha ?? DEFAULT_ALPHA;
|
|
12714
|
+
this.lineWidthPx = options.lineWidthPx ?? DEFAULT_LINE_WIDTH_PX;
|
|
12715
|
+
this.unregister = host.registerOverlay((ctx) => this.render(ctx));
|
|
12716
|
+
const invalidate = () => {
|
|
12717
|
+
this.storeDirty = true;
|
|
12718
|
+
host.requestRender();
|
|
12719
|
+
};
|
|
12720
|
+
this.unsubscribers.push(
|
|
12721
|
+
roster.onChange(() => host.requestRender()),
|
|
12722
|
+
host.store.onChange(invalidate),
|
|
12723
|
+
host.layerManager.on("change", invalidate)
|
|
12724
|
+
);
|
|
12725
|
+
}
|
|
12726
|
+
get disposed() {
|
|
12727
|
+
return this.isDisposed;
|
|
12728
|
+
}
|
|
12729
|
+
dispose() {
|
|
12730
|
+
if (this.isDisposed) return;
|
|
12731
|
+
this.isDisposed = true;
|
|
12732
|
+
for (const unsub of this.unsubscribers) unsub();
|
|
12733
|
+
this.unsubscribers.length = 0;
|
|
12734
|
+
this.unregister?.();
|
|
12735
|
+
this.unregister = null;
|
|
12736
|
+
this.signatures = [];
|
|
12737
|
+
this.outlines = [];
|
|
12738
|
+
this.host.requestRender();
|
|
12739
|
+
}
|
|
12740
|
+
resolveColor(peer) {
|
|
12741
|
+
return this.colorFor?.(peer) ?? peer.color ?? defaultPeerColor(peer.id);
|
|
12742
|
+
}
|
|
12743
|
+
/** Recomputes outlines only when the selection signature or the store/layers changed. */
|
|
12744
|
+
rebuild() {
|
|
12745
|
+
const peers = this.roster.getPeers();
|
|
12746
|
+
const next = [];
|
|
12747
|
+
for (const peer of peers) {
|
|
12748
|
+
if (peer.selection.length === 0) continue;
|
|
12749
|
+
next.push({ from: peer.from, selection: peer.selection, color: this.resolveColor(peer) });
|
|
12750
|
+
}
|
|
12751
|
+
let changed = this.storeDirty || next.length !== this.signatures.length;
|
|
12752
|
+
if (!changed) {
|
|
12753
|
+
for (let i = 0; i < next.length; i++) {
|
|
12754
|
+
const a = next[i];
|
|
12755
|
+
const b = this.signatures[i];
|
|
12756
|
+
if (!a || !b || a.from !== b.from || a.selection !== b.selection || a.color !== b.color) {
|
|
12757
|
+
changed = true;
|
|
12758
|
+
break;
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
if (!changed) return;
|
|
12763
|
+
this.storeDirty = false;
|
|
12764
|
+
this.signatures = next;
|
|
12765
|
+
if (next.length === 0) {
|
|
12766
|
+
this.outlines = [];
|
|
12767
|
+
return;
|
|
12768
|
+
}
|
|
12769
|
+
const colorById = /* @__PURE__ */ new Map();
|
|
12770
|
+
for (const sig of next) {
|
|
12771
|
+
for (const id of sig.selection) if (!colorById.has(id)) colorById.set(id, sig.color);
|
|
12772
|
+
}
|
|
12773
|
+
const layers = this.host.layerManager;
|
|
12774
|
+
const rects = computeElementRects(
|
|
12775
|
+
this.host.store,
|
|
12776
|
+
(element) => colorById.has(element.id) && layers.isLayerVisible(element.layerId) ? element.id : null
|
|
12777
|
+
);
|
|
12778
|
+
this.outlines = rects.map((rect) => ({ rect, color: colorById.get(rect.id) ?? "#2563eb" }));
|
|
12779
|
+
}
|
|
12780
|
+
render(ctx) {
|
|
12781
|
+
if (this.isDisposed) return;
|
|
12782
|
+
this.rebuild();
|
|
12783
|
+
if (this.outlines.length === 0) return;
|
|
12784
|
+
const zoom = this.host.camera.zoom;
|
|
12785
|
+
const inv = zoom > 0 && Number.isFinite(zoom) ? 1 / zoom : 1;
|
|
12786
|
+
ctx.save();
|
|
12787
|
+
ctx.globalAlpha = this.alpha;
|
|
12788
|
+
ctx.lineWidth = this.lineWidthPx * inv;
|
|
12789
|
+
for (const { rect, color } of this.outlines) {
|
|
12790
|
+
ctx.save();
|
|
12791
|
+
ctx.strokeStyle = color;
|
|
12792
|
+
ctx.translate(rect.x + rect.w / 2, rect.y + rect.h / 2);
|
|
12793
|
+
if (rect.rotation !== 0) ctx.rotate(rect.rotation);
|
|
12794
|
+
ctx.strokeRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
|
|
12795
|
+
ctx.restore();
|
|
12796
|
+
}
|
|
12797
|
+
ctx.restore();
|
|
12798
|
+
}
|
|
12799
|
+
};
|
|
12800
|
+
|
|
12801
|
+
// src/canvas/attach-awareness.ts
|
|
12802
|
+
function attachAwareness(viewport, channel, options) {
|
|
12803
|
+
const {
|
|
12804
|
+
roster: rosterOptions,
|
|
12805
|
+
cursors: cursorOptions,
|
|
12806
|
+
selections: selectionOptions,
|
|
12807
|
+
publish,
|
|
12808
|
+
...localOptions
|
|
12809
|
+
} = options;
|
|
12810
|
+
const roster = new PeerRoster(rosterOptions);
|
|
12811
|
+
let local = null;
|
|
12812
|
+
let cursors = null;
|
|
12813
|
+
let selections = null;
|
|
12814
|
+
const unsubscribers = [];
|
|
12815
|
+
try {
|
|
12816
|
+
local = publish === false ? null : new LocalAwareness(viewport, {
|
|
12817
|
+
...localOptions,
|
|
12818
|
+
send: (data) => channel.sendPresence(data)
|
|
12819
|
+
});
|
|
12820
|
+
cursors = cursorOptions === false ? null : new RemoteCursorOverlay(viewport, roster, cursorOptions ?? {});
|
|
12821
|
+
selections = selectionOptions === void 0 || selectionOptions === false ? null : new RemoteSelectionOverlay(
|
|
12822
|
+
viewport,
|
|
12823
|
+
roster,
|
|
12824
|
+
selectionOptions === true ? {} : selectionOptions
|
|
12825
|
+
);
|
|
12826
|
+
if (publish !== false) unsubscribers.push(roster.onDiscover(() => local?.announce()));
|
|
12827
|
+
unsubscribers.push(
|
|
12828
|
+
channel.onPresence((from, data) => {
|
|
12829
|
+
roster.apply(from, data);
|
|
12830
|
+
})
|
|
12831
|
+
);
|
|
12832
|
+
unsubscribers.push(channel.onPresenceLeave((from) => roster.remove(from)));
|
|
12833
|
+
} catch (error) {
|
|
12834
|
+
for (let i = unsubscribers.length - 1; i >= 0; i--) {
|
|
12835
|
+
try {
|
|
12836
|
+
unsubscribers[i]?.();
|
|
12837
|
+
} catch {
|
|
12838
|
+
}
|
|
12839
|
+
}
|
|
12840
|
+
unsubscribers.length = 0;
|
|
12841
|
+
try {
|
|
12842
|
+
selections?.dispose();
|
|
12843
|
+
} catch {
|
|
12844
|
+
}
|
|
12845
|
+
try {
|
|
12846
|
+
cursors?.dispose();
|
|
12847
|
+
} catch {
|
|
12848
|
+
}
|
|
12849
|
+
try {
|
|
12850
|
+
local?.dispose();
|
|
12851
|
+
} catch {
|
|
12852
|
+
}
|
|
12853
|
+
try {
|
|
12854
|
+
roster.dispose();
|
|
12855
|
+
} catch {
|
|
12856
|
+
}
|
|
12857
|
+
throw error;
|
|
12858
|
+
}
|
|
12859
|
+
let disposed = false;
|
|
12860
|
+
return {
|
|
12861
|
+
roster,
|
|
12862
|
+
local,
|
|
12863
|
+
cursors,
|
|
12864
|
+
selections,
|
|
12865
|
+
announce: () => local?.announce(),
|
|
12866
|
+
setFields: (fields) => local?.setFields(fields),
|
|
12867
|
+
dispose: () => {
|
|
12868
|
+
if (disposed) return;
|
|
12869
|
+
disposed = true;
|
|
12870
|
+
try {
|
|
12871
|
+
local?.dispose();
|
|
12872
|
+
} catch {
|
|
12873
|
+
}
|
|
12874
|
+
try {
|
|
12875
|
+
cursors?.dispose();
|
|
12876
|
+
} catch {
|
|
12877
|
+
}
|
|
12878
|
+
try {
|
|
12879
|
+
selections?.dispose();
|
|
12880
|
+
} catch {
|
|
12881
|
+
}
|
|
12882
|
+
try {
|
|
12883
|
+
roster.dispose();
|
|
12884
|
+
} catch {
|
|
12885
|
+
}
|
|
12886
|
+
for (const unsub of unsubscribers) {
|
|
12887
|
+
try {
|
|
12888
|
+
unsub();
|
|
12889
|
+
} catch {
|
|
12890
|
+
}
|
|
12891
|
+
}
|
|
12892
|
+
unsubscribers.length = 0;
|
|
12893
|
+
}
|
|
12894
|
+
};
|
|
12895
|
+
}
|
|
12896
|
+
|
|
12103
12897
|
// src/tools/hand-tool.ts
|
|
12104
12898
|
var HandTool = class {
|
|
12105
12899
|
name = "hand";
|
|
@@ -13765,7 +14559,7 @@ var MeasureTool = class {
|
|
|
13765
14559
|
// src/tools/path-tool.ts
|
|
13766
14560
|
var EPS2 = 1e-6;
|
|
13767
14561
|
var DEFAULT_COMMIT_TAP_RADIUS_PX = 12;
|
|
13768
|
-
function
|
|
14562
|
+
function samePoint3(a, b) {
|
|
13769
14563
|
return Math.abs(a.x - b.x) < EPS2 && Math.abs(a.y - b.y) < EPS2;
|
|
13770
14564
|
}
|
|
13771
14565
|
var PathTool = class {
|
|
@@ -13909,7 +14703,7 @@ var PathTool = class {
|
|
|
13909
14703
|
return;
|
|
13910
14704
|
}
|
|
13911
14705
|
const last = this.lastWaypoint();
|
|
13912
|
-
if (this.cursor && last && !
|
|
14706
|
+
if (this.cursor && last && !samePoint3(this.cursor, last)) {
|
|
13913
14707
|
this.waypoints.push({ ...this.cursor });
|
|
13914
14708
|
}
|
|
13915
14709
|
this.scheduleEmission();
|
|
@@ -13970,7 +14764,7 @@ var PathTool = class {
|
|
|
13970
14764
|
* world point.
|
|
13971
14765
|
*/
|
|
13972
14766
|
withinCommitRadius(point, last, ctx) {
|
|
13973
|
-
if (
|
|
14767
|
+
if (samePoint3(point, last)) return true;
|
|
13974
14768
|
if (this.hasSnappingGrid()) return false;
|
|
13975
14769
|
const zoom = ctx.camera.zoom;
|
|
13976
14770
|
if (!(zoom > 0)) return false;
|
|
@@ -14018,7 +14812,7 @@ var PathTool = class {
|
|
|
14018
14812
|
measure(cursor) {
|
|
14019
14813
|
const points = this.waypoints.map((p) => ({ ...p }));
|
|
14020
14814
|
const last = points[points.length - 1];
|
|
14021
|
-
if (cursor && (!last || !
|
|
14815
|
+
if (cursor && (!last || !samePoint3(cursor, last))) points.push({ ...cursor });
|
|
14022
14816
|
const { total, cumulative } = pathDistanceCells(points, {
|
|
14023
14817
|
gridSize: this.gridSize,
|
|
14024
14818
|
gridType: this.gridType,
|
|
@@ -14666,8 +15460,10 @@ var PingTool = class {
|
|
|
14666
15460
|
};
|
|
14667
15461
|
|
|
14668
15462
|
// src/index.ts
|
|
14669
|
-
var VERSION = "0.
|
|
15463
|
+
var VERSION = "0.65.0";
|
|
14670
15464
|
export {
|
|
15465
|
+
AWARENESS_MAX_SELECTION,
|
|
15466
|
+
AWARENESS_PRESENCE_KIND,
|
|
14671
15467
|
ArrowTool,
|
|
14672
15468
|
AutoSave,
|
|
14673
15469
|
Camera,
|
|
@@ -14686,6 +15482,7 @@ export {
|
|
|
14686
15482
|
LASER_TRAIL_PRESENCE_KIND,
|
|
14687
15483
|
LaserTool,
|
|
14688
15484
|
LayerManager,
|
|
15485
|
+
LocalAwareness,
|
|
14689
15486
|
LocalStorageAdapter,
|
|
14690
15487
|
MEASURE_PRESENCE_KIND,
|
|
14691
15488
|
MeasureTool,
|
|
@@ -14694,16 +15491,20 @@ export {
|
|
|
14694
15491
|
NoteTool,
|
|
14695
15492
|
PATH_PRESENCE_KIND,
|
|
14696
15493
|
PATH_PRESENCE_MAX_POINTS,
|
|
15494
|
+
PEER_COLORS,
|
|
14697
15495
|
PING_PRESENCE_KIND,
|
|
14698
15496
|
PathTool,
|
|
15497
|
+
PeerRoster,
|
|
14699
15498
|
PencilTool,
|
|
14700
15499
|
PingInput,
|
|
14701
15500
|
PingTool,
|
|
15501
|
+
RemoteCursorOverlay,
|
|
14702
15502
|
RemoteFocusReceiver,
|
|
14703
15503
|
RemoteLaserOverlay,
|
|
14704
15504
|
RemoteMeasureOverlay,
|
|
14705
15505
|
RemotePathOverlay,
|
|
14706
15506
|
RemotePingOverlay,
|
|
15507
|
+
RemoteSelectionOverlay,
|
|
14707
15508
|
SelectTool,
|
|
14708
15509
|
ShapeTool,
|
|
14709
15510
|
TemplateTool,
|
|
@@ -14712,6 +15513,7 @@ export {
|
|
|
14712
15513
|
VERSION,
|
|
14713
15514
|
Viewport,
|
|
14714
15515
|
applyCameraView,
|
|
15516
|
+
attachAwareness,
|
|
14715
15517
|
boundsIntersect,
|
|
14716
15518
|
cameraOriginForView,
|
|
14717
15519
|
captureCameraView,
|
|
@@ -14725,6 +15527,7 @@ export {
|
|
|
14725
15527
|
createStroke,
|
|
14726
15528
|
createTemplate,
|
|
14727
15529
|
createText,
|
|
15530
|
+
defaultPeerColor,
|
|
14728
15531
|
drawHexPath,
|
|
14729
15532
|
elementRectsEqual,
|
|
14730
15533
|
exportImage,
|
|
@@ -14747,6 +15550,7 @@ export {
|
|
|
14747
15550
|
getHexCellsInSquare,
|
|
14748
15551
|
getHexDistance,
|
|
14749
15552
|
gridDistanceCells,
|
|
15553
|
+
isAwarenessPresence,
|
|
14750
15554
|
isFocusPresence,
|
|
14751
15555
|
isLaserTrailPresence,
|
|
14752
15556
|
isMeasurePresence,
|