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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{p as t,H as e,h as s,d as r,t as o}from"./p-pebXO4LU.js";import{K as n,a as i}from"./p-DXpYcAnT.js";const a=t(class extends e{constructor(t){super(),!1!==t&&this.__registerHost(),this.__attachShadow()}core;showEdgeIndicators=!0;edgeIndicatorPadding=8;remoteCursors=new Map;objectVersion=0;cleanupIntervalId;objectChangeRafId=null;componentDidLoad(){this.core.store.state.objects?.onAwarenessChange((t=>{this.handleAwarenessChange(t)})),this.core.store.state.objects?.onObjectsChange((()=>{this.handleRemoteObjectChange()})),this.cleanupIntervalId=setInterval((()=>{this.cleanupStaleCursors()}),3e3)}disconnectedCallback(){this.cleanupIntervalId&&clearInterval(this.cleanupIntervalId),null!==this.objectChangeRafId&&cancelAnimationFrame(this.objectChangeRafId)}handleAwarenessChange(t){const e=this.core.store.state.objects?.localClientId,s=Date.now(),r=new Map(this.remoteCursors),o=new Set;t.forEach(((t,n)=>{if(n===e)return;if(!t.user)return;o.add(n);const i=t.user,a=t.cursor,l=t.activeObjectId||null,c=t.selectionBox||null,d=r.get(n);r.set(n,{clientId:n,user:i,cursor:a,activeObjectId:l,selectionBox:c,lastUpdated:s,lastCursorMove:!d||!d.cursor!=!a||a&&d.cursor&&(a.x!==d.cursor.x||a.y!==d.cursor.y)?s:d?.lastCursorMove??s})}));for(const t of r.keys())o.has(t)||r.delete(t);this.remoteCursors=r}cleanupStaleCursors(){const t=Date.now();let e=!1;const s=new Map(this.remoteCursors);for(const[r,o]of s)t-o.lastUpdated>3e4&&(s.delete(r),e=!0);e&&(this.remoteCursors=s)}isStale(t){return Date.now()-t.lastCursorMove>1e4}hasActiveDrawingCursors(){for(const t of this.remoteCursors.values())if(t.activeObjectId)return!0;return!1}handleRemoteObjectChange(){this.hasActiveDrawingCursors()&&null===this.objectChangeRafId&&(this.objectChangeRafId=requestAnimationFrame((()=>{this.objectChangeRafId=null,this.objectVersion++})))}getActiveObjectTip(t){const e=this.core.store.state.objects?.findById(t);if(!e)return null;if(e instanceof n&&!e.isCompleted){const t=e.points[e.points.length-1];return t?{x:(t[0]-e.x)/e.scale+e.translateX,y:(t[1]-e.y)/e.scale+e.translateY}:null}return e instanceof i&&!e.isCompleted?{x:(e.endX-e.x)/e.scale+e.translateX,y:(e.endY-e.y)/e.scale+e.translateY}:null}worldToScreen(t,e){const{scale:s,translateX:r,translateY:o}=this.core.store.state;return{x:t*s+r,y:e*s+o}}isInViewport(t,e){const{viewportWidth:s,viewportHeight:r}=this.core.store.state;return t>=0&&t<=s&&e>=0&&e<=r}clampToEdge(t,e){const{viewportWidth:s,viewportHeight:r}=this.core.store.state,o=this.edgeIndicatorPadding,n=Math.max(o,Math.min(s-o,t)),i=Math.max(o,Math.min(r-o,e)),a=n-o,l=s-o-n,c=i-o;let d="top";const h=Math.min(a,l,c,r-o-i);return d=h===a?"left":h===l?"right":h===c?"top":"bottom",{x:n,y:i,angle:Math.atan2(e-i,t-n),edge:d}}getUserDisplayName(t){return t.displayName?t.displayName:t.firstName||t.lastName?[t.firstName,t.lastName].filter(Boolean).join(" "):"Unknown"}render(){const t=Array.from(this.remoteCursors.values());return s(r,{key:"4dd962322c7e955b9038c55cb10f8ffda1e0b246"},t.map((t=>{if(!t.cursor)return null;let e,s=!1;if(t.activeObjectId){const r=this.getActiveObjectTip(t.activeObjectId);r?(s=!0,e=this.worldToScreen(r.x,r.y)):e=this.worldToScreen(t.cursor.x,t.cursor.y)}else e=this.worldToScreen(t.cursor.x,t.cursor.y);const r=this.isInViewport(e.x,e.y),o=this.isStale(t),n=t.user.color||"#6B7280";return r?this.renderCursor(t,e.x,e.y,n,o,s):this.showEdgeIndicators?this.renderEdgeIndicator(t,e.x,e.y,n,o,s):null})),t.map((t=>{if(!t.selectionBox)return null;const e=t.user.color||"#6B7280",r=t.selectionBox,o=this.worldToScreen(r.x,r.y),{scale:n}=this.core.store.state;return s("div",{key:`selection-box-${t.clientId}`,class:"remote-selection-box",style:{transform:`translate(${o.x}px, ${o.y}px)`,width:r.width*n+"px",height:r.height*n+"px",backgroundColor:`color-mix(in srgb, ${e} 20%, transparent)`,borderColor:`color-mix(in srgb, ${e} 50%, transparent)`}})})))}renderCursor(t,e,r,o,n,i){return s("div",{key:`cursor-${t.clientId}`,class:{"awareness-cursor":!0,stale:n,"tracking-object":i},style:{transform:`translate(${e}px, ${r}px)`}},s("svg",{class:"cursor-arrow",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},s("path",{d:"M5 3L19 12L12 13L9 20L5 3Z",fill:o,stroke:"#ffffff","stroke-width":"1.5","stroke-linejoin":"round"})),s("span",{class:"cursor-label",style:{backgroundColor:o}},this.getUserDisplayName(t.user)))}renderEdgeIndicator(t,e,r,o,n,i){const a=this.clampToEdge(e,r),l=180*a.angle/Math.PI+90;let c=0,d=0;"left"===a.edge?c=20:"right"===a.edge?c=-20:"top"===a.edge?d=20:"bottom"===a.edge&&(d=-20);const h=this.getUserDisplayName(t.user);return s("div",{key:`edge-${t.clientId}`,class:{"edge-indicator":!0,stale:n,"tracking-object":i},style:{transform:`translate(${a.x}px, ${a.y}px)`}},s("svg",{class:"edge-arrow",width:"16",height:"16",viewBox:"0 0 16 16",style:{transform:`rotate(${l}deg)`}},s("path",{d:"M8 1L14 13H2L8 1Z",fill:o,stroke:"#ffffff","stroke-width":"1.5","stroke-linejoin":"round"})),s("span",{class:"edge-label",style:{backgroundColor:o,transform:`translate(${c}px, ${d}px)`}},h))}static get style(){return":host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}"}},[513,"kritzel-awareness-cursors",{core:[16],showEdgeIndicators:[4,"show-edge-indicators"],edgeIndicatorPadding:[2,"edge-indicator-padding"],remoteCursors:[32],objectVersion:[32]}]);function l(){"undefined"!=typeof customElements&&["kritzel-awareness-cursors"].forEach((t=>{"kritzel-awareness-cursors"===t&&(customElements.get(o(t))||customElements.define(o(t),a))}))}export{a as K,l as d}
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-
|
|
2
|
-
export { A as APP_STATE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, w as CURRENT_WORKSPACE_SCHEMA_VERSION, D as DEFAULT_BRUSH_CONFIG, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, I as IndexedDBSyncProvider, v as KritzelAlignment, p as KritzelAnchorManager, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, q as KritzelThemeManager, o as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, y as WORKSPACE_MIGRATIONS, u as darkTheme, t as lightTheme, x as runMigrations } from './workspace.migrations-
|
|
1
|
+
import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-D48_Bqvh.js';
|
|
2
|
+
export { A as APP_STATE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, w as CURRENT_WORKSPACE_SCHEMA_VERSION, D as DEFAULT_BRUSH_CONFIG, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, I as IndexedDBSyncProvider, v as KritzelAlignment, p as KritzelAnchorManager, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, q as KritzelThemeManager, o as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, y as WORKSPACE_MIGRATIONS, u as darkTheme, t as lightTheme, x as runMigrations } from './workspace.migrations-D48_Bqvh.js';
|
|
3
3
|
import * as Y from 'yjs';
|
|
4
4
|
import { WebsocketProvider } from 'y-websocket';
|
|
5
5
|
import 'y-indexeddb';
|
|
@@ -339,6 +339,7 @@ const readVarUint = decoder => {
|
|
|
339
339
|
* This is a lightweight alternative to y-webrtc for browser-tab-only sync
|
|
340
340
|
*/
|
|
341
341
|
class BroadcastSyncProvider {
|
|
342
|
+
type = 'local';
|
|
342
343
|
doc;
|
|
343
344
|
channel;
|
|
344
345
|
_synced = false;
|
|
@@ -420,6 +421,10 @@ class BroadcastSyncProvider {
|
|
|
420
421
|
disconnect() {
|
|
421
422
|
// BroadcastChannel doesn't have explicit disconnect
|
|
422
423
|
}
|
|
424
|
+
async reconnect() {
|
|
425
|
+
this.disconnect();
|
|
426
|
+
return this.connect();
|
|
427
|
+
}
|
|
423
428
|
destroy() {
|
|
424
429
|
this.doc.off('update', this.handleDocUpdate);
|
|
425
430
|
this.channel.close();
|
|
@@ -430,6 +435,7 @@ class BroadcastSyncProvider {
|
|
|
430
435
|
* WebSocket sync provider for real-time collaboration
|
|
431
436
|
*/
|
|
432
437
|
class WebSocketSyncProvider {
|
|
438
|
+
type = 'network';
|
|
433
439
|
provider;
|
|
434
440
|
isConnected = false;
|
|
435
441
|
_quiet = false;
|
|
@@ -518,6 +524,10 @@ class WebSocketSyncProvider {
|
|
|
518
524
|
}
|
|
519
525
|
this.isConnected = false;
|
|
520
526
|
}
|
|
527
|
+
async reconnect() {
|
|
528
|
+
this.disconnect();
|
|
529
|
+
return this.connect();
|
|
530
|
+
}
|
|
521
531
|
destroy() {
|
|
522
532
|
if (this.provider) {
|
|
523
533
|
this.provider.destroy();
|
|
@@ -531,6 +541,7 @@ class WebSocketSyncProvider {
|
|
|
531
541
|
* Supports multiplexing - multiple documents can share the same WebSocket connection
|
|
532
542
|
*/
|
|
533
543
|
class HocuspocusSyncProvider {
|
|
544
|
+
type = 'network';
|
|
534
545
|
provider;
|
|
535
546
|
isConnected = false;
|
|
536
547
|
isSynced = false;
|
|
@@ -538,16 +549,87 @@ class HocuspocusSyncProvider {
|
|
|
538
549
|
isDestroyed = false;
|
|
539
550
|
connectTimeout = null;
|
|
540
551
|
pendingConnectReject = null;
|
|
552
|
+
connectionTimeoutMs;
|
|
553
|
+
_connectionStatus = 'disconnected';
|
|
554
|
+
visibilityHandler = null;
|
|
555
|
+
onlineHandler = null;
|
|
541
556
|
get awareness() {
|
|
542
557
|
return this.provider.awareness;
|
|
543
558
|
}
|
|
559
|
+
get connectionStatus() {
|
|
560
|
+
return this._connectionStatus;
|
|
561
|
+
}
|
|
544
562
|
// Static shared WebSocket instance for multiplexing
|
|
545
563
|
static sharedWebSocketProvider = null;
|
|
546
564
|
constructor(docName, doc, options) {
|
|
547
565
|
const name = options?.name || docName;
|
|
548
566
|
const url = options?.url || 'ws://localhost:1234';
|
|
567
|
+
this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
|
|
549
568
|
// Use provided websocketProvider or the static shared one
|
|
550
569
|
const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
570
|
+
// Build reconnect config from options
|
|
571
|
+
const reconnectConfig = {};
|
|
572
|
+
if (options?.delay !== undefined)
|
|
573
|
+
reconnectConfig.delay = options.delay;
|
|
574
|
+
if (options?.factor !== undefined)
|
|
575
|
+
reconnectConfig.factor = options.factor;
|
|
576
|
+
if (options?.maxAttempts !== undefined)
|
|
577
|
+
reconnectConfig.maxAttempts = options.maxAttempts;
|
|
578
|
+
if (options?.minDelay !== undefined)
|
|
579
|
+
reconnectConfig.minDelay = options.minDelay;
|
|
580
|
+
if (options?.maxDelay !== undefined)
|
|
581
|
+
reconnectConfig.maxDelay = options.maxDelay;
|
|
582
|
+
const onConnect = () => {
|
|
583
|
+
if (this.isDestroyed) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
this.isConnected = true;
|
|
587
|
+
this._connectionStatus = 'connected';
|
|
588
|
+
if (!options?.quiet) {
|
|
589
|
+
console.info(`Hocuspocus connected: ${name}`);
|
|
590
|
+
}
|
|
591
|
+
if (options?.onConnect) {
|
|
592
|
+
options.onConnect();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
const onDisconnect = () => {
|
|
596
|
+
if (this.isDestroyed) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
this.isConnected = false;
|
|
600
|
+
this.isSynced = false;
|
|
601
|
+
this._connectionStatus = 'disconnected';
|
|
602
|
+
if (!options?.quiet) {
|
|
603
|
+
console.info(`Hocuspocus disconnected: ${name}`);
|
|
604
|
+
}
|
|
605
|
+
if (options?.onDisconnect) {
|
|
606
|
+
options.onDisconnect();
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
const onSynced = () => {
|
|
610
|
+
if (this.isDestroyed) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
this.isSynced = true;
|
|
614
|
+
this._connectionStatus = 'synced';
|
|
615
|
+
if (!options?.quiet) {
|
|
616
|
+
console.info(`Hocuspocus synced: ${name}`);
|
|
617
|
+
}
|
|
618
|
+
if (options?.onSynced) {
|
|
619
|
+
options.onSynced();
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
const onStatus = (data) => {
|
|
623
|
+
if (this.isDestroyed) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (data.status === 'connecting') {
|
|
627
|
+
this._connectionStatus = 'connecting';
|
|
628
|
+
}
|
|
629
|
+
if (options?.onStatus) {
|
|
630
|
+
options.onStatus(data);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
551
633
|
if (websocketProvider) {
|
|
552
634
|
// Multiplexing mode - use shared WebSocket connection
|
|
553
635
|
this.usesSharedSocket = true;
|
|
@@ -556,48 +638,11 @@ class HocuspocusSyncProvider {
|
|
|
556
638
|
name,
|
|
557
639
|
document: doc,
|
|
558
640
|
token: options?.token || null,
|
|
559
|
-
onStatus
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
onConnect: () => {
|
|
565
|
-
if (this.isConnected || this.isDestroyed) {
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
this.isConnected = true;
|
|
569
|
-
if (!options?.quiet) {
|
|
570
|
-
console.info(`Hocuspocus connected: ${name}`);
|
|
571
|
-
}
|
|
572
|
-
if (options?.onConnect) {
|
|
573
|
-
options.onConnect();
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
onDisconnect: () => {
|
|
577
|
-
if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
this.isConnected = false;
|
|
581
|
-
this.isSynced = false;
|
|
582
|
-
if (!options?.quiet) {
|
|
583
|
-
console.info(`Hocuspocus disconnected: ${name}`);
|
|
584
|
-
}
|
|
585
|
-
if (options?.onDisconnect) {
|
|
586
|
-
options.onDisconnect();
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
onSynced: () => {
|
|
590
|
-
if (this.isSynced || this.isDestroyed) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
this.isSynced = true;
|
|
594
|
-
if (!options?.quiet) {
|
|
595
|
-
console.info(`Hocuspocus synced: ${name}`);
|
|
596
|
-
}
|
|
597
|
-
if (options?.onSynced) {
|
|
598
|
-
options.onSynced();
|
|
599
|
-
}
|
|
600
|
-
},
|
|
641
|
+
onStatus,
|
|
642
|
+
onConnect,
|
|
643
|
+
onDisconnect,
|
|
644
|
+
onSynced,
|
|
645
|
+
...reconnectConfig,
|
|
601
646
|
};
|
|
602
647
|
// Add optional settings
|
|
603
648
|
if (options?.forceSyncInterval !== undefined) {
|
|
@@ -622,48 +667,11 @@ class HocuspocusSyncProvider {
|
|
|
622
667
|
document: doc,
|
|
623
668
|
token: options?.token || null,
|
|
624
669
|
autoConnect: false,
|
|
625
|
-
onStatus
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
onConnect: () => {
|
|
631
|
-
if (this.isConnected || this.isDestroyed) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
this.isConnected = true;
|
|
635
|
-
if (!options?.quiet) {
|
|
636
|
-
console.info(`Hocuspocus connected: ${name}`);
|
|
637
|
-
}
|
|
638
|
-
if (options?.onConnect) {
|
|
639
|
-
options.onConnect();
|
|
640
|
-
}
|
|
641
|
-
},
|
|
642
|
-
onDisconnect: () => {
|
|
643
|
-
if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
this.isConnected = false;
|
|
647
|
-
this.isSynced = false;
|
|
648
|
-
if (!options?.quiet) {
|
|
649
|
-
console.info(`Hocuspocus disconnected: ${name}`);
|
|
650
|
-
}
|
|
651
|
-
if (options?.onDisconnect) {
|
|
652
|
-
options.onDisconnect();
|
|
653
|
-
}
|
|
654
|
-
},
|
|
655
|
-
onSynced: () => {
|
|
656
|
-
if (this.isSynced || this.isDestroyed) {
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
this.isSynced = true;
|
|
660
|
-
if (!options?.quiet) {
|
|
661
|
-
console.info(`Hocuspocus synced: ${name}`);
|
|
662
|
-
}
|
|
663
|
-
if (options?.onSynced) {
|
|
664
|
-
options.onSynced();
|
|
665
|
-
}
|
|
666
|
-
},
|
|
670
|
+
onStatus,
|
|
671
|
+
onConnect,
|
|
672
|
+
onDisconnect,
|
|
673
|
+
onSynced,
|
|
674
|
+
...reconnectConfig,
|
|
667
675
|
};
|
|
668
676
|
// Add optional settings
|
|
669
677
|
if (options?.forceSyncInterval !== undefined) {
|
|
@@ -680,6 +688,35 @@ class HocuspocusSyncProvider {
|
|
|
680
688
|
console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
|
|
681
689
|
}
|
|
682
690
|
}
|
|
691
|
+
this.setupBrowserEventListeners();
|
|
692
|
+
}
|
|
693
|
+
setupBrowserEventListeners() {
|
|
694
|
+
if (typeof document !== 'undefined') {
|
|
695
|
+
this.visibilityHandler = () => {
|
|
696
|
+
if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
|
|
697
|
+
this.provider.connect();
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
document.addEventListener('visibilitychange', this.visibilityHandler);
|
|
701
|
+
}
|
|
702
|
+
if (typeof window !== 'undefined') {
|
|
703
|
+
this.onlineHandler = () => {
|
|
704
|
+
if (!this.isConnected && !this.isDestroyed) {
|
|
705
|
+
this.provider.connect();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
window.addEventListener('online', this.onlineHandler);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
removeBrowserEventListeners() {
|
|
712
|
+
if (this.visibilityHandler && typeof document !== 'undefined') {
|
|
713
|
+
document.removeEventListener('visibilitychange', this.visibilityHandler);
|
|
714
|
+
this.visibilityHandler = null;
|
|
715
|
+
}
|
|
716
|
+
if (this.onlineHandler && typeof window !== 'undefined') {
|
|
717
|
+
window.removeEventListener('online', this.onlineHandler);
|
|
718
|
+
this.onlineHandler = null;
|
|
719
|
+
}
|
|
683
720
|
}
|
|
684
721
|
/**
|
|
685
722
|
* Create a shared WebSocket connection for multiplexing
|
|
@@ -742,6 +779,7 @@ class HocuspocusSyncProvider {
|
|
|
742
779
|
if (this.isSynced || this.isDestroyed) {
|
|
743
780
|
return;
|
|
744
781
|
}
|
|
782
|
+
this._connectionStatus = 'connecting';
|
|
745
783
|
return new Promise((resolve, reject) => {
|
|
746
784
|
// Store reject function so we can cancel the connection if destroyed
|
|
747
785
|
this.pendingConnectReject = reject;
|
|
@@ -749,7 +787,7 @@ class HocuspocusSyncProvider {
|
|
|
749
787
|
this.pendingConnectReject = null;
|
|
750
788
|
this.connectTimeout = null;
|
|
751
789
|
reject(new Error('Hocuspocus connection timeout'));
|
|
752
|
-
},
|
|
790
|
+
}, this.connectionTimeoutMs);
|
|
753
791
|
const syncHandler = () => {
|
|
754
792
|
if (this.connectTimeout) {
|
|
755
793
|
clearTimeout(this.connectTimeout);
|
|
@@ -779,6 +817,10 @@ class HocuspocusSyncProvider {
|
|
|
779
817
|
}
|
|
780
818
|
});
|
|
781
819
|
}
|
|
820
|
+
async reconnect() {
|
|
821
|
+
this.disconnect();
|
|
822
|
+
return this.connect();
|
|
823
|
+
}
|
|
782
824
|
disconnect() {
|
|
783
825
|
// Cancel any pending connection attempt
|
|
784
826
|
if (this.connectTimeout) {
|
|
@@ -799,6 +841,7 @@ class HocuspocusSyncProvider {
|
|
|
799
841
|
}
|
|
800
842
|
this.isConnected = false;
|
|
801
843
|
this.isSynced = false;
|
|
844
|
+
this._connectionStatus = 'disconnected';
|
|
802
845
|
}
|
|
803
846
|
destroy() {
|
|
804
847
|
// Mark as destroyed first to prevent any callbacks from doing work
|
|
@@ -811,11 +854,13 @@ class HocuspocusSyncProvider {
|
|
|
811
854
|
if (this.pendingConnectReject) {
|
|
812
855
|
this.pendingConnectReject = null; // Don't reject, just abandon the promise
|
|
813
856
|
}
|
|
857
|
+
this.removeBrowserEventListeners();
|
|
814
858
|
if (this.provider) {
|
|
815
859
|
this.provider.destroy();
|
|
816
860
|
}
|
|
817
861
|
this.isConnected = false;
|
|
818
862
|
this.isSynced = false;
|
|
863
|
+
this._connectionStatus = 'disconnected';
|
|
819
864
|
}
|
|
820
865
|
}
|
|
821
866
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as registerInstance, h, H as Host, c as createEvent, g as getElement } from './index-MV-81ybv.js';
|
|
2
|
-
import { b as KritzelPath, d as KritzelLine, z as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, B as KritzelDevicesHelper, E as KritzelMouseButton, F as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, v as KritzelAlignment, I as IndexedDBSyncProvider, G as KritzelSelectionGroup, J as KritzelSelectionBox, L as KritzelIconRegistry, M as KritzelKeyboardHelper, N as KritzelBaseHandler, O as KritzelToolRegistry, P as KritzelBaseObject, o as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, x as runMigrations, w as CURRENT_WORKSPACE_SCHEMA_VERSION, y as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, A as APP_STATE_MIGRATIONS, Q as ObjectHelper, m as KritzelCursorHelper, p as KritzelAnchorManager, q as KritzelThemeManager, R as KritzelClassHelper, T as KritzelEventHelper, U as KritzelBaseTool, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-
|
|
2
|
+
import { b as KritzelPath, d as KritzelLine, z as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, B as KritzelDevicesHelper, E as KritzelMouseButton, F as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, v as KritzelAlignment, I as IndexedDBSyncProvider, G as KritzelSelectionGroup, J as KritzelSelectionBox, L as KritzelIconRegistry, M as KritzelKeyboardHelper, N as KritzelBaseHandler, O as KritzelToolRegistry, P as KritzelBaseObject, o as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, x as runMigrations, w as CURRENT_WORKSPACE_SCHEMA_VERSION, y as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, A as APP_STATE_MIGRATIONS, Q as ObjectHelper, m as KritzelCursorHelper, p as KritzelAnchorManager, q as KritzelThemeManager, R as KritzelClassHelper, T as KritzelEventHelper, U as KritzelBaseTool, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-D48_Bqvh.js';
|
|
3
3
|
import * as Y from 'yjs';
|
|
4
4
|
import 'y-websocket';
|
|
5
5
|
import 'y-indexeddb';
|
|
@@ -154,7 +154,7 @@ const KritzelAvatar = class {
|
|
|
154
154
|
};
|
|
155
155
|
KritzelAvatar.style = kritzelAvatarCss();
|
|
156
156
|
|
|
157
|
-
const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0
|
|
157
|
+
const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}`;
|
|
158
158
|
|
|
159
159
|
const STALE_THRESHOLD_MS = 10_000;
|
|
160
160
|
const REMOVE_THRESHOLD_MS = 30_000;
|
|
@@ -205,6 +205,10 @@ const KritzelAwarenessCursors = class {
|
|
|
205
205
|
const cursor = state.cursor;
|
|
206
206
|
const activeObjectId = state.activeObjectId || null;
|
|
207
207
|
const selectionBox = state.selectionBox || null;
|
|
208
|
+
const existing = updated.get(clientId);
|
|
209
|
+
const cursorMoved = !existing ||
|
|
210
|
+
!existing.cursor !== !cursor ||
|
|
211
|
+
(cursor && existing.cursor && (cursor.x !== existing.cursor.x || cursor.y !== existing.cursor.y));
|
|
208
212
|
updated.set(clientId, {
|
|
209
213
|
clientId,
|
|
210
214
|
user,
|
|
@@ -212,6 +216,7 @@ const KritzelAwarenessCursors = class {
|
|
|
212
216
|
activeObjectId,
|
|
213
217
|
selectionBox,
|
|
214
218
|
lastUpdated: now,
|
|
219
|
+
lastCursorMove: cursorMoved ? now : (existing?.lastCursorMove ?? now),
|
|
215
220
|
});
|
|
216
221
|
});
|
|
217
222
|
// Remove cursors for disconnected clients
|
|
@@ -237,7 +242,7 @@ const KritzelAwarenessCursors = class {
|
|
|
237
242
|
}
|
|
238
243
|
}
|
|
239
244
|
isStale(cursor) {
|
|
240
|
-
return Date.now() - cursor.
|
|
245
|
+
return Date.now() - cursor.lastCursorMove > STALE_THRESHOLD_MS;
|
|
241
246
|
}
|
|
242
247
|
hasActiveDrawingCursors() {
|
|
243
248
|
for (const cursor of this.remoteCursors.values()) {
|
|
@@ -326,7 +331,7 @@ const KritzelAwarenessCursors = class {
|
|
|
326
331
|
}
|
|
327
332
|
render() {
|
|
328
333
|
const cursors = Array.from(this.remoteCursors.values());
|
|
329
|
-
return (h(Host, { key: '
|
|
334
|
+
return (h(Host, { key: '4dd962322c7e955b9038c55cb10f8ffda1e0b246' }, cursors.map(remoteCursor => {
|
|
330
335
|
if (!remoteCursor.cursor)
|
|
331
336
|
return null;
|
|
332
337
|
// When a remote user is actively drawing, derive cursor position from
|
|
@@ -19818,6 +19823,14 @@ class KritzelViewport {
|
|
|
19818
19823
|
startX = 0;
|
|
19819
19824
|
/** Starting Y position for pan/zoom gestures */
|
|
19820
19825
|
startY = 0;
|
|
19826
|
+
/** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
|
|
19827
|
+
static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
|
|
19828
|
+
/** Screen X position where the current touch interaction started */
|
|
19829
|
+
_touchStartScreenX = 0;
|
|
19830
|
+
/** Screen Y position where the current touch interaction started */
|
|
19831
|
+
_touchStartScreenY = 0;
|
|
19832
|
+
/** Whether the touch movement threshold has been exceeded for cursor broadcasting */
|
|
19833
|
+
_touchCursorBroadcastActive = false;
|
|
19821
19834
|
/**
|
|
19822
19835
|
* Creates a new KritzelViewport instance and initializes viewport state.
|
|
19823
19836
|
* Sets up debounced handlers for viewport updates and scaling end events.
|
|
@@ -19945,7 +19958,13 @@ class KritzelViewport {
|
|
|
19945
19958
|
}
|
|
19946
19959
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
19947
19960
|
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
19961
|
+
if (activePointers.length === 1) {
|
|
19962
|
+
this._touchStartScreenX = event.clientX;
|
|
19963
|
+
this._touchStartScreenY = event.clientY;
|
|
19964
|
+
this._touchCursorBroadcastActive = false;
|
|
19965
|
+
}
|
|
19948
19966
|
if (activePointers.length === 2) {
|
|
19967
|
+
this._core.store.state.objects?.clearCursorPosition();
|
|
19949
19968
|
const currentPath = this._core.store.currentPath;
|
|
19950
19969
|
if (currentPath) {
|
|
19951
19970
|
this._core.store.state.objects.remove(obj => obj.id === currentPath.id);
|
|
@@ -20006,10 +20025,24 @@ class KritzelViewport {
|
|
|
20006
20025
|
const hostRect = this._core.store.state.host.getBoundingClientRect();
|
|
20007
20026
|
const xRelativeToHost = event.clientX - hostRect.left;
|
|
20008
20027
|
const yRelativeToHost = event.clientY - hostRect.top;
|
|
20009
|
-
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
20010
|
-
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
20011
|
-
this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
20012
20028
|
const activePointers = Array.from(this._core.store.state.pointers.values());
|
|
20029
|
+
if (this._core.store.state.isScaling || activePointers.length > 1) {
|
|
20030
|
+
this._core.store.state.objects?.clearCursorPosition();
|
|
20031
|
+
}
|
|
20032
|
+
else {
|
|
20033
|
+
this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
|
|
20034
|
+
this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
|
|
20035
|
+
if (!this._touchCursorBroadcastActive) {
|
|
20036
|
+
const dx = event.clientX - this._touchStartScreenX;
|
|
20037
|
+
const dy = event.clientY - this._touchStartScreenY;
|
|
20038
|
+
if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
|
|
20039
|
+
this._touchCursorBroadcastActive = true;
|
|
20040
|
+
}
|
|
20041
|
+
}
|
|
20042
|
+
if (this._touchCursorBroadcastActive) {
|
|
20043
|
+
this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
|
|
20044
|
+
}
|
|
20045
|
+
}
|
|
20013
20046
|
if (activePointers.length === 2) {
|
|
20014
20047
|
const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
|
|
20015
20048
|
const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
|
|
@@ -20061,6 +20094,7 @@ class KritzelViewport {
|
|
|
20061
20094
|
}
|
|
20062
20095
|
}
|
|
20063
20096
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
20097
|
+
this._touchCursorBroadcastActive = false;
|
|
20064
20098
|
if (this._core.store.state.pointers.size === 0) {
|
|
20065
20099
|
this._debounceEndScaling();
|
|
20066
20100
|
}
|
|
@@ -21340,6 +21374,42 @@ class KritzelObjectMap {
|
|
|
21340
21374
|
}
|
|
21341
21375
|
this._awareness.setLocalStateField('selectionBox', null);
|
|
21342
21376
|
}
|
|
21377
|
+
/**
|
|
21378
|
+
* Removes selection groups whose owner is no longer present in awareness.
|
|
21379
|
+
* Called when remote clients disconnect to prevent orphaned selection groups
|
|
21380
|
+
* from persisting in the workspace state.
|
|
21381
|
+
*/
|
|
21382
|
+
removeOrphanedSelectionGroups() {
|
|
21383
|
+
if (!this._awareness) {
|
|
21384
|
+
return;
|
|
21385
|
+
}
|
|
21386
|
+
const states = this._awareness.getStates();
|
|
21387
|
+
const activeUserIds = new Set();
|
|
21388
|
+
states.forEach(state => {
|
|
21389
|
+
const userId = state.user?.id;
|
|
21390
|
+
if (userId) {
|
|
21391
|
+
activeUserIds.add(userId);
|
|
21392
|
+
}
|
|
21393
|
+
});
|
|
21394
|
+
const localUserId = this._core?.user?.id;
|
|
21395
|
+
const orphanedGroups = this.quadtree.filter(o => o instanceof KritzelSelectionGroup
|
|
21396
|
+
&& o.userId != null
|
|
21397
|
+
&& o.userId !== localUserId
|
|
21398
|
+
&& !activeUserIds.has(o.userId));
|
|
21399
|
+
for (const group of orphanedGroups) {
|
|
21400
|
+
this.quadtree.remove(o => o.id === group.id);
|
|
21401
|
+
this._idMap.delete(group.id);
|
|
21402
|
+
if (this._objectsMap) {
|
|
21403
|
+
this._ydoc.transact(() => {
|
|
21404
|
+
this._objectsMap.delete(group.id);
|
|
21405
|
+
}, 'local');
|
|
21406
|
+
}
|
|
21407
|
+
}
|
|
21408
|
+
if (orphanedGroups.length > 0) {
|
|
21409
|
+
this._core?.store.invalidateSelectionCache();
|
|
21410
|
+
this._core?.rerender();
|
|
21411
|
+
}
|
|
21412
|
+
}
|
|
21343
21413
|
/**
|
|
21344
21414
|
* Registers a callback to be invoked when the awareness state changes.
|
|
21345
21415
|
* The callback receives the full awareness states map.
|
|
@@ -21473,16 +21543,28 @@ class KritzelObjectMap {
|
|
|
21473
21543
|
this.handleObjectsChange(event);
|
|
21474
21544
|
};
|
|
21475
21545
|
this._objectsMap.observe(this._objectsObserver);
|
|
21476
|
-
//
|
|
21477
|
-
|
|
21478
|
-
|
|
21546
|
+
// Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
|
|
21547
|
+
// Local providers are awaited so data is available immediately; network providers sync in the background
|
|
21548
|
+
// via Yjs observers that are already registered above.
|
|
21549
|
+
const localProviders = this._providers.filter(p => p.type === 'local');
|
|
21550
|
+
const networkProviders = this._providers.filter(p => p.type === 'network');
|
|
21551
|
+
// Await local providers for immediate data availability
|
|
21552
|
+
const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
|
|
21553
|
+
localResults.forEach((result, i) => {
|
|
21479
21554
|
if (result.status === 'rejected') {
|
|
21480
|
-
console.error(`[Kritzel] Sync provider "${
|
|
21555
|
+
console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
|
|
21481
21556
|
}
|
|
21482
21557
|
});
|
|
21558
|
+
// Connect network providers in the background (remote data arrives via Yjs observers)
|
|
21559
|
+
for (const provider of networkProviders) {
|
|
21560
|
+
provider.connect().catch(err => {
|
|
21561
|
+
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
21562
|
+
});
|
|
21563
|
+
}
|
|
21483
21564
|
this._isReady = true;
|
|
21484
21565
|
// Find the first provider that exposes awareness (network providers)
|
|
21485
|
-
|
|
21566
|
+
// Awareness is available immediately after provider construction, before connect() resolves
|
|
21567
|
+
for (const provider of networkProviders) {
|
|
21486
21568
|
if (provider.awareness) {
|
|
21487
21569
|
this._awareness = provider.awareness;
|
|
21488
21570
|
break;
|
|
@@ -21490,7 +21572,11 @@ class KritzelObjectMap {
|
|
|
21490
21572
|
}
|
|
21491
21573
|
// Subscribe to awareness changes
|
|
21492
21574
|
if (this._awareness) {
|
|
21493
|
-
this._awarenessChangeHandler = () => {
|
|
21575
|
+
this._awarenessChangeHandler = (change) => {
|
|
21576
|
+
// Clean up selection groups belonging to disconnected users
|
|
21577
|
+
if (change.removed.length > 0) {
|
|
21578
|
+
this.removeOrphanedSelectionGroups();
|
|
21579
|
+
}
|
|
21494
21580
|
const now = Date.now();
|
|
21495
21581
|
const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
|
|
21496
21582
|
// Clear any pending timeout since we have a new event
|
|
@@ -21753,11 +21839,27 @@ class KritzelObjectMap {
|
|
|
21753
21839
|
}
|
|
21754
21840
|
this.quadtree.reset();
|
|
21755
21841
|
this._idMap.clear();
|
|
21756
|
-
this.
|
|
21842
|
+
const localUserId = this._core?.user?.id;
|
|
21843
|
+
const staleSelectionGroupIds = [];
|
|
21844
|
+
this._objectsMap.forEach((serialized, key) => {
|
|
21757
21845
|
const object = this._reviver.revive(serialized);
|
|
21846
|
+
// Remove remote selection groups on startup — they are transient UI state
|
|
21847
|
+
// that should not survive an app restart. The owning user's session is gone.
|
|
21848
|
+
if (object instanceof KritzelSelectionGroup && object.userId != null && object.userId !== localUserId) {
|
|
21849
|
+
staleSelectionGroupIds.push(key);
|
|
21850
|
+
return;
|
|
21851
|
+
}
|
|
21758
21852
|
this.quadtree.insert(object);
|
|
21759
21853
|
this._idMap.set(object.id, object);
|
|
21760
21854
|
});
|
|
21855
|
+
// Clean up stale remote selection groups from Yjs
|
|
21856
|
+
if (staleSelectionGroupIds.length > 0) {
|
|
21857
|
+
this._ydoc.transact(() => {
|
|
21858
|
+
for (const id of staleSelectionGroupIds) {
|
|
21859
|
+
this._objectsMap.delete(id);
|
|
21860
|
+
}
|
|
21861
|
+
}, 'local');
|
|
21862
|
+
}
|
|
21761
21863
|
}
|
|
21762
21864
|
/**
|
|
21763
21865
|
* Resets the object map by clearing both the local quadtree and the Yjs objects map.
|
|
@@ -22429,13 +22531,24 @@ class KritzelAppStateMap {
|
|
|
22429
22531
|
this.handleWorkspacesChange(event);
|
|
22430
22532
|
};
|
|
22431
22533
|
this._workspacesMap.observe(this._workspacesObserver);
|
|
22432
|
-
//
|
|
22433
|
-
|
|
22434
|
-
|
|
22534
|
+
// Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
|
|
22535
|
+
// Local providers are awaited so data is available immediately; network providers sync in the background
|
|
22536
|
+
// via Yjs observers that are already registered above.
|
|
22537
|
+
const localProviders = this._providers.filter(p => p.type === 'local');
|
|
22538
|
+
const networkProviders = this._providers.filter(p => p.type === 'network');
|
|
22539
|
+
// Await local providers for immediate data availability
|
|
22540
|
+
const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
|
|
22541
|
+
localResults.forEach((result, i) => {
|
|
22435
22542
|
if (result.status === 'rejected') {
|
|
22436
|
-
console.error(`[Kritzel] Sync provider "${
|
|
22543
|
+
console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
|
|
22437
22544
|
}
|
|
22438
22545
|
});
|
|
22546
|
+
// Connect network providers in the background (remote data arrives via Yjs observers)
|
|
22547
|
+
for (const provider of networkProviders) {
|
|
22548
|
+
provider.connect().catch(err => {
|
|
22549
|
+
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
22550
|
+
});
|
|
22551
|
+
}
|
|
22439
22552
|
this._isReady = true;
|
|
22440
22553
|
// Run any pending schema migrations before loading data
|
|
22441
22554
|
const quietMigrations = !core.store.state.debugInfo.showMigrationInfo;
|
|
@@ -28386,7 +28499,7 @@ const KritzelPortal = class {
|
|
|
28386
28499
|
* This file is auto-generated by the version bump scripts.
|
|
28387
28500
|
* Do not modify manually.
|
|
28388
28501
|
*/
|
|
28389
|
-
const KRITZEL_VERSION = '0.1.
|
|
28502
|
+
const KRITZEL_VERSION = '0.1.76';
|
|
28390
28503
|
|
|
28391
28504
|
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)}`;
|
|
28392
28505
|
|