kritzel-stencil 0.1.74 → 0.1.76
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 +131 -86
- package/dist/cjs/kritzel-active-users_42.cjs.entry.js +132 -19
- package/dist/cjs/{workspace.migrations-Dyt35LBC.js → workspace.migrations-DkmVO6dE.js} +106 -44
- package/dist/collection/classes/core/viewport.class.js +32 -3
- package/dist/collection/classes/managers/anchor.manager.js +101 -44
- package/dist/collection/classes/providers/broadcast-sync-provider.class.js +5 -0
- package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +120 -85
- package/dist/collection/classes/providers/indexeddb-sync-provider.class.js +5 -0
- package/dist/collection/classes/providers/websocket-sync-provider.class.js +5 -0
- package/dist/collection/classes/structures/app-state-map.structure.js +15 -4
- package/dist/collection/classes/structures/object-map.structure.js +75 -7
- package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +2 -2
- package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +7 -2
- package/dist/collection/constants/version.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-awareness-cursors.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/{p-B4Oqnl55.js → p-31FVoNWR.js} +1 -1
- package/dist/components/p-jdYmu4SA.js +9 -0
- package/dist/components/p-xNwOWoiT.js +1 -0
- package/dist/esm/index.js +132 -87
- package/dist/esm/kritzel-active-users_42.entry.js +132 -19
- package/dist/esm/{workspace.migrations-B99F1MdT.js → workspace.migrations-D48_Bqvh.js} +106 -44
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-775a7246.entry.js +9 -0
- package/dist/stencil/{p-B99F1MdT.js → p-D48_Bqvh.js} +1 -1
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/core/viewport.class.d.ts +8 -0
- package/dist/types/classes/managers/anchor.manager.d.ts +4 -0
- package/dist/types/classes/providers/broadcast-sync-provider.class.d.ts +2 -0
- package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +37 -1
- package/dist/types/classes/providers/indexeddb-sync-provider.class.d.ts +2 -0
- package/dist/types/classes/providers/websocket-sync-provider.class.d.ts +2 -0
- package/dist/types/classes/structures/object-map.structure.d.ts +6 -0
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/interfaces/remote-cursor.interface.d.ts +1 -0
- package/dist/types/interfaces/sync-provider.interface.d.ts +16 -0
- package/package.json +1 -1
- package/dist/components/p-BSipRoFx.js +0 -1
- package/dist/components/p-RJWe82kG.js +0 -9
- package/dist/stencil/p-2a60e1bc.entry.js +0 -9
|
@@ -22386,6 +22386,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
|
|
|
22386
22386
|
* IndexedDB sync provider for local persistence
|
|
22387
22387
|
*/
|
|
22388
22388
|
class IndexedDBSyncProvider {
|
|
22389
|
+
type = 'local';
|
|
22389
22390
|
provider;
|
|
22390
22391
|
isConnected = false;
|
|
22391
22392
|
constructor(docName, doc, options) {
|
|
@@ -22407,6 +22408,10 @@ class IndexedDBSyncProvider {
|
|
|
22407
22408
|
// IndexedDB doesn't need explicit disconnect
|
|
22408
22409
|
this.isConnected = false;
|
|
22409
22410
|
}
|
|
22411
|
+
async reconnect() {
|
|
22412
|
+
this.disconnect();
|
|
22413
|
+
return this.connect();
|
|
22414
|
+
}
|
|
22410
22415
|
destroy() {
|
|
22411
22416
|
if (this.provider) {
|
|
22412
22417
|
this.provider.destroy();
|
|
@@ -25602,16 +25607,22 @@ class KritzelAnchorManager {
|
|
|
25602
25607
|
if (!KritzelClassHelper.isInstanceOf(selectedObject, 'KritzelLine'))
|
|
25603
25608
|
return null;
|
|
25604
25609
|
const line = selectedObject;
|
|
25605
|
-
const
|
|
25606
|
-
const
|
|
25610
|
+
const startAnchorVizRaw = this.computeAnchorVisualization(line, 'start');
|
|
25611
|
+
const endAnchorVizRaw = this.computeAnchorVisualization(line, 'end');
|
|
25612
|
+
const startAnchorViz = this.isFiniteAnchorVisualization(startAnchorVizRaw) ? startAnchorVizRaw : null;
|
|
25613
|
+
const endAnchorViz = this.isFiniteAnchorVisualization(endAnchorVizRaw) ? endAnchorVizRaw : null;
|
|
25607
25614
|
if (!startAnchorViz && !endAnchorViz)
|
|
25608
25615
|
return null;
|
|
25609
|
-
const scale = this._core.store.state.scale;
|
|
25610
|
-
const
|
|
25616
|
+
const scale = this.getSafeScale(this._core.store.state.scale);
|
|
25617
|
+
const lineScale = this.getSafeScale(line.scale);
|
|
25618
|
+
const lineStrokeWidth = line.strokeWidth / lineScale;
|
|
25611
25619
|
const indicatorStrokeWidth = `${2 / scale}`;
|
|
25612
25620
|
const dashLength = Math.max(lineStrokeWidth * 2, 4 / scale);
|
|
25613
25621
|
const dashArray = `${dashLength} ${dashLength}`;
|
|
25614
25622
|
const indicatorRadius = 8 / scale;
|
|
25623
|
+
if (!this.areFiniteNumbers(lineStrokeWidth, dashLength, indicatorRadius)) {
|
|
25624
|
+
return null;
|
|
25625
|
+
}
|
|
25615
25626
|
return {
|
|
25616
25627
|
lineStrokeWidth,
|
|
25617
25628
|
indicatorStrokeWidth,
|
|
@@ -25633,31 +25644,39 @@ class KritzelAnchorManager {
|
|
|
25633
25644
|
const snapCandidate = this.getSnapCandidate();
|
|
25634
25645
|
if (!snapCandidate)
|
|
25635
25646
|
return null;
|
|
25636
|
-
|
|
25647
|
+
if (!this.areFiniteNumbers(snapCandidate.centerX, snapCandidate.centerY, snapCandidate.lineEndpointX, snapCandidate.lineEndpointY)) {
|
|
25648
|
+
return null;
|
|
25649
|
+
}
|
|
25650
|
+
const scale = this.getSafeScale(this._core.store.state.scale);
|
|
25637
25651
|
const indicatorRadius = 8 / scale;
|
|
25638
25652
|
const indicatorStrokeWidth = `${2 / scale}`;
|
|
25639
|
-
const
|
|
25640
|
-
?
|
|
25641
|
-
:
|
|
25642
|
-
const
|
|
25653
|
+
const lineStrokeWidthNum = this.areFiniteNumbers(snapCandidate.lineStrokeWidth) && snapCandidate.lineStrokeWidth > 0
|
|
25654
|
+
? snapCandidate.lineStrokeWidth
|
|
25655
|
+
: (4 / scale);
|
|
25656
|
+
const lineStrokeWidth = `${lineStrokeWidthNum}`;
|
|
25643
25657
|
const dashLength = Math.max(lineStrokeWidthNum * 2, 4 / scale);
|
|
25644
25658
|
const dashArray = `${dashLength} ${dashLength}`;
|
|
25645
25659
|
const lineStroke = snapCandidate.lineStroke || '#000000';
|
|
25646
|
-
let
|
|
25647
|
-
let
|
|
25660
|
+
let edgeX = this.areFiniteNumbers(snapCandidate.edgeX) ? snapCandidate.edgeX : undefined;
|
|
25661
|
+
let edgeY = this.areFiniteNumbers(snapCandidate.edgeY) ? snapCandidate.edgeY : undefined;
|
|
25662
|
+
let solidLineEndX = edgeX;
|
|
25663
|
+
let solidLineEndY = edgeY;
|
|
25648
25664
|
let arrowPoints;
|
|
25649
|
-
|
|
25650
|
-
|
|
25651
|
-
|
|
25665
|
+
const arrowOffset = this.areFiniteNumbers(snapCandidate.arrowOffset) && snapCandidate.arrowOffset > 0
|
|
25666
|
+
? snapCandidate.arrowOffset
|
|
25667
|
+
: undefined;
|
|
25668
|
+
if (arrowOffset !== undefined && edgeX !== undefined && edgeY !== undefined) {
|
|
25669
|
+
const dx = snapCandidate.lineEndpointX - edgeX;
|
|
25670
|
+
const dy = snapCandidate.lineEndpointY - edgeY;
|
|
25652
25671
|
const length = Math.sqrt(dx * dx + dy * dy);
|
|
25653
|
-
if (length >
|
|
25654
|
-
solidLineEndX =
|
|
25655
|
-
solidLineEndY =
|
|
25672
|
+
if (length > arrowOffset) {
|
|
25673
|
+
solidLineEndX = edgeX + (dx / length) * arrowOffset;
|
|
25674
|
+
solidLineEndY = edgeY + (dy / length) * arrowOffset;
|
|
25656
25675
|
}
|
|
25657
25676
|
// Calculate arrow head points
|
|
25658
25677
|
// Direction from line endpoint to edge (arrow direction)
|
|
25659
|
-
const arrowDx =
|
|
25660
|
-
const arrowDy =
|
|
25678
|
+
const arrowDx = edgeX - snapCandidate.lineEndpointX;
|
|
25679
|
+
const arrowDy = edgeY - snapCandidate.lineEndpointY;
|
|
25661
25680
|
const arrowLengthTotal = Math.sqrt(arrowDx * arrowDx + arrowDy * arrowDy);
|
|
25662
25681
|
if (arrowLengthTotal > 0) {
|
|
25663
25682
|
const ux = arrowDx / arrowLengthTotal;
|
|
@@ -25666,11 +25685,11 @@ class KritzelAnchorManager {
|
|
|
25666
25685
|
const px = -uy;
|
|
25667
25686
|
const py = ux;
|
|
25668
25687
|
// Arrow dimensions
|
|
25669
|
-
const arrowLength =
|
|
25688
|
+
const arrowLength = arrowOffset;
|
|
25670
25689
|
const arrowWidth = arrowLength; // 1:1 ratio
|
|
25671
25690
|
// Arrow tip at edge
|
|
25672
|
-
const tipX =
|
|
25673
|
-
const tipY =
|
|
25691
|
+
const tipX = edgeX;
|
|
25692
|
+
const tipY = edgeY;
|
|
25674
25693
|
// Arrow base
|
|
25675
25694
|
const baseX = tipX - ux * arrowLength;
|
|
25676
25695
|
const baseY = tipY - uy * arrowLength;
|
|
@@ -25679,9 +25698,31 @@ class KritzelAnchorManager {
|
|
|
25679
25698
|
const leftY = baseY + py * arrowWidth / 2;
|
|
25680
25699
|
const rightX = baseX - px * arrowWidth / 2;
|
|
25681
25700
|
const rightY = baseY - py * arrowWidth / 2;
|
|
25682
|
-
|
|
25701
|
+
if (this.areFiniteNumbers(tipX, tipY, leftX, leftY, rightX, rightY)) {
|
|
25702
|
+
arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
|
|
25703
|
+
}
|
|
25683
25704
|
}
|
|
25684
25705
|
}
|
|
25706
|
+
if (!this.areFiniteNumbers(indicatorRadius, lineStrokeWidthNum, dashLength)) {
|
|
25707
|
+
return null;
|
|
25708
|
+
}
|
|
25709
|
+
const snapLinePath = (() => {
|
|
25710
|
+
if (snapCandidate.controlX !== undefined &&
|
|
25711
|
+
snapCandidate.controlY !== undefined &&
|
|
25712
|
+
snapCandidate.t !== undefined &&
|
|
25713
|
+
this.areFiniteNumbers(snapCandidate.controlX, snapCandidate.controlY, snapCandidate.t)) {
|
|
25714
|
+
const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
|
|
25715
|
+
// Ensure meaningful range
|
|
25716
|
+
if (startT >= 1)
|
|
25717
|
+
return undefined;
|
|
25718
|
+
const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
|
|
25719
|
+
if (!this.areFiniteNumbers(segment.start.x, segment.start.y, segment.control.x, segment.control.y, segment.end.x, segment.end.y)) {
|
|
25720
|
+
return undefined;
|
|
25721
|
+
}
|
|
25722
|
+
return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
|
|
25723
|
+
}
|
|
25724
|
+
return undefined;
|
|
25725
|
+
})();
|
|
25685
25726
|
return {
|
|
25686
25727
|
indicatorRadius,
|
|
25687
25728
|
indicatorStrokeWidth,
|
|
@@ -25692,27 +25733,15 @@ class KritzelAnchorManager {
|
|
|
25692
25733
|
centerY: snapCandidate.centerY,
|
|
25693
25734
|
lineEndpointX: snapCandidate.lineEndpointX,
|
|
25694
25735
|
lineEndpointY: snapCandidate.lineEndpointY,
|
|
25695
|
-
edgeX
|
|
25696
|
-
edgeY
|
|
25697
|
-
arrowOffset
|
|
25736
|
+
edgeX,
|
|
25737
|
+
edgeY,
|
|
25738
|
+
arrowOffset,
|
|
25698
25739
|
arrowStyle: snapCandidate.arrowStyle,
|
|
25699
25740
|
arrowFill: snapCandidate.arrowFill,
|
|
25700
25741
|
solidLineEndX,
|
|
25701
25742
|
solidLineEndY,
|
|
25702
25743
|
arrowPoints,
|
|
25703
|
-
snapLinePath
|
|
25704
|
-
if (snapCandidate.controlX !== undefined &&
|
|
25705
|
-
snapCandidate.controlY !== undefined &&
|
|
25706
|
-
snapCandidate.t !== undefined) {
|
|
25707
|
-
const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
|
|
25708
|
-
// Ensure meaningful range
|
|
25709
|
-
if (startT >= 1)
|
|
25710
|
-
return undefined;
|
|
25711
|
-
const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
|
|
25712
|
-
return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
|
|
25713
|
-
}
|
|
25714
|
-
return undefined;
|
|
25715
|
-
})(),
|
|
25744
|
+
snapLinePath,
|
|
25716
25745
|
};
|
|
25717
25746
|
}
|
|
25718
25747
|
// ============================================
|
|
@@ -25890,12 +25919,19 @@ class KritzelAnchorManager {
|
|
|
25890
25919
|
return null;
|
|
25891
25920
|
const centerX = targetObject.centerX;
|
|
25892
25921
|
const centerY = targetObject.centerY;
|
|
25922
|
+
if (!this.areFiniteNumbers(clipInfo.worldX, clipInfo.worldY, centerX, centerY)) {
|
|
25923
|
+
return null;
|
|
25924
|
+
}
|
|
25925
|
+
const pathD = this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined;
|
|
25926
|
+
if (pathD && !this.isFinitePathData(pathD)) {
|
|
25927
|
+
return null;
|
|
25928
|
+
}
|
|
25893
25929
|
return {
|
|
25894
25930
|
edgeX: clipInfo.worldX,
|
|
25895
25931
|
edgeY: clipInfo.worldY,
|
|
25896
25932
|
centerX,
|
|
25897
25933
|
centerY,
|
|
25898
|
-
pathD
|
|
25934
|
+
pathD,
|
|
25899
25935
|
};
|
|
25900
25936
|
}
|
|
25901
25937
|
/**
|
|
@@ -26608,9 +26644,10 @@ class KritzelAnchorManager {
|
|
|
26608
26644
|
const sin = Math.sin(line.rotation);
|
|
26609
26645
|
const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
|
|
26610
26646
|
const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
|
|
26647
|
+
const safeLineScale = this.getSafeScale(line.scale);
|
|
26611
26648
|
return {
|
|
26612
|
-
x: rotatedX /
|
|
26613
|
-
y: rotatedY /
|
|
26649
|
+
x: rotatedX / safeLineScale + line.translateX,
|
|
26650
|
+
y: rotatedY / safeLineScale + line.translateY,
|
|
26614
26651
|
};
|
|
26615
26652
|
}
|
|
26616
26653
|
/**
|
|
@@ -26624,8 +26661,9 @@ class KritzelAnchorManager {
|
|
|
26624
26661
|
* @returns Object with x and y coordinates in the line's local SVG space.
|
|
26625
26662
|
*/
|
|
26626
26663
|
lineWorldToLocal(line, worldX, worldY) {
|
|
26627
|
-
const
|
|
26628
|
-
const
|
|
26664
|
+
const safeLineScale = this.getSafeScale(line.scale);
|
|
26665
|
+
const dx = (worldX - line.translateX) * safeLineScale;
|
|
26666
|
+
const dy = (worldY - line.translateY) * safeLineScale;
|
|
26629
26667
|
const cx = line.totalWidth / 2;
|
|
26630
26668
|
const cy = line.totalHeight / 2;
|
|
26631
26669
|
const cos = Math.cos(-line.rotation);
|
|
@@ -26637,6 +26675,30 @@ class KritzelAnchorManager {
|
|
|
26637
26675
|
y: rotatedY + line.y,
|
|
26638
26676
|
};
|
|
26639
26677
|
}
|
|
26678
|
+
getSafeScale(scale) {
|
|
26679
|
+
if (Number.isFinite(scale) && Math.abs(scale) > 1e-6) {
|
|
26680
|
+
return scale;
|
|
26681
|
+
}
|
|
26682
|
+
return 1;
|
|
26683
|
+
}
|
|
26684
|
+
areFiniteNumbers(...values) {
|
|
26685
|
+
return values.every(value => value !== undefined && Number.isFinite(value));
|
|
26686
|
+
}
|
|
26687
|
+
isFiniteAnchorVisualization(visualization) {
|
|
26688
|
+
if (!visualization) {
|
|
26689
|
+
return false;
|
|
26690
|
+
}
|
|
26691
|
+
if (!this.areFiniteNumbers(visualization.edgeX, visualization.edgeY, visualization.centerX, visualization.centerY)) {
|
|
26692
|
+
return false;
|
|
26693
|
+
}
|
|
26694
|
+
if (visualization.pathD && !this.isFinitePathData(visualization.pathD)) {
|
|
26695
|
+
return false;
|
|
26696
|
+
}
|
|
26697
|
+
return true;
|
|
26698
|
+
}
|
|
26699
|
+
isFinitePathData(pathD) {
|
|
26700
|
+
return !pathD.includes('Infinity') && !pathD.includes('NaN');
|
|
26701
|
+
}
|
|
26640
26702
|
/**
|
|
26641
26703
|
* Checks if an object can be used as an anchor target.
|
|
26642
26704
|
* Excludes selection-related objects (SelectionBox, SelectionGroup) and
|
|
@@ -21,6 +21,14 @@ export class KritzelViewport {
|
|
|
21
21
|
startX = 0;
|
|
22
22
|
/** Starting Y position for pan/zoom gestures */
|
|
23
23
|
startY = 0;
|
|
24
|
+
/** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
|
|
25
|
+
static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
|
|
26
|
+
/** Screen X position where the current touch interaction started */
|
|
27
|
+
_touchStartScreenX = 0;
|
|
28
|
+
/** Screen Y position where the current touch interaction started */
|
|
29
|
+
_touchStartScreenY = 0;
|
|
30
|
+
/** Whether the touch movement threshold has been exceeded for cursor broadcasting */
|
|
31
|
+
_touchCursorBroadcastActive = false;
|
|
24
32
|
/**
|
|
25
33
|
* Creates a new KritzelViewport instance and initializes viewport state.
|
|
26
34
|
* Sets up debounced handlers for viewport updates and scaling end events.
|
|
@@ -148,7 +156,13 @@ export class KritzelViewport {
|
|
|
148
156
|
}
|
|
149
157
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
150
158
|
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
159
|
+
if (activePointers.length === 1) {
|
|
160
|
+
this._touchStartScreenX = event.clientX;
|
|
161
|
+
this._touchStartScreenY = event.clientY;
|
|
162
|
+
this._touchCursorBroadcastActive = false;
|
|
163
|
+
}
|
|
151
164
|
if (activePointers.length === 2) {
|
|
165
|
+
this._core.store.state.objects?.clearCursorPosition();
|
|
152
166
|
const currentPath = this._core.store.currentPath;
|
|
153
167
|
if (currentPath) {
|
|
154
168
|
this._core.store.state.objects.remove(obj => obj.id === currentPath.id);
|
|
@@ -209,10 +223,24 @@ export class KritzelViewport {
|
|
|
209
223
|
const hostRect = this._core.store.state.host.getBoundingClientRect();
|
|
210
224
|
const xRelativeToHost = event.clientX - hostRect.left;
|
|
211
225
|
const yRelativeToHost = event.clientY - hostRect.top;
|
|
212
|
-
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
213
|
-
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
214
|
-
this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
215
226
|
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
227
|
+
if (this._core.store.state.isScaling || activePointers.length > 1) {
|
|
228
|
+
this._core.store.state.objects?.clearCursorPosition();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
232
|
+
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
233
|
+
if (!this._touchCursorBroadcastActive) {
|
|
234
|
+
const dx = event.clientX - this._touchStartScreenX;
|
|
235
|
+
const dy = event.clientY - this._touchStartScreenY;
|
|
236
|
+
if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
|
|
237
|
+
this._touchCursorBroadcastActive = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this._touchCursorBroadcastActive) {
|
|
241
|
+
this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
216
244
|
if (activePointers.length === 2) {
|
|
217
245
|
const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
|
|
218
246
|
const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
|
|
@@ -264,6 +292,7 @@ export class KritzelViewport {
|
|
|
264
292
|
}
|
|
265
293
|
}
|
|
266
294
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
295
|
+
this._touchCursorBroadcastActive = false;
|
|
267
296
|
if (this._core.store.state.pointers.size === 0) {
|
|
268
297
|
this._debounceEndScaling();
|
|
269
298
|
}
|
|
@@ -286,16 +286,22 @@ export class KritzelAnchorManager {
|
|
|
286
286
|
if (!KritzelClassHelper.isInstanceOf(selectedObject, 'KritzelLine'))
|
|
287
287
|
return null;
|
|
288
288
|
const line = selectedObject;
|
|
289
|
-
const
|
|
290
|
-
const
|
|
289
|
+
const startAnchorVizRaw = this.computeAnchorVisualization(line, 'start');
|
|
290
|
+
const endAnchorVizRaw = this.computeAnchorVisualization(line, 'end');
|
|
291
|
+
const startAnchorViz = this.isFiniteAnchorVisualization(startAnchorVizRaw) ? startAnchorVizRaw : null;
|
|
292
|
+
const endAnchorViz = this.isFiniteAnchorVisualization(endAnchorVizRaw) ? endAnchorVizRaw : null;
|
|
291
293
|
if (!startAnchorViz && !endAnchorViz)
|
|
292
294
|
return null;
|
|
293
|
-
const scale = this._core.store.state.scale;
|
|
294
|
-
const
|
|
295
|
+
const scale = this.getSafeScale(this._core.store.state.scale);
|
|
296
|
+
const lineScale = this.getSafeScale(line.scale);
|
|
297
|
+
const lineStrokeWidth = line.strokeWidth / lineScale;
|
|
295
298
|
const indicatorStrokeWidth = `${2 / scale}`;
|
|
296
299
|
const dashLength = Math.max(lineStrokeWidth * 2, 4 / scale);
|
|
297
300
|
const dashArray = `${dashLength} ${dashLength}`;
|
|
298
301
|
const indicatorRadius = 8 / scale;
|
|
302
|
+
if (!this.areFiniteNumbers(lineStrokeWidth, dashLength, indicatorRadius)) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
299
305
|
return {
|
|
300
306
|
lineStrokeWidth,
|
|
301
307
|
indicatorStrokeWidth,
|
|
@@ -317,31 +323,39 @@ export class KritzelAnchorManager {
|
|
|
317
323
|
const snapCandidate = this.getSnapCandidate();
|
|
318
324
|
if (!snapCandidate)
|
|
319
325
|
return null;
|
|
320
|
-
|
|
326
|
+
if (!this.areFiniteNumbers(snapCandidate.centerX, snapCandidate.centerY, snapCandidate.lineEndpointX, snapCandidate.lineEndpointY)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const scale = this.getSafeScale(this._core.store.state.scale);
|
|
321
330
|
const indicatorRadius = 8 / scale;
|
|
322
331
|
const indicatorStrokeWidth = `${2 / scale}`;
|
|
323
|
-
const
|
|
324
|
-
?
|
|
325
|
-
:
|
|
326
|
-
const
|
|
332
|
+
const lineStrokeWidthNum = this.areFiniteNumbers(snapCandidate.lineStrokeWidth) && snapCandidate.lineStrokeWidth > 0
|
|
333
|
+
? snapCandidate.lineStrokeWidth
|
|
334
|
+
: (4 / scale);
|
|
335
|
+
const lineStrokeWidth = `${lineStrokeWidthNum}`;
|
|
327
336
|
const dashLength = Math.max(lineStrokeWidthNum * 2, 4 / scale);
|
|
328
337
|
const dashArray = `${dashLength} ${dashLength}`;
|
|
329
338
|
const lineStroke = snapCandidate.lineStroke || '#000000';
|
|
330
|
-
let
|
|
331
|
-
let
|
|
339
|
+
let edgeX = this.areFiniteNumbers(snapCandidate.edgeX) ? snapCandidate.edgeX : undefined;
|
|
340
|
+
let edgeY = this.areFiniteNumbers(snapCandidate.edgeY) ? snapCandidate.edgeY : undefined;
|
|
341
|
+
let solidLineEndX = edgeX;
|
|
342
|
+
let solidLineEndY = edgeY;
|
|
332
343
|
let arrowPoints;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
344
|
+
const arrowOffset = this.areFiniteNumbers(snapCandidate.arrowOffset) && snapCandidate.arrowOffset > 0
|
|
345
|
+
? snapCandidate.arrowOffset
|
|
346
|
+
: undefined;
|
|
347
|
+
if (arrowOffset !== undefined && edgeX !== undefined && edgeY !== undefined) {
|
|
348
|
+
const dx = snapCandidate.lineEndpointX - edgeX;
|
|
349
|
+
const dy = snapCandidate.lineEndpointY - edgeY;
|
|
336
350
|
const length = Math.sqrt(dx * dx + dy * dy);
|
|
337
|
-
if (length >
|
|
338
|
-
solidLineEndX =
|
|
339
|
-
solidLineEndY =
|
|
351
|
+
if (length > arrowOffset) {
|
|
352
|
+
solidLineEndX = edgeX + (dx / length) * arrowOffset;
|
|
353
|
+
solidLineEndY = edgeY + (dy / length) * arrowOffset;
|
|
340
354
|
}
|
|
341
355
|
// Calculate arrow head points
|
|
342
356
|
// Direction from line endpoint to edge (arrow direction)
|
|
343
|
-
const arrowDx =
|
|
344
|
-
const arrowDy =
|
|
357
|
+
const arrowDx = edgeX - snapCandidate.lineEndpointX;
|
|
358
|
+
const arrowDy = edgeY - snapCandidate.lineEndpointY;
|
|
345
359
|
const arrowLengthTotal = Math.sqrt(arrowDx * arrowDx + arrowDy * arrowDy);
|
|
346
360
|
if (arrowLengthTotal > 0) {
|
|
347
361
|
const ux = arrowDx / arrowLengthTotal;
|
|
@@ -350,11 +364,11 @@ export class KritzelAnchorManager {
|
|
|
350
364
|
const px = -uy;
|
|
351
365
|
const py = ux;
|
|
352
366
|
// Arrow dimensions
|
|
353
|
-
const arrowLength =
|
|
367
|
+
const arrowLength = arrowOffset;
|
|
354
368
|
const arrowWidth = arrowLength; // 1:1 ratio
|
|
355
369
|
// Arrow tip at edge
|
|
356
|
-
const tipX =
|
|
357
|
-
const tipY =
|
|
370
|
+
const tipX = edgeX;
|
|
371
|
+
const tipY = edgeY;
|
|
358
372
|
// Arrow base
|
|
359
373
|
const baseX = tipX - ux * arrowLength;
|
|
360
374
|
const baseY = tipY - uy * arrowLength;
|
|
@@ -363,9 +377,31 @@ export class KritzelAnchorManager {
|
|
|
363
377
|
const leftY = baseY + py * arrowWidth / 2;
|
|
364
378
|
const rightX = baseX - px * arrowWidth / 2;
|
|
365
379
|
const rightY = baseY - py * arrowWidth / 2;
|
|
366
|
-
|
|
380
|
+
if (this.areFiniteNumbers(tipX, tipY, leftX, leftY, rightX, rightY)) {
|
|
381
|
+
arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
|
|
382
|
+
}
|
|
367
383
|
}
|
|
368
384
|
}
|
|
385
|
+
if (!this.areFiniteNumbers(indicatorRadius, lineStrokeWidthNum, dashLength)) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const snapLinePath = (() => {
|
|
389
|
+
if (snapCandidate.controlX !== undefined &&
|
|
390
|
+
snapCandidate.controlY !== undefined &&
|
|
391
|
+
snapCandidate.t !== undefined &&
|
|
392
|
+
this.areFiniteNumbers(snapCandidate.controlX, snapCandidate.controlY, snapCandidate.t)) {
|
|
393
|
+
const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
|
|
394
|
+
// Ensure meaningful range
|
|
395
|
+
if (startT >= 1)
|
|
396
|
+
return undefined;
|
|
397
|
+
const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
|
|
398
|
+
if (!this.areFiniteNumbers(segment.start.x, segment.start.y, segment.control.x, segment.control.y, segment.end.x, segment.end.y)) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
|
|
402
|
+
}
|
|
403
|
+
return undefined;
|
|
404
|
+
})();
|
|
369
405
|
return {
|
|
370
406
|
indicatorRadius,
|
|
371
407
|
indicatorStrokeWidth,
|
|
@@ -376,27 +412,15 @@ export class KritzelAnchorManager {
|
|
|
376
412
|
centerY: snapCandidate.centerY,
|
|
377
413
|
lineEndpointX: snapCandidate.lineEndpointX,
|
|
378
414
|
lineEndpointY: snapCandidate.lineEndpointY,
|
|
379
|
-
edgeX
|
|
380
|
-
edgeY
|
|
381
|
-
arrowOffset
|
|
415
|
+
edgeX,
|
|
416
|
+
edgeY,
|
|
417
|
+
arrowOffset,
|
|
382
418
|
arrowStyle: snapCandidate.arrowStyle,
|
|
383
419
|
arrowFill: snapCandidate.arrowFill,
|
|
384
420
|
solidLineEndX,
|
|
385
421
|
solidLineEndY,
|
|
386
422
|
arrowPoints,
|
|
387
|
-
snapLinePath
|
|
388
|
-
if (snapCandidate.controlX !== undefined &&
|
|
389
|
-
snapCandidate.controlY !== undefined &&
|
|
390
|
-
snapCandidate.t !== undefined) {
|
|
391
|
-
const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
|
|
392
|
-
// Ensure meaningful range
|
|
393
|
-
if (startT >= 1)
|
|
394
|
-
return undefined;
|
|
395
|
-
const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
|
|
396
|
-
return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
|
|
397
|
-
}
|
|
398
|
-
return undefined;
|
|
399
|
-
})(),
|
|
423
|
+
snapLinePath,
|
|
400
424
|
};
|
|
401
425
|
}
|
|
402
426
|
// ============================================
|
|
@@ -574,12 +598,19 @@ export class KritzelAnchorManager {
|
|
|
574
598
|
return null;
|
|
575
599
|
const centerX = targetObject.centerX;
|
|
576
600
|
const centerY = targetObject.centerY;
|
|
601
|
+
if (!this.areFiniteNumbers(clipInfo.worldX, clipInfo.worldY, centerX, centerY)) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const pathD = this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined;
|
|
605
|
+
if (pathD && !this.isFinitePathData(pathD)) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
577
608
|
return {
|
|
578
609
|
edgeX: clipInfo.worldX,
|
|
579
610
|
edgeY: clipInfo.worldY,
|
|
580
611
|
centerX,
|
|
581
612
|
centerY,
|
|
582
|
-
pathD
|
|
613
|
+
pathD,
|
|
583
614
|
};
|
|
584
615
|
}
|
|
585
616
|
/**
|
|
@@ -1292,9 +1323,10 @@ export class KritzelAnchorManager {
|
|
|
1292
1323
|
const sin = Math.sin(line.rotation);
|
|
1293
1324
|
const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
|
|
1294
1325
|
const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
|
|
1326
|
+
const safeLineScale = this.getSafeScale(line.scale);
|
|
1295
1327
|
return {
|
|
1296
|
-
x: rotatedX /
|
|
1297
|
-
y: rotatedY /
|
|
1328
|
+
x: rotatedX / safeLineScale + line.translateX,
|
|
1329
|
+
y: rotatedY / safeLineScale + line.translateY,
|
|
1298
1330
|
};
|
|
1299
1331
|
}
|
|
1300
1332
|
/**
|
|
@@ -1308,8 +1340,9 @@ export class KritzelAnchorManager {
|
|
|
1308
1340
|
* @returns Object with x and y coordinates in the line's local SVG space.
|
|
1309
1341
|
*/
|
|
1310
1342
|
lineWorldToLocal(line, worldX, worldY) {
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1343
|
+
const safeLineScale = this.getSafeScale(line.scale);
|
|
1344
|
+
const dx = (worldX - line.translateX) * safeLineScale;
|
|
1345
|
+
const dy = (worldY - line.translateY) * safeLineScale;
|
|
1313
1346
|
const cx = line.totalWidth / 2;
|
|
1314
1347
|
const cy = line.totalHeight / 2;
|
|
1315
1348
|
const cos = Math.cos(-line.rotation);
|
|
@@ -1321,6 +1354,30 @@ export class KritzelAnchorManager {
|
|
|
1321
1354
|
y: rotatedY + line.y,
|
|
1322
1355
|
};
|
|
1323
1356
|
}
|
|
1357
|
+
getSafeScale(scale) {
|
|
1358
|
+
if (Number.isFinite(scale) && Math.abs(scale) > 1e-6) {
|
|
1359
|
+
return scale;
|
|
1360
|
+
}
|
|
1361
|
+
return 1;
|
|
1362
|
+
}
|
|
1363
|
+
areFiniteNumbers(...values) {
|
|
1364
|
+
return values.every(value => value !== undefined && Number.isFinite(value));
|
|
1365
|
+
}
|
|
1366
|
+
isFiniteAnchorVisualization(visualization) {
|
|
1367
|
+
if (!visualization) {
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
if (!this.areFiniteNumbers(visualization.edgeX, visualization.edgeY, visualization.centerX, visualization.centerY)) {
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
if (visualization.pathD && !this.isFinitePathData(visualization.pathD)) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
return true;
|
|
1377
|
+
}
|
|
1378
|
+
isFinitePathData(pathD) {
|
|
1379
|
+
return !pathD.includes('Infinity') && !pathD.includes('NaN');
|
|
1380
|
+
}
|
|
1324
1381
|
/**
|
|
1325
1382
|
* Checks if an object can be used as an anchor target.
|
|
1326
1383
|
* Excludes selection-related objects (SelectionBox, SelectionGroup) and
|
|
@@ -6,6 +6,7 @@ import * as decoding from "lib0/decoding";
|
|
|
6
6
|
* This is a lightweight alternative to y-webrtc for browser-tab-only sync
|
|
7
7
|
*/
|
|
8
8
|
export class BroadcastSyncProvider {
|
|
9
|
+
type = 'local';
|
|
9
10
|
doc;
|
|
10
11
|
channel;
|
|
11
12
|
_synced = false;
|
|
@@ -87,6 +88,10 @@ export class BroadcastSyncProvider {
|
|
|
87
88
|
disconnect() {
|
|
88
89
|
// BroadcastChannel doesn't have explicit disconnect
|
|
89
90
|
}
|
|
91
|
+
async reconnect() {
|
|
92
|
+
this.disconnect();
|
|
93
|
+
return this.connect();
|
|
94
|
+
}
|
|
90
95
|
destroy() {
|
|
91
96
|
this.doc.off('update', this.handleDocUpdate);
|
|
92
97
|
this.channel.close();
|