@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.
- package/LICENSE +21 -0
- package/README.md +172 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +16766 -0
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/types/audit.d.ts +42 -0
- package/lib/types/audit.js +39 -0
- package/lib/types/client/ProjectImportMenu.d.ts +30 -0
- package/lib/types/client/ProjectImportMenu.js +70 -0
- package/lib/types/client/SheetPanel.d.ts +33 -0
- package/lib/types/client/SheetPanel.js +1766 -0
- package/lib/types/client/SheetToggle.d.ts +18 -0
- package/lib/types/client/SheetToggle.js +26 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.js +110 -0
- package/lib/types/client/persistence/changes.d.ts +15 -0
- package/lib/types/client/persistence/changes.js +51 -0
- package/lib/types/client/persistence/ids.d.ts +34 -0
- package/lib/types/client/persistence/ids.js +48 -0
- package/lib/types/client/persistence/merge.d.ts +35 -0
- package/lib/types/client/persistence/merge.js +362 -0
- package/lib/types/client/store.d.ts +61 -0
- package/lib/types/client/store.js +152 -0
- package/lib/types/client/surface-context.d.ts +27 -0
- package/lib/types/client/surface-context.js +104 -0
- package/lib/types/client/surface-control.d.ts +2 -0
- package/lib/types/client/surface-control.js +12 -0
- package/lib/types/client/transport/workbookApi.d.ts +159 -0
- package/lib/types/client/transport/workbookApi.js +246 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
- package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
- package/lib/types/client/univer/WorkbookCanvas.js +87 -0
- package/lib/types/client/univer/WorkbookView.d.ts +3 -0
- package/lib/types/client/univer/WorkbookView.js +2 -0
- package/lib/types/client/univer/loader.d.ts +18 -0
- package/lib/types/client/univer/loader.js +81 -0
- package/lib/types/client/univer/standalone-entry.d.ts +1 -0
- package/lib/types/client/univer/standalone-entry.js +19 -0
- package/lib/types/client/versions/VersionChrome.d.ts +33 -0
- package/lib/types/client/versions/VersionChrome.js +21 -0
- package/lib/types/client/versions/inspect.d.ts +70 -0
- package/lib/types/client/versions/inspect.js +88 -0
- package/lib/types/client/versions/preview-decoration.d.ts +11 -0
- package/lib/types/client/versions/preview-decoration.js +54 -0
- package/lib/types/client/versions/records.d.ts +19 -0
- package/lib/types/client/versions/records.js +70 -0
- package/lib/types/client/workspace-host.d.ts +61 -0
- package/lib/types/client/workspace-host.js +124 -0
- package/lib/types/collaboration-hub.d.ts +31 -0
- package/lib/types/collaboration-hub.js +69 -0
- package/lib/types/contract/ids.d.ts +24 -0
- package/lib/types/contract/ids.js +46 -0
- package/lib/types/contract/mutations.d.ts +25 -0
- package/lib/types/contract/mutations.js +782 -0
- package/lib/types/contract/operations.d.ts +54 -0
- package/lib/types/contract/operations.js +203 -0
- package/lib/types/contract/snapshots.d.ts +176 -0
- package/lib/types/contract/snapshots.js +351 -0
- package/lib/types/contract/univer-api-catalog.d.ts +29 -0
- package/lib/types/contract/univer-api-catalog.js +264 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/errors.js +12 -0
- package/lib/types/http/assets-routes.d.ts +3 -0
- package/lib/types/http/assets-routes.js +169 -0
- package/lib/types/http/collaboration.d.ts +10 -0
- package/lib/types/http/collaboration.js +154 -0
- package/lib/types/http/history.d.ts +15 -0
- package/lib/types/http/history.js +134 -0
- package/lib/types/http/project-files.d.ts +79 -0
- package/lib/types/http/project-files.js +206 -0
- package/lib/types/http/project-scope.d.ts +50 -0
- package/lib/types/http/project-scope.js +52 -0
- package/lib/types/http/routes.d.ts +98 -0
- package/lib/types/http/routes.js +323 -0
- package/lib/types/http/standalone.d.ts +9 -0
- package/lib/types/http/standalone.js +31 -0
- package/lib/types/http/trunk.d.ts +10 -0
- package/lib/types/http/trunk.js +322 -0
- package/lib/types/http/worktree.d.ts +9 -0
- package/lib/types/http/worktree.js +145 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.js +156 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +15 -0
- package/lib/types/request-params.d.ts +21 -0
- package/lib/types/request-params.js +76 -0
- package/lib/types/store/paths.d.ts +68 -0
- package/lib/types/store/paths.js +148 -0
- package/lib/types/store/project.d.ts +88 -0
- package/lib/types/store/project.js +120 -0
- package/lib/types/store/read.d.ts +110 -0
- package/lib/types/store/read.js +404 -0
- package/lib/types/store/retention.d.ts +27 -0
- package/lib/types/store/retention.js +111 -0
- package/lib/types/store/write.d.ts +22 -0
- package/lib/types/store/write.js +210 -0
- package/lib/types/tools/arguments.d.ts +40 -0
- package/lib/types/tools/arguments.js +130 -0
- package/lib/types/tools/cells.d.ts +50 -0
- package/lib/types/tools/cells.js +298 -0
- package/lib/types/tools/index.d.ts +2 -0
- package/lib/types/tools/index.js +250 -0
- package/lib/types/tools/render.d.ts +47 -0
- package/lib/types/tools/render.js +290 -0
- package/lib/types/tools/univer-api.d.ts +18 -0
- package/lib/types/tools/univer-api.js +198 -0
- package/lib/types/tools/workbook-catalog.d.ts +3 -0
- package/lib/types/tools/workbook-catalog.js +83 -0
- package/lib/types/tools/workbook-edit.d.ts +192 -0
- package/lib/types/tools/workbook-edit.js +912 -0
- package/lib/types/tools/workbook-find.d.ts +11 -0
- package/lib/types/tools/workbook-find.js +89 -0
- package/lib/types/tools/workbook-history.d.ts +2 -0
- package/lib/types/tools/workbook-history.js +59 -0
- package/lib/types/tools/workbook-read.d.ts +21 -0
- package/lib/types/tools/workbook-read.js +204 -0
- package/lib/types/types.d.ts +377 -0
- package/lib/types/types.js +34 -0
- package/lib/types/univer/apply-operation.d.ts +14 -0
- package/lib/types/univer/apply-operation.js +398 -0
- package/lib/types/univer/chunk.d.ts +68 -0
- package/lib/types/univer/chunk.js +16 -0
- package/lib/types/univer/decoration.d.ts +34 -0
- package/lib/types/univer/decoration.js +88 -0
- package/lib/types/univer/diagnostics.d.ts +31 -0
- package/lib/types/univer/diagnostics.js +141 -0
- package/lib/types/univer/engine.d.ts +33 -0
- package/lib/types/univer/engine.js +338 -0
- package/lib/types/univer/entry.d.ts +8 -0
- package/lib/types/univer/entry.js +11 -0
- package/lib/types/univer/facade.d.ts +107 -0
- package/lib/types/univer/facade.js +1 -0
- package/lib/types/univer/fonts.d.ts +202 -0
- package/lib/types/univer/fonts.js +140 -0
- package/lib/types/univer/plugins/collaboration.d.ts +23 -0
- package/lib/types/univer/plugins/collaboration.js +47 -0
- package/lib/types/univer/plugins/export.d.ts +39 -0
- package/lib/types/univer/plugins/export.js +246 -0
- package/lib/types/univer/plugins/history.d.ts +24 -0
- package/lib/types/univer/plugins/history.js +50 -0
- package/lib/types/univer/plugins/version.d.ts +13 -0
- package/lib/types/univer/plugins/version.js +13 -0
- package/lib/types/version/diff.d.ts +36 -0
- package/lib/types/version/diff.js +156 -0
- package/lib/types/version/grouping.d.ts +7 -0
- package/lib/types/version/grouping.js +94 -0
- package/lib/types/version/records.d.ts +17 -0
- package/lib/types/version/records.js +102 -0
- package/lib/types/version/session.d.ts +36 -0
- package/lib/types/version/session.js +89 -0
- package/lib/types/version/summary.d.ts +24 -0
- package/lib/types/version/summary.js +111 -0
- package/lib/types/workbook-manager.d.ts +191 -0
- package/lib/types/workbook-manager.js +935 -0
- package/lib/types/xlsx-export-worker.d.ts +5 -0
- package/lib/types/xlsx-export-worker.js +24 -0
- package/lib/types/xlsx-export.d.ts +21 -0
- package/lib/types/xlsx-export.js +371 -0
- package/lib/types/xlsx-import-worker.d.ts +1 -0
- package/lib/types/xlsx-import-worker.js +19 -0
- package/lib/univer-client.js +690 -0
- package/lib/workbook-page.js +23826 -0
- package/lib/xlsx-export-worker.js +104 -0
- package/lib/xlsx-import-worker.js +46 -0
- package/package.json +106 -3
- package/src/audit.ts +58 -0
- package/src/client/ProjectImportMenu.tsx +100 -0
- package/src/client/SheetPanel.module.css +1578 -0
- package/src/client/SheetPanel.tsx +1934 -0
- package/src/client/SheetToggle.module.css +73 -0
- package/src/client/SheetToggle.tsx +65 -0
- package/src/client/contract.d.ts +19 -0
- package/src/client/index.ts +131 -0
- package/src/client/persistence/changes.ts +58 -0
- package/src/client/persistence/ids.ts +60 -0
- package/src/client/persistence/merge.ts +392 -0
- package/src/client/store.ts +188 -0
- package/src/client/surface-context.ts +123 -0
- package/src/client/surface-control.ts +13 -0
- package/src/client/transport/workbookApi.ts +355 -0
- package/src/client/univer/SheetView.module.css +110 -0
- package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
- package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
- package/src/client/univer/WorkbookCanvas.tsx +128 -0
- package/src/client/univer/WorkbookView.module.css +30 -0
- package/src/client/univer/WorkbookView.tsx +3 -0
- package/src/client/univer/loader.ts +90 -0
- package/src/client/univer/react-dom-client.d.ts +10 -0
- package/src/client/univer/standalone-entry.tsx +19 -0
- package/src/client/univer/standalone.css +12 -0
- package/src/client/univer/univer-locales.d.ts +89 -0
- package/src/client/univer/univer-modules.d.ts +26 -0
- package/src/client/versions/VersionChrome.tsx +114 -0
- package/src/client/versions/inspect.ts +138 -0
- package/src/client/versions/preview-decoration.ts +54 -0
- package/src/client/versions/records.ts +78 -0
- package/src/client/workspace-host.ts +147 -0
- package/src/collaboration-hub.ts +83 -0
- package/src/contract/ids.ts +50 -0
- package/src/contract/mutations.ts +791 -0
- package/src/contract/operations.ts +221 -0
- package/src/contract/snapshots.ts +383 -0
- package/src/contract/univer-api-catalog.ts +289 -0
- package/src/css-modules.d.ts +6 -0
- package/src/errors.ts +25 -0
- package/src/http/assets-routes.ts +177 -0
- package/src/http/collaboration.ts +144 -0
- package/src/http/history.ts +151 -0
- package/src/http/project-files.ts +227 -0
- package/src/http/project-scope.ts +77 -0
- package/src/http/routes.ts +364 -0
- package/src/http/standalone.ts +32 -0
- package/src/http/trunk.ts +322 -0
- package/src/http/worktree.ts +119 -0
- package/src/index.ts +165 -0
- package/src/invariant.ts +17 -0
- package/src/request-params.ts +92 -0
- package/src/store/paths.ts +165 -0
- package/src/store/project.ts +137 -0
- package/src/store/read.ts +454 -0
- package/src/store/retention.ts +106 -0
- package/src/store/write.ts +230 -0
- package/src/tools/arguments.ts +128 -0
- package/src/tools/cells.ts +298 -0
- package/src/tools/index.ts +274 -0
- package/src/tools/render.ts +301 -0
- package/src/tools/univer-api.ts +243 -0
- package/src/tools/workbook-catalog.ts +94 -0
- package/src/tools/workbook-edit.ts +987 -0
- package/src/tools/workbook-find.ts +80 -0
- package/src/tools/workbook-history.ts +63 -0
- package/src/tools/workbook-read.ts +210 -0
- package/src/types.ts +432 -0
- package/src/univer/apply-operation.ts +369 -0
- package/src/univer/chunk.ts +77 -0
- package/src/univer/decoration.ts +111 -0
- package/src/univer/diagnostics.ts +136 -0
- package/src/univer/engine.ts +370 -0
- package/src/univer/entry.ts +14 -0
- package/src/univer/facade.ts +91 -0
- package/src/univer/fonts.ts +148 -0
- package/src/univer/overrides.css +33 -0
- package/src/univer/plugins/collaboration.ts +47 -0
- package/src/univer/plugins/export.ts +236 -0
- package/src/univer/plugins/history.ts +49 -0
- package/src/univer/plugins/version.ts +13 -0
- package/src/version/diff.ts +169 -0
- package/src/version/grouping.ts +96 -0
- package/src/version/records.ts +113 -0
- package/src/version/session.ts +90 -0
- package/src/version/summary.ts +117 -0
- package/src/workbook-manager.ts +1111 -0
- package/src/xlsx-export-worker.ts +34 -0
- package/src/xlsx-export.ts +373 -0
- 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
|
+
}
|