@dxos/plugin-sheet 0.6.14-main.8b352a0 → 0.6.14-staging.8758a12
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-R65IDJHN.mjs → SheetContainer-GPJOTYCI.mjs} +41 -30
- package/dist/lib/browser/SheetContainer-GPJOTYCI.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5KVQ5IPW.mjs → chunk-BVUN7SHF.mjs} +4 -2
- package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +7 -0
- package/dist/lib/browser/{chunk-I2DKJ72A.mjs → chunk-CYOQA6UK.mjs} +277 -79
- package/dist/lib/browser/chunk-CYOQA6UK.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-KCYJSOFB.mjs → chunk-VMSX6Z4X.mjs} +51 -17
- package/dist/lib/browser/chunk-VMSX6Z4X.mjs.map +7 -0
- package/dist/lib/browser/{compute-graph-SJT67236.mjs → compute-graph-GGWUX644.mjs} +4 -4
- package/dist/lib/browser/index.mjs +38 -10
- 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-6BO4C5X2.cjs → SheetContainer-VSC6XF3M.cjs} +60 -50
- package/dist/lib/node/SheetContainer-VSC6XF3M.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-DEPJHN47.cjs → chunk-545PZPK3.cjs} +313 -116
- package/dist/lib/node/chunk-545PZPK3.cjs.map +7 -0
- package/dist/lib/node/{chunk-2XJ5I4UF.cjs → chunk-AWKOWDMI.cjs} +8 -6
- package/dist/lib/node/chunk-AWKOWDMI.cjs.map +7 -0
- package/dist/lib/node/{chunk-JF5XNTF3.cjs → chunk-O7XR4R7Y.cjs} +61 -26
- package/dist/lib/node/chunk-O7XR4R7Y.cjs.map +7 -0
- package/dist/lib/node/{compute-graph-AQBDL7HO.cjs → compute-graph-KGWA2QLE.cjs} +21 -21
- package/dist/lib/node/{compute-graph-AQBDL7HO.cjs.map → compute-graph-KGWA2QLE.cjs.map} +2 -2
- package/dist/lib/node/index.cjs +66 -38
- 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-MJXC5E3P.mjs → SheetContainer-SJK25GKT.mjs} +41 -30
- package/dist/lib/node-esm/SheetContainer-SJK25GKT.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-XBEHKYO7.mjs → chunk-CR4K75EL.mjs} +51 -17
- package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-25V7WY4R.mjs → chunk-EZ6K2W62.mjs} +277 -79
- package/dist/lib/node-esm/chunk-EZ6K2W62.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-5TXLF6PL.mjs → chunk-UIBWRHW7.mjs} +4 -2
- package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +7 -0
- package/dist/lib/node-esm/{compute-graph-FRCKXEYK.mjs → compute-graph-2SCZT7N5.mjs} +4 -4
- package/dist/lib/node-esm/index.mjs +38 -10
- 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/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/util.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 +5 -2
- package/dist/types/src/components/Toolbar/Toolbar.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 +1 -1
- 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/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 +10 -5
- 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 +36 -36
- package/src/SheetPlugin.tsx +20 -0
- package/src/components/GridSheet/GridSheet.tsx +96 -36
- package/src/components/GridSheet/util.ts +13 -7
- package/src/components/SheetContainer/SheetContainer.stories.tsx +2 -0
- package/src/components/Toolbar/Toolbar.tsx +62 -41
- 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 +4 -2
- package/src/defs/util.ts +1 -0
- 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 +10 -5
- package/src/types.ts +20 -0
- package/dist/lib/browser/SheetContainer-R65IDJHN.mjs.map +0 -7
- package/dist/lib/browser/chunk-5KVQ5IPW.mjs.map +0 -7
- package/dist/lib/browser/chunk-I2DKJ72A.mjs.map +0 -7
- package/dist/lib/browser/chunk-KCYJSOFB.mjs.map +0 -7
- package/dist/lib/node/SheetContainer-6BO4C5X2.cjs.map +0 -7
- package/dist/lib/node/chunk-2XJ5I4UF.cjs.map +0 -7
- package/dist/lib/node/chunk-DEPJHN47.cjs.map +0 -7
- package/dist/lib/node/chunk-JF5XNTF3.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-MJXC5E3P.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-25V7WY4R.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-5TXLF6PL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XBEHKYO7.mjs.map +0 -7
- /package/dist/lib/browser/{compute-graph-SJT67236.mjs.map → compute-graph-GGWUX644.mjs.map} +0 -0
- /package/dist/lib/node-esm/{compute-graph-FRCKXEYK.mjs.map → compute-graph-2SCZT7N5.mjs.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
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 {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
inRange,
|
|
29
29
|
rangeFromIndex,
|
|
30
30
|
rangeToIndex,
|
|
31
|
+
styleKey,
|
|
31
32
|
type StyleKey,
|
|
32
33
|
type StyleValue,
|
|
33
34
|
} from '../../defs';
|
|
@@ -89,10 +90,11 @@ type CommentAction = { key: CommentKey; value: CommentValue; cellContent?: strin
|
|
|
89
90
|
type StyleAction = { key: StyleKey; value: StyleValue };
|
|
90
91
|
|
|
91
92
|
export type ToolbarAction = StyleAction | AlignAction | CommentAction;
|
|
93
|
+
export type ToolbarActionAnnotated = ToolbarAction & { unset?: boolean };
|
|
92
94
|
|
|
93
95
|
export type ToolbarActionType = ToolbarAction['key'];
|
|
94
96
|
|
|
95
|
-
export type ToolbarActionHandler = (action:
|
|
97
|
+
export type ToolbarActionHandler = (action: ToolbarActionAnnotated) => void;
|
|
96
98
|
|
|
97
99
|
export type ToolbarProps = ThemedClassName<
|
|
98
100
|
PropsWithChildren<{
|
|
@@ -100,9 +102,9 @@ export type ToolbarProps = ThemedClassName<
|
|
|
100
102
|
}>
|
|
101
103
|
>;
|
|
102
104
|
|
|
103
|
-
const [ToolbarContextProvider, useToolbarContext] = createContext<{
|
|
104
|
-
|
|
105
|
-
);
|
|
105
|
+
const [ToolbarContextProvider, useToolbarContext] = createContext<{
|
|
106
|
+
onAction: (action: ToolbarActionAnnotated) => void;
|
|
107
|
+
}>('Toolbar');
|
|
106
108
|
|
|
107
109
|
// TODO(Zan): Factor out, copied this from MarkdownPlugin.
|
|
108
110
|
const sectionToolbarLayout =
|
|
@@ -117,13 +119,16 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
117
119
|
|
|
118
120
|
// TODO(Zan): Externalize the toolbar action handler. E.g., Toolbar/keys should both fire events.
|
|
119
121
|
const handleAction = useCallback(
|
|
120
|
-
(action:
|
|
122
|
+
(action: ToolbarActionAnnotated) => {
|
|
121
123
|
switch (action.key) {
|
|
122
124
|
case 'alignment':
|
|
123
|
-
if (
|
|
124
|
-
const index =
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
if (cursorFallbackRange) {
|
|
126
|
+
const index =
|
|
127
|
+
model.sheet.ranges?.findIndex(
|
|
128
|
+
(range) =>
|
|
129
|
+
range.key === action.key &&
|
|
130
|
+
inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
|
|
131
|
+
) ?? -1;
|
|
127
132
|
const nextRangeEntity = {
|
|
128
133
|
range: rangeToIndex(model.sheet, cursorFallbackRange),
|
|
129
134
|
key: action.key,
|
|
@@ -131,14 +136,21 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
131
136
|
};
|
|
132
137
|
if (index < 0) {
|
|
133
138
|
model.sheet.ranges?.push(nextRangeEntity);
|
|
139
|
+
} else if (model.sheet.ranges![index].value === action.value) {
|
|
140
|
+
model.sheet.ranges?.splice(index, 1);
|
|
134
141
|
} else {
|
|
135
142
|
model.sheet.ranges?.splice(index, 1, nextRangeEntity);
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
break;
|
|
139
146
|
case 'style':
|
|
140
|
-
if (action.
|
|
141
|
-
const index = model.sheet.ranges?.findIndex(
|
|
147
|
+
if (action.unset) {
|
|
148
|
+
const index = model.sheet.ranges?.findIndex(
|
|
149
|
+
(range) =>
|
|
150
|
+
range.key === action.key &&
|
|
151
|
+
cursorFallbackRange &&
|
|
152
|
+
inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),
|
|
153
|
+
);
|
|
142
154
|
if (index >= 0) {
|
|
143
155
|
model.sheet.ranges?.splice(index, 1);
|
|
144
156
|
}
|
|
@@ -172,6 +184,7 @@ const ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {
|
|
|
172
184
|
<ToolbarContextProvider onAction={handleAction}>
|
|
173
185
|
<NaturalToolbar.Root
|
|
174
186
|
classNames={[
|
|
187
|
+
'pli-0.5',
|
|
175
188
|
...(role === 'section'
|
|
176
189
|
? ['z-[2] group-focus-within/section:visible', !hasAttention && 'invisible', sectionToolbarLayout]
|
|
177
190
|
: ['attention-surface']),
|
|
@@ -207,20 +220,20 @@ const Alignment = () => {
|
|
|
207
220
|
const { onAction } = useToolbarContext('Alignment');
|
|
208
221
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
209
222
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
: undefined,
|
|
217
|
-
[cursor, model.sheet.ranges],
|
|
218
|
-
);
|
|
223
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
224
|
+
const value = cursor
|
|
225
|
+
? model.sheet.ranges?.findLast(
|
|
226
|
+
({ range, key }) => key === alignKey && inRange(rangeFromIndex(model.sheet, range), cursor),
|
|
227
|
+
)?.value
|
|
228
|
+
: undefined;
|
|
219
229
|
|
|
220
230
|
return (
|
|
221
231
|
<NaturalToolbar.ToggleGroup
|
|
222
232
|
type='single'
|
|
223
|
-
value={
|
|
233
|
+
value={
|
|
234
|
+
// TODO(thure): providing `undefined` leaves the last item active which was active rather than showing none.
|
|
235
|
+
value ?? 'never'
|
|
236
|
+
}
|
|
224
237
|
onValueChange={(value: AlignValue) => onAction?.({ key: alignKey, value })}
|
|
225
238
|
>
|
|
226
239
|
{alignmentOptions.map(({ value, icon }) => (
|
|
@@ -235,25 +248,27 @@ const Alignment = () => {
|
|
|
235
248
|
);
|
|
236
249
|
};
|
|
237
250
|
|
|
238
|
-
const styleOptions: ButtonProps<StyleValue>[] = [
|
|
251
|
+
const styleOptions: ButtonProps<StyleValue>[] = [
|
|
252
|
+
{ value: 'highlight', icon: 'ph--highlighter--regular' },
|
|
253
|
+
{ value: 'softwrap', icon: 'ph--paragraph--regular' },
|
|
254
|
+
];
|
|
239
255
|
|
|
240
256
|
const Styles = () => {
|
|
241
|
-
const {
|
|
257
|
+
const { cursorFallbackRange, model } = useSheetContext();
|
|
242
258
|
const { onAction } = useToolbarContext('Styles');
|
|
243
259
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
244
260
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
);
|
|
261
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
262
|
+
const activeValues = cursorFallbackRange
|
|
263
|
+
? model.sheet.ranges
|
|
264
|
+
?.filter(
|
|
265
|
+
({ range, key }) => key === 'style' && inRange(rangeFromIndex(model.sheet, range), cursorFallbackRange.from),
|
|
266
|
+
)
|
|
267
|
+
.reduce((acc, { value }) => {
|
|
268
|
+
acc.add(value);
|
|
269
|
+
return acc;
|
|
270
|
+
}, new Set())
|
|
271
|
+
: undefined;
|
|
257
272
|
|
|
258
273
|
return (
|
|
259
274
|
<>
|
|
@@ -262,10 +277,15 @@ const Styles = () => {
|
|
|
262
277
|
itemType='toggle'
|
|
263
278
|
key={value}
|
|
264
279
|
pressed={activeValues?.has(value)}
|
|
265
|
-
onPressedChange={(nextPressed: boolean) =>
|
|
280
|
+
onPressedChange={(nextPressed: boolean) => {
|
|
281
|
+
onAction?.({ key: 'style', value, unset: !nextPressed });
|
|
282
|
+
}}
|
|
266
283
|
icon={icon}
|
|
267
284
|
>
|
|
268
|
-
{t(
|
|
285
|
+
{t('toolbar action label', {
|
|
286
|
+
key: t(`range key ${styleKey} label`),
|
|
287
|
+
value: t(`range value ${value} label`),
|
|
288
|
+
})}
|
|
269
289
|
</ToolbarItem>
|
|
270
290
|
))}
|
|
271
291
|
</>
|
|
@@ -281,6 +301,7 @@ const Actions = () => {
|
|
|
281
301
|
const { cursorFallbackRange, cursor, model } = useSheetContext();
|
|
282
302
|
const { t } = useTranslation(SHEET_PLUGIN);
|
|
283
303
|
|
|
304
|
+
// TODO(thure): Can this O(n) call be memoized?
|
|
284
305
|
const overlapsCommentAnchor = (model.sheet.threads ?? [])
|
|
285
306
|
.filter(nonNullable)
|
|
286
307
|
.filter((thread) => thread.status !== 'resolved')
|
|
@@ -304,16 +325,16 @@ const Actions = () => {
|
|
|
304
325
|
icon='ph--chat-text--regular'
|
|
305
326
|
data-testid='editor.toolbar.comment'
|
|
306
327
|
onClick={() => {
|
|
307
|
-
if (!
|
|
328
|
+
if (!cursorFallbackRange) {
|
|
308
329
|
return;
|
|
309
330
|
}
|
|
310
331
|
return onAction?.({
|
|
311
332
|
key: 'comment',
|
|
312
333
|
value: rangeToIndex(model.sheet, cursorFallbackRange),
|
|
313
|
-
cellContent: model.getCellText(
|
|
334
|
+
cellContent: model.getCellText(cursorFallbackRange.from),
|
|
314
335
|
});
|
|
315
336
|
}}
|
|
316
|
-
disabled={!
|
|
337
|
+
disabled={!cursorFallbackRange || overlapsCommentAnchor}
|
|
317
338
|
>
|
|
318
339
|
{t(tooltipLabelKey)}
|
|
319
340
|
</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 });
|
|
@@ -16,7 +16,7 @@ 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 => {
|
|
@@ -37,7 +37,9 @@ export const cellClassNameForRange = ({ key, value }: SheetType['ranges'][number
|
|
|
37
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
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);
|
package/src/translations.ts
CHANGED
|
@@ -14,10 +14,13 @@ export default [
|
|
|
14
14
|
'create sheet section label': 'Create sheet',
|
|
15
15
|
'cell placeholder': 'Cell value...',
|
|
16
16
|
'range key alignment label': 'Align',
|
|
17
|
-
'range
|
|
18
|
-
'range value
|
|
19
|
-
'range value
|
|
20
|
-
'
|
|
17
|
+
'range key style label': 'Style',
|
|
18
|
+
'range value start label': 'Align left',
|
|
19
|
+
'range value center label': 'Align center',
|
|
20
|
+
'range value end label': 'Align right',
|
|
21
|
+
'range value softwrap label': 'Wrap text',
|
|
22
|
+
'range value highlight label': 'Highlight',
|
|
23
|
+
'toolbar action label': '{{value}}',
|
|
21
24
|
'selection overlaps existing comment label': 'Selected cell already has a comment',
|
|
22
25
|
'comment label': 'Add comment',
|
|
23
26
|
'comment ranges not supported label': 'Commenting on ranges is not yet supported',
|
|
@@ -30,7 +33,9 @@ export default [
|
|
|
30
33
|
'add row after label': 'Add row after',
|
|
31
34
|
'delete row label': 'Delete row',
|
|
32
35
|
'range list heading': 'Ranges',
|
|
33
|
-
'range title': '{{position}} — {{
|
|
36
|
+
'range title': '{{position}} — {{value}}',
|
|
37
|
+
'col dropped label': 'Deleted a column',
|
|
38
|
+
'row dropped label': 'Deleted a row',
|
|
34
39
|
},
|
|
35
40
|
},
|
|
36
41
|
},
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
MetadataRecordsProvides,
|
|
9
9
|
SurfaceProvides,
|
|
10
10
|
TranslationsProvides,
|
|
11
|
+
IntentData,
|
|
11
12
|
} from '@dxos/app-framework';
|
|
12
13
|
import { ref, S, TypedObject } from '@dxos/echo-schema';
|
|
13
14
|
import { type SchemaProvides } from '@dxos/plugin-client';
|
|
@@ -15,13 +16,32 @@ import { type MarkdownExtensionProvides } from '@dxos/plugin-markdown';
|
|
|
15
16
|
import { type SpaceInitProvides } from '@dxos/plugin-space';
|
|
16
17
|
import { ThreadType } from '@dxos/plugin-space/types';
|
|
17
18
|
import { type StackProvides } from '@dxos/plugin-stack';
|
|
19
|
+
import { type DxGridAxis } from '@dxos/react-ui-grid';
|
|
18
20
|
|
|
19
21
|
import { SHEET_PLUGIN } from './meta';
|
|
22
|
+
import { type SheetModel } from './model';
|
|
20
23
|
|
|
21
24
|
const SHEET_ACTION = `${SHEET_PLUGIN}/action`;
|
|
22
25
|
|
|
23
26
|
export enum SheetAction {
|
|
24
27
|
CREATE = `${SHEET_ACTION}/create`,
|
|
28
|
+
INSERT_AXIS = `${SHEET_ACTION}/axis-insert`,
|
|
29
|
+
DROP_AXIS = `${SHEET_ACTION}/axis-drop`,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type RestoreAxis = {
|
|
33
|
+
axis: DxGridAxis;
|
|
34
|
+
axisIndex: string;
|
|
35
|
+
index: number;
|
|
36
|
+
axisMeta?: S.Schema.Type<typeof RowColumnMeta>;
|
|
37
|
+
values: CellScalarValue[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export namespace SheetAction {
|
|
41
|
+
export type Create = IntentData<{ sheet: SheetType }>;
|
|
42
|
+
export type InsertAxis = IntentData<{ model: SheetModel; axis: DxGridAxis; index: number; count?: number }>;
|
|
43
|
+
export type DropAxis = IntentData<{ model: SheetModel; axis: DxGridAxis; axisIndex: string }>;
|
|
44
|
+
export type DropAxisRestore = IntentData<RestoreAxis & { model: SheetModel }>;
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
// TODO(Zan): Move this to the plugin-space plugin or another common location
|