kritzel-stencil 0.4.21 → 0.4.23
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 +50 -1
- package/dist/cjs/kritzel-active-users_47.cjs.entry.js +135 -31
- package/dist/collection/classes/providers/sync/hocuspocus-sync-provider.class.js +21 -0
- package/dist/collection/classes/providers/sync/websocket-sync-provider.class.js +29 -1
- package/dist/collection/classes/structures/app-state-map.structure.js +4 -1
- package/dist/collection/classes/structures/object-map.structure.js +66 -4
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +42 -9
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +51 -17
- package/dist/collection/constants/version.js +1 -1
- package/dist/components/index.js +51 -2
- package/dist/components/kritzel-editor.js +16 -11
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/{p-BdCrcQ0_.js → p-Cb-xlpsJ.js} +1 -1
- package/dist/components/{p-C_f_QElb.js → p-EijwLEZx.js} +120 -21
- package/dist/esm/index.js +50 -1
- package/dist/esm/kritzel-active-users_47.entry.js +135 -31
- package/dist/stencil/index.esm.js +50 -1
- package/dist/stencil/{p-832b80e9.entry.js → p-9e38407e.entry.js} +135 -31
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/providers/sync/hocuspocus-sync-provider.class.d.ts +5 -0
- package/dist/types/classes/providers/sync/websocket-sync-provider.class.d.ts +6 -0
- package/dist/types/classes/structures/object-map.structure.d.ts +26 -2
- package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +3 -0
- package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +9 -1
- package/dist/types/components.d.ts +8 -1
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/interfaces/sync-provider.interface.d.ts +13 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -570,9 +570,15 @@ class WebSocketSyncProvider {
|
|
|
570
570
|
provider;
|
|
571
571
|
isConnected = false;
|
|
572
572
|
_quiet = false;
|
|
573
|
+
_isSynced = false;
|
|
574
|
+
_syncedResolvers = [];
|
|
573
575
|
get awareness() {
|
|
574
576
|
return this.provider.awareness;
|
|
575
577
|
}
|
|
578
|
+
/** y-websocket applies remote updates with the underlying provider as the Yjs transaction origin. */
|
|
579
|
+
get updateOrigin() {
|
|
580
|
+
return this.provider;
|
|
581
|
+
}
|
|
576
582
|
constructor(docName, doc, options) {
|
|
577
583
|
const url = options?.url || 'ws://localhost:1234';
|
|
578
584
|
const roomName = options?.roomName || docName;
|
|
@@ -619,11 +625,29 @@ class WebSocketSyncProvider {
|
|
|
619
625
|
}
|
|
620
626
|
});
|
|
621
627
|
this.provider.on('sync', (synced) => {
|
|
622
|
-
if (synced
|
|
628
|
+
if (!synced) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
this._isSynced = true;
|
|
632
|
+
this.releaseSyncedWaiters();
|
|
633
|
+
if (!this._quiet) {
|
|
623
634
|
console.info('WebSocket synced');
|
|
624
635
|
}
|
|
625
636
|
});
|
|
626
637
|
}
|
|
638
|
+
releaseSyncedWaiters() {
|
|
639
|
+
const resolvers = this._syncedResolvers;
|
|
640
|
+
this._syncedResolvers = [];
|
|
641
|
+
resolvers.forEach(resolve => resolve());
|
|
642
|
+
}
|
|
643
|
+
async whenSynced() {
|
|
644
|
+
if (this._isSynced || this.provider.synced) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
return new Promise(resolve => {
|
|
648
|
+
this._syncedResolvers.push(resolve);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
627
651
|
async connect() {
|
|
628
652
|
if (this.isConnected) {
|
|
629
653
|
return;
|
|
@@ -655,6 +679,8 @@ class WebSocketSyncProvider {
|
|
|
655
679
|
this.provider.disconnect();
|
|
656
680
|
}
|
|
657
681
|
this.isConnected = false;
|
|
682
|
+
this._isSynced = false;
|
|
683
|
+
this.releaseSyncedWaiters();
|
|
658
684
|
}
|
|
659
685
|
async reconnect() {
|
|
660
686
|
this.disconnect();
|
|
@@ -665,6 +691,8 @@ class WebSocketSyncProvider {
|
|
|
665
691
|
this.provider.destroy();
|
|
666
692
|
}
|
|
667
693
|
this.isConnected = false;
|
|
694
|
+
this._isSynced = false;
|
|
695
|
+
this.releaseSyncedWaiters();
|
|
668
696
|
}
|
|
669
697
|
}
|
|
670
698
|
|
|
@@ -686,9 +714,14 @@ class HocuspocusSyncProvider {
|
|
|
686
714
|
_connectionStatus = 'disconnected';
|
|
687
715
|
visibilityHandler = null;
|
|
688
716
|
onlineHandler = null;
|
|
717
|
+
syncedResolvers = [];
|
|
689
718
|
get awareness() {
|
|
690
719
|
return this.provider.awareness;
|
|
691
720
|
}
|
|
721
|
+
/** HocuspocusProvider applies remote updates with itself as the Yjs transaction origin. */
|
|
722
|
+
get updateOrigin() {
|
|
723
|
+
return this.provider;
|
|
724
|
+
}
|
|
692
725
|
get connectionStatus() {
|
|
693
726
|
return this._connectionStatus;
|
|
694
727
|
}
|
|
@@ -745,6 +778,7 @@ class HocuspocusSyncProvider {
|
|
|
745
778
|
}
|
|
746
779
|
this.isSynced = true;
|
|
747
780
|
this._connectionStatus = 'synced';
|
|
781
|
+
this.releaseSyncedWaiters();
|
|
748
782
|
if (!options?.quiet) {
|
|
749
783
|
console.info(`Hocuspocus synced: ${name}`);
|
|
750
784
|
}
|
|
@@ -955,6 +989,19 @@ class HocuspocusSyncProvider {
|
|
|
955
989
|
this.disconnect();
|
|
956
990
|
return this.connect();
|
|
957
991
|
}
|
|
992
|
+
releaseSyncedWaiters() {
|
|
993
|
+
const resolvers = this.syncedResolvers;
|
|
994
|
+
this.syncedResolvers = [];
|
|
995
|
+
resolvers.forEach(resolve => resolve());
|
|
996
|
+
}
|
|
997
|
+
async whenSynced() {
|
|
998
|
+
if (this.isSynced || this.isDestroyed || this.provider.isSynced) {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
return new Promise(resolve => {
|
|
1002
|
+
this.syncedResolvers.push(resolve);
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
958
1005
|
disconnect() {
|
|
959
1006
|
// Cancel any pending connection attempt
|
|
960
1007
|
if (this.connectTimeout) {
|
|
@@ -976,6 +1023,7 @@ class HocuspocusSyncProvider {
|
|
|
976
1023
|
this.isConnected = false;
|
|
977
1024
|
this.isSynced = false;
|
|
978
1025
|
this._connectionStatus = 'disconnected';
|
|
1026
|
+
this.releaseSyncedWaiters();
|
|
979
1027
|
}
|
|
980
1028
|
destroy() {
|
|
981
1029
|
// Mark as destroyed first to prevent any callbacks from doing work
|
|
@@ -995,6 +1043,7 @@ class HocuspocusSyncProvider {
|
|
|
995
1043
|
this.isConnected = false;
|
|
996
1044
|
this.isSynced = false;
|
|
997
1045
|
this._connectionStatus = 'disconnected';
|
|
1046
|
+
this.releaseSyncedWaiters();
|
|
998
1047
|
}
|
|
999
1048
|
}
|
|
1000
1049
|
|
|
@@ -2168,6 +2168,8 @@ const KritzelEditor = class {
|
|
|
2168
2168
|
this.login = index.createEvent(this, "login");
|
|
2169
2169
|
this.isPublicChange = index.createEvent(this, "isPublicChange");
|
|
2170
2170
|
this.awarenessChange = index.createEvent(this, "awarenessChange");
|
|
2171
|
+
this.syncingChange = index.createEvent(this, "syncingChange");
|
|
2172
|
+
this.loadingChange = index.createEvent(this, "loadingChange");
|
|
2171
2173
|
}
|
|
2172
2174
|
get host() { return index.getElement(this); }
|
|
2173
2175
|
scaleMax = ABSOLUTE_SCALE_MAX;
|
|
@@ -2407,6 +2409,9 @@ const KritzelEditor = class {
|
|
|
2407
2409
|
login;
|
|
2408
2410
|
isPublicChange;
|
|
2409
2411
|
awarenessChange;
|
|
2412
|
+
/** Emitted while remote changes are being applied to the active workspace. Stays silent when the remote holds nothing new. */
|
|
2413
|
+
syncingChange;
|
|
2414
|
+
loadingChange;
|
|
2410
2415
|
isEngineReady = false;
|
|
2411
2416
|
isControlsReady = false;
|
|
2412
2417
|
isWorkspaceManagerReady = false;
|
|
@@ -3225,40 +3230,40 @@ const KritzelEditor = class {
|
|
|
3225
3230
|
const isLoggedIn = this.isLoggedIn;
|
|
3226
3231
|
const shouldShowCurrentUser = isLoggedIn;
|
|
3227
3232
|
const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
|
|
3228
|
-
return (index.h(index.Host, { key: '
|
|
3233
|
+
return (index.h(index.Host, { key: 'ff7d505697bd4c4bd840dadcb04a934b7e243a01' }, index.h("div", { key: 'b40076c3c79ad3f879e80c58b412b79266de2ea8', class: "editor-content", style: {
|
|
3229
3234
|
opacity: this.isEditorVisible ? '1' : '0',
|
|
3230
3235
|
visibility: this.isEditorVisible ? 'visible' : 'hidden',
|
|
3231
3236
|
transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
|
|
3232
|
-
} }, index.h("div", { key: '
|
|
3237
|
+
} }, index.h("div", { key: '1b1171eea60a36ad06da55a3da564bc356526266', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '48b59f3f57ace57debc19a295d4fb642e8c3b23d', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'c66a6faae8bf7115dcce47630ad820af9ba76eb0', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: '834baedc5f20328deb4ee586f693313306895a71', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: 'f800508a2e076ee3e89820ffff4df81842b175b3', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: '2835ea84e8fa92c41637adaa29d9005f209b2da9', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), this.contextMenuState && this.contextMenuState.items.length > 0 && (index.h("kritzel-context-menu", { key: '6c49e899b7f933d3f06cd1f8696a590d542214a7', class: "context-menu", items: this.contextMenuState.items, objects: this.contextMenuState.objects, style: {
|
|
3233
3238
|
position: 'absolute',
|
|
3234
3239
|
left: `${this.contextMenuState.position.x}px`,
|
|
3235
3240
|
top: `${this.contextMenuState.position.y}px`,
|
|
3236
3241
|
zIndex: '10002',
|
|
3237
|
-
}, onActionSelected: event => this.handleContextMenuActionSelected(event), onClose: () => this.hideContextMenu() })), index.h("kritzel-sync-indicator", { key: '
|
|
3242
|
+
}, onActionSelected: event => this.handleContextMenuActionSelected(event), onClose: () => this.hideContextMenu() })), index.h("kritzel-sync-indicator", { key: '9622af1634a939d6689d5a133496d536f5d7865f', visible: this.isSyncing, text: "Syncing..." }), index.h("kritzel-loading-overlay", { key: '00f142a3eb8b23fbd0bdb6ec53ae95855434516c', visible: this.isLoadingOverlayVisible, text: this.resolvedTerms['editor.loading'] ?? 'Loading...' }), index.h("kritzel-engine", { key: '39b1d7bfce0a5347fec60cd135d6db8fb4250bfa', ref: el => {
|
|
3238
3243
|
if (el) {
|
|
3239
3244
|
this.engineRef = el;
|
|
3240
3245
|
}
|
|
3241
|
-
}, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, 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), onNotificationsChange: event => this.handleNotificationsChange(event), onContextMenuStateChange: event => this.handleContextMenuStateChange(event), onSyncingChange: event => (this.isSyncing = event.detail), onLoadingChange: event => (this.isLoadingOverlayVisible = event.detail) }), index.h("kritzel-controls", { key: '
|
|
3246
|
+
}, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, 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), onNotificationsChange: event => this.handleNotificationsChange(event), onContextMenuStateChange: event => this.handleContextMenuStateChange(event), onSyncingChange: event => (this.isSyncing = event.detail), onLoadingChange: event => (this.isLoadingOverlayVisible = event.detail) }), index.h("kritzel-controls", { key: '9a708f21ab92ecc6953a35f5db8001b2403f330e', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
|
|
3242
3247
|
if (el) {
|
|
3243
3248
|
this.controlsRef = el;
|
|
3244
3249
|
}
|
|
3245
|
-
}, controls: this.normalizedControls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '
|
|
3250
|
+
}, controls: this.normalizedControls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '6a2b1cd72099db86952ac31aad9655693d0a6a0d', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: 'b3756514bf8de6de8ac7586c626f9759da7f231d', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: 'd5e118bc4d60b867006bac287c5516baf3aa890e', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'e4065d36beb9283cdbbf3ae9800f4f8011c0b4f0', ref: el => {
|
|
3246
3251
|
if (el) {
|
|
3247
3252
|
this.settingsRef = el;
|
|
3248
3253
|
}
|
|
3249
|
-
}, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '
|
|
3254
|
+
}, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '4e18d9b42c9e7fef9433577b983c508559461da2', ref: el => {
|
|
3250
3255
|
if (el) {
|
|
3251
3256
|
this.exportRef = el;
|
|
3252
3257
|
}
|
|
3253
|
-
}, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '
|
|
3258
|
+
}, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '56e6b29c9c3dcd7b68af4a1ef7d34224d592ce34', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '3b90bcc7e583fcf3c6845d222ad181ccf234a423', user: this.user, terms: this.resolvedTerms, onClick: () => this.currentUserDialogRef?.open() }), shouldShowCurrentUser && (index.h("kritzel-current-user-dialog", { key: 'f87702c5a708b6afdb2bc3760895e3e3650a4cb5', ref: el => {
|
|
3254
3259
|
if (el) {
|
|
3255
3260
|
this.currentUserDialogRef = el;
|
|
3256
3261
|
}
|
|
3257
|
-
}, user: this.user, terms: this.resolvedTerms, onLogoutRequest: this.handleCurrentUserLogout })), shouldShowLoginButton && index.h("kritzel-button", { key: '
|
|
3262
|
+
}, user: this.user, terms: this.resolvedTerms, onLogoutRequest: this.handleCurrentUserLogout })), shouldShowLoginButton && index.h("kritzel-button", { key: '015bc0f4c66352136de66d35d28c20070366933f', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in'), index.h("kritzel-more-menu", { key: 'b3fdf9fdb2a34508002e82dfdef9e9bdbbe57ecc', items: this.resolvedMoreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: 'bac4fbbfa04c261e2b128e766cc43c1062aec2ff', ref: el => {
|
|
3258
3263
|
if (el) {
|
|
3259
3264
|
this.shareDialogRef = el;
|
|
3260
3265
|
}
|
|
3261
|
-
}, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '
|
|
3266
|
+
}, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'a7d52bad9e752ad546adf80d257c6a46de031e76', ref: el => {
|
|
3262
3267
|
if (el) {
|
|
3263
3268
|
this.loginDialogRef = el;
|
|
3264
3269
|
}
|
|
@@ -22301,6 +22306,10 @@ class KritzelObjectMap {
|
|
|
22301
22306
|
_awarenessChangeHandler = null;
|
|
22302
22307
|
_awarenessChangeCallbacks = [];
|
|
22303
22308
|
_objectsChangeCallbacks = [];
|
|
22309
|
+
_remoteChangesCallbacks = [];
|
|
22310
|
+
_docUpdateHandler = null;
|
|
22311
|
+
_remoteOrigins = new Set();
|
|
22312
|
+
_appliedRemoteChanges = false;
|
|
22304
22313
|
_lastAwarenessEmitTime = 0;
|
|
22305
22314
|
_awarenessEmitTimeout = null;
|
|
22306
22315
|
AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
|
|
@@ -22324,12 +22333,44 @@ class KritzelObjectMap {
|
|
|
22324
22333
|
return this._isReady;
|
|
22325
22334
|
}
|
|
22326
22335
|
/**
|
|
22327
|
-
* Promise that settles when network providers finish connecting
|
|
22328
|
-
* Resolves immediately if no network providers.
|
|
22336
|
+
* Promise that settles when network providers finish connecting and complete
|
|
22337
|
+
* their Yjs sync handshake. Resolves immediately if no network providers.
|
|
22329
22338
|
*/
|
|
22330
22339
|
whenNetworkSynced() {
|
|
22331
22340
|
return this._networkSyncPromise ?? Promise.resolve();
|
|
22332
22341
|
}
|
|
22342
|
+
/**
|
|
22343
|
+
* Whether any configured provider synchronizes over the network.
|
|
22344
|
+
*/
|
|
22345
|
+
get hasNetworkSyncProvider() {
|
|
22346
|
+
return this._providers.some(provider => provider.type === 'network');
|
|
22347
|
+
}
|
|
22348
|
+
/**
|
|
22349
|
+
* Whether remote-originated changes can be told apart from local writes.
|
|
22350
|
+
* Requires every network provider to expose its Yjs update origin.
|
|
22351
|
+
*/
|
|
22352
|
+
get supportsRemoteChangeDetection() {
|
|
22353
|
+
const networkProviders = this._providers.filter(provider => provider.type === 'network');
|
|
22354
|
+
return networkProviders.length > 0 && networkProviders.every(provider => provider.updateOrigin !== undefined);
|
|
22355
|
+
}
|
|
22356
|
+
/**
|
|
22357
|
+
* Whether at least one update originating from a network provider has been
|
|
22358
|
+
* applied to this document. Stays `false` when the remote held no data the
|
|
22359
|
+
* local document was missing, because Yjs suppresses empty updates.
|
|
22360
|
+
*/
|
|
22361
|
+
get hasAppliedRemoteChanges() {
|
|
22362
|
+
return this._appliedRemoteChanges;
|
|
22363
|
+
}
|
|
22364
|
+
/**
|
|
22365
|
+
* Registers a callback invoked whenever a network provider applies changes to this document.
|
|
22366
|
+
* @returns A function that unregisters the callback.
|
|
22367
|
+
*/
|
|
22368
|
+
onRemoteChangesApplied(callback) {
|
|
22369
|
+
this._remoteChangesCallbacks.push(callback);
|
|
22370
|
+
return () => {
|
|
22371
|
+
this._remoteChangesCallbacks = this._remoteChangesCallbacks.filter(registered => registered !== callback);
|
|
22372
|
+
};
|
|
22373
|
+
}
|
|
22333
22374
|
/**
|
|
22334
22375
|
* Returns the Yjs Awareness instance, if a network provider is available.
|
|
22335
22376
|
*/
|
|
@@ -22572,6 +22613,22 @@ class KritzelObjectMap {
|
|
|
22572
22613
|
const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
|
|
22573
22614
|
console.info(`[Kritzel] Workspace sync providers initialized for ${docName}: ${providerList || 'none'}`);
|
|
22574
22615
|
}
|
|
22616
|
+
// Registered before any provider connects so no remote update can be missed.
|
|
22617
|
+
for (const provider of this._providers) {
|
|
22618
|
+
if (provider.type === 'network' && provider.updateOrigin !== undefined) {
|
|
22619
|
+
this._remoteOrigins.add(provider.updateOrigin);
|
|
22620
|
+
}
|
|
22621
|
+
}
|
|
22622
|
+
this._docUpdateHandler = (_update, origin) => {
|
|
22623
|
+
if (!this._remoteOrigins.has(origin)) {
|
|
22624
|
+
return;
|
|
22625
|
+
}
|
|
22626
|
+
this._appliedRemoteChanges = true;
|
|
22627
|
+
for (const callback of [...this._remoteChangesCallbacks]) {
|
|
22628
|
+
callback();
|
|
22629
|
+
}
|
|
22630
|
+
};
|
|
22631
|
+
this._ydoc.on('update', this._docUpdateHandler);
|
|
22575
22632
|
// captureTimeout is effectively infinite — undo-step boundaries are
|
|
22576
22633
|
// marked explicitly via markUndoBoundary() in insert/remove/reset and
|
|
22577
22634
|
// at stroke/gesture boundaries in the tools. This prevents a single
|
|
@@ -22625,12 +22682,15 @@ class KritzelObjectMap {
|
|
|
22625
22682
|
if (showSyncProviderInfo && networkProviders.length > 0) {
|
|
22626
22683
|
console.info(`[Kritzel] Workspace network providers connecting in background: ${networkProviders.length}`);
|
|
22627
22684
|
}
|
|
22628
|
-
const networkConnectPromises = networkProviders.map(provider => provider
|
|
22685
|
+
const networkConnectPromises = networkProviders.map(provider => provider
|
|
22686
|
+
.connect()
|
|
22687
|
+
.then(() => provider.whenSynced?.())
|
|
22688
|
+
.catch(err => {
|
|
22629
22689
|
if (showSyncProviderInfo) {
|
|
22630
22690
|
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
22631
22691
|
}
|
|
22632
22692
|
}));
|
|
22633
|
-
//
|
|
22693
|
+
// Settles once remote data has been reconciled, not merely once the transport connected
|
|
22634
22694
|
this._networkSyncPromise = networkConnectPromises.length > 0 ? Promise.allSettled(networkConnectPromises).then(() => undefined) : null;
|
|
22635
22695
|
this._isReady = true;
|
|
22636
22696
|
// Find the first provider that exposes awareness (network providers)
|
|
@@ -23288,6 +23348,13 @@ class KritzelObjectMap {
|
|
|
23288
23348
|
this._objectsMap.unobserve(this._objectsObserver);
|
|
23289
23349
|
this._objectsObserver = null;
|
|
23290
23350
|
}
|
|
23351
|
+
if (this._ydoc && this._docUpdateHandler) {
|
|
23352
|
+
this._ydoc.off('update', this._docUpdateHandler);
|
|
23353
|
+
}
|
|
23354
|
+
this._docUpdateHandler = null;
|
|
23355
|
+
this._remoteOrigins.clear();
|
|
23356
|
+
this._remoteChangesCallbacks = [];
|
|
23357
|
+
this._appliedRemoteChanges = false;
|
|
23291
23358
|
// Remove undo manager event listeners
|
|
23292
23359
|
if (this._undoManager) {
|
|
23293
23360
|
if (this._stackItemAddedHandler) {
|
|
@@ -23783,7 +23850,10 @@ class KritzelAppStateMap {
|
|
|
23783
23850
|
if (showSyncProviderInfo && networkProviders.length > 0) {
|
|
23784
23851
|
console.info(`[Kritzel] App-state network providers connecting in background: ${networkProviders.length}`);
|
|
23785
23852
|
}
|
|
23786
|
-
const networkConnectPromises = networkProviders.map(provider => provider
|
|
23853
|
+
const networkConnectPromises = networkProviders.map(provider => provider
|
|
23854
|
+
.connect()
|
|
23855
|
+
.then(() => provider.whenSynced?.())
|
|
23856
|
+
.catch(err => {
|
|
23787
23857
|
if (showSyncProviderInfo) {
|
|
23788
23858
|
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
23789
23859
|
}
|
|
@@ -27887,7 +27957,7 @@ const KritzelEngine = class {
|
|
|
27887
27957
|
contextMenuStateChange;
|
|
27888
27958
|
/** Emitted when the combined loading state (external `isLoading` prop or internal workspace loading) changes. */
|
|
27889
27959
|
loadingChange;
|
|
27890
|
-
/** Emitted
|
|
27960
|
+
/** Emitted while remote changes are being applied to the active workspace. Stays silent when the remote holds nothing new. */
|
|
27891
27961
|
syncingChange;
|
|
27892
27962
|
forceUpdate = 0;
|
|
27893
27963
|
isSyncing = false;
|
|
@@ -29474,6 +29544,7 @@ const KritzelEngine = class {
|
|
|
29474
29544
|
_sceneBootstrapOpacity = 1;
|
|
29475
29545
|
_sceneBootstrapTransitionEnabled = false;
|
|
29476
29546
|
_sceneBootstrapTransitionTimer = null;
|
|
29547
|
+
_remoteChangesUnsubscribe = null;
|
|
29477
29548
|
_defaultUndoState = {
|
|
29478
29549
|
canUndo: false,
|
|
29479
29550
|
canRedo: false,
|
|
@@ -29491,9 +29562,16 @@ const KritzelEngine = class {
|
|
|
29491
29562
|
this.loadingChange.emit(this.core.store.state.isLoading);
|
|
29492
29563
|
}
|
|
29493
29564
|
syncSyncingState(isSyncing) {
|
|
29565
|
+
if (this.isSyncing === isSyncing) {
|
|
29566
|
+
return;
|
|
29567
|
+
}
|
|
29494
29568
|
this.isSyncing = isSyncing;
|
|
29495
29569
|
this.syncingChange.emit(isSyncing);
|
|
29496
29570
|
}
|
|
29571
|
+
clearRemoteChangesSubscription() {
|
|
29572
|
+
this._remoteChangesUnsubscribe?.();
|
|
29573
|
+
this._remoteChangesUnsubscribe = null;
|
|
29574
|
+
}
|
|
29497
29575
|
isNetworkAvailable() {
|
|
29498
29576
|
return typeof navigator !== 'undefined' && navigator.onLine;
|
|
29499
29577
|
}
|
|
@@ -29605,6 +29683,7 @@ const KritzelEngine = class {
|
|
|
29605
29683
|
disconnectedCallback() {
|
|
29606
29684
|
this.throttledPointerMoveMulti.cancel();
|
|
29607
29685
|
this._revealedImageKeys.clear();
|
|
29686
|
+
this.clearRemoteChangesSubscription();
|
|
29608
29687
|
if (this._sceneBootstrapTransitionTimer !== null) {
|
|
29609
29688
|
clearTimeout(this._sceneBootstrapTransitionTimer);
|
|
29610
29689
|
this._sceneBootstrapTransitionTimer = null;
|
|
@@ -29859,21 +29938,7 @@ const KritzelEngine = class {
|
|
|
29859
29938
|
this._workspaceInitializationTargetKey = targetKey;
|
|
29860
29939
|
try {
|
|
29861
29940
|
await initializationPromise;
|
|
29862
|
-
|
|
29863
|
-
const initializedObjects = this.core.store.objects;
|
|
29864
|
-
const networkSyncPromise = initializedObjects?.whenNetworkSynced();
|
|
29865
|
-
if (networkSyncPromise && this.isNetworkAvailable()) {
|
|
29866
|
-
this.syncSyncingState(true);
|
|
29867
|
-
void networkSyncPromise.finally(() => {
|
|
29868
|
-
// Only clear syncing for the currently active object map.
|
|
29869
|
-
if (this.core.store.objects === initializedObjects) {
|
|
29870
|
-
this.syncSyncingState(false);
|
|
29871
|
-
}
|
|
29872
|
-
});
|
|
29873
|
-
}
|
|
29874
|
-
else {
|
|
29875
|
-
this.syncSyncingState(false);
|
|
29876
|
-
}
|
|
29941
|
+
this.trackRemoteSynchronization();
|
|
29877
29942
|
}
|
|
29878
29943
|
finally {
|
|
29879
29944
|
// Only the most recent init clears loading state to prevent race conditions
|
|
@@ -29885,6 +29950,45 @@ const KritzelEngine = class {
|
|
|
29885
29950
|
}
|
|
29886
29951
|
}
|
|
29887
29952
|
}
|
|
29953
|
+
/**
|
|
29954
|
+
* Reports syncing only while remote data is actually being reconciled into the
|
|
29955
|
+
* active workspace. Yjs suppresses updates that carry nothing new, so a workspace
|
|
29956
|
+
* that already matches the remote never reports syncing at all.
|
|
29957
|
+
*/
|
|
29958
|
+
trackRemoteSynchronization() {
|
|
29959
|
+
this.clearRemoteChangesSubscription();
|
|
29960
|
+
this.syncSyncingState(false);
|
|
29961
|
+
const initializedObjects = this.core.store.objects;
|
|
29962
|
+
if (!initializedObjects?.hasNetworkSyncProvider || !this.isNetworkAvailable()) {
|
|
29963
|
+
return;
|
|
29964
|
+
}
|
|
29965
|
+
if (initializedObjects.supportsRemoteChangeDetection) {
|
|
29966
|
+
this._remoteChangesUnsubscribe = initializedObjects.onRemoteChangesApplied(() => {
|
|
29967
|
+
if (this.core.store.objects !== initializedObjects) {
|
|
29968
|
+
return;
|
|
29969
|
+
}
|
|
29970
|
+
this.syncSyncingState(true);
|
|
29971
|
+
});
|
|
29972
|
+
// The network provider may have already applied remote changes between
|
|
29973
|
+
// object-map initialization and this subscription (e.g. an already-connected
|
|
29974
|
+
// shared socket resolves the sync handshake almost immediately). Without this
|
|
29975
|
+
// check that update is missed entirely and the indicator never appears.
|
|
29976
|
+
if (initializedObjects.hasAppliedRemoteChanges) {
|
|
29977
|
+
this.syncSyncingState(true);
|
|
29978
|
+
}
|
|
29979
|
+
}
|
|
29980
|
+
else {
|
|
29981
|
+
// Custom providers without an update origin cannot be diffed, so fall back to reporting the whole handshake.
|
|
29982
|
+
this.syncSyncingState(true);
|
|
29983
|
+
}
|
|
29984
|
+
void initializedObjects.whenNetworkSynced().finally(() => {
|
|
29985
|
+
if (this.core.store.objects !== initializedObjects) {
|
|
29986
|
+
return;
|
|
29987
|
+
}
|
|
29988
|
+
this.clearRemoteChangesSubscription();
|
|
29989
|
+
this.syncSyncingState(false);
|
|
29990
|
+
});
|
|
29991
|
+
}
|
|
29888
29992
|
emitObjectsChange() {
|
|
29889
29993
|
const objectsMap = this.core.store.objects;
|
|
29890
29994
|
if (!objectsMap) {
|
|
@@ -32029,7 +32133,7 @@ const KritzelPortal = class {
|
|
|
32029
32133
|
* This file is auto-generated by the version bump scripts.
|
|
32030
32134
|
* Do not modify manually.
|
|
32031
32135
|
*/
|
|
32032
|
-
const KRITZEL_VERSION = '0.4.
|
|
32136
|
+
const KRITZEL_VERSION = '0.4.23';
|
|
32033
32137
|
|
|
32034
32138
|
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-tab-panel{display:none}.settings-tab-panel.is-active{display:block}.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)}`;
|
|
32035
32139
|
|
|
@@ -17,9 +17,14 @@ export class HocuspocusSyncProvider {
|
|
|
17
17
|
_connectionStatus = 'disconnected';
|
|
18
18
|
visibilityHandler = null;
|
|
19
19
|
onlineHandler = null;
|
|
20
|
+
syncedResolvers = [];
|
|
20
21
|
get awareness() {
|
|
21
22
|
return this.provider.awareness;
|
|
22
23
|
}
|
|
24
|
+
/** HocuspocusProvider applies remote updates with itself as the Yjs transaction origin. */
|
|
25
|
+
get updateOrigin() {
|
|
26
|
+
return this.provider;
|
|
27
|
+
}
|
|
23
28
|
get connectionStatus() {
|
|
24
29
|
return this._connectionStatus;
|
|
25
30
|
}
|
|
@@ -76,6 +81,7 @@ export class HocuspocusSyncProvider {
|
|
|
76
81
|
}
|
|
77
82
|
this.isSynced = true;
|
|
78
83
|
this._connectionStatus = 'synced';
|
|
84
|
+
this.releaseSyncedWaiters();
|
|
79
85
|
if (!options?.quiet) {
|
|
80
86
|
console.info(`Hocuspocus synced: ${name}`);
|
|
81
87
|
}
|
|
@@ -286,6 +292,19 @@ export class HocuspocusSyncProvider {
|
|
|
286
292
|
this.disconnect();
|
|
287
293
|
return this.connect();
|
|
288
294
|
}
|
|
295
|
+
releaseSyncedWaiters() {
|
|
296
|
+
const resolvers = this.syncedResolvers;
|
|
297
|
+
this.syncedResolvers = [];
|
|
298
|
+
resolvers.forEach(resolve => resolve());
|
|
299
|
+
}
|
|
300
|
+
async whenSynced() {
|
|
301
|
+
if (this.isSynced || this.isDestroyed || this.provider.isSynced) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
return new Promise(resolve => {
|
|
305
|
+
this.syncedResolvers.push(resolve);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
289
308
|
disconnect() {
|
|
290
309
|
// Cancel any pending connection attempt
|
|
291
310
|
if (this.connectTimeout) {
|
|
@@ -307,6 +326,7 @@ export class HocuspocusSyncProvider {
|
|
|
307
326
|
this.isConnected = false;
|
|
308
327
|
this.isSynced = false;
|
|
309
328
|
this._connectionStatus = 'disconnected';
|
|
329
|
+
this.releaseSyncedWaiters();
|
|
310
330
|
}
|
|
311
331
|
destroy() {
|
|
312
332
|
// Mark as destroyed first to prevent any callbacks from doing work
|
|
@@ -326,5 +346,6 @@ export class HocuspocusSyncProvider {
|
|
|
326
346
|
this.isConnected = false;
|
|
327
347
|
this.isSynced = false;
|
|
328
348
|
this._connectionStatus = 'disconnected';
|
|
349
|
+
this.releaseSyncedWaiters();
|
|
329
350
|
}
|
|
330
351
|
}
|
|
@@ -8,9 +8,15 @@ export class WebSocketSyncProvider {
|
|
|
8
8
|
provider;
|
|
9
9
|
isConnected = false;
|
|
10
10
|
_quiet = false;
|
|
11
|
+
_isSynced = false;
|
|
12
|
+
_syncedResolvers = [];
|
|
11
13
|
get awareness() {
|
|
12
14
|
return this.provider.awareness;
|
|
13
15
|
}
|
|
16
|
+
/** y-websocket applies remote updates with the underlying provider as the Yjs transaction origin. */
|
|
17
|
+
get updateOrigin() {
|
|
18
|
+
return this.provider;
|
|
19
|
+
}
|
|
14
20
|
constructor(docName, doc, options) {
|
|
15
21
|
const url = options?.url || 'ws://localhost:1234';
|
|
16
22
|
const roomName = options?.roomName || docName;
|
|
@@ -57,11 +63,29 @@ export class WebSocketSyncProvider {
|
|
|
57
63
|
}
|
|
58
64
|
});
|
|
59
65
|
this.provider.on('sync', (synced) => {
|
|
60
|
-
if (synced
|
|
66
|
+
if (!synced) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this._isSynced = true;
|
|
70
|
+
this.releaseSyncedWaiters();
|
|
71
|
+
if (!this._quiet) {
|
|
61
72
|
console.info('WebSocket synced');
|
|
62
73
|
}
|
|
63
74
|
});
|
|
64
75
|
}
|
|
76
|
+
releaseSyncedWaiters() {
|
|
77
|
+
const resolvers = this._syncedResolvers;
|
|
78
|
+
this._syncedResolvers = [];
|
|
79
|
+
resolvers.forEach(resolve => resolve());
|
|
80
|
+
}
|
|
81
|
+
async whenSynced() {
|
|
82
|
+
if (this._isSynced || this.provider.synced) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return new Promise(resolve => {
|
|
86
|
+
this._syncedResolvers.push(resolve);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
65
89
|
async connect() {
|
|
66
90
|
if (this.isConnected) {
|
|
67
91
|
return;
|
|
@@ -93,6 +117,8 @@ export class WebSocketSyncProvider {
|
|
|
93
117
|
this.provider.disconnect();
|
|
94
118
|
}
|
|
95
119
|
this.isConnected = false;
|
|
120
|
+
this._isSynced = false;
|
|
121
|
+
this.releaseSyncedWaiters();
|
|
96
122
|
}
|
|
97
123
|
async reconnect() {
|
|
98
124
|
this.disconnect();
|
|
@@ -103,5 +129,7 @@ export class WebSocketSyncProvider {
|
|
|
103
129
|
this.provider.destroy();
|
|
104
130
|
}
|
|
105
131
|
this.isConnected = false;
|
|
132
|
+
this._isSynced = false;
|
|
133
|
+
this.releaseSyncedWaiters();
|
|
106
134
|
}
|
|
107
135
|
}
|
|
@@ -120,7 +120,10 @@ export class KritzelAppStateMap {
|
|
|
120
120
|
if (showSyncProviderInfo && networkProviders.length > 0) {
|
|
121
121
|
console.info(`[Kritzel] App-state network providers connecting in background: ${networkProviders.length}`);
|
|
122
122
|
}
|
|
123
|
-
const networkConnectPromises = networkProviders.map(provider => provider
|
|
123
|
+
const networkConnectPromises = networkProviders.map(provider => provider
|
|
124
|
+
.connect()
|
|
125
|
+
.then(() => provider.whenSynced?.())
|
|
126
|
+
.catch(err => {
|
|
124
127
|
if (showSyncProviderInfo) {
|
|
125
128
|
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
126
129
|
}
|