@dxos/plugin-sheet 0.6.11 → 0.6.12-main.5cc132e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{SheetContainer-U4H5D34A.mjs → SheetContainer-Y7ZMFBAP.mjs} +568 -109
- package/dist/lib/browser/SheetContainer-Y7ZMFBAP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-D5AGLXJP.mjs → chunk-GNNVBNCX.mjs} +55 -47
- package/dist/lib/browser/chunk-GNNVBNCX.mjs.map +7 -0
- package/dist/lib/browser/{chunk-APHOLYUB.mjs → chunk-PGKZPKUD.mjs} +2 -2
- package/dist/lib/browser/chunk-VBF7YENS.mjs +8 -0
- package/dist/lib/browser/{chunk-FUAGSXA4.mjs → chunk-WUPTZUTX.mjs} +6 -3
- package/dist/lib/browser/chunk-WUPTZUTX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +15 -6
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing.mjs +3 -3
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{SheetContainer-AXQV3ZT5.cjs → SheetContainer-KEOKUKAQ.cjs} +509 -62
- package/dist/lib/node/SheetContainer-KEOKUKAQ.cjs.map +7 -0
- package/dist/lib/node/{chunk-PYXHNAAK.cjs → chunk-57PB2HPY.cjs} +5 -5
- package/dist/lib/node/{chunk-CN3RPESU.cjs → chunk-6LWBQAQZ.cjs} +9 -9
- package/dist/lib/node/{chunk-DSYKOI4E.cjs → chunk-VJU3NPUJ.cjs} +8 -5
- package/dist/lib/node/chunk-VJU3NPUJ.cjs.map +7 -0
- package/dist/lib/node/{chunk-5KKJ4NPP.cjs → chunk-ZRQZFV5T.cjs} +70 -57
- package/dist/lib/node/chunk-ZRQZFV5T.cjs.map +7 -0
- package/dist/lib/node/index.cjs +31 -23
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing.cjs +6 -6
- package/dist/lib/node/types.cjs +9 -9
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/SheetContainer-Y7ZMFBAP.mjs +2231 -0
- package/dist/lib/node-esm/SheetContainer-Y7ZMFBAP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-GNNVBNCX.mjs +3243 -0
- package/dist/lib/node-esm/chunk-GNNVBNCX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-JRL5LGCE.mjs +18 -0
- package/dist/lib/node-esm/chunk-JRL5LGCE.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PGKZPKUD.mjs +175 -0
- package/dist/lib/node-esm/chunk-PGKZPKUD.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-VBF7YENS.mjs +8 -0
- package/dist/lib/node-esm/chunk-VBF7YENS.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WUPTZUTX.mjs +85 -0
- package/dist/lib/node-esm/chunk-WUPTZUTX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +257 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/meta.mjs +9 -0
- package/dist/lib/node-esm/meta.mjs.map +7 -0
- package/dist/lib/node-esm/testing.mjs +92 -0
- package/dist/lib/node-esm/testing.mjs.map +7 -0
- package/dist/lib/node-esm/types.mjs +22 -0
- package/dist/lib/node-esm/types.mjs.map +7 -0
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/decorations.d.ts +24 -0
- package/dist/types/src/components/Sheet/decorations.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/formatting.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/sheet-context.d.ts +2 -0
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/threads.d.ts +2 -0
- package/dist/types/src/components/Sheet/threads.d.ts.map +1 -0
- package/dist/types/src/components/SheetContainer.d.ts +2 -3
- package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +19 -3
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +17 -12
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/model/index.d.ts +1 -0
- package/dist/types/src/model/index.d.ts.map +1 -1
- package/dist/types/src/model/model.d.ts +0 -16
- package/dist/types/src/model/model.d.ts.map +1 -1
- package/dist/types/src/model/util.d.ts +24 -0
- package/dist/types/src/model/util.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +17 -12
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +72 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +36 -32
- package/src/SheetPlugin.tsx +8 -15
- package/src/components/CellEditor/extension.test.ts +1 -2
- package/src/components/ComputeGraph/graph.browser.test.ts +1 -2
- package/src/components/Sheet/Sheet.stories.tsx +5 -1
- package/src/components/Sheet/Sheet.tsx +45 -8
- package/src/components/Sheet/decorations.ts +62 -0
- package/src/components/Sheet/formatting.ts +3 -3
- package/src/components/Sheet/sheet-context.tsx +9 -1
- package/src/components/Sheet/threads.tsx +201 -0
- package/src/components/SheetContainer.tsx +72 -18
- package/src/components/Toolbar/Toolbar.tsx +54 -12
- package/src/model/index.ts +1 -0
- package/src/model/model.browser.test.ts +1 -2
- package/src/model/model.ts +9 -43
- package/src/model/types.test.ts +1 -2
- package/src/model/util.ts +67 -0
- package/src/translations.ts +6 -1
- package/src/types.ts +26 -3
- package/dist/lib/browser/SheetContainer-U4H5D34A.mjs.map +0 -7
- package/dist/lib/browser/chunk-D5AGLXJP.mjs.map +0 -7
- package/dist/lib/browser/chunk-FUAGSXA4.mjs.map +0 -7
- package/dist/lib/browser/chunk-NU4PBN33.mjs +0 -8
- package/dist/lib/node/SheetContainer-AXQV3ZT5.cjs.map +0 -7
- package/dist/lib/node/chunk-5KKJ4NPP.cjs.map +0 -7
- package/dist/lib/node/chunk-DSYKOI4E.cjs.map +0 -7
- /package/dist/lib/browser/{chunk-APHOLYUB.mjs.map → chunk-PGKZPKUD.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-NU4PBN33.mjs.map → chunk-VBF7YENS.mjs.map} +0 -0
- /package/dist/lib/node/{chunk-PYXHNAAK.cjs.map → chunk-57PB2HPY.cjs.map} +0 -0
- /package/dist/lib/node/{chunk-CN3RPESU.cjs.map → chunk-6LWBQAQZ.cjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-sheet",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12-main.5cc132e",
|
|
4
4
|
"description": "Braneframe sketch plugin",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -10,14 +10,16 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"browser": "./dist/lib/browser/index.mjs",
|
|
12
12
|
"node": {
|
|
13
|
-
"
|
|
13
|
+
"require": "./dist/lib/node/index.cjs",
|
|
14
|
+
"default": "./dist/lib/node-esm/index.mjs"
|
|
14
15
|
},
|
|
15
16
|
"types": "./dist/types/src/index.d.ts"
|
|
16
17
|
},
|
|
17
18
|
"./meta": {
|
|
18
19
|
"browser": "./dist/lib/browser/meta.mjs",
|
|
19
20
|
"node": {
|
|
20
|
-
"
|
|
21
|
+
"require": "./dist/lib/node/meta.cjs",
|
|
22
|
+
"default": "./dist/lib/node-esm/meta.mjs"
|
|
21
23
|
},
|
|
22
24
|
"types": "./dist/types/src/meta.d.ts"
|
|
23
25
|
},
|
|
@@ -31,7 +33,8 @@
|
|
|
31
33
|
"./types": {
|
|
32
34
|
"browser": "./dist/lib/browser/types.mjs",
|
|
33
35
|
"node": {
|
|
34
|
-
"
|
|
36
|
+
"require": "./dist/lib/node/types.cjs",
|
|
37
|
+
"default": "./dist/lib/node-esm/types.mjs"
|
|
35
38
|
},
|
|
36
39
|
"types": "./dist/types/src/types.d.ts"
|
|
37
40
|
}
|
|
@@ -76,25 +79,26 @@
|
|
|
76
79
|
"re-resizable": "^6.9.17",
|
|
77
80
|
"react-markdown": "^8.0.5",
|
|
78
81
|
"react-resize-detector": "^11.0.1",
|
|
79
|
-
"@dxos/app-framework": "0.6.
|
|
80
|
-
"@dxos/async": "0.6.
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/invariant": "0.6.
|
|
87
|
-
"@dxos/keys": "0.6.
|
|
88
|
-
"@dxos/
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/plugin-
|
|
91
|
-
"@dxos/plugin-
|
|
92
|
-
"@dxos/plugin-
|
|
93
|
-
"@dxos/plugin-
|
|
94
|
-
"@dxos/react-
|
|
95
|
-
"@dxos/react-
|
|
96
|
-
"@dxos/
|
|
97
|
-
"@dxos/
|
|
82
|
+
"@dxos/app-framework": "0.6.12-main.5cc132e",
|
|
83
|
+
"@dxos/async": "0.6.12-main.5cc132e",
|
|
84
|
+
"@dxos/client": "0.6.12-main.5cc132e",
|
|
85
|
+
"@dxos/crypto": "0.6.12-main.5cc132e",
|
|
86
|
+
"@dxos/context": "0.6.12-main.5cc132e",
|
|
87
|
+
"@dxos/echo-schema": "0.6.12-main.5cc132e",
|
|
88
|
+
"@dxos/debug": "0.6.12-main.5cc132e",
|
|
89
|
+
"@dxos/invariant": "0.6.12-main.5cc132e",
|
|
90
|
+
"@dxos/keys": "0.6.12-main.5cc132e",
|
|
91
|
+
"@dxos/log": "0.6.12-main.5cc132e",
|
|
92
|
+
"@dxos/plugin-attention": "0.6.12-main.5cc132e",
|
|
93
|
+
"@dxos/plugin-client": "0.6.12-main.5cc132e",
|
|
94
|
+
"@dxos/plugin-graph": "0.6.12-main.5cc132e",
|
|
95
|
+
"@dxos/plugin-script": "0.6.12-main.5cc132e",
|
|
96
|
+
"@dxos/plugin-space": "0.6.12-main.5cc132e",
|
|
97
|
+
"@dxos/react-client": "0.6.12-main.5cc132e",
|
|
98
|
+
"@dxos/react-ui-attention": "0.6.12-main.5cc132e",
|
|
99
|
+
"@dxos/plugin-stack": "0.6.12-main.5cc132e",
|
|
100
|
+
"@dxos/react-ui-editor": "0.6.12-main.5cc132e",
|
|
101
|
+
"@dxos/util": "0.6.12-main.5cc132e"
|
|
98
102
|
},
|
|
99
103
|
"devDependencies": {
|
|
100
104
|
"@lezer/generator": "^1.7.1",
|
|
@@ -106,20 +110,20 @@
|
|
|
106
110
|
"@types/react-window": "^1.8.8",
|
|
107
111
|
"react": "~18.2.0",
|
|
108
112
|
"react-dom": "~18.2.0",
|
|
109
|
-
"vite": "
|
|
110
|
-
"@dxos/echo-generator": "0.6.
|
|
111
|
-
"@dxos/random": "0.6.
|
|
112
|
-
"@dxos/react-ui": "0.6.
|
|
113
|
-
"@dxos/react-ui-types": "0.6.
|
|
114
|
-
"@dxos/
|
|
115
|
-
"@dxos/
|
|
113
|
+
"vite": "5.4.7",
|
|
114
|
+
"@dxos/echo-generator": "0.6.12-main.5cc132e",
|
|
115
|
+
"@dxos/random": "0.6.12-main.5cc132e",
|
|
116
|
+
"@dxos/react-ui": "0.6.12-main.5cc132e",
|
|
117
|
+
"@dxos/react-ui-types": "0.6.12-main.5cc132e",
|
|
118
|
+
"@dxos/react-ui-theme": "0.6.12-main.5cc132e",
|
|
119
|
+
"@dxos/storybook-utils": "0.6.12-main.5cc132e"
|
|
116
120
|
},
|
|
117
121
|
"optionalDependencies": {
|
|
118
122
|
"@phosphor-icons/react": "^2.1.5",
|
|
119
123
|
"react": "^18.0.0",
|
|
120
124
|
"react-dom": "^18.0.0",
|
|
121
|
-
"@dxos/react-ui
|
|
122
|
-
"@dxos/react-ui": "0.6.
|
|
125
|
+
"@dxos/react-ui": "0.6.12-main.5cc132e",
|
|
126
|
+
"@dxos/react-ui-theme": "0.6.12-main.5cc132e"
|
|
123
127
|
},
|
|
124
128
|
"publishConfig": {
|
|
125
129
|
"access": "public"
|
package/src/SheetPlugin.tsx
CHANGED
|
@@ -5,13 +5,7 @@
|
|
|
5
5
|
import { type IconProps, GridNine } from '@phosphor-icons/react';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
NavigationAction,
|
|
10
|
-
parseIntentPlugin,
|
|
11
|
-
resolvePlugin,
|
|
12
|
-
type PluginDefinition,
|
|
13
|
-
type LayoutCoordinate,
|
|
14
|
-
} from '@dxos/app-framework';
|
|
8
|
+
import { NavigationAction, parseIntentPlugin, resolvePlugin, type PluginDefinition } from '@dxos/app-framework';
|
|
15
9
|
import { create } from '@dxos/echo-schema';
|
|
16
10
|
import { parseClientPlugin } from '@dxos/plugin-client';
|
|
17
11
|
import { type ActionGroup, createExtension, isActionGroup } from '@dxos/plugin-graph';
|
|
@@ -30,7 +24,7 @@ import {
|
|
|
30
24
|
import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './components/ComputeGraph/edge-function';
|
|
31
25
|
import { ComputeGraphContextProvider } from './components/ComputeGraph/graph-context';
|
|
32
26
|
import meta, { SHEET_PLUGIN } from './meta';
|
|
33
|
-
import { SheetModel } from './model';
|
|
27
|
+
import { compareIndexPositions, SheetModel } from './model';
|
|
34
28
|
import translations from './translations';
|
|
35
29
|
import { createSheet, SheetAction, type SheetPluginProvides, SheetType } from './types';
|
|
36
30
|
|
|
@@ -138,6 +132,11 @@ export const SheetPlugin = (): PluginDefinition<SheetPluginProvides> => {
|
|
|
138
132
|
},
|
|
139
133
|
],
|
|
140
134
|
},
|
|
135
|
+
thread: {
|
|
136
|
+
predicate: (data) => data instanceof SheetType,
|
|
137
|
+
createSort: (sheet) => (anchorA, anchorB) =>
|
|
138
|
+
!anchorA || !anchorB ? 0 : compareIndexPositions(sheet, anchorA, anchorB),
|
|
139
|
+
},
|
|
141
140
|
surface: {
|
|
142
141
|
component: ({ data, role = 'never' }) => {
|
|
143
142
|
// TODO(burdon): Standardize wrapper (with room for toolbar).
|
|
@@ -147,13 +146,7 @@ export const SheetPlugin = (): PluginDefinition<SheetPluginProvides> => {
|
|
|
147
146
|
case 'article':
|
|
148
147
|
case 'section': {
|
|
149
148
|
return (
|
|
150
|
-
<SheetContainer
|
|
151
|
-
sheet={data.object}
|
|
152
|
-
space={space}
|
|
153
|
-
role={role}
|
|
154
|
-
coordinate={data.coordinate as LayoutCoordinate}
|
|
155
|
-
remoteFunctionUrl={remoteFunctionUrl}
|
|
156
|
-
/>
|
|
149
|
+
<SheetContainer sheet={data.object} space={space} role={role} remoteFunctionUrl={remoteFunctionUrl} />
|
|
157
150
|
);
|
|
158
151
|
}
|
|
159
152
|
}
|
|
@@ -6,9 +6,8 @@ import { CompletionContext, type CompletionSource } from '@codemirror/autocomple
|
|
|
6
6
|
import { EditorState } from '@codemirror/state';
|
|
7
7
|
// @ts-ignore
|
|
8
8
|
import { testTree } from '@lezer/generator/test';
|
|
9
|
-
import { expect } from 'chai';
|
|
10
9
|
import { spreadsheet } from 'codemirror-lang-spreadsheet';
|
|
11
|
-
import { describe, test } from 'vitest';
|
|
10
|
+
import { describe, expect, test } from 'vitest';
|
|
12
11
|
|
|
13
12
|
import { sheetExtension } from './extension';
|
|
14
13
|
import { defaultFunctions } from '../../model/functions';
|
|
@@ -18,6 +18,7 @@ import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
|
18
18
|
import { Sheet } from './Sheet';
|
|
19
19
|
import { type SizeMap } from './grid';
|
|
20
20
|
import { useSheetContext } from './sheet-context';
|
|
21
|
+
import { addressToIndex, rangeToIndex } from '../../model';
|
|
21
22
|
import { createTestSheet, testSheetName } from '../../testing';
|
|
22
23
|
import { ValueTypeEnum, SheetType } from '../../types';
|
|
23
24
|
import { type ComputeGraph, createComputeGraph } from '../ComputeGraph';
|
|
@@ -36,7 +37,7 @@ const SheetWithToolbar = ({ debug, space }: { debug?: boolean; space: Space }) =
|
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const idx = range ? model.
|
|
40
|
+
const idx = range ? rangeToIndex(model.sheet, range) : addressToIndex(model.sheet, cursor);
|
|
40
41
|
model.sheet.formatting[idx] ??= {};
|
|
41
42
|
const format = model.sheet.formatting[idx];
|
|
42
43
|
|
|
@@ -75,6 +76,9 @@ const SheetWithToolbar = ({ debug, space }: { debug?: boolean; space: Space }) =
|
|
|
75
76
|
format.precision = 2;
|
|
76
77
|
break;
|
|
77
78
|
}
|
|
79
|
+
case 'comment': {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
};
|
|
80
84
|
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
} from './grid';
|
|
61
61
|
import { type GridSize, handleArrowNav, handleNav, useRangeSelect } from './nav';
|
|
62
62
|
import { type SheetContextProps, SheetContextProvider, useSheetContext } from './sheet-context';
|
|
63
|
+
import { useThreads } from './threads';
|
|
63
64
|
import { getRectUnion, getRelativeClientRect, scrollIntoView } from './util';
|
|
64
65
|
import {
|
|
65
66
|
type CellIndex,
|
|
@@ -68,6 +69,8 @@ import {
|
|
|
68
69
|
columnLetter,
|
|
69
70
|
posEquals,
|
|
70
71
|
rangeToA1Notation,
|
|
72
|
+
addressToIndex,
|
|
73
|
+
addressFromIndex,
|
|
71
74
|
} from '../../model';
|
|
72
75
|
import {
|
|
73
76
|
CellEditor,
|
|
@@ -141,6 +144,10 @@ const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numR
|
|
|
141
144
|
// Scrolling.
|
|
142
145
|
const { rowsRef, columnsRef, contentRef } = useScrollHandlers();
|
|
143
146
|
|
|
147
|
+
// Threads.
|
|
148
|
+
// TODO(Zan): Move this to an extension once we have an extension model.
|
|
149
|
+
useThreads();
|
|
150
|
+
|
|
144
151
|
//
|
|
145
152
|
// Order of Row/columns.
|
|
146
153
|
//
|
|
@@ -170,21 +177,21 @@ const SheetMain = forwardRef<HTMLDivElement, SheetMainProps>(({ classNames, numR
|
|
|
170
177
|
}, [rows, columns]);
|
|
171
178
|
|
|
172
179
|
const handleMoveRows: SheetRowsProps['onMove'] = (from, to, num = 1) => {
|
|
173
|
-
const cursorIdx = cursor ? model.
|
|
180
|
+
const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
|
|
174
181
|
const [rows] = model.sheet.rows.splice(from, num);
|
|
175
182
|
model.sheet.rows.splice(to, 0, rows);
|
|
176
183
|
if (cursorIdx) {
|
|
177
|
-
setCursor(model.
|
|
184
|
+
setCursor(addressFromIndex(model.sheet, cursorIdx));
|
|
178
185
|
}
|
|
179
186
|
setRows([...model.sheet.rows]);
|
|
180
187
|
};
|
|
181
188
|
|
|
182
189
|
const handleMoveColumns: SheetColumnsProps['onMove'] = (from, to, num = 1) => {
|
|
183
|
-
const cursorIdx = cursor ? model.
|
|
190
|
+
const cursorIdx = cursor ? addressToIndex(model.sheet, cursor) : undefined;
|
|
184
191
|
const columns = model.sheet.columns.splice(from, num);
|
|
185
192
|
model.sheet.columns.splice(to, 0, ...columns);
|
|
186
193
|
if (cursorIdx) {
|
|
187
|
-
setCursor(model.
|
|
194
|
+
setCursor(addressFromIndex(model.sheet, cursorIdx));
|
|
188
195
|
}
|
|
189
196
|
setColumns([...model.sheet.columns]);
|
|
190
197
|
};
|
|
@@ -882,7 +889,7 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
|
|
|
882
889
|
const style: CSSProperties = { position: 'absolute', top, left, width, height };
|
|
883
890
|
const cell = { row, column };
|
|
884
891
|
const id = addressToA1Notation(cell);
|
|
885
|
-
const idx = model.
|
|
892
|
+
const idx = addressToIndex(model.sheet, cell);
|
|
886
893
|
const active = posEquals(cursor, cell);
|
|
887
894
|
if (active && editing) {
|
|
888
895
|
const value = initialText.current ?? model.getCellText(cell) ?? '';
|
|
@@ -1003,16 +1010,46 @@ type SheetCellProps = {
|
|
|
1003
1010
|
};
|
|
1004
1011
|
|
|
1005
1012
|
const SheetCell = ({ id, cell, style, active, onSelect }: SheetCellProps) => {
|
|
1006
|
-
const {
|
|
1013
|
+
const {
|
|
1014
|
+
formatting,
|
|
1015
|
+
editing,
|
|
1016
|
+
setRange,
|
|
1017
|
+
decorations,
|
|
1018
|
+
model: { sheet },
|
|
1019
|
+
} = useSheetContext();
|
|
1007
1020
|
const { value, classNames } = formatting.getFormatting(cell);
|
|
1008
1021
|
|
|
1022
|
+
const decorationsForCell = decorations.getDecorationsForCell(addressToIndex(sheet, cell)) ?? [];
|
|
1023
|
+
const decorationAddedClasses = useMemo(
|
|
1024
|
+
() => decorationsForCell.flatMap((d) => d.classNames ?? []),
|
|
1025
|
+
[decorationsForCell],
|
|
1026
|
+
);
|
|
1027
|
+
const decoratedContent = decorationsForCell.reduce(
|
|
1028
|
+
(children, { decorate }) => {
|
|
1029
|
+
if (!decorate) {
|
|
1030
|
+
return children;
|
|
1031
|
+
}
|
|
1032
|
+
const DecoratorComponent = decorate;
|
|
1033
|
+
return <DecoratorComponent>{children}</DecoratorComponent>;
|
|
1034
|
+
},
|
|
1035
|
+
<div
|
|
1036
|
+
role='none'
|
|
1037
|
+
className={mx(
|
|
1038
|
+
'flex flex-grow bs-full is-full px-2 items-center truncate cursor-pointer',
|
|
1039
|
+
...decorationAddedClasses,
|
|
1040
|
+
)}
|
|
1041
|
+
>
|
|
1042
|
+
{value}
|
|
1043
|
+
</div>,
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1009
1046
|
return (
|
|
1010
1047
|
<div
|
|
1011
1048
|
{...{ [`data-${CELL_DATA_KEY}`]: id }}
|
|
1012
1049
|
role='cell'
|
|
1013
1050
|
style={style}
|
|
1014
1051
|
className={mx(
|
|
1015
|
-
'
|
|
1052
|
+
'border border-gridLine cursor-pointer',
|
|
1016
1053
|
fragments.cell,
|
|
1017
1054
|
active && ['z-20', fragments.cellSelected],
|
|
1018
1055
|
classNames,
|
|
@@ -1026,7 +1063,7 @@ const SheetCell = ({ id, cell, style, active, onSelect }: SheetCellProps) => {
|
|
|
1026
1063
|
}}
|
|
1027
1064
|
onDoubleClick={() => onSelect?.(cell, true)}
|
|
1028
1065
|
>
|
|
1029
|
-
{
|
|
1066
|
+
{decoratedContent}
|
|
1030
1067
|
</div>
|
|
1031
1068
|
);
|
|
1032
1069
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { create } from '@dxos/echo-schema';
|
|
6
|
+
|
|
7
|
+
export type Decoration = {
|
|
8
|
+
type: string;
|
|
9
|
+
/**
|
|
10
|
+
* A wrapping render function to encapsulate cell content. This function is applied between
|
|
11
|
+
* the cell's border and its padding/layout/content, allowing for custom rendering or
|
|
12
|
+
* additional elements to be inserted around the cell's main content.
|
|
13
|
+
*/
|
|
14
|
+
decorate?: (props: { children: React.ReactNode }) => React.ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* An array of CSS class names to be applied to the content of the SheetCell.
|
|
17
|
+
* These classes can be used to style the cell's content independently of its structure.
|
|
18
|
+
*/
|
|
19
|
+
classNames?: string[];
|
|
20
|
+
cellIndex: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const createDecorations = () => {
|
|
24
|
+
// Reactive object to hold decorations
|
|
25
|
+
// TODO(Zan): Use CELL ID's to key the decoration map.
|
|
26
|
+
// TODO(Zan): Consider maintaining an index of decorations by type.
|
|
27
|
+
const { decorations } = create<{ decorations: Record<string, Decoration[]> }>({ decorations: {} });
|
|
28
|
+
|
|
29
|
+
const addDecoration = (cellIndex: string, decorator: Decoration) => {
|
|
30
|
+
decorations[cellIndex] = [...(decorations[cellIndex] || []), decorator];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const removeDecoration = (cellIndex: string, type?: string) => {
|
|
34
|
+
if (type) {
|
|
35
|
+
decorations[cellIndex] = (decorations[cellIndex] || []).filter((d) => d.type !== type);
|
|
36
|
+
} else {
|
|
37
|
+
delete decorations[cellIndex];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// TODO(Zan): I should check if returning the a value from a map in a deep signal is a reactive slice.
|
|
42
|
+
const getDecorationsForCell = (cellIndex: string): Decoration[] | undefined => {
|
|
43
|
+
return decorations[cellIndex];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getAllDecorations = (): Decoration[] => {
|
|
47
|
+
const result: Decoration[] = [];
|
|
48
|
+
for (const decoratorArray of Object.values(decorations)) {
|
|
49
|
+
for (const decorator of decoratorArray) {
|
|
50
|
+
result.push(decorator);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
addDecoration,
|
|
58
|
+
removeDecoration,
|
|
59
|
+
getDecorationsForCell,
|
|
60
|
+
getAllDecorations,
|
|
61
|
+
} as const;
|
|
62
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ClassNameValue } from '@dxos/react-ui-types';
|
|
6
6
|
|
|
7
|
-
import { type SheetModel, type CellAddress, inRange } from '../../model';
|
|
7
|
+
import { type SheetModel, type CellAddress, inRange, addressToIndex, rangeFromIndex } from '../../model';
|
|
8
8
|
import { ValueTypeEnum } from '../../types';
|
|
9
9
|
|
|
10
10
|
export class FormattingModel {
|
|
@@ -23,7 +23,7 @@ export class FormattingModel {
|
|
|
23
23
|
const locales = undefined;
|
|
24
24
|
|
|
25
25
|
// Cell-specific formatting.
|
|
26
|
-
const idx = this.model.
|
|
26
|
+
const idx = addressToIndex(this.model.sheet, cell);
|
|
27
27
|
let formatting = this.model.sheet.formatting?.[idx] ?? {};
|
|
28
28
|
const classNames = [...(formatting?.classNames ?? [])];
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ export class FormattingModel {
|
|
|
31
31
|
// TODO(burdon): NOTE: D0 means the D column.
|
|
32
32
|
// TODO(burdon): Cache model formatting (e.g., for ranges). Create class out of this function.
|
|
33
33
|
for (const [idx, _formatting] of Object.entries(this.model.sheet.formatting)) {
|
|
34
|
-
const range = this.model.
|
|
34
|
+
const range = rangeFromIndex(this.model.sheet, idx);
|
|
35
35
|
if (inRange(range, cell)) {
|
|
36
36
|
if (_formatting.classNames) {
|
|
37
37
|
classNames.push(..._formatting.classNames);
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type PropsWithChildren, createContext, useContext, useState, useEffect } from 'react';
|
|
5
|
+
import React, { type PropsWithChildren, createContext, useContext, useState, useEffect, useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { invariant } from '@dxos/invariant';
|
|
8
8
|
import { type FunctionType } from '@dxos/plugin-script';
|
|
9
9
|
import { fullyQualifiedId, type Space } from '@dxos/react-client/echo';
|
|
10
10
|
|
|
11
|
+
import { createDecorations } from './decorations';
|
|
11
12
|
import { FormattingModel } from './formatting';
|
|
12
13
|
import { type CellAddress, type CellRange, defaultFunctions, SheetModel } from '../../model';
|
|
13
14
|
import { type SheetType } from '../../types';
|
|
@@ -36,6 +37,9 @@ export type SheetContextType = {
|
|
|
36
37
|
// Events.
|
|
37
38
|
// TODO(burdon): Generalize.
|
|
38
39
|
onInfo?: () => void;
|
|
40
|
+
|
|
41
|
+
// Decorations.
|
|
42
|
+
decorations: ReturnType<typeof createDecorations>;
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const SheetContext = createContext<SheetContextType | null>(null);
|
|
@@ -104,9 +108,12 @@ export const SheetContextProvider = ({
|
|
|
104
108
|
}: PropsWithChildren<SheetContextProps>) => {
|
|
105
109
|
const graph = useComputeGraph(space, options);
|
|
106
110
|
|
|
111
|
+
// TODO(Zan): We should offer a version of set range and set cursor that scrolls to
|
|
112
|
+
// that cell or range if it is not visible.
|
|
107
113
|
const [cursor, setCursor] = useState<CellAddress>();
|
|
108
114
|
const [range, setRange] = useState<CellRange>();
|
|
109
115
|
const [editing, setEditing] = useState<boolean>(false);
|
|
116
|
+
const decorations = useMemo(() => createDecorations(), []);
|
|
110
117
|
|
|
111
118
|
const [[model, formatting] = [], setModels] = useState<[SheetModel, FormattingModel] | undefined>(undefined);
|
|
112
119
|
useEffect(() => {
|
|
@@ -142,6 +149,7 @@ export const SheetContextProvider = ({
|
|
|
142
149
|
setEditing,
|
|
143
150
|
// TODO(burdon): Change to event.
|
|
144
151
|
onInfo,
|
|
152
|
+
decorations,
|
|
145
153
|
}}
|
|
146
154
|
>
|
|
147
155
|
{children}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { effect } from '@preact/signals-core';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type IntentResolver, LayoutAction, useIntentDispatcher, useIntentResolver } from '@dxos/app-framework';
|
|
9
|
+
import { debounce } from '@dxos/async';
|
|
10
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
11
|
+
import { Icon, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
13
|
+
|
|
14
|
+
import { type Decoration } from './decorations';
|
|
15
|
+
import { useSheetContext } from './sheet-context';
|
|
16
|
+
import { SHEET_PLUGIN } from '../../meta';
|
|
17
|
+
import { addressFromIndex, addressToIndex, type CellAddress, closest } from '../../model';
|
|
18
|
+
|
|
19
|
+
const CommentIndicator = () => {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
role='none'
|
|
23
|
+
className='absolute top-0 right-0 w-0 h-0 border-t-8 border-l-8 border-t-cmCommentSurface border-l-transparent'
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ThreadedCellWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
29
|
+
const dispatch = useIntentDispatcher();
|
|
30
|
+
const [isHovered, setIsHovered] = React.useState(true);
|
|
31
|
+
const { t } = useTranslation(SHEET_PLUGIN);
|
|
32
|
+
|
|
33
|
+
const handleClick = React.useCallback(
|
|
34
|
+
(_event: React.MouseEvent) => {
|
|
35
|
+
void dispatch({ action: LayoutAction.SET_LAYOUT, data: { element: 'complementary', state: true } });
|
|
36
|
+
},
|
|
37
|
+
[dispatch],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
role='none'
|
|
43
|
+
className={mx('relative h-full is-full')}
|
|
44
|
+
onMouseEnter={() => {
|
|
45
|
+
setIsHovered(true);
|
|
46
|
+
}}
|
|
47
|
+
onMouseLeave={() => {
|
|
48
|
+
setIsHovered(false);
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<CommentIndicator />
|
|
52
|
+
{isHovered && (
|
|
53
|
+
<div className='absolute inset-0 flex items-center justify-end pr-1'>
|
|
54
|
+
<button
|
|
55
|
+
className='ch-button text-xs min-bs-0 p-1'
|
|
56
|
+
onClick={handleClick}
|
|
57
|
+
aria-label={t('open comment for sheet cell')}
|
|
58
|
+
>
|
|
59
|
+
<Icon icon='ph--chat--regular' aria-hidden={true} />
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const createThreadDecoration = (cellIndex: string, threadId: string, sheetId: string): Decoration => {
|
|
69
|
+
return {
|
|
70
|
+
type: 'comment',
|
|
71
|
+
cellIndex,
|
|
72
|
+
decorate: (props) => <ThreadedCellWrapper {...props} />,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const useUpdateCursorOnThreadSelection = () => {
|
|
77
|
+
const { setCursor, model } = useSheetContext();
|
|
78
|
+
|
|
79
|
+
const handleScrollIntoView: IntentResolver = useCallback(
|
|
80
|
+
({ action, data }) => {
|
|
81
|
+
switch (action) {
|
|
82
|
+
case LayoutAction.SCROLL_INTO_VIEW: {
|
|
83
|
+
if (!data?.id || data?.cursor === undefined || data?.id !== fullyQualifiedId(model.sheet)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
|
|
88
|
+
const cellAddress = addressFromIndex(model.sheet, data.cursor);
|
|
89
|
+
setCursor(cellAddress);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
[model.sheet, setCursor],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
useIntentResolver(SHEET_PLUGIN, handleScrollIntoView);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const useSelectThreadOnCursorChange = () => {
|
|
100
|
+
const { cursor, model } = useSheetContext();
|
|
101
|
+
const dispatch = useIntentDispatcher();
|
|
102
|
+
|
|
103
|
+
const activeThreads = useMemo(
|
|
104
|
+
() =>
|
|
105
|
+
model.sheet.threads?.filter(
|
|
106
|
+
(thread): thread is NonNullable<typeof thread> => !!thread && thread.status === 'active',
|
|
107
|
+
) ?? [],
|
|
108
|
+
[JSON.stringify(model.sheet.threads)],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const activeThreadAddresses = useMemo(
|
|
112
|
+
() =>
|
|
113
|
+
activeThreads
|
|
114
|
+
.map((thread) => thread.anchor)
|
|
115
|
+
.filter((anchor): anchor is NonNullable<typeof anchor> => anchor !== undefined)
|
|
116
|
+
.map((anchor) => addressFromIndex(model.sheet, anchor)),
|
|
117
|
+
[activeThreads, model.sheet],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const selectClosestThread = useCallback(
|
|
121
|
+
(cellAddress: CellAddress) => {
|
|
122
|
+
if (!cellAddress || !activeThreads) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const closestThreadAnchor = closest(cellAddress, activeThreadAddresses);
|
|
127
|
+
if (closestThreadAnchor) {
|
|
128
|
+
const closestThread = activeThreads.find(
|
|
129
|
+
(thread) => thread && thread.anchor === addressToIndex(model.sheet, closestThreadAnchor),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (closestThread) {
|
|
133
|
+
void dispatch([
|
|
134
|
+
{ action: 'dxos.org/plugin/thread/action/select', data: { current: fullyQualifiedId(closestThread) } },
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
[dispatch, activeThreads, activeThreadAddresses, model.sheet],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const debounced = useMemo(() => {
|
|
143
|
+
return debounce((cursor: CellAddress) => requestAnimationFrame(() => selectClosestThread(cursor)), 50);
|
|
144
|
+
}, [selectClosestThread]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!cursor) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
debounced(cursor);
|
|
151
|
+
}, [cursor, selectClosestThread]);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const useThreadDecorations = () => {
|
|
155
|
+
const { decorations, model } = useSheetContext();
|
|
156
|
+
const sheet = useMemo(() => model.sheet, [model.sheet]);
|
|
157
|
+
const sheetId = useMemo(() => fullyQualifiedId(sheet), [sheet]);
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const unsubscribe = effect(() => {
|
|
161
|
+
const activeThreadAnchors = new Set<string>();
|
|
162
|
+
if (!sheet.threads) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Process active threads
|
|
167
|
+
for (const thread of sheet.threads) {
|
|
168
|
+
if (!thread || thread.anchor === undefined || thread.status === 'resolved') {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
activeThreadAnchors.add(thread.anchor);
|
|
173
|
+
const index = thread.anchor;
|
|
174
|
+
|
|
175
|
+
// Add decoration only if it doesn't already exist
|
|
176
|
+
const existingDecorations = decorations.getDecorationsForCell(index);
|
|
177
|
+
if (!existingDecorations || !existingDecorations.some((d) => d.type === 'comment')) {
|
|
178
|
+
decorations.addDecoration(index, createThreadDecoration(index, thread.id, sheetId));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Remove decorations for resolved or deleted threads
|
|
183
|
+
for (const decoration of decorations.getAllDecorations()) {
|
|
184
|
+
if (decoration.type !== 'comment') {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!activeThreadAnchors.has(decoration.cellIndex)) {
|
|
189
|
+
decorations.removeDecoration(decoration.cellIndex, 'comment');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return () => unsubscribe();
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const useThreads = () => {
|
|
198
|
+
useUpdateCursorOnThreadSelection();
|
|
199
|
+
useSelectThreadOnCursorChange();
|
|
200
|
+
useThreadDecorations();
|
|
201
|
+
};
|