@neta-art/cohub 12.2.0 → 12.4.0

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 (93) hide show
  1. package/README.md +87 -3
  2. package/dist/board/editor/context.d.ts +2 -0
  3. package/dist/board/editor/document.d.ts +3 -1
  4. package/dist/board/editor/document.js +8 -6
  5. package/dist/board/editor/editor.d.ts +7 -3
  6. package/dist/board/editor/editor.js +8 -1
  7. package/dist/board/editor/index.d.ts +3 -3
  8. package/dist/board/editor/index.js +2 -2
  9. package/dist/board/editor/interaction.d.ts +23 -2
  10. package/dist/board/editor/interaction.js +99 -5
  11. package/dist/board/editor/query.d.ts +1 -1
  12. package/dist/board/editor/query.js +3 -7
  13. package/dist/board/editor/selection.js +13 -1
  14. package/dist/board/editor/tool.d.ts +4 -1
  15. package/dist/board/editor/tool.js +6 -1
  16. package/dist/board/model/asset-key.d.ts +9 -1
  17. package/dist/board/model/asset-key.js +27 -7
  18. package/dist/board/model/index.d.ts +5 -5
  19. package/dist/board/model/index.js +4 -4
  20. package/dist/board/model/items/create.js +1 -1
  21. package/dist/board/model/items/definition.d.ts +9 -1
  22. package/dist/board/model/items/index.d.ts +2 -2
  23. package/dist/board/model/items/registry.d.ts +2 -1
  24. package/dist/board/model/items/registry.js +1 -0
  25. package/dist/board/model/text-metrics.d.ts +21 -1
  26. package/dist/board/model/text-metrics.js +24 -1
  27. package/dist/board/model/themes.d.ts +49 -42
  28. package/dist/board/model/themes.js +49 -44
  29. package/dist/board/render/index.d.ts +8 -6
  30. package/dist/board/render/index.js +7 -5
  31. package/dist/board/render/labels.d.ts +15 -0
  32. package/dist/board/render/labels.js +14 -0
  33. package/dist/board/render/lod.d.ts +9 -1
  34. package/dist/board/render/lod.js +7 -1
  35. package/dist/board/render/media-interaction.d.ts +5 -2
  36. package/dist/board/render/media-interaction.js +14 -2
  37. package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
  38. package/dist/board/render/renderers/board-renderer-registry.d.ts +12 -1
  39. package/dist/board/render/renderers/board-renderer-registry.js +9 -4
  40. package/dist/board/render/renderers/far-plate.d.ts +4 -1
  41. package/dist/board/render/renderers/far-plate.js +5 -1
  42. package/dist/board/render/renderers/file-card-renderer.js +44 -24
  43. package/dist/board/render/renderers/frame-card-renderer.js +9 -3
  44. package/dist/board/render/renderers/image-card-renderer.js +2 -2
  45. package/dist/board/render/renderers/model-card-renderer.js +2 -2
  46. package/dist/board/render/renderers/task-card-renderer.js +2 -2
  47. package/dist/board/render/renderers/video-card-renderer.js +2 -2
  48. package/dist/board/render/style.d.ts +1 -0
  49. package/dist/board/render/style.js +3 -1
  50. package/dist/board/render/text-edit.d.ts +32 -0
  51. package/dist/board/render/text-edit.js +134 -0
  52. package/dist/board/render/text-fit.d.ts +1 -3
  53. package/dist/board/render/text-fit.js +4 -43
  54. package/dist/board/render/text-measurement.d.ts +12 -2
  55. package/dist/board/render/text-measurement.js +55 -10
  56. package/dist/board/replica/index.d.ts +4 -2
  57. package/dist/board/replica/index.js +34 -21
  58. package/dist/board/replica/indexeddb.d.ts +16 -0
  59. package/dist/board/replica/indexeddb.js +155 -0
  60. package/dist/board/stage/assets.d.ts +16 -4
  61. package/dist/board/stage/assets.js +155 -30
  62. package/dist/board/stage/bench.d.ts +35 -0
  63. package/dist/board/stage/bench.js +153 -0
  64. package/dist/board/stage/device.d.ts +41 -0
  65. package/dist/board/stage/device.js +62 -0
  66. package/dist/board/stage/frame-budget.d.ts +19 -0
  67. package/dist/board/stage/frame-budget.js +69 -0
  68. package/dist/board/stage/idle.d.ts +4 -0
  69. package/dist/board/stage/idle.js +26 -0
  70. package/dist/board/stage/index.d.ts +9 -5
  71. package/dist/board/stage/index.js +5 -2
  72. package/dist/board/stage/presses.d.ts +21 -0
  73. package/dist/board/stage/presses.js +66 -0
  74. package/dist/board/stage/scene.d.ts +4 -0
  75. package/dist/board/stage/scene.js +45 -13
  76. package/dist/board/stage/stage.d.ts +21 -2
  77. package/dist/board/stage/stage.js +309 -89
  78. package/dist/board/stage/texture-pool.d.ts +6 -4
  79. package/dist/board/stage/texture-pool.js +42 -8
  80. package/dist/board/stage/views.d.ts +1 -1
  81. package/dist/board/stage/views.js +2 -2
  82. package/dist/chunks/environment.d.ts +6 -0
  83. package/dist/chunks/http.d.ts +9 -1
  84. package/dist/chunks/http.js +791 -4
  85. package/dist/index.d.ts +52 -1
  86. package/dist/index.js +65 -733
  87. package/dist/protocol/dist/board-model.d.ts +15 -1
  88. package/dist/protocol/dist/board-model.js +12 -2
  89. package/dist/protocol/dist/board-schema.d.ts +2 -1
  90. package/dist/protocol/dist/board-schema.js +4 -1
  91. package/dist/protocol/dist/core/media.js +4 -0
  92. package/dist/protocol/dist/index.d.ts +2 -2
  93. package/package.json +2 -1
