@tita-agent/dsh-data-table 0.0.0-stage → 0.2.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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Turning a diff into a canvas overlay (docs/issue/0022 P5).
3
+ *
4
+ * The *order* of a diff is not the same as its direction, and which tones may be
5
+ * painted depends on which side of the comparison the canvas is showing: a
6
+ * removed cell does not exist in the snapshot that is on screen, so painting it
7
+ * would tint an empty cell. That rule is the whole reason this is a module.
8
+ */
9
+ import { versionChangeDirection } from '#contracts'
10
+ import type { WorkbookChange } from '../../types.ts'
11
+ import type { WorkbookDecoration } from '../univer/loader.ts'
12
+
13
+ /**
14
+ * Turn a change list into the canvas overlay (PRD v3.3 §7.2): every changed
15
+ * cell, toned by direction, plus the first changed cell as the focus so
16
+ * switching what is inspected moves the viewport onto what actually changed.
17
+ *
18
+ * A removed cell exists only in the baseline, so it cannot be tinted in this
19
+ * snapshot; it is counted in the bar instead of being painted nowhere.
20
+ */
21
+ export function toDecoration(changes: WorkbookChange[], side: 'before' | 'after'): WorkbookDecoration | undefined {
22
+ const cells: WorkbookDecoration['cells'] = []
23
+ let focus: WorkbookDecoration['focus']
24
+ for (const change of changes) {
25
+ if (change.kind !== 'cell' || change.row === undefined || change.column === undefined) continue
26
+ const sheetRef = change.sheetId ?? change.sheetName
27
+ const direction = versionChangeDirection({
28
+ kind: 'cell',
29
+ target: '',
30
+ description: '',
31
+ row: change.row,
32
+ column: change.column,
33
+ ...(change.before === undefined ? {} : { before: change.before }),
34
+ ...(change.after === undefined ? {} : { after: change.after }),
35
+ })
36
+ // A diff is directional, and the canvas shows exactly one of its two sides:
37
+ // - showing the newer side, an addition/replacement is paint-able (green /
38
+ // amber) and a deletion has no cell here at all;
39
+ // - showing the older side — the default when previewing an old Version
40
+ // against the current content — it is the other way round, and the cell
41
+ // that no longer exists is the red one.
42
+ // Painting the wrong side's cells would tint empty cells and claim a change
43
+ // the reader cannot see.
44
+ const tone = direction === 'add' ? 'add' as const : direction === 'remove' ? 'remove' as const : 'modify' as const
45
+ const paintable = side === 'after' ? tone !== 'remove' : tone !== 'add'
46
+ if (!paintable) continue
47
+ cells.push({ ...(sheetRef === undefined ? {} : { sheetRef }), row: change.row, column: change.column, tone })
48
+ if (focus === undefined) {
49
+ focus = { ...(sheetRef === undefined ? {} : { sheetRef }), row: change.row, column: change.column }
50
+ }
51
+ }
52
+ if (cells.length === 0 && focus === undefined) return undefined
53
+ return { cells, ...(focus === undefined ? {} : { focus }) }
54
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * A stored Version as the panel renders it (docs/issue/0022 P5).
3
+ *
4
+ * The history payload and the shared `VersionManager` speak different shapes:
5
+ * the payload is what the store knows (revisions, steps, lineage), and the row
6
+ * model is what a person sees. This is that translation, and nothing else — it
7
+ * is the module PRD 0002 keeps touching, so it is a module rather than twelve
8
+ * helpers at the bottom of the panel.
9
+ */
10
+ import { formatVersionTime, type VersionManagerEntry } from '@tita-agent/ui/versions'
11
+ import type { WorkbookHistoryVersion } from '../../types.ts'
12
+ import type { HistoryEntry } from '../transport/workbookApi.ts'
13
+
14
+ /** The history payload's Version, under the name the panel's rows use. */
15
+ export type HistoryVersion = WorkbookHistoryVersion
16
+
17
+ /** The shared component formats Version times; re-exported so callers did not have to move. */
18
+ export const formatHistoryTime = formatVersionTime
19
+
20
+ export function historyVersionsFromEntries(entries: HistoryEntry[]): HistoryVersion[] {
21
+ return entries.map((entry, index) => ({
22
+ versionId: `revision:${entry.revision}`,
23
+ number: index + 1,
24
+ startRevision: entry.revision,
25
+ revision: entry.revision,
26
+ parentRevision: entry.parentRevision ?? Math.max(0, entry.revision - 1),
27
+ actor: entry.actor,
28
+ timestamp: entry.timestamp,
29
+ summary: entry.summary,
30
+ stepCount: 1,
31
+ steps: [{ index: 0, opId: entry.opId, type: entry.type as HistoryVersion['steps'][number]['type'], summary: entry.summary }],
32
+ canPreview: entry.canPreview === true,
33
+ status: 'final',
34
+ // A legacy operation-log row is a deliberate step, not session churn.
35
+ kind: 'named',
36
+ branch: 'main',
37
+ }))
38
+ }
39
+
40
+ /** Shared drawer entry shape (docs/issue/0002 Phase 3 / PRD v3 §7). */
41
+ export function toVersionManagerEntry(version: HistoryVersion, index: number, all: HistoryVersion[]): VersionManagerEntry {
42
+ const status: VersionManagerEntry['status'] = version.branch === 'superseded'
43
+ ? 'superseded'
44
+ : version.status === 'final'
45
+ ? 'committed'
46
+ : 'open'
47
+ const restoredFromNumber = version.restoredFromNumber
48
+ ?? (version.restoredFromRevision === undefined
49
+ ? undefined
50
+ : all.find(candidate => candidate.revision === version.restoredFromRevision)?.number)
51
+ const restoredFrom = restoredFromNumber === undefined
52
+ ? undefined
53
+ : all.find(candidate => candidate.number === restoredFromNumber)
54
+ return {
55
+ id: version.versionId,
56
+ number: version.number ?? all.length - index,
57
+ revision: version.revision,
58
+ summary: version.summary,
59
+ actor: version.actor === 'agent' ? 'agent' : 'user',
60
+ time: formatHistoryTime(version.timestamp),
61
+ timestamp: version.timestamp,
62
+ status,
63
+ kind: version.kind,
64
+ ...(restoredFrom === undefined
65
+ ? (restoredFromNumber === undefined ? {} : { restoredFromNumber })
66
+ : { restoredFromTime: formatHistoryTime(restoredFrom.timestamp) }),
67
+ stepCount: version.stepCount,
68
+ replayable: version.canPreview,
69
+ steps: version.steps.map(step => ({
70
+ id: step.opId,
71
+ index: step.index,
72
+ label: `Step ${step.index + 1}`,
73
+ summary: step.summary,
74
+ ...(step.revision === undefined ? {} : { revision: step.revision }),
75
+ })),
76
+ ...(version.intent === undefined ? {} : { intent: version.intent }),
77
+ }
78
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Resident host for the embedded Table workspace.
3
+ *
4
+ * The dock renders only the active tab's body (`DockSurface` → `renderTab`), so
5
+ * switching DSH tabs — or switching conversation sessions — unmounts the pane's
6
+ * React tree. When the workspace lived in that tree, every re-entry disposed the
7
+ * Univer engine, re-fetched the whole snapshot and rebuilt the sheet, which is
8
+ * exactly the "reloads on every visit" the panel used to show.
9
+ *
10
+ * The workspace therefore lives in one long-lived container that is created
11
+ * once and merely *moves* between the pane and an off-screen parking lot. Re-
12
+ * entering the tab re-parents the existing DOM and React tree, so the editor,
13
+ * its scroll position, its unsaved drafts and its collaboration stream all
14
+ * survive. A long stay in the parking lot releases it again, so the engine is
15
+ * not held for the life of the page either.
16
+ *
17
+ * The DOM access and the timer are injected so the adoption/parking bookkeeping
18
+ * can be tested without a browser (this repository has no DOM test
19
+ * environment).
20
+ *
21
+ * @module dsh-data-table/client/workspace-host
22
+ */
23
+
24
+ /** How long a parked workspace is kept warm before it is released. */
25
+ export const PARKED_RELEASE_MS = 10 * 60 * 1000
26
+
27
+ /** Minimal DOM surface this module needs; injectable for tests. */
28
+ export interface WorkspaceHostEnvironment {
29
+ /** Create the element that owns the workspace. */
30
+ createContainer(): HTMLElement
31
+ /** Create the off-screen element a parked workspace is moved into. */
32
+ createParkingLot(): HTMLElement
33
+ /** The document body the parking lot is attached to. */
34
+ body(): HTMLElement
35
+ /** Schedule the idle release (injectable so tests stay deterministic). */
36
+ schedule(callback: () => void, delayMs: number): unknown
37
+ /** Cancel a scheduled idle release. */
38
+ cancel(handle: unknown): void
39
+ }
40
+
41
+ /** Off-screen geometry: keeps the engine's measured size while it is not shown. */
42
+ const PARKED_STYLE = 'position:fixed;left:-20000px;top:0;overflow:hidden;pointer-events:none;contain:strict;'
43
+ const ADOPTED_STYLE = 'width:100%;height:100%;min-width:0;min-height:0;display:flex;flex-direction:column;'
44
+
45
+ const domEnvironment: WorkspaceHostEnvironment = {
46
+ createContainer: () => {
47
+ const element = document.createElement('div')
48
+ element.setAttribute('data-data-table-workspace', '')
49
+ return element
50
+ },
51
+ createParkingLot: () => {
52
+ const element = document.createElement('div')
53
+ element.setAttribute('data-data-table-parked', '')
54
+ element.style.cssText = `${PARKED_STYLE}width:0;height:0;`
55
+ return element
56
+ },
57
+ body: () => document.body,
58
+ schedule: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
59
+ cancel: handle => { globalThis.clearTimeout(handle as ReturnType<typeof setTimeout>) },
60
+ }
61
+
62
+ let environment: WorkspaceHostEnvironment = domEnvironment
63
+ let container: HTMLElement | null = null
64
+ let parkingLot: HTMLElement | null = null
65
+ let releaseTimer: unknown = null
66
+ let unmount: (() => void) | null = null
67
+
68
+ /** Replace the DOM environment (tests only). Resets any resident workspace. */
69
+ export function setWorkspaceHostEnvironment(next: WorkspaceHostEnvironment): void {
70
+ environment = next
71
+ releaseTimer = null
72
+ unmount = null
73
+ container = null
74
+ parkingLot = null
75
+ }
76
+
77
+ /** The resident container, or null when nothing is resident. */
78
+ export function workspaceHostElement(): HTMLElement | null {
79
+ return container
80
+ }
81
+
82
+ /** Whether the resident workspace is currently parked off-screen. */
83
+ export function isWorkspaceHostParked(): boolean {
84
+ return container !== null && container.parentElement === parking()
85
+ }
86
+
87
+ /**
88
+ * Put the resident workspace inside `target`, creating it on first use.
89
+ * @param target - the pane element that should display the workspace.
90
+ * @param mount - called once per workspace lifetime; may return a disposer.
91
+ * @returns the resident container.
92
+ */
93
+ export function adoptWorkspaceHost(target: HTMLElement, mount: (container: HTMLElement) => void | (() => void)): HTMLElement {
94
+ if (container === null) {
95
+ container = environment.createContainer()
96
+ unmount = mount(container) ?? null
97
+ }
98
+ cancelRelease()
99
+ if (container.parentElement === target) return container
100
+ container.style.cssText = ADOPTED_STYLE
101
+ target.append(container)
102
+ return container
103
+ }
104
+
105
+ /**
106
+ * Move the resident workspace off-screen without destroying it.
107
+ *
108
+ * Its measured size is kept so the engine sees no resize while parked; that is
109
+ * what makes re-adoption invisible instead of a relayout. The workspace is
110
+ * released after {@link PARKED_RELEASE_MS} so a tab the user has moved on from
111
+ * does not pin a whole engine for the life of the page.
112
+ */
113
+ export function parkWorkspaceHost(): void {
114
+ if (container === null || container.parentElement === null) return
115
+ const rect = container.getBoundingClientRect()
116
+ container.style.cssText = `${PARKED_STYLE}width:${Math.max(Math.round(rect.width), 320)}px;height:${Math.max(Math.round(rect.height), 240)}px;`
117
+ parking().append(container)
118
+ cancelRelease()
119
+ releaseTimer = environment.schedule(() => { releaseTimer = null; disposeWorkspaceHost() }, PARKED_RELEASE_MS)
120
+ }
121
+
122
+ /** Release the resident workspace, unmounting its React tree. Safe when empty. */
123
+ export function disposeWorkspaceHost(): void {
124
+ cancelRelease()
125
+ const dispose = unmount
126
+ unmount = null
127
+ container?.remove()
128
+ parkingLot?.remove()
129
+ container = null
130
+ parkingLot = null
131
+ // Unmount last: the disposer tears down effects that may still touch the DOM.
132
+ dispose?.()
133
+ }
134
+
135
+ function cancelRelease(): void {
136
+ if (releaseTimer === null) return
137
+ environment.cancel(releaseTimer)
138
+ releaseTimer = null
139
+ }
140
+
141
+ function parking(): HTMLElement {
142
+ if (parkingLot === null || parkingLot.parentElement === null) {
143
+ parkingLot = environment.createParkingLot()
144
+ environment.body().append(parkingLot)
145
+ }
146
+ return parkingLot
147
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * In-process collaboration transport for Datasheet.
3
+ *
4
+ * This is deliberately small and dependency-free. It provides the same
5
+ * server-to-client fan-out seam that a WebSocket/Univer collaboration server
6
+ * would provide, while keeping the DSH plugin self-contained. Durable state
7
+ * still lives in WorkbookManager; the hub is only a live notification layer.
8
+ */
9
+
10
+ import type { WorkbookOperation, WorkbookPresence } from './types.ts'
11
+
12
+ export interface WorkbookCollaborationEvent {
13
+ workbookId: string
14
+ operation: WorkbookOperation
15
+ }
16
+
17
+ type WorkbookCollaborationListener = (event: WorkbookCollaborationEvent) => void
18
+
19
+ const listeners = new Map<string, Set<WorkbookCollaborationListener>>()
20
+ export const MAX_LISTENERS_PER_WORKBOOK = 64
21
+
22
+ export interface WorkbookPresenceEvent {
23
+ workbookId: string
24
+ presence: WorkbookPresence
25
+ }
26
+
27
+ type WorkbookPresenceListener = (event: WorkbookPresenceEvent) => void
28
+
29
+ const presenceListeners = new Map<string, Set<WorkbookPresenceListener>>()
30
+ /** Presence is intentionally capped separately from durable operation streams. */
31
+ export const MAX_PRESENCE_LISTENERS_PER_WORKBOOK = 128
32
+
33
+ /** Subscribe to committed operations for one workbook. */
34
+ export function subscribeWorkbookCollaboration(workbookId: string, listener: WorkbookCollaborationListener): () => void {
35
+ const current = listeners.get(workbookId) ?? new Set<WorkbookCollaborationListener>()
36
+ if (current.size >= MAX_LISTENERS_PER_WORKBOOK) {
37
+ throw new Error(`工作簿协同连接数已达上限(最多 ${MAX_LISTENERS_PER_WORKBOOK} 个)`)
38
+ }
39
+ current.add(listener)
40
+ listeners.set(workbookId, current)
41
+ return () => {
42
+ current.delete(listener)
43
+ if (current.size === 0 && listeners.get(workbookId) === current) listeners.delete(workbookId)
44
+ }
45
+ }
46
+
47
+ /** Fan out a committed operation to currently connected workbook sessions. */
48
+ export function publishWorkbookCollaboration(operation: WorkbookOperation): void {
49
+ const current = listeners.get(operation.workbookId)
50
+ if (current === undefined) return
51
+ const event = { workbookId: operation.workbookId, operation }
52
+ for (const listener of [...current]) {
53
+ try {
54
+ listener(event)
55
+ } catch {
56
+ // A disconnected response must not affect persistence or other clients.
57
+ }
58
+ }
59
+ }
60
+
61
+ /** Subscribe to ephemeral selection/editing state for one workbook. */
62
+ export function subscribeWorkbookPresence(workbookId: string, listener: WorkbookPresenceListener): () => void {
63
+ const current = presenceListeners.get(workbookId) ?? new Set<WorkbookPresenceListener>()
64
+ if (current.size >= MAX_PRESENCE_LISTENERS_PER_WORKBOOK) {
65
+ throw new Error(`工作簿 presence 连接数已达上限(最多 ${MAX_PRESENCE_LISTENERS_PER_WORKBOOK} 个)`)
66
+ }
67
+ current.add(listener)
68
+ presenceListeners.set(workbookId, current)
69
+ return () => {
70
+ current.delete(listener)
71
+ if (current.size === 0 && presenceListeners.get(workbookId) === current) presenceListeners.delete(workbookId)
72
+ }
73
+ }
74
+
75
+ /** Fan out ephemeral presence without touching WorkbookManager or revision. */
76
+ export function publishWorkbookPresence(presence: WorkbookPresence): void {
77
+ const current = presenceListeners.get(presence.workbookId)
78
+ if (current === undefined) return
79
+ const event = { workbookId: presence.workbookId, presence }
80
+ for (const listener of [...current]) {
81
+ try { listener(event) } catch { /* disconnected clients are harmless */ }
82
+ }
83
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Identifier vocabulary of the workbook repository (docs/issue/0022 P0).
3
+ *
4
+ * Pure string work plus one id mint: it knows nothing about the disk, about
5
+ * Univer, or about Versions as a concept. It is the node half's vocabulary —
6
+ * the browser addresses ids as opaque strings and never mints a workbook,
7
+ * worktree or Version id — but nothing here reads or writes anything, so the
8
+ * split is about who *creates* ids, not about what they are.
9
+ */
10
+ import { randomUUID } from 'node:crypto'
11
+ import { storageSegment } from '#contracts'
12
+ import { DEFAULT_WORKBOOK } from '../types.ts'
13
+
14
+ /**
15
+ * Normalize a workbook id to a safe file name segment.
16
+ *
17
+ * The character set is the shared one (`storageSegment`), so a name that survives
18
+ * in one plugin survives in the next: it keeps 汉字 — the id *is* the file name,
19
+ * and a newly created workbook is named `yyyymmdd-中文名` (see `storageNameOf` in
20
+ * `tools/workbook-catalog.ts`) — while spaces, punctuation and path separators
21
+ * become `-`.
22
+ *
23
+ * Deliberately no date prefix and no truncation here: this runs on every lookup,
24
+ * including ids minted by earlier versions, and "normalizing" an existing id is
25
+ * renaming someone's file.
26
+ */
27
+ export function normalizeWorkbookId(id: string): string {
28
+ const safe = storageSegment(id)
29
+ return safe.length === 0 ? DEFAULT_WORKBOOK : safe
30
+ }
31
+
32
+ /** Normalize an isolated worktree id to a safe file-name segment. */
33
+ export function normalizeWorktreeId(id: string): string {
34
+ const safe = storageSegment(id)
35
+ return safe.length === 0 ? newWorktreeId() : safe
36
+ }
37
+
38
+ /** Mint a fresh worktree id, for the callers that do not name one. */
39
+ export function newWorktreeId(): string {
40
+ return `wt-${randomUUID().slice(0, 8)}`
41
+ }
42
+
43
+ /**
44
+ * Id of the Version that continues `previousVersionId`. A continuation keeps
45
+ * the `user-version:` prefix the store groups by, so it is a Version like any
46
+ * other rather than a second kind of thing.
47
+ */
48
+ export function nextContinuationVersionId(workbookId: string, previousVersionId: string): string {
49
+ return `user-version:${workbookId}:continuation:${previousVersionId.slice(-24)}:${randomUUID().slice(0, 8)}`
50
+ }