kritzel-stencil 0.1.71 → 0.1.73

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.
Files changed (31) hide show
  1. package/dist/cjs/index.cjs.js +58 -18
  2. package/dist/cjs/kritzel-active-users_41.cjs.entry.js +38 -10
  3. package/dist/cjs/{workspace.migrations-Cma2Vh5E.js → workspace.migrations-DcwqsqPC.js} +7 -0
  4. package/dist/collection/classes/core/core.class.js +0 -2
  5. package/dist/collection/classes/core/workspace.class.js +7 -0
  6. package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +57 -17
  7. package/dist/collection/classes/structures/object-map.structure.js +34 -5
  8. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +2 -1
  9. package/dist/collection/constants/version.js +1 -1
  10. package/dist/components/index.js +1 -1
  11. package/dist/components/kritzel-editor.js +1 -1
  12. package/dist/components/kritzel-engine.js +1 -1
  13. package/dist/components/kritzel-settings.js +1 -1
  14. package/dist/components/kritzel-workspace-manager.js +1 -1
  15. package/dist/components/{p-BB6jZPvJ.js → p-BgDhcDNw.js} +1 -1
  16. package/dist/components/{p-MrsvScCa.js → p-By3NOY-k.js} +2 -2
  17. package/dist/components/{p-D5rvyCKa.js → p-DhMlShij.js} +1 -1
  18. package/dist/components/{p-D_Rh1g93.js → p-Dp8idtVD.js} +1 -1
  19. package/dist/esm/index.js +59 -19
  20. package/dist/esm/kritzel-active-users_41.entry.js +38 -10
  21. package/dist/esm/{workspace.migrations-BeeAeDP0.js → workspace.migrations-BGixvB76.js} +7 -0
  22. package/dist/stencil/index.esm.js +1 -1
  23. package/dist/stencil/p-67c79d75.entry.js +9 -0
  24. package/dist/stencil/{p-BeeAeDP0.js → p-BGixvB76.js} +1 -1
  25. package/dist/stencil/stencil.esm.js +1 -1
  26. package/dist/types/classes/core/workspace.class.d.ts +5 -0
  27. package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +3 -0
  28. package/dist/types/classes/structures/object-map.structure.d.ts +3 -0
  29. package/dist/types/constants/version.d.ts +1 -1
  30. package/package.json +1 -1
  31. package/dist/stencil/p-47cfd58a.entry.js +0 -9
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var workspace_migrations = require('./workspace.migrations-Cma2Vh5E.js');
3
+ var workspace_migrations = require('./workspace.migrations-DcwqsqPC.js');
4
4
  var Y = require('yjs');
5
5
  var yWebsocket = require('y-websocket');
6
6
  require('y-indexeddb');