@@ -1,6 +1,7 @@
1
1
  import { BoardDelta, BoardDocument, BoardPatch } from "../../protocol/dist/board-model.js";
2
2
  import { BoardApplyResult, BoardPlaybackSnapshot, BoardReadResult } from "../../protocol/dist/board.js";
3
3
  import "../../protocol/dist/index.js";
4
+ import { BoardReplicaDatabaseOptions, BoardReplicaScope, clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
4
5
  //#region src/board/replica/index.d.ts
5
6
  type BoardPendingPatch = {
6
7
  mutationId: string;
@@ -27,6 +28,7 @@ type BoardRemote = {
27
28
  playback: BoardPlaybackSnapshot | null;
28
29
  };
29
30
  }) => void;
31
+ resync?: () => void;
30
32
  }): () => void;
31
33
  };
32
34
  type BoardReplicaStorage = {
@@ -62,9 +64,9 @@ declare function createBoardReplica(options: BoardReplicaOptions): {
62
64
  receive: (payload: BoardChangedPayload) => void;
63
65
  receivePlayback: (next: BoardPlaybackSnapshot | null) => void;
64
66
  refresh: () => Promise<void>;
65
- retry(): Promise<void>;
67
+ retry: () => Promise<void>;
66
68
  dispose(): void;
67
69
  };
68
70
  type BoardReplica = ReturnType<typeof createBoardReplica>;
69
71
  //#endregion
70
- export { BoardChangedPayload, BoardPendingPatch, BoardRemote, BoardReplica, BoardReplicaOptions, BoardReplicaState, BoardReplicaStorage, createBoardReplica };
72
+ export { BoardChangedPayload, BoardPendingPatch, BoardRemote, BoardReplica, type BoardReplicaDatabaseOptions, BoardReplicaOptions, type BoardReplicaScope, BoardReplicaState, BoardReplicaStorage, clearBoardReplicaStorage, createBoardReplica, createIndexedDbBoardReplicaStorage };
@@ -1,6 +1,7 @@
1
1
  import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
2
2
  import { createBoardEntityId } from "../model/items/id.js";
3
3
  import { shareBoardDocument } from "../model/document-share.js";
4
+ import { clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
4
5
  //#region src/board/replica/index.ts
5
6
  function memoryStorage() {
6
7
  return {
@@ -47,6 +48,7 @@ function createBoardReplica(options) {
47
48
  let retryTimer = null;
48
49
  let retryDelay = RETRY_BASE_MS;
49
50
  let disposed = false;
51
+ let restored = false;
50
52
  let unpersisted = null;
51
53
  let persistTimer = null;
52
54
  function subscribeRemote() {
@@ -54,7 +56,8 @@ function createBoardReplica(options) {
54
56
  try {
55
57
  return remote.subscribe({
56
58
  changed: (event) => receive(event.payload),
57
- playback: (event) => receivePlayback(event.payload.playback)
59
+ playback: (event) => receivePlayback(event.payload.playback),
60
+ resync: () => void retry()
58
61
  });
59
62
  } catch {
60
63
  return null;
@@ -157,7 +160,7 @@ function createBoardReplica(options) {
157
160
  retryDelay = Math.min(RETRY_MAX_MS, retryDelay * 2);
158
161
  }
159
162
  async function flush() {
160
- if (sending || disposed || !loaded) return;
163
+ if (sending || disposed || !loaded || !restored) return;
161
164
  sending = true;
162
165
  try {
163
166
  while (pending.length && !disposed) {
@@ -217,6 +220,30 @@ function createBoardReplica(options) {
217
220
  }
218
221
  emit();
219
222
  }
223
+ function retry() {
224
+ if (disposed) return Promise.resolve();
225
+ if (retryTimer) clearTimeout(retryTimer);
226
+ retryTimer = null;
227
+ retryDelay = RETRY_BASE_MS;
228
+ readError = null;
229
+ writeError = null;
230
+ emit();
231
+ return refresh().then(flush);
232
+ }
233
+ function onOnline() {
234
+ if (pending.length || readError || writeError) retry();
235
+ }
236
+ async function restore() {
237
+ const [cached, stored] = await Promise.all([storage.readDocument().catch(() => null), storage.listPending().catch(() => [])]);
238
+ if (disposed) return;
239
+ const known = new Set(stored.map((entry) => entry.mutationId));
240
+ pending = [...stored, ...pending.filter((entry) => !known.has(entry.mutationId))];
241
+ restored = true;
242
+ const parsed = cached && !server ? parseBoardDocument(cached.document) : null;
243
+ if (cached && parsed?.ok) adoptServer(parsed.document, cached.version, false);
244
+ else recompute();
245
+ emit();
246
+ }
220
247
  function receivePlayback(next) {
221
248
  playbackRevision += 1;
222
249
  playback = next;
@@ -232,15 +259,8 @@ function createBoardReplica(options) {
232
259
  },
233
260
  async start() {
234
261
  unsubscribeRemote ??= subscribeRemote();
235
- const [cached, stored] = await Promise.all([storage.readDocument().catch(() => null), storage.listPending().catch(() => [])]);
236
- if (disposed) return;
237
- pending = stored;
238
- if (cached && !server) {
239
- const parsed = parseBoardDocument(cached.document);
240
- if (parsed.ok) adoptServer(parsed.document, cached.version, false);
241
- }
242
- emit();
243
- await refresh();
262
+ globalThis.addEventListener?.("online", onOnline);
263
+ await Promise.all([restore(), refresh()]);
244
264
  await flush();
245
265
  },
246
266
  apply(patch) {
@@ -256,17 +276,10 @@ function createBoardReplica(options) {
256
276
  receive,
257
277
  receivePlayback,
258
278
  refresh,
259
- retry() {
260
- if (retryTimer) clearTimeout(retryTimer);
261
- retryTimer = null;
262
- retryDelay = RETRY_BASE_MS;
263
- readError = null;
264
- writeError = null;
265
- emit();
266
- return refresh().then(flush);
267
- },
279
+ retry,
268
280
  dispose() {
269
281
  disposed = true;
282
+ globalThis.removeEventListener?.("online", onOnline);
270
283
  unsubscribeRemote?.();
271
284
  unsubscribeRemote = null;
272
285
  listeners.clear();
@@ -276,4 +289,4 @@ function createBoardReplica(options) {
276
289
  };
277
290
  }
278
291
  //#endregion
279
- export { createBoardReplica };
292
+ export { clearBoardReplicaStorage, createBoardReplica, createIndexedDbBoardReplicaStorage };
@@ -0,0 +1,16 @@
1
+ import { BoardReplicaStorage } from "./index.js";
2
+ //#region src/board/replica/indexeddb.d.ts
3
+ type BoardReplicaScope = {
4
+ userKey: string;
5
+ spaceId: string;
6
+ boardId: string;
7
+ };
8
+ type BoardReplicaDatabaseOptions = {
9
+ database?: string;
10
+ };
11
+ declare function createIndexedDbBoardReplicaStorage(scope: BoardReplicaScope, options?: BoardReplicaDatabaseOptions): BoardReplicaStorage;
12
+ declare function clearBoardReplicaStorage(options?: BoardReplicaDatabaseOptions & {
13
+ userKey?: string;
14
+ }): Promise<void>;
15
+ //#endregion
16
+ export { BoardReplicaDatabaseOptions, BoardReplicaScope, clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage };
@@ -0,0 +1,155 @@
1
+ //#region src/board/replica/indexeddb.ts
2
+ const DATABASE = "cohub-board";
3
+ const VERSION = 1;
4
+ const OPEN_TIMEOUT_MS = 3e3;
5
+ const connections = /* @__PURE__ */ new Map();
6
+ function open(name) {
7
+ const existing = connections.get(name);
8
+ if (existing) return existing;
9
+ const opening = new Promise((resolve, reject) => {
10
+ const factory = globalThis.indexedDB;
11
+ if (!factory) {
12
+ reject(/* @__PURE__ */ new Error("IndexedDB is unavailable."));
13
+ return;
14
+ }
15
+ let late = false;
16
+ const timer = setTimeout(() => {
17
+ late = true;
18
+ reject(/* @__PURE__ */ new Error("IndexedDB did not open in time."));
19
+ }, OPEN_TIMEOUT_MS);
20
+ const request = factory.open(name, VERSION);
21
+ request.onupgradeneeded = () => {
22
+ const db = request.result;
23
+ db.createObjectStore("documents", { keyPath: "board" }).createIndex("user", "userKey");
24
+ const pending = db.createObjectStore("pending", { keyPath: ["board", "mutationId"] });
25
+ pending.createIndex("board", "board");
26
+ pending.createIndex("user", "userKey");
27
+ };
28
+ request.onsuccess = () => {
29
+ clearTimeout(timer);
30
+ const db = request.result;
31
+ if (late) {
32
+ db.close();
33
+ return;
34
+ }
35
+ const forget = () => {
36
+ if (connections.get(name) === opening) connections.delete(name);
37
+ };
38
+ db.onversionchange = () => {
39
+ db.close();
40
+ forget();
41
+ };
42
+ db.onclose = forget;
43
+ resolve(db);
44
+ };
45
+ request.onerror = () => {
46
+ clearTimeout(timer);
47
+ reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB failed to open."));
48
+ };
49
+ });
50
+ connections.set(name, opening);
51
+ opening.catch(() => {
52
+ if (connections.get(name) === opening) connections.delete(name);
53
+ });
54
+ return opening;
55
+ }
56
+ async function transact(name, stores, mode, work, retry = true) {
57
+ const db = await open(name);
58
+ let tx;
59
+ try {
60
+ tx = db.transaction(stores, mode);
61
+ } catch (error) {
62
+ if (!retry) throw error;
63
+ db.close();
64
+ connections.delete(name);
65
+ return transact(name, stores, mode, work, false);
66
+ }
67
+ return new Promise((resolve, reject) => {
68
+ const result = work(tx);
69
+ tx.oncomplete = () => resolve(result());
70
+ tx.onerror = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed."));
71
+ tx.onabort = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted."));
72
+ });
73
+ }
74
+ function deleteByIndex(store, index, key) {
75
+ const request = store.index(index).openKeyCursor(IDBKeyRange.only(key));
76
+ request.onsuccess = () => {
77
+ const cursor = request.result;
78
+ if (!cursor) return;
79
+ store.delete(cursor.primaryKey);
80
+ cursor.continue();
81
+ };
82
+ }
83
+ let lastOrder = 0;
84
+ function createIndexedDbBoardReplicaStorage(scope, options = {}) {
85
+ const name = options.database ?? DATABASE;
86
+ const board = JSON.stringify([
87
+ scope.userKey,
88
+ scope.spaceId,
89
+ scope.boardId
90
+ ]);
91
+ return {
92
+ listPending: () => transact(name, ["pending"], "readonly", (tx) => {
93
+ const request = tx.objectStore("pending").index("board").getAll(IDBKeyRange.only(board));
94
+ return () => request.result.sort((a, b) => a.order - b.order).map(({ mutationId, patch }) => ({
95
+ mutationId,
96
+ patch
97
+ }));
98
+ }),
99
+ putPending: ({ mutationId, patch }) => transact(name, ["pending"], "readwrite", (tx) => {
100
+ lastOrder = Math.max(Date.now(), lastOrder + 1);
101
+ const record = {
102
+ board,
103
+ mutationId,
104
+ userKey: scope.userKey,
105
+ order: lastOrder,
106
+ patch
107
+ };
108
+ tx.objectStore("pending").put(record);
109
+ return () => void 0;
110
+ }),
111
+ deletePending: (mutationId) => transact(name, ["pending"], "readwrite", (tx) => {
112
+ tx.objectStore("pending").delete([board, mutationId]);
113
+ return () => void 0;
114
+ }),
115
+ readDocument: () => transact(name, ["documents"], "readonly", (tx) => {
116
+ const request = tx.objectStore("documents").get(board);
117
+ return () => {
118
+ const record = request.result;
119
+ return record ? {
120
+ version: record.version,
121
+ document: record.document
122
+ } : null;
123
+ };
124
+ }),
125
+ writeDocument: (version, document) => transact(name, ["documents"], "readwrite", (tx) => {
126
+ const store = tx.objectStore("documents");
127
+ const request = store.get(board);
128
+ request.onsuccess = () => {
129
+ const current = request.result;
130
+ if (current && current.version > version) return;
131
+ const record = {
132
+ board,
133
+ userKey: scope.userKey,
134
+ version,
135
+ document
136
+ };
137
+ store.put(record);
138
+ };
139
+ return () => void 0;
140
+ })
141
+ };
142
+ }
143
+ async function clearBoardReplicaStorage(options = {}) {
144
+ const { userKey } = options;
145
+ await transact(options.database ?? DATABASE, ["documents", "pending"], "readwrite", (tx) => {
146
+ for (const name of ["documents", "pending"]) {
147
+ const store = tx.objectStore(name);
148
+ if (userKey === void 0) store.clear();
149
+ else deleteByIndex(store, "user", userKey);
150
+ }
151
+ return () => void 0;
152
+ });
153
+ }
154
+ //#endregion
155
+ export { clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage };
@@ -1,6 +1,6 @@
1
1
  import { ModelFormat } from "../../protocol/dist/fs/mime.js";
2
2
  import { BoardSceneItem } from "../model/scene.js";
3
- import { BoardAssetMedia } from "../model/asset-key.js";
3
+ import { BoardAssetMedia, BoardAssetRef } from "../model/asset-key.js";
4
4
  import { BoardAssetUrlOptions } from "../model/media-playback.js";
5
5
  import "../model/index.js";
6
6
  import { LruBudget } from "./lru.js";
@@ -25,6 +25,7 @@ type BoardModelPreviews = {
25
25
  type BoardAssetManager = {
26
26
  assetKey: (item: BoardSceneItem) => string | null;
27
27
  invalidatePath: (path: string) => void;
28
+ invalidateItemAssets: () => void;
28
29
  requestItem: (item: BoardSceneItem, edge?: number) => void;
29
30
  /** World point used to prioritize queued loads. */
30
31
  focus: (point: {
@@ -33,25 +34,35 @@ type BoardAssetManager = {
33
34
  } | null) => void;
34
35
  getTexture: (key: string) => Texture | null;
35
36
  getNaturalSize: (key: string) => Size | null;
37
+ getTone: (key: string) => number | null;
36
38
  hasError: (key: string) => boolean;
37
39
  acquire: (key: string) => void;
38
40
  release: (key: string) => void;
41
+ prefetch?: (requests: readonly BoardAssetPrefetch[]) => void;
39
42
  withTextures: <T>(items: BoardSceneItem[], use: (textures: Map<string, Texture>) => T | Promise<T>, options?: {
40
43
  timeoutMs?: number;
41
44
  edge?: (item: BoardSceneItem) => number;
42
45
  }) => Promise<T>;
43
- subscribe: (listener: (key: string) => void) => () => void;
44
- /** Current texture and cooling-cache usage. */
46
+ subscribe: (listener: (key: string, change: BoardAssetChange) => void) => () => void;
45
47
  stats: () => {
46
48
  textures: number;
47
49
  cooling: number;
48
50
  bytes: number;
51
+ loading?: number;
52
+ prefetching?: number;
49
53
  };
50
54
  destroy: () => void;
51
55
  };
56
+ type BoardAssetPrefetch = {
57
+ item: BoardSceneItem;
58
+ edge: number;
59
+ };
60
+ type BoardAssetChange = "texture" | "tone";
52
61
  type BoardAssetManagerOptions = {
53
62
  /** Resolve a Space file path to a URL a texture can load from; null when unavailable. */
54
63
  resolveFileUrl: (path: string, options?: BoardAssetUrlOptions) => Promise<string | null>;
64
+ /** undefined keeps the built-in rule, null shows none; cached until `invalidateItemAssets`. */
65
+ itemAsset?: (item: BoardSceneItem) => BoardAssetRef | null | undefined;
55
66
  concurrency?: number;
56
67
  videoConcurrency?: number;
57
68
  loadVideoPreviews?: boolean;
@@ -62,8 +73,9 @@ type BoardAssetManagerOptions = {
62
73
  unloadTexture?: (url: string, texture: Texture | null, media: BoardAssetMedia) => Promise<void>;
63
74
  /** An unshared copy of an image texture at `edge`; null keeps evicting outright. */
64
75
  shrinkTexture?: (texture: Texture, edge: number) => Promise<Texture | null>;
76
+ readTone?: ((texture: Texture) => Promise<number | null>) | null;
65
77
  now?: () => number;
66
78
  };
67
79
  declare function createBoardAssetManager(options: BoardAssetManagerOptions): BoardAssetManager;
68
80
  //#endregion
69
- export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, createBoardAssetManager };
81
+ export { BoardAssetChange, BoardAssetManager, BoardAssetManagerOptions, BoardAssetPrefetch, BoardModelPreviewRequest, BoardModelPreviews, createBoardAssetManager };