@dxos/plugin-sheet 0.6.14-staging.9e90729 → 0.6.14-staging.a52de51
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-JBB7W52Y.mjs → SheetContainer-P3NF5KEI.mjs} +60 -48
- package/dist/lib/browser/SheetContainer-P3NF5KEI.mjs.map +7 -0
- package/dist/lib/browser/{chunk-XXDHBYZQ.mjs → chunk-4LKIURJA.mjs} +337 -122
- package/dist/lib/browser/chunk-4LKIURJA.mjs.map +7 -0
- package/dist/lib/browser/{chunk-3QWIMZZJ.mjs → chunk-BVUN7SHF.mjs} +5 -3
- package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +7 -0
- package/dist/lib/browser/{chunk-D3QTX46O.mjs → chunk-RABELMEQ.mjs} +3 -2
- package/dist/lib/browser/{chunk-D3QTX46O.mjs.map → chunk-RABELMEQ.mjs.map} +3 -3
- package/dist/lib/browser/{chunk-2ZK3YMZG.mjs → chunk-VMSX6Z4X.mjs} +58 -20
- package/dist/lib/browser/chunk-VMSX6Z4X.mjs.map +7 -0
- package/dist/lib/browser/{compute-graph-ZQKB6QVP.mjs → compute-graph-GGWUX644.mjs} +4 -4
- package/dist/lib/browser/index.mjs +48 -21
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types.mjs +2 -2
- package/dist/lib/node/{SheetContainer-L37HUFCF.cjs → SheetContainer-MPFKXY26.cjs} +72 -63
- package/dist/lib/node/SheetContainer-MPFKXY26.cjs.map +7 -0
- package/dist/lib/node/{chunk-QIFIGEKV.cjs → chunk-2ZVZI2KJ.cjs} +6 -5
- package/dist/lib/node/{chunk-QIFIGEKV.cjs.map → chunk-2ZVZI2KJ.cjs.map} +3 -3
- package/dist/lib/node/{chunk-V7E5JZBD.cjs → chunk-AWKOWDMI.cjs} +9 -7
- package/dist/lib/node/chunk-AWKOWDMI.cjs.map +7 -0
- package/dist/lib/node/{chunk-QNFMTK3X.cjs → chunk-O7XR4R7Y.cjs} +69 -29
- package/dist/lib/node/chunk-O7XR4R7Y.cjs.map +7 -0
- package/dist/lib/node/{chunk-7AWAC3R3.cjs → chunk-OO24XJBV.cjs} +353 -139
- package/dist/lib/node/chunk-OO24XJBV.cjs.map +7 -0
- package/dist/lib/node/{compute-graph-RJB6OVW6.cjs → compute-graph-KGWA2QLE.cjs} +21 -21
- package/dist/lib/node/{compute-graph-RJB6OVW6.cjs.map → compute-graph-KGWA2QLE.cjs.map} +2 -2
- package/dist/lib/node/index.cjs +74 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.cjs +3 -3
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +7 -7
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/{SheetContainer-CLKFEM35.mjs → SheetContainer-22IOAW3B.mjs} +60 -48
- package/dist/lib/node-esm/SheetContainer-22IOAW3B.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs → chunk-BM2Q3FFC.mjs} +3 -2
- package/dist/lib/node-esm/{chunk-VCYJWE3O.mjs.map → chunk-BM2Q3FFC.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-3JYJEOCF.mjs → chunk-BW36PM2Y.mjs} +337 -122
- package/dist/lib/node-esm/chunk-BW36PM2Y.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-JBTORSST.mjs → chunk-CR4K75EL.mjs} +58 -20
- package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-SPQFLYC2.mjs → chunk-UIBWRHW7.mjs} +5 -3
- package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +7 -0
- package/dist/lib/node-esm/{compute-graph-VKRG526N.mjs → compute-graph-2SCZT7N5.mjs} +4 -4
- package/dist/lib/node-esm/index.mjs +48 -21
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +1 -1
- package/dist/lib/node-esm/types.mjs +2 -2
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/FunctionEditor/FunctionEditor.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/util.d.ts +2 -2
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
- package/dist/types/src/components/RangeList/RangeList.d.ts +2 -4
- package/dist/types/src/components/RangeList/RangeList.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer/SheetContainer.d.ts +6 -3
- package/dist/types/src/components/SheetContainer/SheetContainer.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +6 -3
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +4 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/compute-graph/compute-graph.d.ts +2 -2
- package/dist/types/src/compute-graph/compute-graph.d.ts.map +1 -1
- package/dist/types/src/compute-graph/functions/async-function.d.ts.map +1 -1
- package/dist/types/src/compute-graph/functions/edge-function.d.ts.map +1 -1
- package/dist/types/src/defs/sheet-range-types.d.ts +2 -2
- package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
- package/dist/types/src/defs/util.d.ts +1 -1
- package/dist/types/src/defs/util.d.ts.map +1 -1
- package/dist/types/src/integrations/thread-ranges.d.ts +1 -2
- package/dist/types/src/integrations/thread-ranges.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.d.ts +12 -5
- package/dist/types/src/model/sheet-model.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +12 -3
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +33 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +37 -36
- package/src/SheetPlugin.tsx +27 -7
- package/src/components/FunctionEditor/FunctionEditor.tsx +1 -5
- package/src/components/GridSheet/GridSheet.tsx +107 -42
- package/src/components/GridSheet/util.ts +26 -16
- package/src/components/RangeList/RangeList.tsx +27 -12
- package/src/components/SheetContainer/SheetContainer.stories.tsx +4 -2
- package/src/components/SheetContainer/SheetContainer.tsx +19 -12
- package/src/components/Toolbar/Toolbar.tsx +72 -59
- package/src/compute-graph/compute-graph.ts +22 -7
- package/src/compute-graph/functions/async-function.ts +1 -0
- package/src/compute-graph/functions/edge-function.ts +5 -3
- package/src/defs/sheet-range-types.ts +8 -6
- package/src/defs/util.ts +1 -0
- package/src/integrations/thread-ranges.ts +3 -3
- package/src/meta.ts +1 -0
- package/src/model/sheet-model.test.ts +5 -3
- package/src/model/sheet-model.ts +93 -21
- package/src/translations.ts +12 -3
- package/src/types.ts +21 -1
- package/dist/lib/browser/SheetContainer-JBB7W52Y.mjs.map +0 -7
- package/dist/lib/browser/chunk-2ZK3YMZG.mjs.map +0 -7
- package/dist/lib/browser/chunk-3QWIMZZJ.mjs.map +0 -7
- package/dist/lib/browser/chunk-XXDHBYZQ.mjs.map +0 -7
- package/dist/lib/node/SheetContainer-L37HUFCF.cjs.map +0 -7
- package/dist/lib/node/chunk-7AWAC3R3.cjs.map +0 -7
- package/dist/lib/node/chunk-QNFMTK3X.cjs.map +0 -7
- package/dist/lib/node/chunk-V7E5JZBD.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-CLKFEM35.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-3JYJEOCF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JBTORSST.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SPQFLYC2.mjs.map +0 -7
- /package/dist/lib/browser/{compute-graph-ZQKB6QVP.mjs.map → compute-graph-GGWUX644.mjs.map} +0 -0
- /package/dist/lib/node-esm/{compute-graph-VKRG526N.mjs.map → compute-graph-2SCZT7N5.mjs.map} +0 -0
|
@@ -3,23 +3,24 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
|
-
import React, { type PropsWithChildren, useCallback
|
|
6
|
+
import React, { type PropsWithChildren, useCallback } from 'react';
|
|
7
7
|
|
|
8
8
|
import { useIntentDispatcher } from '@dxos/app-framework';
|
|
9
9
|
import {
|
|
10
10
|
Icon,
|
|
11
|
+
type ThemedClassName,
|
|
11
12
|
Toolbar as NaturalToolbar,
|
|
12
|
-
Tooltip,
|
|
13
|
-
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
14
13
|
type ToolbarButtonProps as NaturalToolbarButtonProps,
|
|
14
|
+
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
15
15
|
type ToolbarToggleProps as NaturalToolbarToggleProps,
|
|
16
|
-
|
|
16
|
+
Tooltip,
|
|
17
17
|
useTranslation,
|
|
18
18
|
} from '@dxos/react-ui';
|
|
19
19
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
20
20
|
import { nonNullable } from '@dxos/util';
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
|
+
alignKey,
|
|
23
24
|
type AlignKey,
|
|
24
25
|
type AlignValue,
|
|
25
26
|
type CommentKey,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
inRange,
|
|
28
29
|
rangeFromIndex,
|
|
29
30
|
rangeToIndex,
|
|
31
|
+
styleKey,
|
|
30
32
|
type StyleKey,
|
|
31
33
|
type StyleValue,
|
|
32
34
|
} from '../../defs';
|
|
@@ -88,10 +90,11 @@ type CommentAction = { key: CommentKey; value: CommentValue; cellContent?: strin
|
|
|
88
90
|
type StyleAction = { key: StyleKey; value: StyleValue };
|
|
89
91
|
|
|
90
92
|
export type ToolbarAction = StyleAction | AlignAction | CommentAction;
|
|
93
|
+
export type ToolbarActionAnnotated = ToolbarAction & { unset?: boolean };
|
|
91
94
|
|
|
92
95
|
export type ToolbarActionType = ToolbarAction['key'];
|
|
93
96
|
|
|
94
|
-
export type ToolbarActionHandler = (action:
|
|
97
|
+
export type ToolbarActionHandler = (action: ToolbarActionAnnotated) => void;
|
|
95
98
|
|
|
96
99
|
export type ToolbarProps = ThemedClassName<
|
|
97
100
|
PropsWithChildren<{
|
|
@@ -99,13 +102,9 @@ export type ToolbarProps = ThemedClassName<
|
|
|
99
102
|
}>
|
|
100
103
|
>;
|
|
101
104
|
|
|
102
|
-
const [ToolbarContextProvider, useToolbarContext] = createContext<{
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// TODO(Zan): Factor out, copied this from MarkdownPlugin.
|
|
107
|
-
const sectionToolbarLayout =
|
|
108
|
-
'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
|
|
105
|
+
const [ToolbarContextProvider, useToolbarContext] = createContext<{
|
|
106
|
+
onAction: (action: ToolbarActionAnnotated) => void;
|
|
107
|
+
}>('Toolbar');
|
|
109
108
|
|
|
110
109
|
type Range = SheetType['ranges'][number];
|
|
111
110
|
|
|
@@ -116,13 +115,16 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
116
115
|
|
|
117
116
|
// TODO(Zan): Externalize the toolbar action handler. E.g., Toolbar/keys should both fire events.
|
|
118
117
|
const handleAction = useCallback(
|
|
119
|
-
(action:
|
|
118
|
+
(action: ToolbarActionAnnotated) => {
|
|
120
119
|
switch (action.key) {
|
|
121
|
-
case '
|
|
122
|
-
if (
|
|
123
|
-
const index =
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
case 'alignment':
|
|
121
|
+
if (cursorFallbackRange) {
|
|
122
|
+
const index =
|
|
123
|
+
model.sheet.ranges?.findIndex(
|
|
124
|
+
(range) =>
|
|
125
|
+
range.key === action.key &&
|
|
126
|
+
inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
|
|
127
|
+
) ?? -1;
|
|
126
128
|
const nextRangeEntity = {
|
|
127
129
|
range: rangeToIndex(model.sheet, cursorFallbackRange),
|
|
128
130
|
key: action.key,
|
|
@@ -130,14 +132,21 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
130
132
|
};
|
|
131
133
|
if (index < 0) {
|
|
132
134
|
model.sheet.ranges?.push(nextRangeEntity);
|
|
135
|
+
} else if (model.sheet.ranges![index].value === action.value) {
|
|
136
|
+
model.sheet.ranges?.splice(index, 1);
|
|
133
137
|
} else {
|
|
134
138
|
model.sheet.ranges?.splice(index, 1, nextRangeEntity);
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
break;
|
|
138
142
|
case 'style':
|
|
139
|
-
if (action.
|
|
140
|
-
const index = model.sheet.ranges?.findIndex(
|
|
143
|
+
if (action.unset) {
|
|
144
|
+
const index = model.sheet.ranges?.findIndex(
|
|
145
|
+
(range) =>
|
|
146
|
+
range.key === action.key &&
|
|
147
|
+
cursorFallbackRange &&
|
|
148
|
+
inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
|
|
149
|
+
);
|
|
141
150
|
if (index >= 0) {
|
|
142
151
|
model.sheet.ranges?.splice(index, 1);
|
|
143
152
|
}
|
|
@@ -169,14 +178,7 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
169
178
|
|
|
170
179
|
return (
|
|
171
180
|
<ToolbarContextProvider onAction={handleAction}>
|
|
172
|
-
<NaturalToolbar.Root
|
|
173
|
-
classNames={[
|
|
174
|
-
...(role === 'section'
|
|
175
|
-
? ['z-[2] group-focus-within/section:visible', !hasAttention && 'invisible', sectionToolbarLayout]
|
|
176
|
-
: ['attention-surface']),
|
|
177
|
-
classNames,
|
|
178
|
-
]}
|
|
179
|
-
>
|
|
181
|
+
<NaturalToolbar.Root classNames={['pli-0.5', !hasAttention && 'opacity-20', classNames]}>
|
|
180
182
|
{children}
|
|
181
183
|
</NaturalToolbar.Root>
|
|
182
184
|
</ToolbarContextProvider>
|
|
@@ -206,50 +208,55 @@ const Alignment = () => {
|
|
|
206
208
|
const { onAction } = useToolbarContext('Alignment');
|
|
207
209
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
208
210
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
: undefined,
|
|
216
|
-
[cursor, model.sheet.ranges],
|
|
217
|
-
);
|
|
211
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
212
|
+
const value = cursor
|
|
213
|
+
? model.sheet.ranges?.findLast(
|
|
214
|
+
({ range, key }) => key === alignKey && inRange(rangeFromIndex(model.sheet, range), cursor),
|
|
215
|
+
)?.value
|
|
216
|
+
: undefined;
|
|
218
217
|
|
|
219
218
|
return (
|
|
220
219
|
<NaturalToolbar.ToggleGroup
|
|
221
220
|
type='single'
|
|
222
|
-
value={
|
|
223
|
-
|
|
221
|
+
value={
|
|
222
|
+
// TODO(thure): providing `undefined` leaves the last item active which was active rather than showing none.
|
|
223
|
+
value ?? 'never'
|
|
224
|
+
}
|
|
225
|
+
onValueChange={(value: AlignValue) => onAction?.({ key: alignKey, value })}
|
|
224
226
|
>
|
|
225
227
|
{alignmentOptions.map(({ value, icon }) => (
|
|
226
228
|
<ToolbarItem itemType='toggleGroupItem' key={value} value={value} icon={icon}>
|
|
227
|
-
{t(
|
|
229
|
+
{t('toolbar action label', {
|
|
230
|
+
key: t(`range key ${alignKey} label`),
|
|
231
|
+
value: t(`range value ${value} label`),
|
|
232
|
+
})}
|
|
228
233
|
</ToolbarItem>
|
|
229
234
|
))}
|
|
230
235
|
</NaturalToolbar.ToggleGroup>
|
|
231
236
|
);
|
|
232
237
|
};
|
|
233
238
|
|
|
234
|
-
const styleOptions: ButtonProps<StyleValue>[] = [
|
|
239
|
+
const styleOptions: ButtonProps<StyleValue>[] = [
|
|
240
|
+
{ value: 'highlight', icon: 'ph--highlighter--regular' },
|
|
241
|
+
{ value: 'softwrap', icon: 'ph--paragraph--regular' },
|
|
242
|
+
];
|
|
235
243
|
|
|
236
244
|
const Styles = () => {
|
|
237
|
-
const {
|
|
245
|
+
const { cursorFallbackRange, model } = useSheetContext();
|
|
238
246
|
const { onAction } = useToolbarContext('Styles');
|
|
239
247
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
240
248
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
);
|
|
249
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
250
|
+
const activeValues = cursorFallbackRange
|
|
251
|
+
? model.sheet.ranges
|
|
252
|
+
?.filter(
|
|
253
|
+
({ range, key }) => key === 'style' && inRange(rangeFromIndex(model.sheet, range), cursorFallbackRange.from),
|
|
254
|
+
)
|
|
255
|
+
.reduce((acc, { value }) => {
|
|
256
|
+
acc.add(value);
|
|
257
|
+
return acc;
|
|
258
|
+
}, new Set())
|
|
259
|
+
: undefined;
|
|
253
260
|
|
|
254
261
|
return (
|
|
255
262
|
<>
|
|
@@ -258,10 +265,15 @@ const Styles = () => {
|
|
|
258
265
|
itemType='toggle'
|
|
259
266
|
key={value}
|
|
260
267
|
pressed={activeValues?.has(value)}
|
|
261
|
-
onPressedChange={(nextPressed: boolean) =>
|
|
268
|
+
onPressedChange={(nextPressed: boolean) => {
|
|
269
|
+
onAction?.({ key: 'style', value, unset: !nextPressed });
|
|
270
|
+
}}
|
|
262
271
|
icon={icon}
|
|
263
272
|
>
|
|
264
|
-
{t(
|
|
273
|
+
{t('toolbar action label', {
|
|
274
|
+
key: t(`range key ${styleKey} label`),
|
|
275
|
+
value: t(`range value ${value} label`),
|
|
276
|
+
})}
|
|
265
277
|
</ToolbarItem>
|
|
266
278
|
))}
|
|
267
279
|
</>
|
|
@@ -277,6 +289,7 @@ const Actions = () => {
|
|
|
277
289
|
const { cursorFallbackRange, cursor, model } = useSheetContext();
|
|
278
290
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
279
291
|
|
|
292
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
280
293
|
const overlapsCommentAnchor = (model.sheet.threads ?? [])
|
|
281
294
|
.filter(nonNullable)
|
|
282
295
|
.filter((thread) => thread.status !== 'resolved')
|
|
@@ -300,16 +313,16 @@ const Actions = () => {
|
|
|
300
313
|
icon='ph--chat-text--regular'
|
|
301
314
|
data-testid='editor.toolbar.comment'
|
|
302
315
|
onClick={() => {
|
|
303
|
-
if (!
|
|
316
|
+
if (!cursorFallbackRange) {
|
|
304
317
|
return;
|
|
305
318
|
}
|
|
306
319
|
return onAction?.({
|
|
307
320
|
key: 'comment',
|
|
308
321
|
value: rangeToIndex(model.sheet, cursorFallbackRange),
|
|
309
|
-
cellContent: model.getCellText(
|
|
322
|
+
cellContent: model.getCellText(cursorFallbackRange.from),
|
|
310
323
|
});
|
|
311
324
|
}}
|
|
312
|
-
disabled={!
|
|
325
|
+
disabled={!cursorFallbackRange || overlapsCommentAnchor}
|
|
313
326
|
>
|
|
314
327
|
{t(tooltipLabelKey)}
|
|
315
328
|
</ToolbarItem>
|
|
@@ -6,6 +6,7 @@ import { type Listeners } from 'hyperformula/typings/Emitter';
|
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { type Space, Filter, fullyQualifiedId } from '@dxos/client/echo';
|
|
9
|
+
import { FQ_ID_LENGTH } from '@dxos/client/echo';
|
|
9
10
|
import { Resource } from '@dxos/context';
|
|
10
11
|
import { getTypename } from '@dxos/echo-schema';
|
|
11
12
|
import { invariant } from '@dxos/invariant';
|
|
@@ -26,8 +27,7 @@ import {
|
|
|
26
27
|
|
|
27
28
|
// TODO(burdon): Factor out compute-graph.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
const OBJECT_ID_LENGTH = 60; // 33 (space id) + 1 (separator) + 26 (object id).
|
|
30
|
+
const UNKNOWN_BINDING = '__UNKNOWN__';
|
|
31
31
|
|
|
32
32
|
// TODO(burdon): Factory.
|
|
33
33
|
// export type ComputeNodeGenerator = <T>(obj: T) => ComputeNode;
|
|
@@ -44,7 +44,7 @@ export const parseSheetName = (name: string): Partial<ObjectRef> => {
|
|
|
44
44
|
return id ? { type, id } : { id: type };
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export type ComputeGraphEvent = 'functionsUpdated';
|
|
47
|
+
export type ComputeGraphEvent = 'functionsUpdated' | 'valuesUpdated';
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Per-space compute and dependency graph.
|
|
@@ -64,7 +64,7 @@ export class ComputeGraph extends Resource {
|
|
|
64
64
|
public readonly update = new Event<{ type: ComputeGraphEvent }>();
|
|
65
65
|
|
|
66
66
|
// The context is passed to all functions.
|
|
67
|
-
public readonly context
|
|
67
|
+
public readonly context: FunctionContext;
|
|
68
68
|
|
|
69
69
|
constructor(
|
|
70
70
|
private readonly _hf: HyperFormula,
|
|
@@ -72,6 +72,15 @@ export class ComputeGraph extends Resource {
|
|
|
72
72
|
private readonly _options?: Partial<FunctionContextOptions>,
|
|
73
73
|
) {
|
|
74
74
|
super();
|
|
75
|
+
|
|
76
|
+
const contextOptions = {
|
|
77
|
+
...this._options,
|
|
78
|
+
onUpdate: (update) => {
|
|
79
|
+
this._options?.onUpdate?.(update);
|
|
80
|
+
this.update.emit({ type: 'valuesUpdated' });
|
|
81
|
+
},
|
|
82
|
+
} satisfies Partial<FunctionContextOptions>;
|
|
83
|
+
this.context = new FunctionContext(this._hf, this._space, contextOptions);
|
|
75
84
|
this._hf.updateConfig({ context: this.context });
|
|
76
85
|
|
|
77
86
|
// TODO(burdon): If debounce then aggregate changes.
|
|
@@ -209,9 +218,9 @@ export class ComputeGraph extends Resource {
|
|
|
209
218
|
* E.g., spaceId:objectId() => HELLO()
|
|
210
219
|
*/
|
|
211
220
|
mapFunctionBindingFromId(formula: string) {
|
|
212
|
-
|
|
221
|
+
const binding = formula.replace(/(\w+):([a-zA-Z0-9]+)\((.*)\)/g, (match, spaceId, objectId, args) => {
|
|
213
222
|
const id = `${spaceId}:${objectId}`;
|
|
214
|
-
if (id.length !==
|
|
223
|
+
if (id.length !== FQ_ID_LENGTH) {
|
|
215
224
|
return match;
|
|
216
225
|
}
|
|
217
226
|
|
|
@@ -219,9 +228,15 @@ export class ComputeGraph extends Resource {
|
|
|
219
228
|
if (fn?.binding) {
|
|
220
229
|
return `${fn.binding}(${args})`;
|
|
221
230
|
} else {
|
|
222
|
-
return
|
|
231
|
+
return UNKNOWN_BINDING;
|
|
223
232
|
}
|
|
224
233
|
});
|
|
234
|
+
|
|
235
|
+
if (binding.startsWith(`=${UNKNOWN_BINDING}`)) {
|
|
236
|
+
return undefined;
|
|
237
|
+
} else {
|
|
238
|
+
return binding;
|
|
239
|
+
}
|
|
225
240
|
}
|
|
226
241
|
|
|
227
242
|
protected override async _open() {
|
|
@@ -83,6 +83,7 @@ export class FunctionContext {
|
|
|
83
83
|
) {
|
|
84
84
|
this._options = defaultsDeep(_options ?? {}, defaultFunctionContextOptions);
|
|
85
85
|
this._onUpdate = debounce((update) => {
|
|
86
|
+
log('update', update);
|
|
86
87
|
// TODO(burdon): Better way to trigger recalculation? (NOTE: rebuildAndRecalculate resets the undo history.)
|
|
87
88
|
this._hf.resumeEvaluation();
|
|
88
89
|
this._options.onUpdate?.(update);
|
|
@@ -44,7 +44,7 @@ export class EdgeFunctionPlugin extends AsyncFunctionPlugin {
|
|
|
44
44
|
|
|
45
45
|
if (subscribe) {
|
|
46
46
|
const unsubscribe = effect(() => {
|
|
47
|
-
log
|
|
47
|
+
log('function changed', { fn });
|
|
48
48
|
const _ = fn?.version;
|
|
49
49
|
|
|
50
50
|
// TODO(wittjosiah): `ttl` should be 0 to force a recalculation when a new version is deployed.
|
|
@@ -56,13 +56,15 @@ export class EdgeFunctionPlugin extends AsyncFunctionPlugin {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const path = getUserFunctionUrlInMetadata(getMeta(fn));
|
|
59
|
-
const
|
|
59
|
+
const response = await fetch(`${this.context.remoteFunctionUrl}${path}`, {
|
|
60
60
|
method: 'POST',
|
|
61
61
|
headers: { 'Content-Type': 'application/json' },
|
|
62
62
|
body: JSON.stringify({ args: args.filter(nonNullable) }),
|
|
63
63
|
});
|
|
64
|
+
const result = await response.text();
|
|
65
|
+
log('function executed', { result });
|
|
64
66
|
|
|
65
|
-
return
|
|
67
|
+
return result;
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
return this.runAsyncFunction(ast, state, handler(true), { ttl: FUNCTION_TTL });
|
|
@@ -6,7 +6,7 @@ import { type ClassNameValue } from '@dxos/react-ui';
|
|
|
6
6
|
|
|
7
7
|
import { type SheetType } from '../types';
|
|
8
8
|
|
|
9
|
-
export const alignKey = '
|
|
9
|
+
export const alignKey = 'alignment';
|
|
10
10
|
export type AlignKey = typeof alignKey;
|
|
11
11
|
export type AlignValue = 'start' | 'center' | 'end' | 'unset';
|
|
12
12
|
|
|
@@ -16,12 +16,12 @@ export type CommentValue = string;
|
|
|
16
16
|
|
|
17
17
|
export const styleKey = 'style';
|
|
18
18
|
export type StyleKey = typeof styleKey;
|
|
19
|
-
export type StyleValue = 'highlight' | '
|
|
19
|
+
export type StyleValue = 'highlight' | 'softwrap';
|
|
20
20
|
|
|
21
21
|
// TODO(burdon): Reconcile with plugin-table.
|
|
22
22
|
export const cellClassNameForRange = ({ key, value }: SheetType['ranges'][number]): ClassNameValue => {
|
|
23
23
|
switch (key) {
|
|
24
|
-
case
|
|
24
|
+
case alignKey:
|
|
25
25
|
switch (value) {
|
|
26
26
|
case 'start':
|
|
27
27
|
return 'text-start';
|
|
@@ -32,12 +32,14 @@ export const cellClassNameForRange = ({ key, value }: SheetType['ranges'][number
|
|
|
32
32
|
default:
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
|
-
case
|
|
35
|
+
case commentKey:
|
|
36
36
|
return 'bg-gridComment';
|
|
37
|
-
case
|
|
37
|
+
case styleKey:
|
|
38
38
|
switch (value) {
|
|
39
39
|
case 'highlight':
|
|
40
|
-
return 'bg-gridHighlight';
|
|
40
|
+
return '!bg-gridHighlight';
|
|
41
|
+
case 'softwrap':
|
|
42
|
+
return '!whitespace-normal';
|
|
41
43
|
default:
|
|
42
44
|
return undefined;
|
|
43
45
|
}
|
package/src/defs/util.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type IntentResolver, LayoutAction, useIntentDispatcher, useIntentResolver } from '@dxos/app-framework';
|
|
8
8
|
import { debounce } from '@dxos/async';
|
|
@@ -30,7 +30,7 @@ export const parseThreadAnchorAsCellRange = (cursor: string): CompleteCellRange
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const useUpdateFocusedCellOnThreadSelection = (grid:
|
|
33
|
+
export const useUpdateFocusedCellOnThreadSelection = (grid: DxGridElement | null) => {
|
|
34
34
|
const { model, setActiveRefs } = useSheetContext();
|
|
35
35
|
const handleScrollIntoView: IntentResolver = useCallback(
|
|
36
36
|
({ action, data }) => {
|
|
@@ -42,7 +42,7 @@ export const useUpdateFocusedCellOnThreadSelection = (grid: MutableRefObject<DxG
|
|
|
42
42
|
setActiveRefs(data.thread);
|
|
43
43
|
// TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
|
|
44
44
|
const range = parseThreadAnchorAsCellRange(data.cursor);
|
|
45
|
-
range && grid
|
|
45
|
+
range && grid?.setFocus({ ...range.to, plane: 'grid' }, true);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
},
|
package/src/meta.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Trigger } from '@dxos/async';
|
|
8
8
|
import { FunctionType } from '@dxos/plugin-script/types';
|
|
@@ -36,13 +36,15 @@ describe('SheetModel', () => {
|
|
|
36
36
|
// Trigger waits for function invocation.
|
|
37
37
|
const trigger = new Trigger<CellScalarValue>();
|
|
38
38
|
model.setValue(addressFromA1Notation('A1'), '=TEST(100)');
|
|
39
|
-
|
|
39
|
+
// TODO(wittjosiah): Currently this fires twice, once for the binding loading & once for the function invocation.
|
|
40
|
+
const unsubscribe = model.update.on((update) => {
|
|
40
41
|
const { type } = update;
|
|
41
42
|
if (type === 'valuesUpdated') {
|
|
42
43
|
const value = model.getValue(addressFromA1Notation('A1'));
|
|
43
|
-
trigger.wake(value);
|
|
44
|
+
value && trigger.wake(value);
|
|
44
45
|
}
|
|
45
46
|
});
|
|
47
|
+
onTestFinished(() => unsubscribe());
|
|
46
48
|
|
|
47
49
|
// Initial value will be null.
|
|
48
50
|
const v1 = model.getValue(addressFromA1Notation('A1'));
|
package/src/model/sheet-model.ts
CHANGED
|
@@ -8,11 +8,10 @@ import { type SimpleDate, type SimpleDateTime } from 'hyperformula/typings/DateT
|
|
|
8
8
|
|
|
9
9
|
import { Event } from '@dxos/async';
|
|
10
10
|
import { Resource } from '@dxos/context';
|
|
11
|
-
import { getTypename } from '@dxos/echo-schema';
|
|
11
|
+
import { getTypename, FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { PublicKey } from '@dxos/keys';
|
|
14
14
|
import { log } from '@dxos/log';
|
|
15
|
-
import { FieldValueType } from '@dxos/schema';
|
|
16
15
|
|
|
17
16
|
import { DetailedCellError, ExportedCellChange } from '#hyperformula';
|
|
18
17
|
import { type ComputeGraph, type ComputeNode, type ComputeNodeEvent, createSheetName } from '../compute-graph';
|
|
@@ -21,26 +20,29 @@ import {
|
|
|
21
20
|
addressFromIndex,
|
|
22
21
|
addressToA1Notation,
|
|
23
22
|
addressToIndex,
|
|
24
|
-
type CellAddress,
|
|
25
|
-
type CellRange,
|
|
26
23
|
initialize,
|
|
27
24
|
insertIndices,
|
|
28
25
|
isFormula,
|
|
26
|
+
type CellAddress,
|
|
27
|
+
type CellRange,
|
|
28
|
+
ReadonlyException,
|
|
29
29
|
MAX_COLUMNS,
|
|
30
30
|
MAX_ROWS,
|
|
31
|
-
ReadonlyException,
|
|
32
31
|
} from '../defs';
|
|
33
|
-
import { type CellScalarValue, type CellValue, type SheetType } from '../types';
|
|
32
|
+
import { type CellScalarValue, type CellValue, type SheetType, type RestoreAxis } from '../types';
|
|
34
33
|
|
|
35
34
|
// Map sheet types to system types.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
// https://hyperformula.handsontable.com/guide/types-of-values.html
|
|
36
|
+
// - https://github.com/handsontable/hyperformula/blob/master/src/Cell.ts (CellValueType)
|
|
37
|
+
// - https://github.com/handsontable/hyperformula/blob/master/src/interpreter/InterpreterValue.ts (NumberType)
|
|
38
|
+
const typeMap: Record<string, { type: TypeEnum; format?: FormatEnum }> = {
|
|
39
|
+
BOOLEAN: { type: TypeEnum.Boolean },
|
|
40
|
+
NUMBER_RAW: { type: TypeEnum.Number },
|
|
41
|
+
NUMBER_PERCENT: { type: TypeEnum.Number, format: FormatEnum.Percent },
|
|
42
|
+
NUMBER_CURRENCY: { type: TypeEnum.Number, format: FormatEnum.Currency },
|
|
43
|
+
NUMBER_DATETIME: { type: TypeEnum.String, format: FormatEnum.DateTime },
|
|
44
|
+
NUMBER_DATE: { type: TypeEnum.String, format: FormatEnum.Date },
|
|
45
|
+
NUMBER_TIME: { type: TypeEnum.String, format: FormatEnum.Time },
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
const getTopLeft = (range: CellRange): CellAddress => {
|
|
@@ -111,6 +113,12 @@ export class SheetModel extends Resource {
|
|
|
111
113
|
log('initialize', { id: this.id });
|
|
112
114
|
initialize(this._sheet);
|
|
113
115
|
|
|
116
|
+
this._graph.update.on((event) => {
|
|
117
|
+
if (event.type === 'functionsUpdated') {
|
|
118
|
+
this.reset();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
114
122
|
// TODO(burdon): SheetModel should extend ComputeNode and be constructed via the graph.
|
|
115
123
|
this._node = this._graph.getOrCreateNode(createSheetName({ type: getTypename(this._sheet)!, id: this._sheet.id }));
|
|
116
124
|
await this._node.open();
|
|
@@ -134,9 +142,14 @@ export class SheetModel extends Resource {
|
|
|
134
142
|
invariant(this._node);
|
|
135
143
|
const { col, row } = addressFromIndex(this._sheet, key);
|
|
136
144
|
if (isFormula(value)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
const binding = this._graph.mapFunctionBindingFromId(this.mapFormulaIndicesToRefs(value));
|
|
146
|
+
if (binding) {
|
|
147
|
+
value = this._graph.mapFormulaToNative(binding);
|
|
148
|
+
} else {
|
|
149
|
+
// If binding is not found, render the cell as empty.
|
|
150
|
+
// This prevents the cell from momentarily rendering an error while the binding is being loaded.
|
|
151
|
+
value = '';
|
|
152
|
+
}
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
this._node.graph.hf.setCellContents({ sheet: this._node.sheetId, row, col }, value);
|
|
@@ -155,12 +168,68 @@ export class SheetModel extends Resource {
|
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
insertRows(i: number, n = 1) {
|
|
158
|
-
insertIndices(this._sheet.rows, i, n, MAX_ROWS);
|
|
171
|
+
const idx = insertIndices(this._sheet.rows, i, n, MAX_ROWS);
|
|
159
172
|
this.reset();
|
|
173
|
+
return idx;
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
insertColumns(i: number, n = 1) {
|
|
163
|
-
insertIndices(this._sheet.columns, i, n, MAX_COLUMNS);
|
|
177
|
+
const idx = insertIndices(this._sheet.columns, i, n, MAX_COLUMNS);
|
|
178
|
+
this.reset();
|
|
179
|
+
return idx;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
dropRow(rowIndex: string): RestoreAxis {
|
|
183
|
+
const range = {
|
|
184
|
+
from: addressFromIndex(this._sheet, `${this._sheet.columns[0]}@${rowIndex}`),
|
|
185
|
+
to: addressFromIndex(this._sheet, `${this._sheet.columns[this._sheet.columns.length - 1]}@${rowIndex}`),
|
|
186
|
+
};
|
|
187
|
+
const values = this.getCellValues(range).flat();
|
|
188
|
+
const index = this._sheet.rows.indexOf(rowIndex);
|
|
189
|
+
this.clear(range);
|
|
190
|
+
this._sheet.rows.splice(index, 1);
|
|
191
|
+
delete this._sheet.rowMeta[rowIndex];
|
|
192
|
+
this.reset();
|
|
193
|
+
return { axis: 'row', index, axisIndex: rowIndex, axisMeta: this._sheet.rowMeta[rowIndex], values };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
dropColumn(colIndex: string): RestoreAxis {
|
|
197
|
+
const range = {
|
|
198
|
+
from: addressFromIndex(this._sheet, `${colIndex}@${this._sheet.rows[0]}`),
|
|
199
|
+
to: addressFromIndex(this._sheet, `${colIndex}@${this._sheet.rows[this._sheet.rows.length - 1]}`),
|
|
200
|
+
};
|
|
201
|
+
const values = this.getCellValues(range).flat();
|
|
202
|
+
const index = this._sheet.columns.indexOf(colIndex);
|
|
203
|
+
this.clear(range);
|
|
204
|
+
this._sheet.columns.splice(index, 1);
|
|
205
|
+
delete this._sheet.columnMeta[colIndex];
|
|
206
|
+
this.reset();
|
|
207
|
+
return { axis: 'col', index, axisIndex: colIndex, axisMeta: this._sheet.rowMeta[colIndex], values };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
restoreRow({ index, axisIndex, axisMeta, values }: RestoreAxis) {
|
|
211
|
+
this._sheet.rows.splice(index, 0, axisIndex);
|
|
212
|
+
values.forEach((value, col) => {
|
|
213
|
+
if (value) {
|
|
214
|
+
this._sheet.cells[`${this._sheet.columns[col]}@${axisIndex}`] = { value };
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (axisMeta) {
|
|
218
|
+
this._sheet.rowMeta[axisIndex] = axisMeta;
|
|
219
|
+
}
|
|
220
|
+
this.reset();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
restoreColumn({ index, axisIndex, axisMeta, values }: RestoreAxis) {
|
|
224
|
+
this._sheet.columns.splice(index, 0, axisIndex);
|
|
225
|
+
values.forEach((value, row) => {
|
|
226
|
+
if (value) {
|
|
227
|
+
this._sheet.cells[`${axisIndex}@${this._sheet.rows[row]}`] = { value };
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (axisMeta) {
|
|
231
|
+
this._sheet.columnMeta[axisIndex] = axisMeta;
|
|
232
|
+
}
|
|
164
233
|
this.reset();
|
|
165
234
|
}
|
|
166
235
|
|
|
@@ -265,8 +334,11 @@ export class SheetModel extends Resource {
|
|
|
265
334
|
getValue(cell: CellAddress): CellScalarValue {
|
|
266
335
|
// Applies rounding and post-processing.
|
|
267
336
|
invariant(this._node);
|
|
268
|
-
const
|
|
337
|
+
const address = toSimpleCellAddress(this._node.sheetId, cell);
|
|
338
|
+
const value = this._node.graph.hf.getCellValue(address);
|
|
269
339
|
if (value instanceof DetailedCellError) {
|
|
340
|
+
// TODO(wittjosiah): Error details should be shown in cell `title`.
|
|
341
|
+
log.info('cell error', { cell, error: value });
|
|
270
342
|
return value.toString();
|
|
271
343
|
}
|
|
272
344
|
|
|
@@ -276,7 +348,7 @@ export class SheetModel extends Resource {
|
|
|
276
348
|
/**
|
|
277
349
|
* Get value type.
|
|
278
350
|
*/
|
|
279
|
-
|
|
351
|
+
getValueDescription(cell: CellAddress): { type: TypeEnum; format?: FormatEnum } | undefined {
|
|
280
352
|
invariant(this._node);
|
|
281
353
|
const addr = toSimpleCellAddress(this._node.sheetId, cell);
|
|
282
354
|
const type = this._node.graph.hf.getCellValueDetailedType(addr);
|