kritzel-stencil 0.1.81 → 0.1.83
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/cjs/index.cjs.js +1 -1
- package/dist/cjs/kritzel-active-users_42.cjs.entry.js +28 -3
- package/dist/cjs/{workspace.migrations-BVBITEM5.js → workspace.migrations-K5oVASsb.js} +86 -58
- package/dist/collection/classes/handlers/move.handler.js +2 -0
- package/dist/collection/classes/objects/selection-group.class.js +47 -9
- package/dist/collection/classes/objects/shape.class.js +14 -20
- package/dist/collection/classes/objects/text.class.js +23 -29
- package/dist/collection/classes/structures/object-map.structure.js +26 -1
- package/dist/collection/constants/version.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/{p-CwQOwQm0.js → p-CKfjz1gj.js} +2 -2
- package/dist/components/{p-DXhiNQyH.js → p-CLo-TjD_.js} +1 -1
- package/dist/components/{p--7j0gU6F.js → p-CYl_JKvH.js} +1 -1
- package/dist/components/{p-Dxr5oLKF.js → p-DwemlMvt.js} +1 -1
- package/dist/components/{p-D8jD4OI9.js → p-zM6hQ55n.js} +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/kritzel-active-users_42.entry.js +28 -3
- package/dist/esm/{workspace.migrations-BVtbsm5p.js → workspace.migrations-D5iSPf3E.js} +86 -58
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-D5iSPf3E.js +1 -0
- package/dist/stencil/p-c28b30ab.entry.js +9 -0
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/objects/selection-group.class.d.ts +15 -1
- package/dist/types/classes/objects/shape.class.d.ts +0 -1
- package/dist/types/classes/objects/text.class.d.ts +0 -1
- package/dist/types/classes/structures/object-map.structure.d.ts +17 -0
- package/dist/types/constants/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/stencil/p-56122555.entry.js +0 -9
- package/dist/stencil/p-BVtbsm5p.js +0 -1
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var index = require('./index-CFnj_FXt.js');
|
|
4
|
-
var workspace_migrations = require('./workspace.migrations-
|
|
4
|
+
var workspace_migrations = require('./workspace.migrations-K5oVASsb.js');
|
|
5
5
|
var Y = require('yjs');
|
|
6
6
|
require('y-websocket');
|
|
7
7
|
require('y-indexeddb');
|
|
@@ -21354,6 +21354,12 @@ class KritzelObjectMap {
|
|
|
21354
21354
|
_lastAwarenessEmitTime = 0;
|
|
21355
21355
|
_awarenessEmitTimeout = null;
|
|
21356
21356
|
AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
|
|
21357
|
+
/**
|
|
21358
|
+
* When true, update() only modifies local state (quadtree + idMap)
|
|
21359
|
+
* without writing to Yjs. Used during drag operations to avoid
|
|
21360
|
+
* redundant full serializations of every child object per frame.
|
|
21361
|
+
*/
|
|
21362
|
+
_localOnlyMode = false;
|
|
21357
21363
|
/**
|
|
21358
21364
|
* Indicates whether the object map has been initialized and is ready for use.
|
|
21359
21365
|
* @returns `true` if providers are connected and the map is operational
|
|
@@ -21907,6 +21913,25 @@ class KritzelObjectMap {
|
|
|
21907
21913
|
this._ydoc.transact(callback, 'local');
|
|
21908
21914
|
}
|
|
21909
21915
|
}
|
|
21916
|
+
/**
|
|
21917
|
+
* Executes a callback where all update() calls only modify local state
|
|
21918
|
+
* (quadtree + idMap) without writing to Yjs. This avoids expensive full
|
|
21919
|
+
* serializations during continuous operations like dragging many objects.
|
|
21920
|
+
*
|
|
21921
|
+
* Objects updated inside this callback must be persisted later
|
|
21922
|
+
* (e.g., via a normal update() call at drag end).
|
|
21923
|
+
*
|
|
21924
|
+
* @param callback - The function containing operations to execute locally
|
|
21925
|
+
*/
|
|
21926
|
+
withLocalUpdatesOnly(callback) {
|
|
21927
|
+
this._localOnlyMode = true;
|
|
21928
|
+
try {
|
|
21929
|
+
callback();
|
|
21930
|
+
}
|
|
21931
|
+
finally {
|
|
21932
|
+
this._localOnlyMode = false;
|
|
21933
|
+
}
|
|
21934
|
+
}
|
|
21910
21935
|
/**
|
|
21911
21936
|
* Loads all objects from the Yjs objects map into the local quadtree.
|
|
21912
21937
|
* Clears the existing quadtree and repopulates it by deserializing each
|
|
@@ -21998,7 +22023,7 @@ class KritzelObjectMap {
|
|
|
21998
22023
|
}
|
|
21999
22024
|
this.quadtree.update(object);
|
|
22000
22025
|
this._idMap.set(object.id, object);
|
|
22001
|
-
if (this._objectsMap && this.isPersistable(object)) {
|
|
22026
|
+
if (!this._localOnlyMode && this._objectsMap && this.isPersistable(object)) {
|
|
22002
22027
|
const serialized = object.serialize();
|
|
22003
22028
|
const origin = options.temporary ? 'temporary' : 'local';
|
|
22004
22029
|
this._ydoc?.transact(() => {
|
|
@@ -28708,7 +28733,7 @@ const KritzelPortal = class {
|
|
|
28708
28733
|
* This file is auto-generated by the version bump scripts.
|
|
28709
28734
|
* Do not modify manually.
|
|
28710
28735
|
*/
|
|
28711
|
-
const KRITZEL_VERSION = '0.1.
|
|
28736
|
+
const KRITZEL_VERSION = '0.1.83';
|
|
28712
28737
|
|
|
28713
28738
|
const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
|
|
28714
28739
|
|
|
@@ -15250,12 +15250,6 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15250
15250
|
isEditing = false;
|
|
15251
15251
|
editor = null;
|
|
15252
15252
|
content = null;
|
|
15253
|
-
get _editor() {
|
|
15254
|
-
if (!this._editor) {
|
|
15255
|
-
throw new Error('KritzelShape: editor is not initialized');
|
|
15256
|
-
}
|
|
15257
|
-
return this._editor;
|
|
15258
|
-
}
|
|
15259
15253
|
_schema = new Schema({
|
|
15260
15254
|
nodes: addListNodes(schema.spec.nodes, 'paragraph block*', 'block'),
|
|
15261
15255
|
marks: schema.spec.marks,
|
|
@@ -15267,10 +15261,10 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15267
15261
|
* @returns `true` if the editor is empty or contains only whitespace, `false` otherwise.
|
|
15268
15262
|
*/
|
|
15269
15263
|
get isEmpty() {
|
|
15270
|
-
if (!this.
|
|
15264
|
+
if (!this.editor) {
|
|
15271
15265
|
return true;
|
|
15272
15266
|
}
|
|
15273
|
-
const doc = this.
|
|
15267
|
+
const doc = this.editor.state.doc;
|
|
15274
15268
|
if (doc.content.size === 0) {
|
|
15275
15269
|
return true;
|
|
15276
15270
|
}
|
|
@@ -15374,7 +15368,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15374
15368
|
element.style.fontFamily = this.fontFamily;
|
|
15375
15369
|
element.style.fontSize = `${this.fontSize}pt`;
|
|
15376
15370
|
element.style.color = KritzelColorHelper.resolveThemeColor(this.fontColor);
|
|
15377
|
-
if (this.isMounted && this.elementRef === element && this.
|
|
15371
|
+
if (this.isMounted && this.elementRef === element && this.editor.dom.parentElement === element) {
|
|
15378
15372
|
if (!this._core.store.state.isScaling && !this._core.store.state.isPanning) {
|
|
15379
15373
|
requestAnimationFrame(() => this.adjustSizeOnInput());
|
|
15380
15374
|
}
|
|
@@ -15384,7 +15378,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15384
15378
|
this.elementRef.style.whiteSpace = 'pre-wrap';
|
|
15385
15379
|
this.elementRef.style.wordWrap = 'break-word';
|
|
15386
15380
|
this.elementRef.innerHTML = '';
|
|
15387
|
-
this.elementRef.appendChild(this.
|
|
15381
|
+
this.elementRef.appendChild(this.editor.dom);
|
|
15388
15382
|
this.isMounted = true;
|
|
15389
15383
|
requestAnimationFrame(() => this.adjustSizeOnInput());
|
|
15390
15384
|
}
|
|
@@ -15402,8 +15396,8 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15402
15396
|
}),
|
|
15403
15397
|
editable: () => false,
|
|
15404
15398
|
dispatchTransaction: transaction => {
|
|
15405
|
-
const newState = this.
|
|
15406
|
-
this.
|
|
15399
|
+
const newState = this.editor.state.apply(transaction);
|
|
15400
|
+
this.editor.updateState(newState);
|
|
15407
15401
|
if (transaction.docChanged) {
|
|
15408
15402
|
this.content = newState.doc.toJSON();
|
|
15409
15403
|
this.adjustSizeOnInput();
|
|
@@ -15421,11 +15415,11 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15421
15415
|
*/
|
|
15422
15416
|
setContent(content) {
|
|
15423
15417
|
this.content = content;
|
|
15424
|
-
if (this.
|
|
15425
|
-
const newDoc = this.
|
|
15426
|
-
const tr = this.
|
|
15418
|
+
if (this.editor && content) {
|
|
15419
|
+
const newDoc = this.editor.state.schema.nodeFromJSON(content);
|
|
15420
|
+
const tr = this.editor.state.tr.replaceWith(0, this.editor.state.doc.content.size, newDoc.content);
|
|
15427
15421
|
tr.setMeta('fromRemote', true);
|
|
15428
|
-
this.
|
|
15422
|
+
this.editor.dispatch(tr);
|
|
15429
15423
|
}
|
|
15430
15424
|
}
|
|
15431
15425
|
/**
|
|
@@ -15490,13 +15484,13 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15490
15484
|
* @param coords.y - Y coordinate for cursor placement.
|
|
15491
15485
|
*/
|
|
15492
15486
|
focus(coords) {
|
|
15493
|
-
if (this.
|
|
15494
|
-
const doc = this.
|
|
15487
|
+
if (this.editor) {
|
|
15488
|
+
const doc = this.editor.state.doc;
|
|
15495
15489
|
if (coords.x && coords.y && !this.isEmpty) {
|
|
15496
|
-
const pos = this.
|
|
15490
|
+
const pos = this.editor.posAtCoords({ left: coords.x, top: coords.y });
|
|
15497
15491
|
if (pos) {
|
|
15498
|
-
this.
|
|
15499
|
-
this.
|
|
15492
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, pos.pos)));
|
|
15493
|
+
this.editor.focus();
|
|
15500
15494
|
if (KritzelDevicesHelper.isIOS()) {
|
|
15501
15495
|
this.scrollIntoViewOnIOS();
|
|
15502
15496
|
}
|
|
@@ -15504,8 +15498,8 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15504
15498
|
}
|
|
15505
15499
|
}
|
|
15506
15500
|
const end = Math.max(1, doc.content.size - 1);
|
|
15507
|
-
this.
|
|
15508
|
-
this.
|
|
15501
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, end)));
|
|
15502
|
+
this.editor.focus();
|
|
15509
15503
|
if (KritzelDevicesHelper.isIOS()) {
|
|
15510
15504
|
this.scrollIntoViewOnIOS();
|
|
15511
15505
|
}
|
|
@@ -15517,8 +15511,8 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15517
15511
|
*/
|
|
15518
15512
|
scrollIntoViewOnIOS() {
|
|
15519
15513
|
setTimeout(() => {
|
|
15520
|
-
if (this.
|
|
15521
|
-
this.
|
|
15514
|
+
if (this.editor && this.editor.dom) {
|
|
15515
|
+
this.editor.dom.scrollIntoView({
|
|
15522
15516
|
behavior: 'smooth',
|
|
15523
15517
|
block: 'center',
|
|
15524
15518
|
inline: 'nearest',
|
|
@@ -15536,7 +15530,7 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15536
15530
|
KritzelKeyboardHelper.disableInteractiveWidget();
|
|
15537
15531
|
this.uneditedObject = this.clone();
|
|
15538
15532
|
this._core.store.setState('activeTool', KritzelToolRegistry.getTool('text'));
|
|
15539
|
-
this.
|
|
15533
|
+
this.editor.setProps({ editable: () => true });
|
|
15540
15534
|
this.isEditing = true;
|
|
15541
15535
|
this._core.rerender();
|
|
15542
15536
|
this.adjustSizeOnInput();
|
|
@@ -15552,9 +15546,9 @@ class KritzelText extends KritzelBaseObject {
|
|
|
15552
15546
|
*/
|
|
15553
15547
|
save() {
|
|
15554
15548
|
requestAnimationFrame(() => this.adjustSizeOnInput());
|
|
15555
|
-
this.content = this.
|
|
15556
|
-
this.
|
|
15557
|
-
this.
|
|
15549
|
+
this.content = this.editor.state.doc.toJSON();
|
|
15550
|
+
this.editor.setProps({ editable: () => false });
|
|
15551
|
+
this.editor.dom.blur();
|
|
15558
15552
|
this.isEditing = false;
|
|
15559
15553
|
this._core.store.objects.consolidateTemporaryItems();
|
|
15560
15554
|
this._core.store.objects.update(this);
|
|
@@ -17645,12 +17639,6 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17645
17639
|
get viewBox() {
|
|
17646
17640
|
return `${this.x} ${this.y} ${this.width} ${this.height}`;
|
|
17647
17641
|
}
|
|
17648
|
-
get _editor() {
|
|
17649
|
-
if (!this.editor) {
|
|
17650
|
-
throw new Error('KritzelShape: editor is not initialized');
|
|
17651
|
-
}
|
|
17652
|
-
return this.editor;
|
|
17653
|
-
}
|
|
17654
17642
|
/**
|
|
17655
17643
|
* Creates a new KritzelShape instance with optional configuration.
|
|
17656
17644
|
* This constructor initializes the shape with default values that can be
|
|
@@ -17758,7 +17746,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17758
17746
|
if (element === null || this.isInViewport() === false) {
|
|
17759
17747
|
return;
|
|
17760
17748
|
}
|
|
17761
|
-
if (this.isMounted && this.elementRef === element && this.
|
|
17749
|
+
if (this.isMounted && this.elementRef === element && this.editor.dom.parentElement === element) {
|
|
17762
17750
|
return;
|
|
17763
17751
|
}
|
|
17764
17752
|
this.elementRef = element;
|
|
@@ -17774,7 +17762,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17774
17762
|
* @returns void
|
|
17775
17763
|
*/
|
|
17776
17764
|
mountTextEditor(element) {
|
|
17777
|
-
if (element === null
|
|
17765
|
+
if (element === null) {
|
|
17778
17766
|
return;
|
|
17779
17767
|
}
|
|
17780
17768
|
element.style.fontFamily = this.fontFamily;
|
|
@@ -17805,8 +17793,8 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17805
17793
|
}),
|
|
17806
17794
|
editable: () => false,
|
|
17807
17795
|
dispatchTransaction: transaction => {
|
|
17808
|
-
const newState = this.
|
|
17809
|
-
this.
|
|
17796
|
+
const newState = this.editor.state.apply(transaction);
|
|
17797
|
+
this.editor.updateState(newState);
|
|
17810
17798
|
if (transaction.docChanged) {
|
|
17811
17799
|
this.content = newState.doc.toJSON();
|
|
17812
17800
|
if (!transaction.getMeta('fromRemote')) {
|
|
@@ -17868,12 +17856,12 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17868
17856
|
* @returns void
|
|
17869
17857
|
*/
|
|
17870
17858
|
focus(coords) {
|
|
17871
|
-
const doc = this.
|
|
17859
|
+
const doc = this.editor.state.doc;
|
|
17872
17860
|
if (coords?.x && coords?.y) {
|
|
17873
|
-
const pos = this.
|
|
17861
|
+
const pos = this.editor.posAtCoords({ left: coords.x, top: coords.y });
|
|
17874
17862
|
if (pos) {
|
|
17875
|
-
this.
|
|
17876
|
-
this.
|
|
17863
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, pos.pos)));
|
|
17864
|
+
this.editor.focus();
|
|
17877
17865
|
if (KritzelDevicesHelper.isIOS()) {
|
|
17878
17866
|
this.scrollIntoViewOnIOS();
|
|
17879
17867
|
}
|
|
@@ -17881,8 +17869,8 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17881
17869
|
}
|
|
17882
17870
|
}
|
|
17883
17871
|
const end = Math.max(1, doc.content.size - 1);
|
|
17884
|
-
this.
|
|
17885
|
-
this.
|
|
17872
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, end)));
|
|
17873
|
+
this.editor.focus();
|
|
17886
17874
|
if (KritzelDevicesHelper.isIOS()) {
|
|
17887
17875
|
this.scrollIntoViewOnIOS();
|
|
17888
17876
|
}
|
|
@@ -17920,7 +17908,7 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17920
17908
|
KritzelKeyboardHelper.disableInteractiveWidget();
|
|
17921
17909
|
this.uneditedObject = this.clone();
|
|
17922
17910
|
this._core.store.setState('activeTool', KritzelToolRegistry.getTool('shape'));
|
|
17923
|
-
this.
|
|
17911
|
+
this.editor.setProps({ editable: () => true });
|
|
17924
17912
|
this.isEditing = true;
|
|
17925
17913
|
this._core.rerender();
|
|
17926
17914
|
if (event?.clientX && event?.clientY) {
|
|
@@ -17937,9 +17925,9 @@ class KritzelShape extends KritzelBaseObject {
|
|
|
17937
17925
|
* @returns void
|
|
17938
17926
|
*/
|
|
17939
17927
|
save() {
|
|
17940
|
-
this.content = this.
|
|
17941
|
-
this.
|
|
17942
|
-
this.
|
|
17928
|
+
this.content = this.editor.state.doc.toJSON();
|
|
17929
|
+
this.editor.setProps({ editable: () => false });
|
|
17930
|
+
this.editor.dom.blur();
|
|
17943
17931
|
this.isEditing = false;
|
|
17944
17932
|
this._core.store.objects.consolidateTemporaryItems();
|
|
17945
17933
|
this._core.store.objects.update(this);
|
|
@@ -18555,6 +18543,10 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
18555
18543
|
handleColor;
|
|
18556
18544
|
handleStrokeColor;
|
|
18557
18545
|
handleSize = 6;
|
|
18546
|
+
/** Timestamp of the last Yjs persist for children during a drag */
|
|
18547
|
+
_lastChildPersistTime = 0;
|
|
18548
|
+
/** Minimum interval (ms) between child Yjs persists during drag */
|
|
18549
|
+
static CHILD_PERSIST_THROTTLE_MS = 100;
|
|
18558
18550
|
/**
|
|
18559
18551
|
* Gets the array of object IDs contained in this selection group.
|
|
18560
18552
|
* @returns Array of string IDs for the selected objects
|
|
@@ -18840,7 +18832,11 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
18840
18832
|
/**
|
|
18841
18833
|
* Moves the selection group and all its contained objects by calculating the delta from drag coordinates.
|
|
18842
18834
|
* Updates anchor points for any lines connected to the moved objects.
|
|
18843
|
-
*
|
|
18835
|
+
*
|
|
18836
|
+
* Child objects are moved locally without writing to Yjs during the drag to avoid
|
|
18837
|
+
* N+1 full serializations per frame, which can overwhelm the WebSocket server.
|
|
18838
|
+
* Children must be persisted at drag end via {@link persistChildren}.
|
|
18839
|
+
*
|
|
18844
18840
|
* @param startX - The starting x-coordinate of the drag operation (in screen space)
|
|
18845
18841
|
* @param startY - The starting y-coordinate of the drag operation (in screen space)
|
|
18846
18842
|
* @param endX - The ending x-coordinate of the drag operation (in screen space)
|
|
@@ -18851,21 +18847,51 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
18851
18847
|
const deltaY = (startY - endY) / this._core.store.state.scale;
|
|
18852
18848
|
this.translateX += deltaX;
|
|
18853
18849
|
this.translateY += deltaY;
|
|
18854
|
-
|
|
18850
|
+
const now = Date.now();
|
|
18851
|
+
const shouldPersistChildren = now - this._lastChildPersistTime >= KritzelSelectionGroup.CHILD_PERSIST_THROTTLE_MS;
|
|
18852
|
+
if (shouldPersistChildren) {
|
|
18853
|
+
// Periodically persist children to Yjs so remote peers see movement in real-time
|
|
18854
|
+
this._core.store.objects.transaction(() => {
|
|
18855
|
+
this._core.store.objects.update(this);
|
|
18856
|
+
const children = this.objects;
|
|
18857
|
+
for (const obj of children) {
|
|
18858
|
+
obj.move(startX, startY, endX, endY);
|
|
18859
|
+
this._core.anchorManager.updateAnchorsForObject(obj.id);
|
|
18860
|
+
}
|
|
18861
|
+
});
|
|
18862
|
+
this._lastChildPersistTime = now;
|
|
18863
|
+
}
|
|
18864
|
+
else {
|
|
18865
|
+
// Between throttle intervals, only persist the selection group itself.
|
|
18866
|
+
// Children are moved locally for smooth local rendering.
|
|
18855
18867
|
this._core.store.objects.update(this);
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18868
|
+
this._core.store.objects.withLocalUpdatesOnly(() => {
|
|
18869
|
+
const children = this.objects;
|
|
18870
|
+
for (const obj of children) {
|
|
18871
|
+
obj.move(startX, startY, endX, endY);
|
|
18872
|
+
this._core.anchorManager.updateAnchorsForObject(obj.id);
|
|
18873
|
+
}
|
|
18874
|
+
});
|
|
18875
|
+
}
|
|
18863
18876
|
// Update snapshots
|
|
18864
18877
|
this.unchangedObjectSnapshots.forEach(snapshot => {
|
|
18865
18878
|
snapshot.translateX += deltaX;
|
|
18866
18879
|
snapshot.translateY += deltaY;
|
|
18867
18880
|
});
|
|
18868
18881
|
}
|
|
18882
|
+
/**
|
|
18883
|
+
* Persists all child objects to Yjs in a single transaction.
|
|
18884
|
+
* Must be called after a drag operation completes to sync the final
|
|
18885
|
+
* positions of children that were only updated locally during the drag.
|
|
18886
|
+
*/
|
|
18887
|
+
persistChildren() {
|
|
18888
|
+
this._core.store.objects.transaction(() => {
|
|
18889
|
+
const children = this.objects;
|
|
18890
|
+
for (const obj of children) {
|
|
18891
|
+
this._core.store.objects.update(obj);
|
|
18892
|
+
}
|
|
18893
|
+
});
|
|
18894
|
+
}
|
|
18869
18895
|
/**
|
|
18870
18896
|
* Resizes the selection group and scales all contained objects proportionally.
|
|
18871
18897
|
* Uses snapshot values to avoid compounding errors during continuous drag operations.
|
|
@@ -20379,6 +20405,7 @@ class KritzelMoveHandler extends KritzelBaseHandler {
|
|
|
20379
20405
|
if (this._core.store.state.isDragging) {
|
|
20380
20406
|
this._core.store.state.isDragging = false;
|
|
20381
20407
|
if (this.hasMoved) {
|
|
20408
|
+
this._core.store.selectionGroup.persistChildren();
|
|
20382
20409
|
this._core.store.selectionGroup.update();
|
|
20383
20410
|
this._core.engine.emitObjectsChange();
|
|
20384
20411
|
this._core.store.state.hasObjectsChanged = true;
|
|
@@ -20389,6 +20416,7 @@ class KritzelMoveHandler extends KritzelBaseHandler {
|
|
|
20389
20416
|
if (this._core.store.state.isDragging) {
|
|
20390
20417
|
this._core.store.state.isDragging = false;
|
|
20391
20418
|
if (this.hasMoved) {
|
|
20419
|
+
this._core.store.selectionGroup.persistChildren();
|
|
20392
20420
|
this._core.store.selectionGroup.update();
|
|
20393
20421
|
this._core.engine.emitObjectsChange();
|
|
20394
20422
|
this._core.store.state.hasObjectsChanged = true;
|
|
@@ -210,6 +210,7 @@ export class KritzelMoveHandler extends KritzelBaseHandler {
|
|
|
210
210
|
if (this._core.store.state.isDragging) {
|
|
211
211
|
this._core.store.state.isDragging = false;
|
|
212
212
|
if (this.hasMoved) {
|
|
213
|
+
this._core.store.selectionGroup.persistChildren();
|
|
213
214
|
this._core.store.selectionGroup.update();
|
|
214
215
|
this._core.engine.emitObjectsChange();
|
|
215
216
|
this._core.store.state.hasObjectsChanged = true;
|
|
@@ -220,6 +221,7 @@ export class KritzelMoveHandler extends KritzelBaseHandler {
|
|
|
220
221
|
if (this._core.store.state.isDragging) {
|
|
221
222
|
this._core.store.state.isDragging = false;
|
|
222
223
|
if (this.hasMoved) {
|
|
224
|
+
this._core.store.selectionGroup.persistChildren();
|
|
223
225
|
this._core.store.selectionGroup.update();
|
|
224
226
|
this._core.engine.emitObjectsChange();
|
|
225
227
|
this._core.store.state.hasObjectsChanged = true;
|
|
@@ -29,6 +29,10 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
29
29
|
handleColor;
|
|
30
30
|
handleStrokeColor;
|
|
31
31
|
handleSize = 6;
|
|
32
|
+
/** Timestamp of the last Yjs persist for children during a drag */
|
|
33
|
+
_lastChildPersistTime = 0;
|
|
34
|
+
/** Minimum interval (ms) between child Yjs persists during drag */
|
|
35
|
+
static CHILD_PERSIST_THROTTLE_MS = 100;
|
|
32
36
|
/**
|
|
33
37
|
* Gets the array of object IDs contained in this selection group.
|
|
34
38
|
* @returns Array of string IDs for the selected objects
|
|
@@ -314,7 +318,11 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
314
318
|
/**
|
|
315
319
|
* Moves the selection group and all its contained objects by calculating the delta from drag coordinates.
|
|
316
320
|
* Updates anchor points for any lines connected to the moved objects.
|
|
317
|
-
*
|
|
321
|
+
*
|
|
322
|
+
* Child objects are moved locally without writing to Yjs during the drag to avoid
|
|
323
|
+
* N+1 full serializations per frame, which can overwhelm the WebSocket server.
|
|
324
|
+
* Children must be persisted at drag end via {@link persistChildren}.
|
|
325
|
+
*
|
|
318
326
|
* @param startX - The starting x-coordinate of the drag operation (in screen space)
|
|
319
327
|
* @param startY - The starting y-coordinate of the drag operation (in screen space)
|
|
320
328
|
* @param endX - The ending x-coordinate of the drag operation (in screen space)
|
|
@@ -325,21 +333,51 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
325
333
|
const deltaY = (startY - endY) / this._core.store.state.scale;
|
|
326
334
|
this.translateX += deltaX;
|
|
327
335
|
this.translateY += deltaY;
|
|
328
|
-
|
|
336
|
+
const now = Date.now();
|
|
337
|
+
const shouldPersistChildren = now - this._lastChildPersistTime >= KritzelSelectionGroup.CHILD_PERSIST_THROTTLE_MS;
|
|
338
|
+
if (shouldPersistChildren) {
|
|
339
|
+
// Periodically persist children to Yjs so remote peers see movement in real-time
|
|
340
|
+
this._core.store.objects.transaction(() => {
|
|
341
|
+
this._core.store.objects.update(this);
|
|
342
|
+
const children = this.objects;
|
|
343
|
+
for (const obj of children) {
|
|
344
|
+
obj.move(startX, startY, endX, endY);
|
|
345
|
+
this._core.anchorManager.updateAnchorsForObject(obj.id);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
this._lastChildPersistTime = now;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
// Between throttle intervals, only persist the selection group itself.
|
|
352
|
+
// Children are moved locally for smooth local rendering.
|
|
329
353
|
this._core.store.objects.update(this);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
354
|
+
this._core.store.objects.withLocalUpdatesOnly(() => {
|
|
355
|
+
const children = this.objects;
|
|
356
|
+
for (const obj of children) {
|
|
357
|
+
obj.move(startX, startY, endX, endY);
|
|
358
|
+
this._core.anchorManager.updateAnchorsForObject(obj.id);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
337
362
|
// Update snapshots
|
|
338
363
|
this.unchangedObjectSnapshots.forEach(snapshot => {
|
|
339
364
|
snapshot.translateX += deltaX;
|
|
340
365
|
snapshot.translateY += deltaY;
|
|
341
366
|
});
|
|
342
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Persists all child objects to Yjs in a single transaction.
|
|
370
|
+
* Must be called after a drag operation completes to sync the final
|
|
371
|
+
* positions of children that were only updated locally during the drag.
|
|
372
|
+
*/
|
|
373
|
+
persistChildren() {
|
|
374
|
+
this._core.store.objects.transaction(() => {
|
|
375
|
+
const children = this.objects;
|
|
376
|
+
for (const obj of children) {
|
|
377
|
+
this._core.store.objects.update(obj);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
343
381
|
/**
|
|
344
382
|
* Resizes the selection group and scales all contained objects proportionally.
|
|
345
383
|
* Uses snapshot values to avoid compounding errors during continuous drag operations.
|
|
@@ -44,12 +44,6 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
44
44
|
get viewBox() {
|
|
45
45
|
return `${this.x} ${this.y} ${this.width} ${this.height}`;
|
|
46
46
|
}
|
|
47
|
-
get _editor() {
|
|
48
|
-
if (!this.editor) {
|
|
49
|
-
throw new Error('KritzelShape: editor is not initialized');
|
|
50
|
-
}
|
|
51
|
-
return this.editor;
|
|
52
|
-
}
|
|
53
47
|
/**
|
|
54
48
|
* Creates a new KritzelShape instance with optional configuration.
|
|
55
49
|
* This constructor initializes the shape with default values that can be
|
|
@@ -157,7 +151,7 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
157
151
|
if (element === null || this.isInViewport() === false) {
|
|
158
152
|
return;
|
|
159
153
|
}
|
|
160
|
-
if (this.isMounted && this.elementRef === element && this.
|
|
154
|
+
if (this.isMounted && this.elementRef === element && this.editor.dom.parentElement === element) {
|
|
161
155
|
return;
|
|
162
156
|
}
|
|
163
157
|
this.elementRef = element;
|
|
@@ -173,7 +167,7 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
173
167
|
* @returns void
|
|
174
168
|
*/
|
|
175
169
|
mountTextEditor(element) {
|
|
176
|
-
if (element === null
|
|
170
|
+
if (element === null) {
|
|
177
171
|
return;
|
|
178
172
|
}
|
|
179
173
|
element.style.fontFamily = this.fontFamily;
|
|
@@ -204,8 +198,8 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
204
198
|
}),
|
|
205
199
|
editable: () => false,
|
|
206
200
|
dispatchTransaction: transaction => {
|
|
207
|
-
const newState = this.
|
|
208
|
-
this.
|
|
201
|
+
const newState = this.editor.state.apply(transaction);
|
|
202
|
+
this.editor.updateState(newState);
|
|
209
203
|
if (transaction.docChanged) {
|
|
210
204
|
this.content = newState.doc.toJSON();
|
|
211
205
|
if (!transaction.getMeta('fromRemote')) {
|
|
@@ -267,12 +261,12 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
267
261
|
* @returns void
|
|
268
262
|
*/
|
|
269
263
|
focus(coords) {
|
|
270
|
-
const doc = this.
|
|
264
|
+
const doc = this.editor.state.doc;
|
|
271
265
|
if (coords?.x && coords?.y) {
|
|
272
|
-
const pos = this.
|
|
266
|
+
const pos = this.editor.posAtCoords({ left: coords.x, top: coords.y });
|
|
273
267
|
if (pos) {
|
|
274
|
-
this.
|
|
275
|
-
this.
|
|
268
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, pos.pos)));
|
|
269
|
+
this.editor.focus();
|
|
276
270
|
if (KritzelDevicesHelper.isIOS()) {
|
|
277
271
|
this.scrollIntoViewOnIOS();
|
|
278
272
|
}
|
|
@@ -280,8 +274,8 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
280
274
|
}
|
|
281
275
|
}
|
|
282
276
|
const end = Math.max(1, doc.content.size - 1);
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
277
|
+
this.editor.dispatch(this.editor.state.tr.setSelection(TextSelection.create(doc, end)));
|
|
278
|
+
this.editor.focus();
|
|
285
279
|
if (KritzelDevicesHelper.isIOS()) {
|
|
286
280
|
this.scrollIntoViewOnIOS();
|
|
287
281
|
}
|
|
@@ -319,7 +313,7 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
319
313
|
KritzelKeyboardHelper.disableInteractiveWidget();
|
|
320
314
|
this.uneditedObject = this.clone();
|
|
321
315
|
this._core.store.setState('activeTool', KritzelToolRegistry.getTool('shape'));
|
|
322
|
-
this.
|
|
316
|
+
this.editor.setProps({ editable: () => true });
|
|
323
317
|
this.isEditing = true;
|
|
324
318
|
this._core.rerender();
|
|
325
319
|
if (event?.clientX && event?.clientY) {
|
|
@@ -336,9 +330,9 @@ export class KritzelShape extends KritzelBaseObject {
|
|
|
336
330
|
* @returns void
|
|
337
331
|
*/
|
|
338
332
|
save() {
|
|
339
|
-
this.content = this.
|
|
340
|
-
this.
|
|
341
|
-
this.
|
|
333
|
+
this.content = this.editor.state.doc.toJSON();
|
|
334
|
+
this.editor.setProps({ editable: () => false });
|
|
335
|
+
this.editor.dom.blur();
|
|
342
336
|
this.isEditing = false;
|
|
343
337
|
this._core.store.objects.consolidateTemporaryItems();
|
|
344
338
|
this._core.store.objects.update(this);
|