@@ -556,6 +556,9 @@ class HocuspocusSyncProvider {
556
556
  isConnected = false;
557
557
  isSynced = false;
558
558
  usesSharedSocket = false;
559
+ isDestroyed = false;
560
+ connectTimeout = null;
561
+ pendingConnectReject = null;
559
562
  get awareness() {
560
563
  return this.provider.awareness;
561
564
  }
@@ -574,8 +577,13 @@ class HocuspocusSyncProvider {
574
577
  name,
575
578
  document: doc,
576
579
  token: options?.token || null,
580
+ onStatus: (data) => {
581
+ if (options?.onStatus) {
582
+ options.onStatus(data);
583
+ }
584
+ },
577
585
  onConnect: () => {
578
- if (this.isConnected) {
586
+ if (this.isConnected || this.isDestroyed) {
579
587
  return;
580
588
  }
581
589
  this.isConnected = true;
@@ -587,7 +595,7 @@ class HocuspocusSyncProvider {
587
595
  }
588
596
  },
589
597
  onDisconnect: () => {
590
- if (!this.isConnected && !this.isSynced) {
598
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
591
599
  return;
592
600
  }
593
601
  this.isConnected = false;
@@ -600,7 +608,7 @@ class HocuspocusSyncProvider {
600
608
  }
601
609
  },
602
610
  onSynced: () => {
603
- if (this.isSynced) {
611
+ if (this.isSynced || this.isDestroyed) {
604
612
  return;
605
613
  }
606
614
  this.isSynced = true;
@@ -619,9 +627,6 @@ class HocuspocusSyncProvider {
619
627
  if (options?.onAuthenticationFailed) {
620
628
  config.onAuthenticationFailed = options.onAuthenticationFailed;
621
629
  }
622
- if (options?.onStatus) {
623
- config.onStatus = options.onStatus;
624
- }
625
630
  this.provider = new workspace_migrations.HocuspocusProvider(config);
626
631
  // Must call attach() explicitly when using shared socket
627
632
  this.provider.attach();
@@ -637,8 +642,14 @@ class HocuspocusSyncProvider {
637
642
  name,
638
643
  document: doc,
639
644
  token: options?.token || null,
645
+ autoConnect: false,
646
+ onStatus: (data) => {
647
+ if (options?.onStatus) {
648
+ options.onStatus(data);
649
+ }
650
+ },
640
651
  onConnect: () => {
641
- if (this.isConnected) {
652
+ if (this.isConnected || this.isDestroyed) {
642
653
  return;
643
654
  }
644
655
  this.isConnected = true;
@@ -650,7 +661,7 @@ class HocuspocusSyncProvider {
650
661
  }
651
662
  },
652
663
  onDisconnect: () => {
653
- if (!this.isConnected && !this.isSynced) {
664
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
654
665
  return;
655
666
  }
656
667
  this.isConnected = false;
@@ -663,7 +674,7 @@ class HocuspocusSyncProvider {
663
674
  }
664
675
  },
665
676
  onSynced: () => {
666
- if (this.isSynced) {
677
+ if (this.isSynced || this.isDestroyed) {
667
678
  return;
668
679
  }
669
680
  this.isSynced = true;
@@ -682,9 +693,6 @@ class HocuspocusSyncProvider {
682
693
  if (options?.onAuthenticationFailed) {
683
694
  config.onAuthenticationFailed = options.onAuthenticationFailed;
684
695
  }
685
- if (options?.onStatus) {
686
- config.onStatus = options.onStatus;
687
- }
688
696
  if (options?.WebSocketPolyfill) {
689
697
  config.WebSocketPolyfill = options.WebSocketPolyfill;
690
698
  }
@@ -752,22 +760,36 @@ class HocuspocusSyncProvider {
752
760
  };
753
761
  }
754
762
  async connect() {
755
- if (this.isSynced) {
763
+ if (this.isSynced || this.isDestroyed) {
756
764
  return;
757
765
  }
758
766
  return new Promise((resolve, reject) => {
759
- const timeout = setTimeout(() => {
767
+ // Store reject function so we can cancel the connection if destroyed
768
+ this.pendingConnectReject = reject;
769
+ this.connectTimeout = setTimeout(() => {
770
+ this.pendingConnectReject = null;
771
+ this.connectTimeout = null;
760
772
  reject(new Error('Hocuspocus connection timeout'));
761
773
  }, 10000); // 10 second timeout
762
774
  const syncHandler = () => {
763
- clearTimeout(timeout);
775
+ if (this.connectTimeout) {
776
+ clearTimeout(this.connectTimeout);
777
+ this.connectTimeout = null;
778
+ }
779
+ this.pendingConnectReject = null;
764
780
  this.provider.off('synced', syncHandler);
765
- resolve();
781
+ if (!this.isDestroyed) {
782
+ resolve();
783
+ }
766
784
  };
767
785
  this.provider.on('synced', syncHandler);
768
786
  // If already synced, resolve immediately
769
787
  if (this.provider.isSynced) {
770
- clearTimeout(timeout);
788
+ if (this.connectTimeout) {
789
+ clearTimeout(this.connectTimeout);
790
+ this.connectTimeout = null;
791
+ }
792
+ this.pendingConnectReject = null;
771
793
  this.provider.off('synced', syncHandler);
772
794
  resolve();
773
795
  return;
@@ -779,6 +801,14 @@ class HocuspocusSyncProvider {
779
801
  });
780
802
  }
781
803
  disconnect() {
804
+ // Cancel any pending connection attempt
805
+ if (this.connectTimeout) {
806
+ clearTimeout(this.connectTimeout);
807
+ this.connectTimeout = null;
808
+ }
809
+ if (this.pendingConnectReject) {
810
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
811
+ }
782
812
  if (this.provider) {
783
813
  if (this.usesSharedSocket) {
784
814
  // Detach from shared socket instead of disconnecting
@@ -792,6 +822,16 @@ class HocuspocusSyncProvider {
792
822
  this.isSynced = false;
793
823
  }
794
824
  destroy() {
825
+ // Mark as destroyed first to prevent any callbacks from doing work
826
+ this.isDestroyed = true;
827
+ // Cancel any pending connection attempt
828
+ if (this.connectTimeout) {
829
+ clearTimeout(this.connectTimeout);
830
+ this.connectTimeout = null;
831
+ }
832
+ if (this.pendingConnectReject) {
833
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
834
+ }
795
835
  if (this.provider) {
796
836
  this.provider.destroy();
797
837
  }
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-Dc7LOVhs.js');
4
- var workspace_migrations = require('./workspace.migrations-Cma2Vh5E.js');
4
+ var workspace_migrations = require('./workspace.migrations-DcwqsqPC.js');
5
5
  var Y = require('yjs');
6
6
  require('y-websocket');
7
7
  require('y-indexeddb');
@@ -2175,6 +2175,7 @@ const KritzelEditor = class {
2175
2175
  const activeWorkspace = await this.engineRef.getActiveWorkspace();
2176
2176
  activeWorkspace.isPublic = isPublic;
2177
2177
  await this.engineRef.updateWorkspace(activeWorkspace);
2178
+ this.activeWorkspace = activeWorkspace.clone();
2178
2179
  this.isPublicChange.emit({ isPublic, workspaceId: this.activeWorkspace?.id });
2179
2180
  };
2180
2181
  /**
@@ -2288,7 +2289,7 @@ const KritzelEditor = class {
2288
2289
  const isLoggedIn = this.isLoggedIn;
2289
2290
  const shouldShowCurrentUser = isLoggedIn;
2290
2291
  const shouldShowLoginButton = !!this.loginConfig && !isLoggedIn;
2291
- return (index.h(index.Host, { key: '0339b3ea217d55c8192efa53ad8059ff2ffdd578' }, index.h("div", { key: '2149a3f5e251a398e0c7815ebb88d251cbc83d9d', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '727f465560d3a8aa49ebdfba9251df26b748669a', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: '269748350eef82b390893ed5b8b6516eef85fa1f', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: 'c8248639b41f4500718d745b55c988b9f63fde27', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event) }), index.h("kritzel-controls", { key: 'fc1be924d9807aba8aad966bf8ea14785064ed45', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '12800aa2e7833e838b91d194274344a904d5410b', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'cb540f195a5a56159999c7d33fccbf94a9d25764', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: 'fd17ac3acbf352ee2e1ca797bd72b2d9a4183323', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '74879be875fe6db127f4864f8aad08f39901e053', users: this.activeUsers }), shouldShowCurrentUser && (index.h("kritzel-current-user", { key: 'a4bc322c69f0784c39c043f0089853babbdd1584', user: this.user })), shouldShowLoginButton && index.h("kritzel-button", { key: 'fb89dd330087ee2dd7288285071ee28a97bcec8e', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), index.h("kritzel-more-menu", { key: 'bba9421fab99b82143880478329973f33bfb3039', items: this.moreMenuItems }), index.h("kritzel-share-dialog", { key: 'd03c43ce77c16fd3a5cce7aa134d5ef73361d8b3', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'ced23497db2b7abea1f2121085a21ed9208ecba9', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
2292
+ return (index.h(index.Host, { key: '84231308ca4b12863139aeeafa754d544345df3f' }, index.h("div", { key: 'b8cdd2862e9bd892dfeed356e85d608759307bf7', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '2bce2b9564f5a083ba32667718685310d060f23a', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'b013213fc04fe86eefcc324911693fcffb8eea51', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: '7338446462bfdc3995c0897704e9594a875895e1', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event) }), index.h("kritzel-controls", { key: '97b9d8805446afc101f01d99bc96bc24a7054722', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '0986b1e82b8fbf989e716fe07a401faca618f642', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '6f4cfe92bb54f0de291b0a0482742b859e86ecf3', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: 'd669ecae49f721e765d1f8d2f6725633c28cd635', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: 'e44843dcdc9c0ff1e760aadac3f2ec79a7371aba', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '364e09a58fd147f29ce7402406907c6c633124bc', user: this.user }), shouldShowLoginButton && index.h("kritzel-button", { key: '79705ba4c5f991229ae08b90a3397bd3bac56333', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), index.h("kritzel-more-menu", { key: '95bd3a7f755aa941bd3d9ffdbc2c7f433e5beaaa', items: this.moreMenuItems }), index.h("kritzel-share-dialog", { key: '94f6cfdb67b82d35c6e86f37c88c2ec323234360', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'bc4fd1ec6e61f4d4e61c043ad33a0b0e2c3c8792', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
2292
2293
  }
2293
2294
  static get watchers() { return {
2294
2295
  "isEngineReady": [{
@@ -21001,6 +21002,9 @@ class KritzelObjectMap {
21001
21002
  _stackItemPoppedHandler = null;
21002
21003
  _awarenessChangeHandler = null;
21003
21004
  _awarenessChangeCallbacks = [];
21005
+ _lastAwarenessEmitTime = 0;
21006
+ _awarenessEmitTimeout = null;
21007
+ AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
21004
21008
  /**
21005
21009
  * Indicates whether the object map has been initialized and is ready for use.
21006
21010
  * @returns `true` if providers are connected and the map is operational
@@ -21190,9 +21194,32 @@ class KritzelObjectMap {
21190
21194
  // Subscribe to awareness changes
21191
21195
  if (this._awareness) {
21192
21196
  this._awarenessChangeHandler = () => {
21193
- const states = this._awareness.getStates();
21194
- for (const callback of this._awarenessChangeCallbacks) {
21195
- callback(states);
21197
+ const now = Date.now();
21198
+ const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
21199
+ // Clear any pending timeout since we have a new event
21200
+ if (this._awarenessEmitTimeout !== null) {
21201
+ clearTimeout(this._awarenessEmitTimeout);
21202
+ this._awarenessEmitTimeout = null;
21203
+ }
21204
+ if (timeSinceLastEmit >= this.AWARENESS_THROTTLE_INTERVAL) {
21205
+ // Enough time has passed, emit immediately
21206
+ this._lastAwarenessEmitTime = now;
21207
+ const states = this._awareness.getStates();
21208
+ for (const callback of this._awarenessChangeCallbacks) {
21209
+ callback(states);
21210
+ }
21211
+ }
21212
+ else {
21213
+ // Schedule emission for the remaining time
21214
+ const delayMs = this.AWARENESS_THROTTLE_INTERVAL - timeSinceLastEmit;
21215
+ this._awarenessEmitTimeout = setTimeout(() => {
21216
+ this._lastAwarenessEmitTime = Date.now();
21217
+ this._awarenessEmitTimeout = null;
21218
+ const states = this._awareness.getStates();
21219
+ for (const callback of this._awarenessChangeCallbacks) {
21220
+ callback(states);
21221
+ }
21222
+ }, delayMs);
21196
21223
  }
21197
21224
  };
21198
21225
  this._awareness.on('change', this._awarenessChangeHandler);
@@ -21712,11 +21739,14 @@ class KritzelObjectMap {
21712
21739
  this._awareness.off('change', this._awarenessChangeHandler);
21713
21740
  this._awarenessChangeHandler = null;
21714
21741
  }
21742
+ if (this._awarenessEmitTimeout !== null) {
21743
+ clearTimeout(this._awarenessEmitTimeout);
21744
+ this._awarenessEmitTimeout = null;
21745
+ }
21715
21746
  this._awareness = null;
21716
21747
  this._awarenessChangeCallbacks = [];
21717
- // Disconnect and destroy providers
21748
+ // Destroy providers (destroy handles disconnection internally)
21718
21749
  this._providers.forEach(p => {
21719
- p.disconnect();
21720
21750
  p.destroy();
21721
21751
  });
21722
21752
  this._providers = [];
@@ -22879,13 +22909,11 @@ class KritzelCore {
22879
22909
  * @param workspace - The workspace to create
22880
22910
  */
22881
22911
  createWorkspace(workspace) {
22882
- console.log('Creating workspace:', workspace);
22883
22912
  workspace._core = this;
22884
22913
  workspace.createdAt = new Date();
22885
22914
  workspace.updatedAt = new Date();
22886
22915
  this.saveWorkspaceToAppState(workspace);
22887
22916
  this._store.state.workspaces = this.loadWorkspacesFromAppState();
22888
- console.log('Current workspaces after creation:', this._store.state.workspaces);
22889
22917
  }
22890
22918
  /**
22891
22919
  * Updates an existing workspace in the app state.
@@ -27994,7 +28022,7 @@ const KritzelPortal = class {
27994
28022
  * This file is auto-generated by the version bump scripts.
27995
28023
  * Do not modify manually.
27996
28024
  */
27997
- const KRITZEL_VERSION = '0.1.71';
28025
+ const KRITZEL_VERSION = '0.1.73';
27998
28026
 
27999
28027
  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)}`;
28000
28028
 
@@ -25254,6 +25254,13 @@ class KritzelWorkspace {
25254
25254
  }
25255
25255
  return serialized;
25256
25256
  }
25257
+ /**
25258
+ * Creates a shallow clone of this workspace, preserving the prototype chain.
25259
+ * Useful when Stencil needs a new object reference to detect property changes.
25260
+ */
25261
+ clone() {
25262
+ return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
25263
+ }
25257
25264
  /**
25258
25265
  * Deserializes a plain object into this workspace instance.
25259
25266
  * Converts ISO string dates back to Date objects.
@@ -339,13 +339,11 @@ export class KritzelCore {
339
339
  * @param workspace - The workspace to create
340
340
  */
341
341
  createWorkspace(workspace) {
342
- console.log('Creating workspace:', workspace);
343
342
  workspace._core = this;
344
343
  workspace.createdAt = new Date();
345
344
  workspace.updatedAt = new Date();
346
345
  this.saveWorkspaceToAppState(workspace);
347
346
  this._store.state.workspaces = this.loadWorkspacesFromAppState();
348
- console.log('Current workspaces after creation:', this._store.state.workspaces);
349
347
  }
350
348
  /**
351
349
  * Updates an existing workspace in the app state.
@@ -77,6 +77,13 @@ export class KritzelWorkspace {
77
77
  }
78
78
  return serialized;
79
79
  }
80
+ /**
81
+ * Creates a shallow clone of this workspace, preserving the prototype chain.
82
+ * Useful when Stencil needs a new object reference to detect property changes.
83
+ */
84
+ clone() {
85
+ return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
86
+ }
80
87
  /**
81
88
  * Deserializes a plain object into this workspace instance.
82
89
  * Converts ISO string dates back to Date objects.
@@ -8,6 +8,9 @@ export class HocuspocusSyncProvider {
8
8
  isConnected = false;
9
9
  isSynced = false;
10
10
  usesSharedSocket = false;
11
+ isDestroyed = false;
12
+ connectTimeout = null;
13
+ pendingConnectReject = null;
11
14
  get awareness() {
12
15
  return this.provider.awareness;
13
16
  }
@@ -26,8 +29,13 @@ export class HocuspocusSyncProvider {
26
29
  name,
27
30
  document: doc,
28
31
  token: options?.token || null,
32
+ onStatus: (data) => {
33
+ if (options?.onStatus) {
34
+ options.onStatus(data);
35
+ }
36
+ },
29
37
  onConnect: () => {
30
- if (this.isConnected) {
38
+ if (this.isConnected || this.isDestroyed) {
31
39
  return;
32
40
  }
33
41
  this.isConnected = true;
@@ -39,7 +47,7 @@ export class HocuspocusSyncProvider {
39
47
  }
40
48
  },
41
49
  onDisconnect: () => {
42
- if (!this.isConnected && !this.isSynced) {
50
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
43
51
  return;
44
52
  }
45
53
  this.isConnected = false;
@@ -52,7 +60,7 @@ export class HocuspocusSyncProvider {
52
60
  }
53
61
  },
54
62
  onSynced: () => {
55
- if (this.isSynced) {
63
+ if (this.isSynced || this.isDestroyed) {
56
64
  return;
57
65
  }
58
66
  this.isSynced = true;
@@ -71,9 +79,6 @@ export class HocuspocusSyncProvider {
71
79
  if (options?.onAuthenticationFailed) {
72
80
  config.onAuthenticationFailed = options.onAuthenticationFailed;
73
81
  }
74
- if (options?.onStatus) {
75
- config.onStatus = options.onStatus;
76
- }
77
82
  this.provider = new HocuspocusProvider(config);
78
83
  // Must call attach() explicitly when using shared socket
79
84
  this.provider.attach();
@@ -89,8 +94,14 @@ export class HocuspocusSyncProvider {
89
94
  name,
90
95
  document: doc,
91
96
  token: options?.token || null,
97
+ autoConnect: false,
98
+ onStatus: (data) => {
99
+ if (options?.onStatus) {
100
+ options.onStatus(data);
101
+ }
102
+ },
92
103
  onConnect: () => {
93
- if (this.isConnected) {
104
+ if (this.isConnected || this.isDestroyed) {
94
105
  return;
95
106
  }
96
107
  this.isConnected = true;
@@ -102,7 +113,7 @@ export class HocuspocusSyncProvider {
102
113
  }
103
114
  },
104
115
  onDisconnect: () => {
105
- if (!this.isConnected && !this.isSynced) {
116
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
106
117
  return;
107
118
  }
108
119
  this.isConnected = false;
@@ -115,7 +126,7 @@ export class HocuspocusSyncProvider {
115
126
  }
116
127
  },
117
128
  onSynced: () => {
118
- if (this.isSynced) {
129
+ if (this.isSynced || this.isDestroyed) {
119
130
  return;
120
131
  }
121
132
  this.isSynced = true;
@@ -134,9 +145,6 @@ export class HocuspocusSyncProvider {
134
145
  if (options?.onAuthenticationFailed) {
135
146
  config.onAuthenticationFailed = options.onAuthenticationFailed;
136
147
  }
137
- if (options?.onStatus) {
138
- config.onStatus = options.onStatus;
139
- }
140
148
  if (options?.WebSocketPolyfill) {
141
149
  config.WebSocketPolyfill = options.WebSocketPolyfill;
142
150
  }
@@ -204,22 +212,36 @@ export class HocuspocusSyncProvider {
204
212
  };
205
213
  }
206
214
  async connect() {
207
- if (this.isSynced) {
215
+ if (this.isSynced || this.isDestroyed) {
208
216
  return;
209
217
  }
210
218
  return new Promise((resolve, reject) => {
211
- const timeout = setTimeout(() => {
219
+ // Store reject function so we can cancel the connection if destroyed
220
+ this.pendingConnectReject = reject;
221
+ this.connectTimeout = setTimeout(() => {
222
+ this.pendingConnectReject = null;
223
+ this.connectTimeout = null;
212
224
  reject(new Error('Hocuspocus connection timeout'));
213
225
  }, 10000); // 10 second timeout
214
226
  const syncHandler = () => {
215
- clearTimeout(timeout);
227
+ if (this.connectTimeout) {
228
+ clearTimeout(this.connectTimeout);
229
+ this.connectTimeout = null;
230
+ }
231
+ this.pendingConnectReject = null;
216
232
  this.provider.off('synced', syncHandler);
217
- resolve();
233
+ if (!this.isDestroyed) {
234
+ resolve();
235
+ }
218
236
  };
219
237
  this.provider.on('synced', syncHandler);
220
238
  // If already synced, resolve immediately
221
239
  if (this.provider.isSynced) {
222
- clearTimeout(timeout);
240
+ if (this.connectTimeout) {
241
+ clearTimeout(this.connectTimeout);
242
+ this.connectTimeout = null;
243
+ }
244
+ this.pendingConnectReject = null;
223
245
  this.provider.off('synced', syncHandler);
224
246
  resolve();
225
247
  return;
@@ -231,6 +253,14 @@ export class HocuspocusSyncProvider {
231
253
  });
232
254
  }
233
255
  disconnect() {
256
+ // Cancel any pending connection attempt
257
+ if (this.connectTimeout) {
258
+ clearTimeout(this.connectTimeout);
259
+ this.connectTimeout = null;
260
+ }
261
+ if (this.pendingConnectReject) {
262
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
263
+ }
234
264
  if (this.provider) {
235
265
  if (this.usesSharedSocket) {
236
266
  // Detach from shared socket instead of disconnecting
@@ -244,6 +274,16 @@ export class HocuspocusSyncProvider {
244
274
  this.isSynced = false;
245
275
  }
246
276
  destroy() {
277
+ // Mark as destroyed first to prevent any callbacks from doing work
278
+ this.isDestroyed = true;
279
+ // Cancel any pending connection attempt
280
+ if (this.connectTimeout) {
281
+ clearTimeout(this.connectTimeout);
282
+ this.connectTimeout = null;
283
+ }
284
+ if (this.pendingConnectReject) {
285
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
286
+ }
247
287
  if (this.provider) {
248
288
  this.provider.destroy();
249
289
  }
@@ -34,6 +34,9 @@ export class KritzelObjectMap {
34
34
  _stackItemPoppedHandler = null;
35
35
  _awarenessChangeHandler = null;
36
36
  _awarenessChangeCallbacks = [];
37
+ _lastAwarenessEmitTime = 0;
38
+ _awarenessEmitTimeout = null;
39
+ AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
37
40
  /**
38
41
  * Indicates whether the object map has been initialized and is ready for use.
39
42
  * @returns `true` if providers are connected and the map is operational
@@ -223,9 +226,32 @@ export class KritzelObjectMap {
223
226
  // Subscribe to awareness changes
224
227
  if (this._awareness) {
225
228
  this._awarenessChangeHandler = () => {
226
- const states = this._awareness.getStates();
227
- for (const callback of this._awarenessChangeCallbacks) {
228
- callback(states);
229
+ const now = Date.now();
230
+ const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
231
+ // Clear any pending timeout since we have a new event
232
+ if (this._awarenessEmitTimeout !== null) {
233
+ clearTimeout(this._awarenessEmitTimeout);
234
+ this._awarenessEmitTimeout = null;
235
+ }
236
+ if (timeSinceLastEmit >= this.AWARENESS_THROTTLE_INTERVAL) {
237
+ // Enough time has passed, emit immediately
238
+ this._lastAwarenessEmitTime = now;
239
+ const states = this._awareness.getStates();
240
+ for (const callback of this._awarenessChangeCallbacks) {
241
+ callback(states);
242
+ }
243
+ }
244
+ else {
245
+ // Schedule emission for the remaining time
246
+ const delayMs = this.AWARENESS_THROTTLE_INTERVAL - timeSinceLastEmit;
247
+ this._awarenessEmitTimeout = setTimeout(() => {
248
+ this._lastAwarenessEmitTime = Date.now();
249
+ this._awarenessEmitTimeout = null;
250
+ const states = this._awareness.getStates();
251
+ for (const callback of this._awarenessChangeCallbacks) {
252
+ callback(states);
253
+ }
254
+ }, delayMs);
229
255
  }
230
256
  };
231
257
  this._awareness.on('change', this._awarenessChangeHandler);
@@ -752,11 +778,14 @@ export class KritzelObjectMap {
752
778
  this._awareness.off('change', this._awarenessChangeHandler);
753
779
  this._awarenessChangeHandler = null;
754
780
  }
781
+ if (this._awarenessEmitTimeout !== null) {
782
+ clearTimeout(this._awarenessEmitTimeout);
783
+ this._awarenessEmitTimeout = null;
784
+ }
755
785
  this._awareness = null;
756
786
  this._awarenessChangeCallbacks = [];
757
- // Disconnect and destroy providers
787
+ // Destroy providers (destroy handles disconnection internally)
758
788
  this._providers.forEach(p => {
759
- p.disconnect();
760
789
  p.destroy();
761
790
  });
762
791
  this._providers = [];
@@ -583,6 +583,7 @@ export class KritzelEditor {
583
583
  const activeWorkspace = await this.engineRef.getActiveWorkspace();
584
584
  activeWorkspace.isPublic = isPublic;
585
585
  await this.engineRef.updateWorkspace(activeWorkspace);
586
+ this.activeWorkspace = activeWorkspace.clone();
586
587
  this.isPublicChange.emit({ isPublic, workspaceId: this.activeWorkspace?.id });
587
588
  };
588
589
  /**
@@ -696,7 +697,7 @@ export class KritzelEditor {
696
697
  const isLoggedIn = this.isLoggedIn;
697
698
  const shouldShowCurrentUser = isLoggedIn;
698
699
  const shouldShowLoginButton = !!this.loginConfig && !isLoggedIn;
699
- return (h(Host, { key: '0339b3ea217d55c8192efa53ad8059ff2ffdd578' }, h("div", { key: '2149a3f5e251a398e0c7815ebb88d251cbc83d9d', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: '727f465560d3a8aa49ebdfba9251df26b748669a', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), h("kritzel-back-to-content", { key: '269748350eef82b390893ed5b8b6516eef85fa1f', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), h("kritzel-engine", { key: 'c8248639b41f4500718d745b55c988b9f63fde27', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event) }), h("kritzel-controls", { key: 'fc1be924d9807aba8aad966bf8ea14785064ed45', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: '12800aa2e7833e838b91d194274344a904d5410b', class: "top-right-buttons" }, h("kritzel-settings", { key: 'cb540f195a5a56159999c7d33fccbf94a9d25764', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), h("kritzel-export", { key: 'fd17ac3acbf352ee2e1ca797bd72b2d9a4183323', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: '74879be875fe6db127f4864f8aad08f39901e053', users: this.activeUsers }), shouldShowCurrentUser && (h("kritzel-current-user", { key: 'a4bc322c69f0784c39c043f0089853babbdd1584', user: this.user })), shouldShowLoginButton && h("kritzel-button", { key: 'fb89dd330087ee2dd7288285071ee28a97bcec8e', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), h("kritzel-more-menu", { key: 'bba9421fab99b82143880478329973f33bfb3039', items: this.moreMenuItems }), h("kritzel-share-dialog", { key: 'd03c43ce77c16fd3a5cce7aa134d5ef73361d8b3', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: 'ced23497db2b7abea1f2121085a21ed9208ecba9', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
700
+ return (h(Host, { key: '84231308ca4b12863139aeeafa754d544345df3f' }, h("div", { key: 'b8cdd2862e9bd892dfeed356e85d608759307bf7', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: '2bce2b9564f5a083ba32667718685310d060f23a', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), h("kritzel-back-to-content", { key: 'b013213fc04fe86eefcc324911693fcffb8eea51', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), h("kritzel-engine", { key: '7338446462bfdc3995c0897704e9594a875895e1', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event) }), h("kritzel-controls", { key: '97b9d8805446afc101f01d99bc96bc24a7054722', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: '0986b1e82b8fbf989e716fe07a401faca618f642', class: "top-right-buttons" }, h("kritzel-settings", { key: '6f4cfe92bb54f0de291b0a0482742b859e86ecf3', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), h("kritzel-export", { key: 'd669ecae49f721e765d1f8d2f6725633c28cd635', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: 'e44843dcdc9c0ff1e760aadac3f2ec79a7371aba', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '364e09a58fd147f29ce7402406907c6c633124bc', user: this.user }), shouldShowLoginButton && h("kritzel-button", { key: '79705ba4c5f991229ae08b90a3397bd3bac56333', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), h("kritzel-more-menu", { key: '95bd3a7f755aa941bd3d9ffdbc2c7f433e5beaaa', items: this.moreMenuItems }), h("kritzel-share-dialog", { key: '94f6cfdb67b82d35c6e86f37c88c2ec323234360', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: 'bc4fd1ec6e61f4d4e61c043ad33a0b0e2c3c8792', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
700
701
  }
701
702
  static get is() { return "kritzel-editor"; }
702
703
  static get originalStyleUrls() {
@@ -3,4 +3,4 @@
3
3
  * This file is auto-generated by the version bump scripts.
4
4
  * Do not modify manually.
5
5
  */
6
- export const KRITZEL_VERSION = '0.1.71';
6
+ export const KRITZEL_VERSION = '0.1.73';