kritzel-stencil 0.1.73 → 0.1.75
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-Dc7LOVhs.js +2 -2
- package/dist/cjs/index.cjs.js +131 -86
- package/dist/cjs/{kritzel-active-users_41.cjs.entry.js → kritzel-active-users_42.cjs.entry.js} +593 -177
- package/dist/cjs/kritzel-brush-style.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/stencil.cjs.js +1 -1
- package/dist/cjs/{workspace.migrations-DcwqsqPC.js → workspace.migrations-DkmVO6dE.js} +164 -49
- package/dist/collection/classes/core/core.class.js +9 -1
- package/dist/collection/classes/core/store.class.js +20 -6
- package/dist/collection/classes/core/viewport.class.js +9 -3
- package/dist/collection/classes/handlers/selection.handler.js +15 -2
- package/dist/collection/classes/managers/anchor.manager.js +101 -44
- package/dist/collection/classes/objects/base-object.class.js +2 -0
- package/dist/collection/classes/objects/custom-element.class.js +1 -0
- package/dist/collection/classes/objects/group.class.js +1 -0
- package/dist/collection/classes/objects/image.class.js +1 -0
- package/dist/collection/classes/objects/line.class.js +1 -0
- package/dist/collection/classes/objects/path.class.js +1 -0
- package/dist/collection/classes/objects/selection-box.class.js +1 -0
- package/dist/collection/classes/objects/selection-group.class.js +13 -1
- package/dist/collection/classes/objects/shape.class.js +1 -0
- package/dist/collection/classes/objects/text.class.js +1 -0
- 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 +85 -7
- package/dist/collection/classes/tools/brush-tool.class.js +4 -0
- package/dist/collection/classes/tools/line-tool.class.js +4 -0
- package/dist/collection/classes/tools/shape-tool.class.js +2 -0
- package/dist/collection/collection-manifest.json +3 -2
- package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +110 -0
- package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +347 -0
- package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.js +1 -1
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +3 -3
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +150 -109
- package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
- package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.js +1 -1
- package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
- package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
- package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
- package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.js +1 -1
- package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
- package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +1 -1
- package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
- package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
- package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
- package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
- package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
- package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
- package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
- package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
- package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
- package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
- package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
- package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
- package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.js +1 -1
- package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +5 -5
- package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +1 -1
- package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +1 -1
- package/dist/collection/components/ui/kritzel-export/kritzel-export.js +1 -1
- package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
- package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +1 -1
- package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +1 -1
- package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
- package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
- package/dist/collection/constants/schema.constants.js +1 -1
- package/dist/collection/constants/version.js +1 -1
- package/dist/collection/interfaces/remote-cursor.interface.js +1 -0
- package/dist/collection/migrations/workspace.migrations.js +10 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-active-users.js +1 -1
- package/dist/components/kritzel-avatar.js +1 -1
- package/dist/components/kritzel-awareness-cursors.d.ts +11 -0
- package/dist/components/kritzel-awareness-cursors.js +1 -0
- package/dist/components/kritzel-back-to-content.js +1 -1
- package/dist/components/kritzel-brush-style.js +1 -1
- package/dist/components/kritzel-button.js +1 -1
- package/dist/components/kritzel-color-palette.js +1 -1
- package/dist/components/kritzel-color.js +1 -1
- package/dist/components/kritzel-context-menu.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-current-user-dialog.js +1 -1
- package/dist/components/kritzel-current-user.js +1 -1
- package/dist/components/kritzel-cursor-trail.js +1 -1
- package/dist/components/kritzel-dropdown.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-export.js +1 -1
- package/dist/components/kritzel-font-family.js +1 -1
- package/dist/components/kritzel-font-size.js +1 -1
- package/dist/components/kritzel-font.js +1 -1
- package/dist/components/kritzel-input.js +1 -1
- package/dist/components/kritzel-login-dialog.js +1 -1
- package/dist/components/kritzel-master-detail.js +1 -1
- package/dist/components/kritzel-menu-item.js +1 -1
- package/dist/components/kritzel-menu.js +1 -1
- package/dist/components/kritzel-more-menu.js +1 -1
- package/dist/components/kritzel-numeric-input.js +1 -1
- package/dist/components/kritzel-opacity-slider.js +1 -1
- package/dist/components/kritzel-portal.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-share-dialog.js +1 -1
- package/dist/components/kritzel-slide-toggle.js +1 -1
- package/dist/components/kritzel-split-button.js +1 -1
- package/dist/components/kritzel-stroke-size.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/kritzel-tooltip.js +1 -1
- package/dist/components/kritzel-utility-panel.js +1 -1
- package/dist/components/kritzel-workspace-manager.js +1 -1
- package/dist/components/{p-Dp8idtVD.js → p-0kShCfeb.js} +1 -1
- package/dist/components/{p-B47JuZiD.js → p-2OYw6GJ7.js} +1 -1
- package/dist/components/p-7o2FWtFx.js +1 -0
- package/dist/components/{p-C5KuV1pK.js → p-BA0ayKqO.js} +1 -1
- package/dist/components/{p-NbNVTRk6.js → p-BEJQ2kP7.js} +1 -1
- package/dist/components/p-BSipRoFx.js +1 -0
- package/dist/components/{p-CDadAOMw.js → p-BeFUNGEI.js} +1 -1
- package/dist/components/{p-35nrk8s0.js → p-BiByyU2C.js} +1 -1
- package/dist/components/{p-CCAWSyDD.js → p-BiouZo1q.js} +1 -1
- package/dist/components/{p-CSExtYKI.js → p-ByR0VXeU.js} +1 -1
- package/dist/components/{p-1MGcXTLv.js → p-C1uR_ZNW.js} +1 -1
- package/dist/components/{p-x8PzaMuD.js → p-C69Stayh.js} +1 -1
- package/dist/components/{p-Ch0UlFwq.js → p-C7SBI_0T.js} +1 -1
- package/dist/components/{p-DEzfXrGX.js → p-CAIGuV2J.js} +1 -1
- package/dist/components/p-CJ2eHeoV.js +1 -0
- package/dist/components/p-CW-VyJgK.js +1 -0
- package/dist/components/{p-DW4ADV9w.js → p-CZhyKp-f.js} +1 -1
- package/dist/components/p-CsR4owzk.js +1 -0
- package/dist/components/{p-BG1IxseV.js → p-CsoDfhD5.js} +1 -1
- package/dist/components/{p-BgDhcDNw.js → p-D0MQFmqi.js} +1 -1
- package/dist/components/{p-DpFu5yAt.js → p-D1O7DxL4.js} +1 -1
- package/dist/components/{p-B5ouV8EQ.js → p-DRbG92F9.js} +1 -1
- package/dist/components/{p-C3eaM9TB.js → p-DS0xx1eT.js} +1 -1
- package/dist/components/{p-jx8VOz7S.js → p-DSzQ6H2j.js} +1 -1
- package/dist/components/{p-DsIlDGDO.js → p-DXjuuVq9.js} +1 -1
- package/dist/components/p-DXpYcAnT.js +1 -0
- package/dist/components/{p-DiFVw6IQ.js → p-Da46jw3N.js} +1 -1
- package/dist/components/{p-C6kZf91d.js → p-Dj_Qjga5.js} +1 -1
- package/dist/components/{p-Do0Q5-iC.js → p-DvIEvoZu.js} +1 -1
- package/dist/components/{p-CnVzLD5e.js → p-GYI7sDxr.js} +1 -1
- package/dist/components/{p-CcBM_ClD.js → p-HLbqRJGs.js} +1 -1
- package/dist/components/{p-VHyNcODZ.js → p-KQzWumjB.js} +1 -1
- package/dist/components/{p-VAkeZOZL.js → p-TyR-YTXm.js} +1 -1
- package/dist/components/p-WmxufeOo.js +9 -0
- package/dist/components/{p-CHtn5xr6.js → p-b4gyXoju.js} +1 -1
- package/dist/components/p-iRL0wQHQ.js +1 -0
- package/dist/components/{p-CqLaHE27.js → p-kj9wbLY8.js} +1 -1
- package/dist/components/{p-DaHq4iG1.js → p-xM-_OeRO.js} +1 -1
- package/dist/esm/index-MV-81ybv.js +2 -2
- package/dist/esm/index.js +132 -87
- package/dist/esm/{kritzel-active-users_41.entry.js → kritzel-active-users_42.entry.js} +593 -178
- package/dist/esm/kritzel-brush-style.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/stencil.js +1 -1
- package/dist/esm/{workspace.migrations-BGixvB76.js → workspace.migrations-D48_Bqvh.js} +164 -49
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-0dbd9a2f.entry.js +9 -0
- package/dist/stencil/p-D48_Bqvh.js +1 -0
- package/dist/stencil/{p-016ad76a.entry.js → p-fc21e29c.entry.js} +1 -1
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/core/store.class.d.ts +10 -2
- package/dist/types/classes/managers/anchor.manager.d.ts +4 -0
- package/dist/types/classes/objects/base-object.class.d.ts +1 -0
- package/dist/types/classes/objects/selection-group.class.d.ts +5 -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 +38 -0
- package/dist/types/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +26 -0
- package/dist/types/components.d.ts +39 -4
- package/dist/types/constants/schema.constants.d.ts +1 -1
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/interfaces/object.interface.d.ts +1 -0
- package/dist/types/interfaces/remote-cursor.interface.d.ts +17 -0
- package/dist/types/interfaces/sync-provider.interface.d.ts +16 -0
- package/dist/types/interfaces/theme.interface.d.ts +7 -0
- package/package.json +1 -1
- package/dist/components/p-BvToKcu1.js +0 -1
- package/dist/components/p-By3NOY-k.js +0 -9
- package/dist/components/p-CNro30tB.js +0 -1
- package/dist/components/p-Duv3EM3w.js +0 -1
- package/dist/components/p-KFsLHwYm.js +0 -1
- package/dist/components/p-hCORwbZh.js +0 -1
- package/dist/stencil/p-67c79d75.entry.js +0 -9
- package/dist/stencil/p-BGixvB76.js +0 -1
|
@@ -4,6 +4,7 @@ import { HocuspocusProvider, HocuspocusProviderWebsocket } from "@hocuspocus/pro
|
|
|
4
4
|
* Supports multiplexing - multiple documents can share the same WebSocket connection
|
|
5
5
|
*/
|
|
6
6
|
export class HocuspocusSyncProvider {
|
|
7
|
+
type = 'network';
|
|
7
8
|
provider;
|
|
8
9
|
isConnected = false;
|
|
9
10
|
isSynced = false;
|
|
@@ -11,16 +12,87 @@ export class HocuspocusSyncProvider {
|
|
|
11
12
|
isDestroyed = false;
|
|
12
13
|
connectTimeout = null;
|
|
13
14
|
pendingConnectReject = null;
|
|
15
|
+
connectionTimeoutMs;
|
|
16
|
+
_connectionStatus = 'disconnected';
|
|
17
|
+
visibilityHandler = null;
|
|
18
|
+
onlineHandler = null;
|
|
14
19
|
get awareness() {
|
|
15
20
|
return this.provider.awareness;
|
|
16
21
|
}
|
|
22
|
+
get connectionStatus() {
|
|
23
|
+
return this._connectionStatus;
|
|
24
|
+
}
|
|
17
25
|
// Static shared WebSocket instance for multiplexing
|
|
18
26
|
static sharedWebSocketProvider = null;
|
|
19
27
|
constructor(docName, doc, options) {
|
|
20
28
|
const name = options?.name || docName;
|
|
21
29
|
const url = options?.url || 'ws://localhost:1234';
|
|
30
|
+
this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
|
|
22
31
|
// Use provided websocketProvider or the static shared one
|
|
23
32
|
const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
33
|
+
// Build reconnect config from options
|
|
34
|
+
const reconnectConfig = {};
|
|
35
|
+
if (options?.delay !== undefined)
|
|
36
|
+
reconnectConfig.delay = options.delay;
|
|
37
|
+
if (options?.factor !== undefined)
|
|
38
|
+
reconnectConfig.factor = options.factor;
|
|
39
|
+
if (options?.maxAttempts !== undefined)
|
|
40
|
+
reconnectConfig.maxAttempts = options.maxAttempts;
|
|
41
|
+
if (options?.minDelay !== undefined)
|
|
42
|
+
reconnectConfig.minDelay = options.minDelay;
|
|
43
|
+
if (options?.maxDelay !== undefined)
|
|
44
|
+
reconnectConfig.maxDelay = options.maxDelay;
|
|
45
|
+
const onConnect = () => {
|
|
46
|
+
if (this.isDestroyed) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.isConnected = true;
|
|
50
|
+
this._connectionStatus = 'connected';
|
|
51
|
+
if (!options?.quiet) {
|
|
52
|
+
console.info(`Hocuspocus connected: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
if (options?.onConnect) {
|
|
55
|
+
options.onConnect();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const onDisconnect = () => {
|
|
59
|
+
if (this.isDestroyed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.isConnected = false;
|
|
63
|
+
this.isSynced = false;
|
|
64
|
+
this._connectionStatus = 'disconnected';
|
|
65
|
+
if (!options?.quiet) {
|
|
66
|
+
console.info(`Hocuspocus disconnected: ${name}`);
|
|
67
|
+
}
|
|
68
|
+
if (options?.onDisconnect) {
|
|
69
|
+
options.onDisconnect();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const onSynced = () => {
|
|
73
|
+
if (this.isDestroyed) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.isSynced = true;
|
|
77
|
+
this._connectionStatus = 'synced';
|
|
78
|
+
if (!options?.quiet) {
|
|
79
|
+
console.info(`Hocuspocus synced: ${name}`);
|
|
80
|
+
}
|
|
81
|
+
if (options?.onSynced) {
|
|
82
|
+
options.onSynced();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const onStatus = (data) => {
|
|
86
|
+
if (this.isDestroyed) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (data.status === 'connecting') {
|
|
90
|
+
this._connectionStatus = 'connecting';
|
|
91
|
+
}
|
|
92
|
+
if (options?.onStatus) {
|
|
93
|
+
options.onStatus(data);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
24
96
|
if (websocketProvider) {
|
|
25
97
|
// Multiplexing mode - use shared WebSocket connection
|
|
26
98
|
this.usesSharedSocket = true;
|
|
@@ -29,48 +101,11 @@ export class HocuspocusSyncProvider {
|
|
|
29
101
|
name,
|
|
30
102
|
document: doc,
|
|
31
103
|
token: options?.token || null,
|
|
32
|
-
onStatus
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
onConnect: () => {
|
|
38
|
-
if (this.isConnected || this.isDestroyed) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
this.isConnected = true;
|
|
42
|
-
if (!options?.quiet) {
|
|
43
|
-
console.info(`Hocuspocus connected: ${name}`);
|
|
44
|
-
}
|
|
45
|
-
if (options?.onConnect) {
|
|
46
|
-
options.onConnect();
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
onDisconnect: () => {
|
|
50
|
-
if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
this.isConnected = false;
|
|
54
|
-
this.isSynced = false;
|
|
55
|
-
if (!options?.quiet) {
|
|
56
|
-
console.info(`Hocuspocus disconnected: ${name}`);
|
|
57
|
-
}
|
|
58
|
-
if (options?.onDisconnect) {
|
|
59
|
-
options.onDisconnect();
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
onSynced: () => {
|
|
63
|
-
if (this.isSynced || this.isDestroyed) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
this.isSynced = true;
|
|
67
|
-
if (!options?.quiet) {
|
|
68
|
-
console.info(`Hocuspocus synced: ${name}`);
|
|
69
|
-
}
|
|
70
|
-
if (options?.onSynced) {
|
|
71
|
-
options.onSynced();
|
|
72
|
-
}
|
|
73
|
-
},
|
|
104
|
+
onStatus,
|
|
105
|
+
onConnect,
|
|
106
|
+
onDisconnect,
|
|
107
|
+
onSynced,
|
|
108
|
+
...reconnectConfig,
|
|
74
109
|
};
|
|
75
110
|
// Add optional settings
|
|
76
111
|
if (options?.forceSyncInterval !== undefined) {
|
|
@@ -95,48 +130,11 @@ export class HocuspocusSyncProvider {
|
|
|
95
130
|
document: doc,
|
|
96
131
|
token: options?.token || null,
|
|
97
132
|
autoConnect: false,
|
|
98
|
-
onStatus
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
onConnect: () => {
|
|
104
|
-
if (this.isConnected || this.isDestroyed) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
this.isConnected = true;
|
|
108
|
-
if (!options?.quiet) {
|
|
109
|
-
console.info(`Hocuspocus connected: ${name}`);
|
|
110
|
-
}
|
|
111
|
-
if (options?.onConnect) {
|
|
112
|
-
options.onConnect();
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
onDisconnect: () => {
|
|
116
|
-
if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.isConnected = false;
|
|
120
|
-
this.isSynced = false;
|
|
121
|
-
if (!options?.quiet) {
|
|
122
|
-
console.info(`Hocuspocus disconnected: ${name}`);
|
|
123
|
-
}
|
|
124
|
-
if (options?.onDisconnect) {
|
|
125
|
-
options.onDisconnect();
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
onSynced: () => {
|
|
129
|
-
if (this.isSynced || this.isDestroyed) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
this.isSynced = true;
|
|
133
|
-
if (!options?.quiet) {
|
|
134
|
-
console.info(`Hocuspocus synced: ${name}`);
|
|
135
|
-
}
|
|
136
|
-
if (options?.onSynced) {
|
|
137
|
-
options.onSynced();
|
|
138
|
-
}
|
|
139
|
-
},
|
|
133
|
+
onStatus,
|
|
134
|
+
onConnect,
|
|
135
|
+
onDisconnect,
|
|
136
|
+
onSynced,
|
|
137
|
+
...reconnectConfig,
|
|
140
138
|
};
|
|
141
139
|
// Add optional settings
|
|
142
140
|
if (options?.forceSyncInterval !== undefined) {
|
|
@@ -153,6 +151,35 @@ export class HocuspocusSyncProvider {
|
|
|
153
151
|
console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
|
|
154
152
|
}
|
|
155
153
|
}
|
|
154
|
+
this.setupBrowserEventListeners();
|
|
155
|
+
}
|
|
156
|
+
setupBrowserEventListeners() {
|
|
157
|
+
if (typeof document !== 'undefined') {
|
|
158
|
+
this.visibilityHandler = () => {
|
|
159
|
+
if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
|
|
160
|
+
this.provider.connect();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
document.addEventListener('visibilitychange', this.visibilityHandler);
|
|
164
|
+
}
|
|
165
|
+
if (typeof window !== 'undefined') {
|
|
166
|
+
this.onlineHandler = () => {
|
|
167
|
+
if (!this.isConnected && !this.isDestroyed) {
|
|
168
|
+
this.provider.connect();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
window.addEventListener('online', this.onlineHandler);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
removeBrowserEventListeners() {
|
|
175
|
+
if (this.visibilityHandler && typeof document !== 'undefined') {
|
|
176
|
+
document.removeEventListener('visibilitychange', this.visibilityHandler);
|
|
177
|
+
this.visibilityHandler = null;
|
|
178
|
+
}
|
|
179
|
+
if (this.onlineHandler && typeof window !== 'undefined') {
|
|
180
|
+
window.removeEventListener('online', this.onlineHandler);
|
|
181
|
+
this.onlineHandler = null;
|
|
182
|
+
}
|
|
156
183
|
}
|
|
157
184
|
/**
|
|
158
185
|
* Create a shared WebSocket connection for multiplexing
|
|
@@ -215,6 +242,7 @@ export class HocuspocusSyncProvider {
|
|
|
215
242
|
if (this.isSynced || this.isDestroyed) {
|
|
216
243
|
return;
|
|
217
244
|
}
|
|
245
|
+
this._connectionStatus = 'connecting';
|
|
218
246
|
return new Promise((resolve, reject) => {
|
|
219
247
|
// Store reject function so we can cancel the connection if destroyed
|
|
220
248
|
this.pendingConnectReject = reject;
|
|
@@ -222,7 +250,7 @@ export class HocuspocusSyncProvider {
|
|
|
222
250
|
this.pendingConnectReject = null;
|
|
223
251
|
this.connectTimeout = null;
|
|
224
252
|
reject(new Error('Hocuspocus connection timeout'));
|
|
225
|
-
},
|
|
253
|
+
}, this.connectionTimeoutMs);
|
|
226
254
|
const syncHandler = () => {
|
|
227
255
|
if (this.connectTimeout) {
|
|
228
256
|
clearTimeout(this.connectTimeout);
|
|
@@ -252,6 +280,10 @@ export class HocuspocusSyncProvider {
|
|
|
252
280
|
}
|
|
253
281
|
});
|
|
254
282
|
}
|
|
283
|
+
async reconnect() {
|
|
284
|
+
this.disconnect();
|
|
285
|
+
return this.connect();
|
|
286
|
+
}
|
|
255
287
|
disconnect() {
|
|
256
288
|
// Cancel any pending connection attempt
|
|
257
289
|
if (this.connectTimeout) {
|
|
@@ -272,6 +304,7 @@ export class HocuspocusSyncProvider {
|
|
|
272
304
|
}
|
|
273
305
|
this.isConnected = false;
|
|
274
306
|
this.isSynced = false;
|
|
307
|
+
this._connectionStatus = 'disconnected';
|
|
275
308
|
}
|
|
276
309
|
destroy() {
|
|
277
310
|
// Mark as destroyed first to prevent any callbacks from doing work
|
|
@@ -284,10 +317,12 @@ export class HocuspocusSyncProvider {
|
|
|
284
317
|
if (this.pendingConnectReject) {
|
|
285
318
|
this.pendingConnectReject = null; // Don't reject, just abandon the promise
|
|
286
319
|
}
|
|
320
|
+
this.removeBrowserEventListeners();
|
|
287
321
|
if (this.provider) {
|
|
288
322
|
this.provider.destroy();
|
|
289
323
|
}
|
|
290
324
|
this.isConnected = false;
|
|
291
325
|
this.isSynced = false;
|
|
326
|
+
this._connectionStatus = 'disconnected';
|
|
292
327
|
}
|
|
293
328
|
}
|
|
@@ -3,6 +3,7 @@ import { IndexeddbPersistence } from "y-indexeddb";
|
|
|
3
3
|
* IndexedDB sync provider for local persistence
|
|
4
4
|
*/
|
|
5
5
|
export class IndexedDBSyncProvider {
|
|
6
|
+
type = 'local';
|
|
6
7
|
provider;
|
|
7
8
|
isConnected = false;
|
|
8
9
|
constructor(docName, doc, options) {
|
|
@@ -24,6 +25,10 @@ export class IndexedDBSyncProvider {
|
|
|
24
25
|
// IndexedDB doesn't need explicit disconnect
|
|
25
26
|
this.isConnected = false;
|
|
26
27
|
}
|
|
28
|
+
async reconnect() {
|
|
29
|
+
this.disconnect();
|
|
30
|
+
return this.connect();
|
|
31
|
+
}
|
|
27
32
|
destroy() {
|
|
28
33
|
if (this.provider) {
|
|
29
34
|
this.provider.destroy();
|
|
@@ -3,6 +3,7 @@ import { WebsocketProvider } from "y-websocket";
|
|
|
3
3
|
* WebSocket sync provider for real-time collaboration
|
|
4
4
|
*/
|
|
5
5
|
export class WebSocketSyncProvider {
|
|
6
|
+
type = 'network';
|
|
6
7
|
provider;
|
|
7
8
|
isConnected = false;
|
|
8
9
|
_quiet = false;
|
|
@@ -91,6 +92,10 @@ export class WebSocketSyncProvider {
|
|
|
91
92
|
}
|
|
92
93
|
this.isConnected = false;
|
|
93
94
|
}
|
|
95
|
+
async reconnect() {
|
|
96
|
+
this.disconnect();
|
|
97
|
+
return this.connect();
|
|
98
|
+
}
|
|
94
99
|
destroy() {
|
|
95
100
|
if (this.provider) {
|
|
96
101
|
this.provider.destroy();
|
|
@@ -79,13 +79,24 @@ export class KritzelAppStateMap {
|
|
|
79
79
|
this.handleWorkspacesChange(event);
|
|
80
80
|
};
|
|
81
81
|
this._workspacesMap.observe(this._workspacesObserver);
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
|
|
83
|
+
// Local providers are awaited so data is available immediately; network providers sync in the background
|
|
84
|
+
// via Yjs observers that are already registered above.
|
|
85
|
+
const localProviders = this._providers.filter(p => p.type === 'local');
|
|
86
|
+
const networkProviders = this._providers.filter(p => p.type === 'network');
|
|
87
|
+
// Await local providers for immediate data availability
|
|
88
|
+
const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
|
|
89
|
+
localResults.forEach((result, i) => {
|
|
85
90
|
if (result.status === 'rejected') {
|
|
86
|
-
console.error(`[Kritzel] Sync provider "${
|
|
91
|
+
console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
|
|
87
92
|
}
|
|
88
93
|
});
|
|
94
|
+
// Connect network providers in the background (remote data arrives via Yjs observers)
|
|
95
|
+
for (const provider of networkProviders) {
|
|
96
|
+
provider.connect().catch(err => {
|
|
97
|
+
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
89
100
|
this._isReady = true;
|
|
90
101
|
// Run any pending schema migrations before loading data
|
|
91
102
|
const quietMigrations = !core.store.state.debugInfo.showMigrationInfo;
|
|
@@ -34,6 +34,7 @@ export class KritzelObjectMap {
|
|
|
34
34
|
_stackItemPoppedHandler = null;
|
|
35
35
|
_awarenessChangeHandler = null;
|
|
36
36
|
_awarenessChangeCallbacks = [];
|
|
37
|
+
_objectsChangeCallbacks = [];
|
|
37
38
|
_lastAwarenessEmitTime = 0;
|
|
38
39
|
_awarenessEmitTimeout = null;
|
|
39
40
|
AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
|
|
@@ -50,6 +51,19 @@ export class KritzelObjectMap {
|
|
|
50
51
|
get awareness() {
|
|
51
52
|
return this._awareness;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether a network awareness instance is available.
|
|
56
|
+
*/
|
|
57
|
+
get hasAwareness() {
|
|
58
|
+
return !!this._awareness;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns the local client ID from the awareness instance.
|
|
62
|
+
* Used to filter out the local user when rendering remote cursors.
|
|
63
|
+
*/
|
|
64
|
+
get localClientId() {
|
|
65
|
+
return this._awareness?.clientID ?? null;
|
|
66
|
+
}
|
|
53
67
|
/**
|
|
54
68
|
* Sets the local user identity in the awareness state.
|
|
55
69
|
* This broadcasts the user's identity to all connected peers.
|
|
@@ -58,12 +72,21 @@ export class KritzelObjectMap {
|
|
|
58
72
|
if (!this._awareness || !user) {
|
|
59
73
|
return;
|
|
60
74
|
}
|
|
75
|
+
const displayName = user.displayName || user.firstName || 'Anonymous';
|
|
61
76
|
this._awareness.setLocalStateField('user', {
|
|
62
77
|
id: user.id,
|
|
63
|
-
|
|
64
|
-
color: user.color,
|
|
78
|
+
displayName,
|
|
79
|
+
color: user.color || this.generateColorFromName(displayName),
|
|
65
80
|
});
|
|
66
81
|
}
|
|
82
|
+
generateColorFromName(name) {
|
|
83
|
+
let hash = 0;
|
|
84
|
+
for (let i = 0; i < name.length; i++) {
|
|
85
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
86
|
+
}
|
|
87
|
+
const hue = Math.abs(hash % 360);
|
|
88
|
+
return `hsl(${hue}, 45%, 55%)`;
|
|
89
|
+
}
|
|
67
90
|
/**
|
|
68
91
|
* Updates the local cursor position in the awareness state.
|
|
69
92
|
* This broadcasts the cursor position to all connected peers.
|
|
@@ -83,6 +106,37 @@ export class KritzelObjectMap {
|
|
|
83
106
|
}
|
|
84
107
|
this._awareness.setLocalStateField('cursor', null);
|
|
85
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Sets the ID of the object currently being drawn by the local user.
|
|
111
|
+
* Remote clients use this to derive cursor position from the object's latest
|
|
112
|
+
* coordinates instead of the throttled awareness cursor, reducing desync.
|
|
113
|
+
* Pass `null` when drawing ends.
|
|
114
|
+
*/
|
|
115
|
+
setActiveDrawingObject(objectId) {
|
|
116
|
+
if (!this._awareness) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this._awareness.setLocalStateField('activeObjectId', objectId);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Broadcasts the local user's selection box bounds via awareness.
|
|
123
|
+
* Remote clients use this to render the selection rectangle in the user's color.
|
|
124
|
+
*/
|
|
125
|
+
setLocalSelectionBox(box) {
|
|
126
|
+
if (!this._awareness) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this._awareness.setLocalStateField('selectionBox', box);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Clears the local selection box from awareness (e.g., when selection completes or is cancelled).
|
|
133
|
+
*/
|
|
134
|
+
clearLocalSelectionBox() {
|
|
135
|
+
if (!this._awareness) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this._awareness.setLocalStateField('selectionBox', null);
|
|
139
|
+
}
|
|
86
140
|
/**
|
|
87
141
|
* Registers a callback to be invoked when the awareness state changes.
|
|
88
142
|
* The callback receives the full awareness states map.
|
|
@@ -90,6 +144,14 @@ export class KritzelObjectMap {
|
|
|
90
144
|
onAwarenessChange(callback) {
|
|
91
145
|
this._awarenessChangeCallbacks.push(callback);
|
|
92
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Registers a callback to be invoked when remote object changes are received.
|
|
149
|
+
* Used by the awareness cursors component to re-derive cursor positions
|
|
150
|
+
* from the latest object data when a remote user is actively drawing.
|
|
151
|
+
*/
|
|
152
|
+
onObjectsChange(callback) {
|
|
153
|
+
this._objectsChangeCallbacks.push(callback);
|
|
154
|
+
}
|
|
93
155
|
/**
|
|
94
156
|
* Returns the Yjs UndoManager instance for managing undo/redo operations.
|
|
95
157
|
* @returns The UndoManager instance, or `null` if not initialized
|
|
@@ -208,16 +270,28 @@ export class KritzelObjectMap {
|
|
|
208
270
|
this.handleObjectsChange(event);
|
|
209
271
|
};
|
|
210
272
|
this._objectsMap.observe(this._objectsObserver);
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
273
|
+
// Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
|
|
274
|
+
// Local providers are awaited so data is available immediately; network providers sync in the background
|
|
275
|
+
// via Yjs observers that are already registered above.
|
|
276
|
+
const localProviders = this._providers.filter(p => p.type === 'local');
|
|
277
|
+
const networkProviders = this._providers.filter(p => p.type === 'network');
|
|
278
|
+
// Await local providers for immediate data availability
|
|
279
|
+
const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
|
|
280
|
+
localResults.forEach((result, i) => {
|
|
214
281
|
if (result.status === 'rejected') {
|
|
215
|
-
console.error(`[Kritzel] Sync provider "${
|
|
282
|
+
console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
|
|
216
283
|
}
|
|
217
284
|
});
|
|
285
|
+
// Connect network providers in the background (remote data arrives via Yjs observers)
|
|
286
|
+
for (const provider of networkProviders) {
|
|
287
|
+
provider.connect().catch(err => {
|
|
288
|
+
console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
218
291
|
this._isReady = true;
|
|
219
292
|
// Find the first provider that exposes awareness (network providers)
|
|
220
|
-
|
|
293
|
+
// Awareness is available immediately after provider construction, before connect() resolves
|
|
294
|
+
for (const provider of networkProviders) {
|
|
221
295
|
if (provider.awareness) {
|
|
222
296
|
this._awareness = provider.awareness;
|
|
223
297
|
break;
|
|
@@ -388,6 +462,10 @@ export class KritzelObjectMap {
|
|
|
388
462
|
if (updatedObjects.length > 0) {
|
|
389
463
|
this._core?.engine.emitObjectsUpdated(updatedObjects.map(obj => ({ object: obj, changedProperties: [] })));
|
|
390
464
|
}
|
|
465
|
+
// Notify subscribers of remote object changes (e.g., awareness cursors)
|
|
466
|
+
for (const callback of this._objectsChangeCallbacks) {
|
|
467
|
+
callback();
|
|
468
|
+
}
|
|
391
469
|
}
|
|
392
470
|
/**
|
|
393
471
|
* Initializes document metadata if not already set.
|
|
@@ -55,6 +55,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
|
|
|
55
55
|
path.isCompleted = false;
|
|
56
56
|
this._currentPathId = path.id;
|
|
57
57
|
this._core.store.state.objects.insert(path);
|
|
58
|
+
this._core.store.state.objects?.setActiveDrawingObject(path.id);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
@@ -78,6 +79,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
|
|
|
78
79
|
path.isCompleted = false;
|
|
79
80
|
this._currentPathId = path.id;
|
|
80
81
|
this._core.store.state.objects.insert(path);
|
|
82
|
+
this._core.store.state.objects?.setActiveDrawingObject(path.id);
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -168,6 +170,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
|
|
|
168
170
|
this._core.engine.emitObjectsChange();
|
|
169
171
|
this._core.engine.emitObjectsAdded([currentPath]);
|
|
170
172
|
}
|
|
173
|
+
this._core.store.state.objects?.setActiveDrawingObject(null);
|
|
171
174
|
this._currentPathId = null;
|
|
172
175
|
}
|
|
173
176
|
}
|
|
@@ -183,6 +186,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
|
|
|
183
186
|
this._core.engine.emitObjectsChange();
|
|
184
187
|
this._core.engine.emitObjectsAdded([currentPath]);
|
|
185
188
|
}
|
|
189
|
+
this._core.store.state.objects?.setActiveDrawingObject(null);
|
|
186
190
|
this._currentPathId = null;
|
|
187
191
|
}
|
|
188
192
|
}
|
|
@@ -70,6 +70,7 @@ export class KritzelLineTool extends KritzelBaseTool {
|
|
|
70
70
|
line.isCompleted = false;
|
|
71
71
|
this._currentLineId = line.id;
|
|
72
72
|
this._core.store.state.objects.insert(line);
|
|
73
|
+
this._core.store.state.objects?.setActiveDrawingObject(line.id);
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
if (event.pointerType === 'touch' || event.pointerType === 'pen') {
|
|
@@ -99,6 +100,7 @@ export class KritzelLineTool extends KritzelBaseTool {
|
|
|
99
100
|
line.isCompleted = false;
|
|
100
101
|
this._currentLineId = line.id;
|
|
101
102
|
this._core.store.state.objects.insert(line);
|
|
103
|
+
this._core.store.state.objects?.setActiveDrawingObject(line.id);
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
}
|
|
@@ -197,6 +199,7 @@ export class KritzelLineTool extends KritzelBaseTool {
|
|
|
197
199
|
// Switch to selection tool and select the drawn line
|
|
198
200
|
this.selectLineAndSwitchTool(currentLine);
|
|
199
201
|
}
|
|
202
|
+
this._core.store.state.objects?.setActiveDrawingObject(null);
|
|
200
203
|
this._currentLineId = null;
|
|
201
204
|
}
|
|
202
205
|
}
|
|
@@ -214,6 +217,7 @@ export class KritzelLineTool extends KritzelBaseTool {
|
|
|
214
217
|
// Switch to selection tool and select the drawn line
|
|
215
218
|
this.selectLineAndSwitchTool(currentLine);
|
|
216
219
|
}
|
|
220
|
+
this._core.store.state.objects?.setActiveDrawingObject(null);
|
|
217
221
|
this._currentLineId = null;
|
|
218
222
|
}
|
|
219
223
|
}
|
|
@@ -179,6 +179,7 @@ export class KritzelShapeTool extends KritzelBaseTool {
|
|
|
179
179
|
scale: lockScale ? 1 : viewportScale,
|
|
180
180
|
});
|
|
181
181
|
this._core.store.state.objects.insert(this.currentShape);
|
|
182
|
+
this._core.store.state.objects?.setActiveDrawingObject(this.currentShape.id);
|
|
182
183
|
this._core.rerender();
|
|
183
184
|
}
|
|
184
185
|
/**
|
|
@@ -239,6 +240,7 @@ export class KritzelShapeTool extends KritzelBaseTool {
|
|
|
239
240
|
this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
|
|
240
241
|
}
|
|
241
242
|
this.isDrawing = false;
|
|
243
|
+
this._core.store.state.objects?.setActiveDrawingObject(null);
|
|
242
244
|
this.currentShape = null;
|
|
243
245
|
this._core.rerender();
|
|
244
246
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"entries": [
|
|
3
|
-
"components/shared/kritzel-dropdown/kritzel-dropdown.js",
|
|
4
|
-
"components/shared/kritzel-brush-style/kritzel-brush-style.js",
|
|
5
3
|
"components/core/kritzel-engine/kritzel-engine.js",
|
|
6
4
|
"components/ui/kritzel-context-menu/kritzel-context-menu.js",
|
|
5
|
+
"components/shared/kritzel-dropdown/kritzel-dropdown.js",
|
|
6
|
+
"components/shared/kritzel-brush-style/kritzel-brush-style.js",
|
|
7
7
|
"components/shared/kritzel-font-family/kritzel-font-family.js",
|
|
8
8
|
"components/shared/kritzel-line-endings/kritzel-line-endings.js",
|
|
9
9
|
"components/shared/kritzel-pill-tabs/kritzel-pill-tabs.js",
|
|
10
10
|
"components/shared/kritzel-shape-fill/kritzel-shape-fill.js",
|
|
11
|
+
"components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js",
|
|
11
12
|
"components/core/kritzel-cursor-trail/kritzel-cursor-trail.js",
|
|
12
13
|
"components/core/kritzel-editor/kritzel-editor.js",
|
|
13
14
|
"components/shared/kritzel-avatar/kritzel-avatar.js",
|