@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,123 @@
|
|
|
1
|
+
import type { SurfaceSelection } from '@tita-agent/dsh-surface-bridge'
|
|
2
|
+
|
|
3
|
+
export const DATA_SHEET_SURFACE_ID = 'data-sheet'
|
|
4
|
+
|
|
5
|
+
export interface DataSheetFocusTarget {
|
|
6
|
+
readonly workbookId: string
|
|
7
|
+
readonly sheetId: string
|
|
8
|
+
readonly startRow: number
|
|
9
|
+
readonly startColumn: number
|
|
10
|
+
readonly endRow: number
|
|
11
|
+
readonly endColumn: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DataSheetRangeContext extends Omit<DataSheetFocusTarget, 'workbookId'> {
|
|
15
|
+
readonly sheetName: string
|
|
16
|
+
readonly values: readonly (readonly unknown[])[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DataSheetSelectionContext {
|
|
20
|
+
readonly workbookId: string
|
|
21
|
+
readonly workbookName: string
|
|
22
|
+
readonly revision: number
|
|
23
|
+
readonly documentRevision: number
|
|
24
|
+
readonly ranges: readonly DataSheetRangeContext[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Keep source-specific focus data opaque to the bridge, but stable in transcript metadata. */
|
|
28
|
+
export function dataSheetFocusId(target: DataSheetFocusTarget): string {
|
|
29
|
+
return JSON.stringify(target)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Decode a focus id emitted by this source. */
|
|
33
|
+
export function dataSheetFocusTargetOf(value: string): DataSheetFocusTarget | undefined {
|
|
34
|
+
try {
|
|
35
|
+
const target = JSON.parse(value) as Partial<DataSheetFocusTarget>
|
|
36
|
+
if (typeof target.workbookId !== 'string' || typeof target.sheetId !== 'string') return undefined
|
|
37
|
+
for (const key of ['startRow', 'startColumn', 'endRow', 'endColumn'] as const) {
|
|
38
|
+
if (!Number.isSafeInteger(target[key]) || target[key]! < 0) return undefined
|
|
39
|
+
}
|
|
40
|
+
return target as DataSheetFocusTarget
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function columnName(index: number): string {
|
|
47
|
+
let value = index + 1
|
|
48
|
+
let name = ''
|
|
49
|
+
while (value > 0) {
|
|
50
|
+
const remainder = (value - 1) % 26
|
|
51
|
+
name = String.fromCharCode(65 + remainder) + name
|
|
52
|
+
value = Math.floor((value - 1) / 26)
|
|
53
|
+
}
|
|
54
|
+
return name
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cellAddress(row: number, column: number): string {
|
|
58
|
+
return `${columnName(column)}${row + 1}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function rangeAddress(range: DataSheetRangeContext): string {
|
|
62
|
+
const start = cellAddress(range.startRow, range.startColumn)
|
|
63
|
+
const end = cellAddress(range.endRow, range.endColumn)
|
|
64
|
+
return `${range.sheetName}!${start === end ? start : `${start}:${end}`}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cellText(value: unknown): string {
|
|
68
|
+
if (value === null || value === undefined) return ''
|
|
69
|
+
if (typeof value === 'string') return value
|
|
70
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
71
|
+
try { return JSON.stringify(value) ?? '' } catch { return String(value) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function previewText(values: readonly (readonly unknown[])[]): string | undefined {
|
|
75
|
+
if (values.length === 0) return undefined
|
|
76
|
+
const rendered = values.map(row => row.map(cellText).join('\t')).join('\n').slice(0, 2_000)
|
|
77
|
+
return rendered.length === 0 ? undefined : rendered
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Project the active Univer ranges into the bridge's shared context vocabulary. */
|
|
81
|
+
export function projectDataSheetSelection(input: DataSheetSelectionContext): SurfaceSelection | null {
|
|
82
|
+
const ranges = input.ranges.filter(range => range.startRow <= range.endRow && range.startColumn <= range.endColumn)
|
|
83
|
+
if (ranges.length === 0) return null
|
|
84
|
+
const count = ranges.reduce((total, range) => total
|
|
85
|
+
+ (range.endRow - range.startRow + 1) * (range.endColumn - range.startColumn + 1), 0)
|
|
86
|
+
const items = ranges.slice(0, 40).map(range => {
|
|
87
|
+
const address = rangeAddress(range)
|
|
88
|
+
return {
|
|
89
|
+
id: dataSheetFocusId({ workbookId: input.workbookId, ...range }),
|
|
90
|
+
type: 'range',
|
|
91
|
+
label: address,
|
|
92
|
+
...(previewText(range.values) === undefined ? {} : { text: previewText(range.values) }),
|
|
93
|
+
attributes: {
|
|
94
|
+
workbookId: input.workbookId,
|
|
95
|
+
sheetId: range.sheetId,
|
|
96
|
+
sheetName: range.sheetName,
|
|
97
|
+
address,
|
|
98
|
+
startRow: range.startRow,
|
|
99
|
+
startColumn: range.startColumn,
|
|
100
|
+
endRow: range.endRow,
|
|
101
|
+
endColumn: range.endColumn,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
const labels = ranges.map(rangeAddress)
|
|
106
|
+
const summary = ranges.length === 1
|
|
107
|
+
? `已选中 ${labels[0]},共 ${count} 个单元格`
|
|
108
|
+
: `已选中 ${ranges.length} 个区域,共 ${count} 个单元格`
|
|
109
|
+
const note = `工作簿 ID:${input.workbookId};选区:${labels.join('、')}。条目文本最多预览每个区域前 10 行 × 20 列;完整数据用 workbook_read(workbookId="${input.workbookId}", target={kind:"range", sheetId, range}) 读取。写入前先读取最新 revision,再用 workbook_edit。`
|
|
110
|
+
return {
|
|
111
|
+
source: DATA_SHEET_SURFACE_ID,
|
|
112
|
+
revision: input.revision,
|
|
113
|
+
count,
|
|
114
|
+
countLabel: `${count} 个单元格`,
|
|
115
|
+
title: 'Table',
|
|
116
|
+
summary,
|
|
117
|
+
items,
|
|
118
|
+
...(ranges.length > items.length ? { truncated: true } : {}),
|
|
119
|
+
resource: { name: input.workbookId, display: input.workbookName, version: input.documentRevision },
|
|
120
|
+
notes: [note],
|
|
121
|
+
capabilities: [`使用 workbook_read / workbook_edit 操作工作簿 ${input.workbookId};写入时传该 id 和最新 revision。`],
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One live workbook view can honour focus requests from the shared bridge chip. */
|
|
2
|
+
let focusHandler: ((itemId: string) => void) | undefined
|
|
3
|
+
|
|
4
|
+
export function registerDataSheetFocusHandler(handler: (itemId: string) => void): () => void {
|
|
5
|
+
focusHandler = handler
|
|
6
|
+
return () => {
|
|
7
|
+
if (focusHandler === handler) focusHandler = undefined
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function focusDataSheetItem(itemId: string): void {
|
|
12
|
+
focusHandler?.(itemId)
|
|
13
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WorkbookLocalChange,
|
|
3
|
+
WorkbookOperation,
|
|
4
|
+
WorkbookSummary,
|
|
5
|
+
WorkbookWorktree,
|
|
6
|
+
WorkbookWorktreeSummary,
|
|
7
|
+
WorkbookChange,
|
|
8
|
+
WorkbookPresencePayload,
|
|
9
|
+
WorkbookHistoryVersion,
|
|
10
|
+
} from '../../types.ts'
|
|
11
|
+
|
|
12
|
+
export class HttpStatusError extends Error {
|
|
13
|
+
constructor(readonly status: number, message: string, readonly code?: string, readonly details?: unknown) {
|
|
14
|
+
super(message)
|
|
15
|
+
this.name = 'HttpStatusError'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class RevisionConflictError extends HttpStatusError {
|
|
20
|
+
constructor(message = 'workbook revision conflict', details?: unknown) {
|
|
21
|
+
super(409, message, 'REVISION_CONFLICT', details)
|
|
22
|
+
this.name = 'RevisionConflictError'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SESSION_STORAGE_KEY = 'data-sheet.session-id'
|
|
27
|
+
let cachedSessionId: string | null = null
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The DSH Session whose project this page reads and writes.
|
|
31
|
+
*
|
|
32
|
+
* Set once when the panel mounts (the sidebar hands its Session id to the body)
|
|
33
|
+
* or, for the standalone page, read from its URL. Every workbook path is
|
|
34
|
+
* resolved by the Host inside that Session's project —
|
|
35
|
+
* `<project>/storages/data-table` — so a request that names the wrong Session
|
|
36
|
+
* reads and writes another project's workbooks. A request that names **no**
|
|
37
|
+
* Session keeps the pre-project per-user store instead, which is right for a
|
|
38
|
+
* script and wrong for a page: the panel always sets this.
|
|
39
|
+
*/
|
|
40
|
+
let dshSessionId: string | null = null
|
|
41
|
+
|
|
42
|
+
/** Tell the transport which DSH Session this page belongs to. */
|
|
43
|
+
export function setDshSessionId(id: string | undefined): void {
|
|
44
|
+
dshSessionId = id === undefined || id.length === 0 ? null : id
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The Session this page is bound to, when it is bound to one. */
|
|
48
|
+
export function currentDshSessionId(): string | null {
|
|
49
|
+
return dshSessionId
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Route browser requests through the official Electron Connection carrier. */
|
|
53
|
+
export function dataSheetClientPath(path: string): string {
|
|
54
|
+
const transport = (globalThis as { __DSH_TRANSPORT__?: { ownsHost?: boolean } }).__DSH_TRANSPORT__
|
|
55
|
+
return transport?.ownsHost === true
|
|
56
|
+
? `/api/data-sheet?path=${encodeURIComponent(path)}`
|
|
57
|
+
: path
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Stable per-tab scope used by worktree APIs and Host audit logs. */
|
|
61
|
+
export function dataSheetSessionId(): string {
|
|
62
|
+
if (cachedSessionId !== null) return cachedSessionId
|
|
63
|
+
try {
|
|
64
|
+
const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY)
|
|
65
|
+
if (existing !== null && existing.trim().length > 0) {
|
|
66
|
+
cachedSessionId = existing
|
|
67
|
+
return existing
|
|
68
|
+
}
|
|
69
|
+
const generated = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
70
|
+
? crypto.randomUUID()
|
|
71
|
+
: `${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
72
|
+
window.sessionStorage.setItem(SESSION_STORAGE_KEY, generated)
|
|
73
|
+
cachedSessionId = generated
|
|
74
|
+
return generated
|
|
75
|
+
} catch {
|
|
76
|
+
cachedSessionId = `ephemeral-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
77
|
+
return cachedSessionId
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The live collaboration stream for one workbook.
|
|
83
|
+
*
|
|
84
|
+
* `EventSource` cannot set the `x-dsh-session` header every other call carries,
|
|
85
|
+
* so the Session — which decides *which project's* workbook this is — travels as
|
|
86
|
+
* a query parameter it can carry. The `session` parameter is a different thing:
|
|
87
|
+
* this browser tab's own id, used for presence and worktrees.
|
|
88
|
+
*
|
|
89
|
+
* @param workbookId - Workbook to watch.
|
|
90
|
+
* @param after - Revision to resume from.
|
|
91
|
+
* @returns a same-origin path, ready for `dataSheetClientPath`.
|
|
92
|
+
*/
|
|
93
|
+
export function collaborationStreamPath(workbookId: string, after: number): string {
|
|
94
|
+
const project = currentDshSessionId()
|
|
95
|
+
const scope = project === null ? '' : `&sessionId=${encodeURIComponent(project)}`
|
|
96
|
+
return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/collaboration?after=${after}`
|
|
97
|
+
+ `&session=${encodeURIComponent(dataSheetSessionId())}${scope}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The standalone page for one workbook.
|
|
102
|
+
*
|
|
103
|
+
* A new tab has no sidebar to hand it a Session id, so the project travels in
|
|
104
|
+
* the URL: without it the page resolves against the sandbox policy root and
|
|
105
|
+
* could open another project's workbook.
|
|
106
|
+
*
|
|
107
|
+
* @param workbookId - Workbook to open in a plain browser document.
|
|
108
|
+
* @returns a same-origin path.
|
|
109
|
+
*/
|
|
110
|
+
export function standaloneWorkbookPath(workbookId: string): string {
|
|
111
|
+
const project = currentDshSessionId()
|
|
112
|
+
return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/view`
|
|
113
|
+
+ (project === null ? '' : `?sessionId=${encodeURIComponent(project)}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function requestJson<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
117
|
+
const headers = new Headers(init.headers)
|
|
118
|
+
headers.set('accept', 'application/json')
|
|
119
|
+
headers.set('x-data-sheet-session', dataSheetSessionId())
|
|
120
|
+
// Which *project* the Host should resolve the path inside. Distinct from the
|
|
121
|
+
// header above, which is this browser tab's own id for worktrees and presence.
|
|
122
|
+
if (dshSessionId !== null) headers.set('x-dsh-session', dshSessionId)
|
|
123
|
+
if (init.body !== undefined && !headers.has('content-type')) headers.set('content-type', 'application/json')
|
|
124
|
+
let response: Response | null = null
|
|
125
|
+
let networkError: unknown = null
|
|
126
|
+
// GETs and writes carrying an opId are safe to retry: the Host's idempotency
|
|
127
|
+
// check makes a lost response indistinguishable from a successful retry.
|
|
128
|
+
const retryable = init.method === undefined || init.method === 'GET' || init.method === 'HEAD'
|
|
129
|
+
|| typeof init.body === 'string' && init.body.includes('"opId"')
|
|
130
|
+
for (let attempt = 0; attempt < (retryable ? 3 : 1); attempt += 1) {
|
|
131
|
+
try {
|
|
132
|
+
response = await fetch(dataSheetClientPath(path), { ...init, headers })
|
|
133
|
+
networkError = null
|
|
134
|
+
break
|
|
135
|
+
} catch (error) {
|
|
136
|
+
networkError = error
|
|
137
|
+
if (attempt + 1 < (retryable ? 3 : 1)) await new Promise(resolve => window.setTimeout(resolve, 150 * (attempt + 1)))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (response === null) {
|
|
141
|
+
const detail = networkError instanceof Error ? networkError.message : ''
|
|
142
|
+
throw new HttpStatusError(0, detail === 'Failed to fetch' ? '网络请求失败,请确认 DSH 服务仍在运行后重试' : (detail || '网络请求失败'), 'NETWORK_ERROR', { detail })
|
|
143
|
+
}
|
|
144
|
+
let body: unknown = null
|
|
145
|
+
try { body = await response.json() } catch { /* non-json host/proxy failure */ }
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
const record = typeof body === 'object' && body !== null ? body as Record<string, unknown> : {}
|
|
148
|
+
const message = typeof record.error === 'string' ? record.error
|
|
149
|
+
: typeof record.message === 'string' ? record.message
|
|
150
|
+
: `${response.status} ${response.statusText || '请求失败'}`
|
|
151
|
+
const code = typeof record.code === 'string' ? record.code : undefined
|
|
152
|
+
if (response.status === 409 || code === 'REVISION_CONFLICT') throw new RevisionConflictError(message, body)
|
|
153
|
+
throw new HttpStatusError(response.status, message, code, body)
|
|
154
|
+
}
|
|
155
|
+
return body as T
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface WorkbookEnvelope {
|
|
159
|
+
ok?: boolean
|
|
160
|
+
workbookId?: string
|
|
161
|
+
revision?: number
|
|
162
|
+
updatedAt?: string
|
|
163
|
+
snapshot?: object
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ChangesEnvelope {
|
|
167
|
+
revision?: number
|
|
168
|
+
operations?: WorkbookOperation[]
|
|
169
|
+
requiresSnapshot?: boolean
|
|
170
|
+
snapshot?: object
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface HistoryEntry {
|
|
174
|
+
opId: string
|
|
175
|
+
revision: number
|
|
176
|
+
parentRevision?: number
|
|
177
|
+
actor: 'agent' | 'user'
|
|
178
|
+
type: string
|
|
179
|
+
timestamp: string
|
|
180
|
+
summary: string
|
|
181
|
+
canPreview?: boolean
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface HistoryEnvelope {
|
|
185
|
+
entries?: HistoryEntry[]
|
|
186
|
+
versions?: WorkbookHistoryVersion[]
|
|
187
|
+
checkpoints?: number[]
|
|
188
|
+
revision?: number
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface HistoryDiffEnvelope {
|
|
192
|
+
workbookId?: string
|
|
193
|
+
from?: number
|
|
194
|
+
to?: number
|
|
195
|
+
changes?: WorkbookChange[]
|
|
196
|
+
/** True when the server stopped at its diff limit, so the counts are a floor. */
|
|
197
|
+
truncated?: boolean
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface WorkbookMutationEnvelope {
|
|
201
|
+
workbook?: {
|
|
202
|
+
id: string
|
|
203
|
+
name: string
|
|
204
|
+
sheets?: WorkbookSummary['sheets']
|
|
205
|
+
updatedAt?: string
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function fetchWorkbook(id: string): Promise<WorkbookEnvelope> {
|
|
210
|
+
const value = await requestJson<WorkbookEnvelope | object>(`/data-sheet/workbook/${encodeURIComponent(id)}.json`)
|
|
211
|
+
if (typeof value === 'object' && value !== null && 'snapshot' in value) return value as WorkbookEnvelope
|
|
212
|
+
return { ok: true, workbookId: id, revision: 0, snapshot: value as object }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** One entry of the project file chooser's listing; mirrors the Host's shape. */
|
|
216
|
+
export interface ProjectFileEntry {
|
|
217
|
+
name: string
|
|
218
|
+
path: string
|
|
219
|
+
type: 'file' | 'directory'
|
|
220
|
+
size: number
|
|
221
|
+
createdAt: string
|
|
222
|
+
modifiedAt: string
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** One directory of `<project>/storages`, with sizes and dates. */
|
|
226
|
+
export async function fetchProjectFiles(directory: string): Promise<{ directory: string; entries: ProjectFileEntry[]; truncated?: number }> {
|
|
227
|
+
return requestJson(`/data-sheet/project-files.json?dir=${encodeURIComponent(directory)}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Read one file the chooser listed.
|
|
232
|
+
*
|
|
233
|
+
* The browser cannot open a path, so the Host reads it and the bytes arrive here
|
|
234
|
+
* as a buffer, ready for the import pipeline that already existed.
|
|
235
|
+
*/
|
|
236
|
+
export async function readProjectFile(path: string): Promise<{ name: string; data: ArrayBuffer }> {
|
|
237
|
+
const headers = new Headers({ accept: 'application/octet-stream' })
|
|
238
|
+
if (dshSessionId !== null) headers.set('x-dsh-session', dshSessionId)
|
|
239
|
+
const response = await fetch(dataSheetClientPath(`/data-sheet/project-file.json?path=${encodeURIComponent(path)}`), { headers })
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
const body = await response.json().catch(() => null) as { error?: unknown } | null
|
|
242
|
+
throw new HttpStatusError(response.status, typeof body?.error === 'string' ? body.error : `读取文件失败:${response.status}`)
|
|
243
|
+
}
|
|
244
|
+
const encoded = response.headers.get('x-file-name')
|
|
245
|
+
const name = encoded === null ? (path.split('/').pop() ?? path) : decodeURIComponent(encoded)
|
|
246
|
+
return { name, data: await response.arrayBuffer() }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function fetchWorkbooks(): Promise<{ workbooks?: WorkbookSummary[] }> {
|
|
250
|
+
return requestJson('/data-sheet/workbooks.json')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function createWorkbook(name: string): Promise<WorkbookMutationEnvelope> {
|
|
254
|
+
return requestJson('/data-sheet/workbooks.json', {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
body: JSON.stringify({ action: 'create', name }),
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function renameWorkbook(id: string, name: string): Promise<WorkbookMutationEnvelope> {
|
|
261
|
+
return requestJson('/data-sheet/workbooks.json', {
|
|
262
|
+
method: 'POST',
|
|
263
|
+
body: JSON.stringify({ action: 'rename', workbookId: id, name }),
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function fetchChanges(id: string, after: number): Promise<ChangesEnvelope> {
|
|
268
|
+
return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/changes?after=${after}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function fetchHistory(id: string): Promise<HistoryEnvelope> {
|
|
272
|
+
return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface HistorySnapshotEnvelope {
|
|
276
|
+
revision?: number
|
|
277
|
+
snapshot?: object
|
|
278
|
+
step?: number
|
|
279
|
+
stepCount?: number
|
|
280
|
+
/** True when the Step's change list stopped at the server's limit. */
|
|
281
|
+
truncated?: boolean
|
|
282
|
+
/**
|
|
283
|
+
* What the requested Step changed. Present for Step previews, so the canvas
|
|
284
|
+
* can locate and tint the cells instead of leaving the reader to find them
|
|
285
|
+
* (PRD v3.3 §7.2).
|
|
286
|
+
*/
|
|
287
|
+
changes?: WorkbookChange[]
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function fetchHistorySnapshot(id: string, revision: number, step?: number): Promise<HistorySnapshotEnvelope> {
|
|
291
|
+
const query = step === undefined ? `revision=${revision}` : `revision=${revision}&step=${step}`
|
|
292
|
+
return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history?${query}`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function fetchHistoryDiff(id: string, from: number, to: number): Promise<HistoryDiffEnvelope> {
|
|
296
|
+
return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history/diff?from=${from}&to=${to}`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function rollbackWorkbook(id: string, targetRevision: number, baseRevision: number, summary?: string): Promise<{ revision?: number }> {
|
|
300
|
+
return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/rollback`, {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
body: JSON.stringify({ targetRevision, baseRevision, ...(summary === undefined ? {} : { summary }) }),
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function deleteWorkbook(id: string): Promise<void> {
|
|
307
|
+
await requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}.json?action=delete`, { method: 'POST', body: JSON.stringify({ confirm: true }) })
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface SaveResult {
|
|
311
|
+
revision: number
|
|
312
|
+
operation?: WorkbookOperation
|
|
313
|
+
rebasedOperations?: WorkbookOperation[]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function saveOperationChanges(id: string, changes: WorkbookLocalChange[], baseRevision: number, opId: string, version?: { id: string; status: 'open' | 'final' }): Promise<SaveResult | null> {
|
|
317
|
+
const operation = changes.length === 1 ? changes[0] : { type: 'batch' as const, payload: { operations: changes } }
|
|
318
|
+
const body = JSON.stringify({ type: operation.type, payload: operation.payload, actor: 'user', baseRevision, opId, ...(version === undefined ? {} : { versionId: version.id, versionStatus: version.status }) })
|
|
319
|
+
return requestJson<SaveResult | null>(`/data-sheet/workbook/${encodeURIComponent(id)}/operations`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
...(body.length <= 60 * 1024 ? { keepalive: true } : {}),
|
|
322
|
+
body,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface SnapshotSaveOptions {
|
|
327
|
+
/** Named save: the Version gets this summary and closes the session. */
|
|
328
|
+
summary?: string
|
|
329
|
+
/** Session Step: joins the open user Version (PRD v3.1 §4). */
|
|
330
|
+
versionId?: string
|
|
331
|
+
versionStatus?: 'open' | 'final'
|
|
332
|
+
versionKind?: 'named' | 'auto'
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function saveSnapshot(id: string, snapshotData: unknown, baseRevision: number, opId: string, options: SnapshotSaveOptions = {}): Promise<SaveResult | null> {
|
|
336
|
+
return requestJson<SaveResult | null>(`/data-sheet/workbook/${encodeURIComponent(id)}.json`, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
body: JSON.stringify({
|
|
339
|
+
snapshot: snapshotData,
|
|
340
|
+
baseRevision,
|
|
341
|
+
opId,
|
|
342
|
+
...(options.summary === undefined ? {} : { versionSummary: options.summary }),
|
|
343
|
+
...(options.versionId === undefined ? {} : { versionId: options.versionId }),
|
|
344
|
+
...(options.versionStatus === undefined ? {} : { versionStatus: options.versionStatus }),
|
|
345
|
+
...(options.versionKind === undefined ? {} : { versionKind: options.versionKind }),
|
|
346
|
+
}),
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function publishPresence(workbookId: string, presence: WorkbookPresencePayload): Promise<void> {
|
|
351
|
+
await requestJson(`/data-sheet/workbook/${encodeURIComponent(workbookId)}/presence`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
body: JSON.stringify(presence),
|
|
354
|
+
})
|
|
355
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.toolRow {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 6px;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.toolLine {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: baseline;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
font-size: 12px;
|
|
13
|
+
opacity: 0.8;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.toolName {
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.toolState {
|
|
21
|
+
opacity: 0.7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.view {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
/* The inline tool row has an auto-height parent, where `height: 100%`
|
|
28
|
+
resolves to auto and would collapse the flex body to 0; floor the
|
|
29
|
+
sheet height so the embedded table always has room. The rail panel
|
|
30
|
+
(definite-height parent) still fills via `height: 100%`. */
|
|
31
|
+
height: 100%;
|
|
32
|
+
min-height: 320px;
|
|
33
|
+
border: 1px solid var(--tita-line);
|
|
34
|
+
border-radius: 8px;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
background: var(--tita-surface);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bar {
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 8px;
|
|
43
|
+
padding: 6px 10px;
|
|
44
|
+
border-bottom: 1px solid var(--tita-line);
|
|
45
|
+
font-size: 12px;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.title {
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.dimensions {
|
|
57
|
+
opacity: 0.65;
|
|
58
|
+
margin-right: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.button {
|
|
62
|
+
padding: 2px 10px;
|
|
63
|
+
border: 1px solid var(--tita-line-strong);
|
|
64
|
+
border-radius: 6px;
|
|
65
|
+
background: transparent;
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.button:hover {
|
|
71
|
+
background: var(--tita-surface-muted);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.body {
|
|
75
|
+
position: relative;
|
|
76
|
+
flex: 1;
|
|
77
|
+
min-height: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.hint {
|
|
81
|
+
padding: 14px;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
color: var(--tita-ink-muted);
|
|
84
|
+
text-align: center;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hintSub {
|
|
88
|
+
margin-top: 4px;
|
|
89
|
+
opacity: 0.7;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.univer {
|
|
93
|
+
position: absolute;
|
|
94
|
+
inset: 0;
|
|
95
|
+
height: 100%;
|
|
96
|
+
width: 100%;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.toggle {
|
|
100
|
+
padding: 3px 10px;
|
|
101
|
+
border: 1px solid var(--tita-line-strong);
|
|
102
|
+
border-radius: 6px;
|
|
103
|
+
background: transparent;
|
|
104
|
+
font-size: 12px;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.toggle:hover {
|
|
109
|
+
background: var(--tita-surface-muted);
|
|
110
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WorkbookWorkspace } from '../SheetPanel.tsx'
|
|
2
|
+
import styles from './StandaloneWorkbookPage.module.css'
|
|
3
|
+
|
|
4
|
+
export interface StandaloneWorkbookPageProps {
|
|
5
|
+
workbookId: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Full-browser workbook workspace. It deliberately reuses the embedded controller. */
|
|
9
|
+
export function StandaloneWorkbookPage({ workbookId }: StandaloneWorkbookPageProps) {
|
|
10
|
+
return (
|
|
11
|
+
<main className={styles.root}>
|
|
12
|
+
<WorkbookWorkspace open standalone initialWorkbookId={workbookId} />
|
|
13
|
+
</main>
|
|
14
|
+
)
|
|
15
|
+
}
|