@dxos/plugin-sheet 0.6.12-main.5cc132e → 0.6.12-main.c974201
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-V4GCCZTX.mjs +261 -0
- package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GNNVBNCX.mjs → chunk-6ZMQVB4Z.mjs} +358 -678
- package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +7 -0
- package/dist/lib/browser/{chunk-JRL5LGCE.mjs → chunk-QILRZNE5.mjs} +2 -5
- package/dist/lib/browser/chunk-QILRZNE5.mjs.map +7 -0
- package/dist/lib/{node-esm/chunk-WUPTZUTX.mjs → browser/chunk-T3NJFTD4.mjs} +4 -14
- package/dist/lib/browser/chunk-T3NJFTD4.mjs.map +7 -0
- package/dist/lib/browser/{SheetContainer-Y7ZMFBAP.mjs → chunk-U2JHW3L6.mjs} +819 -498
- package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +7 -0
- package/dist/lib/browser/graph-T27BOBOV.mjs +21 -0
- package/dist/lib/browser/graph-T27BOBOV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +58 -55
- 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 +4 -6
- package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs +279 -0
- package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs.map +7 -0
- package/dist/lib/node/{chunk-BJ6ZD7MN.cjs → chunk-BNARJ5GM.cjs} +5 -18
- package/dist/lib/node/chunk-BNARJ5GM.cjs.map +7 -0
- package/dist/lib/node/{chunk-ZRQZFV5T.cjs → chunk-DD6FIXWC.cjs} +359 -679
- package/dist/lib/node/chunk-DD6FIXWC.cjs.map +7 -0
- package/dist/lib/node/{SheetContainer-KEOKUKAQ.cjs → chunk-OTTD7FBK.cjs} +875 -551
- package/dist/lib/node/chunk-OTTD7FBK.cjs.map +7 -0
- package/dist/lib/node/{chunk-VJU3NPUJ.cjs → chunk-Q3HBHPRL.cjs} +8 -19
- package/dist/lib/node/chunk-Q3HBHPRL.cjs.map +7 -0
- package/dist/lib/node/graph-SPKGX7W4.cjs +43 -0
- package/dist/lib/node/graph-SPKGX7W4.cjs.map +7 -0
- package/dist/lib/node/index.cjs +75 -64
- 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 +10 -12
- package/dist/lib/node/types.cjs.map +2 -2
- package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs +262 -0
- package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs.map +7 -0
- package/dist/lib/node-esm/{SheetContainer-Y7ZMFBAP.mjs → chunk-7HVSOTGA.mjs} +820 -498
- package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +7 -0
- package/dist/lib/{browser/chunk-WUPTZUTX.mjs → node-esm/chunk-BMNA27EX.mjs} +5 -14
- package/dist/lib/node-esm/chunk-BMNA27EX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-GNNVBNCX.mjs → chunk-D6KU5MI7.mjs} +359 -677
- package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-JRL5LGCE.mjs → chunk-IU2L277A.mjs} +4 -5
- package/dist/lib/node-esm/chunk-IU2L277A.mjs.map +7 -0
- package/dist/lib/node-esm/graph-U67IO4UC.mjs +22 -0
- package/dist/lib/node-esm/graph-U67IO4UC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +59 -55
- 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 +2 -1
- package/dist/lib/node-esm/types.mjs +5 -6
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/CellEditor.d.ts +23 -3
- package/dist/types/src/components/CellEditor/CellEditor.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts +2 -2
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/extension.d.ts +1 -1
- package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts +11 -0
- package/dist/types/src/components/ComputeGraph/ComputeGraphContextProvider.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/index.d.ts +1 -3
- package/dist/types/src/components/ComputeGraph/index.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.d.ts +10 -0
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -0
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +9 -0
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -0
- package/dist/types/src/components/GridSheet/util.d.ts +6 -0
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/Sheet.d.ts +1 -1
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts +5 -6
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/grid.d.ts +2 -2
- package/dist/types/src/components/Sheet/grid.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/nav.d.ts +3 -3
- package/dist/types/src/components/Sheet/nav.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/sheet-context.d.ts +4 -5
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/threads.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer.d.ts +1 -1
- package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +1 -1
- package/dist/types/src/components/index.d.ts +2 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defs/index.d.ts +3 -0
- package/dist/types/src/defs/index.d.ts.map +1 -0
- package/dist/types/src/{model → defs}/types.d.ts +8 -3
- package/dist/types/src/defs/types.d.ts.map +1 -0
- package/dist/types/src/defs/types.test.d.ts.map +1 -0
- package/dist/types/src/{model → defs}/util.d.ts +8 -4
- package/dist/types/src/defs/util.d.ts.map +1 -0
- package/dist/types/src/extensions/compute.d.ts +5 -0
- package/dist/types/src/extensions/compute.d.ts.map +1 -0
- package/dist/types/src/extensions/compute.stories.d.ts +26 -0
- package/dist/types/src/extensions/compute.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/{components/ComputeGraph → graph}/async-function.d.ts +1 -1
- package/dist/types/src/graph/async-function.d.ts.map +1 -0
- package/dist/types/src/graph/compute-graph.browser.test.d.ts +2 -0
- package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +1 -0
- package/dist/types/src/graph/compute-graph.d.ts +81 -0
- package/dist/types/src/graph/compute-graph.d.ts.map +1 -0
- package/dist/types/src/graph/compute-graph.stories.d.ts +10 -0
- package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -0
- package/dist/types/src/graph/compute-node.d.ts +19 -0
- package/dist/types/src/graph/compute-node.d.ts.map +1 -0
- package/dist/types/src/{components/ComputeGraph/custom.d.ts → graph/custom-function.d.ts} +1 -1
- package/dist/types/src/graph/custom-function.d.ts.map +1 -0
- package/dist/types/src/graph/edge-function.d.ts.map +1 -0
- package/dist/types/src/{model/functions.d.ts → graph/function-defs.d.ts} +1 -1
- package/dist/types/src/graph/function-defs.d.ts.map +1 -0
- package/dist/types/src/graph/hyperformula.test.d.ts +2 -0
- package/dist/types/src/graph/hyperformula.test.d.ts.map +1 -0
- package/dist/types/src/graph/index.d.ts +4 -0
- package/dist/types/src/graph/index.d.ts.map +1 -0
- package/dist/types/src/graph/util.d.ts +2 -0
- package/dist/types/src/graph/util.d.ts.map +1 -0
- package/dist/types/src/hooks/hooks.stories.d.ts +11 -0
- package/dist/types/src/hooks/hooks.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useComputeGraph.d.ts +7 -0
- package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -0
- package/dist/types/src/hooks/useFormattingModel.d.ts +3 -0
- package/dist/types/src/hooks/useFormattingModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useSheetModel.d.ts +8 -0
- package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +1 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/model/formatting-model.d.ts +16 -0
- package/dist/types/src/model/formatting-model.d.ts.map +1 -0
- package/dist/types/src/model/index.d.ts +2 -4
- package/dist/types/src/model/index.d.ts.map +1 -1
- package/dist/types/src/model/{model.d.ts → sheet-model.d.ts} +9 -48
- package/dist/types/src/model/sheet-model.d.ts.map +1 -0
- package/dist/types/src/sanity.test.d.ts +2 -0
- package/dist/types/src/sanity.test.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/testing.d.ts +9 -0
- package/dist/types/src/testing/testing.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +12 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/vendor/hyperformula.mjs +37145 -0
- package/package.json +41 -38
- package/src/SheetPlugin.tsx +39 -59
- package/src/components/CellEditor/CellEditor.stories.tsx +4 -3
- package/src/components/CellEditor/CellEditor.tsx +59 -9
- package/src/components/CellEditor/extension.test.ts +3 -3
- package/src/components/CellEditor/extension.ts +1 -3
- package/src/components/ComputeGraph/ComputeGraphContextProvider.tsx +20 -0
- package/src/components/ComputeGraph/index.ts +1 -3
- package/src/components/GridSheet/GridSheet.stories.tsx +35 -0
- package/src/components/GridSheet/GridSheet.tsx +153 -0
- package/src/components/GridSheet/util.ts +89 -0
- package/src/components/Sheet/Sheet.stories.tsx +41 -82
- package/src/components/Sheet/Sheet.tsx +12 -10
- package/src/components/Sheet/grid.ts +3 -3
- package/src/components/Sheet/nav.ts +19 -19
- package/src/components/Sheet/sheet-context.tsx +10 -80
- package/src/components/Sheet/threads.tsx +10 -6
- package/src/components/SheetContainer.tsx +2 -2
- package/src/components/Toolbar/Toolbar.tsx +1 -2
- package/src/components/index.ts +1 -0
- package/src/defs/index.ts +6 -0
- package/src/{model → defs}/types.test.ts +7 -7
- package/src/{model → defs}/types.ts +23 -14
- package/src/{model → defs}/util.ts +49 -17
- package/src/extensions/compute.stories.tsx +151 -0
- package/src/extensions/compute.ts +98 -0
- package/src/extensions/index.ts +5 -0
- package/src/{components/ComputeGraph → graph}/async-function.ts +3 -1
- package/src/graph/compute-graph.browser.test.ts +104 -0
- package/src/graph/compute-graph.stories.tsx +92 -0
- package/src/graph/compute-graph.ts +290 -0
- package/src/graph/compute-node.ts +51 -0
- package/src/{components/ComputeGraph/custom.ts → graph/custom-function.ts} +2 -6
- package/src/{components/ComputeGraph → graph}/edge-function.ts +2 -1
- package/src/graph/hyperformula.test.ts +15 -0
- package/src/graph/index.ts +7 -0
- package/src/graph/util.ts +8 -0
- package/src/hooks/hooks.stories.tsx +50 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useComputeGraph.ts +20 -0
- package/src/hooks/useFormattingModel.ts +11 -0
- package/src/hooks/useSheetModel.ts +43 -0
- package/src/meta.tsx +1 -5
- package/src/{components/Sheet/formatting.ts → model/formatting-model.ts} +20 -13
- package/src/model/index.ts +2 -4
- package/src/model/{model.ts → sheet-model.ts} +67 -184
- package/src/sanity.test.ts +40 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/testing.tsx +66 -0
- package/src/types.ts +14 -12
- package/dist/lib/browser/SheetContainer-Y7ZMFBAP.mjs.map +0 -7
- package/dist/lib/browser/chunk-GNNVBNCX.mjs.map +0 -7
- package/dist/lib/browser/chunk-JRL5LGCE.mjs.map +0 -7
- package/dist/lib/browser/chunk-PGKZPKUD.mjs +0 -175
- package/dist/lib/browser/chunk-PGKZPKUD.mjs.map +0 -7
- package/dist/lib/browser/chunk-VBF7YENS.mjs +0 -8
- package/dist/lib/browser/chunk-VBF7YENS.mjs.map +0 -7
- package/dist/lib/browser/chunk-WUPTZUTX.mjs.map +0 -7
- package/dist/lib/browser/testing.mjs +0 -92
- package/dist/lib/browser/testing.mjs.map +0 -7
- package/dist/lib/node/SheetContainer-KEOKUKAQ.cjs.map +0 -7
- package/dist/lib/node/chunk-57PB2HPY.cjs +0 -40
- package/dist/lib/node/chunk-57PB2HPY.cjs.map +0 -7
- package/dist/lib/node/chunk-6LWBQAQZ.cjs +0 -202
- package/dist/lib/node/chunk-6LWBQAQZ.cjs.map +0 -7
- package/dist/lib/node/chunk-BJ6ZD7MN.cjs.map +0 -7
- package/dist/lib/node/chunk-VJU3NPUJ.cjs.map +0 -7
- package/dist/lib/node/chunk-ZRQZFV5T.cjs.map +0 -7
- package/dist/lib/node/testing.cjs +0 -111
- package/dist/lib/node/testing.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-Y7ZMFBAP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GNNVBNCX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JRL5LGCE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-PGKZPKUD.mjs +0 -175
- package/dist/lib/node-esm/chunk-PGKZPKUD.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-VBF7YENS.mjs +0 -8
- package/dist/lib/node-esm/chunk-VBF7YENS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WUPTZUTX.mjs.map +0 -7
- package/dist/lib/node-esm/testing.mjs +0 -92
- package/dist/lib/node-esm/testing.mjs.map +0 -7
- package/dist/types/src/components/ComputeGraph/async-function.d.ts.map +0 -1
- package/dist/types/src/components/ComputeGraph/custom.d.ts.map +0 -1
- package/dist/types/src/components/ComputeGraph/edge-function.d.ts.map +0 -1
- package/dist/types/src/components/ComputeGraph/graph-context.d.ts +0 -12
- package/dist/types/src/components/ComputeGraph/graph-context.d.ts.map +0 -1
- package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts +0 -2
- package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts.map +0 -1
- package/dist/types/src/components/ComputeGraph/graph.d.ts +0 -26
- package/dist/types/src/components/ComputeGraph/graph.d.ts.map +0 -1
- package/dist/types/src/components/Sheet/formatting.d.ts +0 -14
- package/dist/types/src/components/Sheet/formatting.d.ts.map +0 -1
- package/dist/types/src/model/functions.d.ts.map +0 -1
- package/dist/types/src/model/model.browser.test.d.ts +0 -2
- package/dist/types/src/model/model.browser.test.d.ts.map +0 -1
- package/dist/types/src/model/model.d.ts.map +0 -1
- package/dist/types/src/model/types.d.ts.map +0 -1
- package/dist/types/src/model/types.test.d.ts.map +0 -1
- package/dist/types/src/model/util.d.ts.map +0 -1
- package/dist/types/src/testing.d.ts +0 -9
- package/dist/types/src/testing.d.ts.map +0 -1
- package/src/components/ComputeGraph/graph-context.tsx +0 -50
- package/src/components/ComputeGraph/graph.browser.test.ts +0 -49
- package/src/components/ComputeGraph/graph.ts +0 -62
- package/src/model/model.browser.test.ts +0 -99
- package/src/testing.ts +0 -50
- /package/dist/types/src/{model → defs}/types.test.d.ts +0 -0
- /package/dist/types/src/{components/ComputeGraph → graph}/edge-function.d.ts +0 -0
- /package/src/{model/functions.ts → graph/function-defs.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import {
|
|
7
|
+
type EditorState,
|
|
8
|
+
type Extension,
|
|
9
|
+
type RangeSet,
|
|
10
|
+
RangeSetBuilder,
|
|
11
|
+
StateEffect,
|
|
12
|
+
StateField,
|
|
13
|
+
type Transaction,
|
|
14
|
+
} from '@codemirror/state';
|
|
15
|
+
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
|
16
|
+
|
|
17
|
+
import { type ComputeNode } from '../graph';
|
|
18
|
+
|
|
19
|
+
const LANGUAGE_TAG = 'dx';
|
|
20
|
+
|
|
21
|
+
// TODO(burdon): Create marker just for our decorator?
|
|
22
|
+
const updateAllDecorations = StateEffect.define<void>();
|
|
23
|
+
|
|
24
|
+
export type ComputeOptions = {};
|
|
25
|
+
|
|
26
|
+
export const compute = (computeNode: ComputeNode, options: ComputeOptions = {}): Extension => {
|
|
27
|
+
const update = (state: EditorState) => {
|
|
28
|
+
const builder = new RangeSetBuilder();
|
|
29
|
+
syntaxTree(state).iterate({
|
|
30
|
+
enter: (node) => {
|
|
31
|
+
if (node.name === 'FencedCode') {
|
|
32
|
+
const cursor = state.selection.main.head;
|
|
33
|
+
if (state.readOnly || cursor < node.from || cursor > node.to) {
|
|
34
|
+
const info = node.node.getChild('CodeInfo');
|
|
35
|
+
if (info) {
|
|
36
|
+
const type = state.sliceDoc(info.from, info.to);
|
|
37
|
+
const text = node.node.getChild('CodeText');
|
|
38
|
+
if (type === LANGUAGE_TAG && text) {
|
|
39
|
+
const content = state.sliceDoc(text.from, text.to);
|
|
40
|
+
// TODO(burdon): Map unique reference onto cell; e.g., track ordered list?
|
|
41
|
+
computeNode.setValue({ col: 0, row: 0 }, content);
|
|
42
|
+
const value = computeNode.getValue({ col: 0, row: 0 });
|
|
43
|
+
builder.add(
|
|
44
|
+
node.from,
|
|
45
|
+
node.to,
|
|
46
|
+
Decoration.replace({
|
|
47
|
+
widget: new DxWidget(String(value)),
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return builder.finish();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
// Graph subscription.
|
|
62
|
+
ViewPlugin.fromClass(
|
|
63
|
+
class {
|
|
64
|
+
private readonly _subscription: any;
|
|
65
|
+
constructor(view: EditorView) {
|
|
66
|
+
this._subscription = computeNode.graph.update.on(() => {
|
|
67
|
+
view.dispatch({
|
|
68
|
+
effects: updateAllDecorations.of(),
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
destroy() {
|
|
74
|
+
this._subscription();
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
// Decorations.
|
|
80
|
+
StateField.define<RangeSet<any>>({
|
|
81
|
+
create: (state) => update(state),
|
|
82
|
+
update: (_: RangeSet<any>, tr: Transaction) => update(tr.state),
|
|
83
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
84
|
+
}),
|
|
85
|
+
];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
class DxWidget extends WidgetType {
|
|
89
|
+
constructor(private readonly value: string) {
|
|
90
|
+
super();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override toDOM(view: EditorView) {
|
|
94
|
+
const div = document.createElement('div');
|
|
95
|
+
div.innerText = this.value;
|
|
96
|
+
return div;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { CellError, ErrorType, EmptyValue, FunctionPlugin, type HyperFormula } from 'hyperformula';
|
|
6
5
|
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
7
6
|
import { type InterpreterValue } from 'hyperformula/typings/interpreter/InterpreterValue';
|
|
8
7
|
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
@@ -12,6 +11,8 @@ import { debounce, type UnsubscribeCallback } from '@dxos/async';
|
|
|
12
11
|
import { type Space } from '@dxos/client/echo';
|
|
13
12
|
import { log } from '@dxos/log';
|
|
14
13
|
|
|
14
|
+
import { CellError, ErrorType, EmptyValue, FunctionPlugin, type HyperFormula } from '#hyperformula';
|
|
15
|
+
|
|
15
16
|
// TODO(burdon): API gateways!
|
|
16
17
|
// https://publicapis.io
|
|
17
18
|
// https://api-ninjas.com/api/cryptoprice
|
|
@@ -82,6 +83,7 @@ export class FunctionContext {
|
|
|
82
83
|
}, this._options.recalculationDelay);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// TODO(burdon): Remove?
|
|
85
87
|
get space() {
|
|
86
88
|
return this._space;
|
|
87
89
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { Trigger } from '@dxos/async';
|
|
8
|
+
import { Client } from '@dxos/client';
|
|
9
|
+
import { create } from '@dxos/client/echo';
|
|
10
|
+
import { Context } from '@dxos/context';
|
|
11
|
+
import { type S } from '@dxos/echo-schema';
|
|
12
|
+
import { FunctionType } from '@dxos/plugin-script/types';
|
|
13
|
+
|
|
14
|
+
import { createComputeGraphRegistry } from './compute-graph';
|
|
15
|
+
import { addressFromA1Notation, createSheet } from '../defs';
|
|
16
|
+
import { SheetModel } from '../model';
|
|
17
|
+
import { type CellScalarValue } from '../types';
|
|
18
|
+
|
|
19
|
+
// TODO(burdon): Vitest issues:
|
|
20
|
+
// - Cannot test Hyperformula
|
|
21
|
+
// - throws "Cannot convert undefined or null to object" in vitest (without browser).
|
|
22
|
+
// - throws "process.nextTick is not a function" (with browser)
|
|
23
|
+
// - throws "Buffer already defined" (if nodeExternal: true in config)
|
|
24
|
+
// - Need better docs; esp. vitest config.
|
|
25
|
+
// - NOTE: For non-browser tests, import types from x-plugin/types (otherwise will bring in react deps).
|
|
26
|
+
// - Can't add flags to our tools?
|
|
27
|
+
// - test.only / test.skip ignored?
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* NOTE: Browser test required for hyperformula due to raw translation files.
|
|
31
|
+
*/
|
|
32
|
+
describe('compute graph', () => {
|
|
33
|
+
// TODO(burdon): Replace with builder.
|
|
34
|
+
const createModel = async (types?: S.Schema<any>[]) => {
|
|
35
|
+
const ctx = new Context();
|
|
36
|
+
const client = new Client();
|
|
37
|
+
if (types) {
|
|
38
|
+
client.addTypes(types);
|
|
39
|
+
}
|
|
40
|
+
await client.initialize();
|
|
41
|
+
await client.halo.createIdentity();
|
|
42
|
+
const space = await client.spaces.create();
|
|
43
|
+
ctx.onDispose(() => client.destroy());
|
|
44
|
+
|
|
45
|
+
const registry = createComputeGraphRegistry();
|
|
46
|
+
await registry.open(ctx);
|
|
47
|
+
|
|
48
|
+
const graph = await registry.createGraph(space);
|
|
49
|
+
await graph.open(ctx);
|
|
50
|
+
|
|
51
|
+
const sheet = createSheet({ rows: 5, columns: 5 });
|
|
52
|
+
const model = new SheetModel(graph, sheet);
|
|
53
|
+
await model.open(ctx);
|
|
54
|
+
|
|
55
|
+
// TODO(burdon): Move event propagation into graph.
|
|
56
|
+
graph.update.on(() => model.update.emit());
|
|
57
|
+
|
|
58
|
+
return { space, graph, model };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
test('map functions', async () => {
|
|
62
|
+
const { space, graph } = await createModel([FunctionType]);
|
|
63
|
+
|
|
64
|
+
// Create script.
|
|
65
|
+
const fn = space.db.add(create(FunctionType, { version: 1, binding: 'TEST' }));
|
|
66
|
+
const id = graph.mapFunctionBindingToId('TEST()');
|
|
67
|
+
expect(id).to.eq(`${fn.id}()`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('cross-node references', async () => {
|
|
71
|
+
const { graph } = await createModel();
|
|
72
|
+
|
|
73
|
+
// Create ndoes.
|
|
74
|
+
const node1 = graph.getOrCreateNode('node-1');
|
|
75
|
+
const node2 = graph.getOrCreateNode('node-2');
|
|
76
|
+
node1.hf.setCellContents({ sheet: node1.sheetId, row: 1, col: 1 }, 100);
|
|
77
|
+
node2.hf.setCellContents({ sheet: node2.sheetId, row: 1, col: 1 }, `=${node1.sheetId}!A1`);
|
|
78
|
+
const value1 = node1.hf.getCellValue({ sheet: node1.sheetId, col: 1, row: 1 });
|
|
79
|
+
const value2 = node1.hf.getCellValue({ sheet: node2.sheetId, col: 1, row: 1 });
|
|
80
|
+
expect(value1).to.eq(value2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('async function', async () => {
|
|
84
|
+
const { graph, model } = await createModel();
|
|
85
|
+
|
|
86
|
+
// Triggers function.
|
|
87
|
+
model.setValue(addressFromA1Notation('A1'), '=TEST()');
|
|
88
|
+
const trigger = new Trigger<CellScalarValue>();
|
|
89
|
+
model.update.on(() => {
|
|
90
|
+
const value = model.getValue(addressFromA1Notation('A1'));
|
|
91
|
+
trigger.wake(value);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Get initial value (i.e., null).
|
|
95
|
+
const v1 = model.getValue(addressFromA1Notation('A1'));
|
|
96
|
+
expect(v1).to.be.null;
|
|
97
|
+
expect(graph.context.info.invocations.TEST).to.eq(undefined);
|
|
98
|
+
|
|
99
|
+
// Wait until async update triggered.
|
|
100
|
+
const v2 = await trigger.wait();
|
|
101
|
+
expect(v2).not.to.be.null;
|
|
102
|
+
expect(graph.context.info.invocations.TEST).to.eq(1);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { FunctionType } from '@dxos/plugin-script/types';
|
|
10
|
+
import { create, useSpace, Filter } from '@dxos/react-client/echo';
|
|
11
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
+
import { Toolbar, Button, Input } from '@dxos/react-ui';
|
|
13
|
+
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
14
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
15
|
+
|
|
16
|
+
import { createSheet } from '../defs';
|
|
17
|
+
import { useComputeGraph, useSheetModel } from '../hooks';
|
|
18
|
+
import { withGraphDecorator } from '../testing';
|
|
19
|
+
import { SheetType } from '../types';
|
|
20
|
+
|
|
21
|
+
const FUNCTION_NAME = 'TEST';
|
|
22
|
+
|
|
23
|
+
const Story = () => {
|
|
24
|
+
const space = useSpace();
|
|
25
|
+
const graph = useComputeGraph(space);
|
|
26
|
+
const [sheet, setSheet] = useState<SheetType>();
|
|
27
|
+
const [text, setText] = useState(`${FUNCTION_NAME}(100)`);
|
|
28
|
+
const [result, setResult] = useState<any>();
|
|
29
|
+
const model = useSheetModel(space, sheet);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (space) {
|
|
32
|
+
const sheet = space.db.add(createSheet());
|
|
33
|
+
setSheet(sheet);
|
|
34
|
+
}
|
|
35
|
+
}, [space]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (space && graph) {
|
|
39
|
+
graph.update.on(() => {
|
|
40
|
+
const f1 = graph.getFunctions({ standard: true, echo: false });
|
|
41
|
+
const f2 = graph.getFunctions({ standard: false, echo: true });
|
|
42
|
+
setResult({ functions: { standard: f1.length, echo: f2.length } });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
space.db.add(create(FunctionType, { version: 1, binding: FUNCTION_NAME }));
|
|
46
|
+
}
|
|
47
|
+
}, [space, graph]);
|
|
48
|
+
|
|
49
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
50
|
+
const handleTest = async () => {
|
|
51
|
+
if (space && graph) {
|
|
52
|
+
const { objects } = await space.db.query(Filter.schema(FunctionType)).run();
|
|
53
|
+
const mapped = graph.mapFunctionBindingToId(text);
|
|
54
|
+
const unmapped = graph.mapFunctionBindingFromId(mapped);
|
|
55
|
+
const internal = graph.mapFormulaToNative(text);
|
|
56
|
+
setResult({ mapped, unmapped, internal, functions: objects.map((object) => object.id) });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
inputRef.current?.focus();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className='flex flex-col gap-2 '>
|
|
64
|
+
<Toolbar.Root>
|
|
65
|
+
<Input.Root>
|
|
66
|
+
<Input.TextInput
|
|
67
|
+
ref={inputRef}
|
|
68
|
+
placeholder='Formula'
|
|
69
|
+
value={text}
|
|
70
|
+
onChange={(ev) => setText(ev.target.value)}
|
|
71
|
+
/>
|
|
72
|
+
</Input.Root>
|
|
73
|
+
<Button onClick={handleTest}>Test</Button>
|
|
74
|
+
</Toolbar.Root>
|
|
75
|
+
<SyntaxHighlighter language='json'>
|
|
76
|
+
{JSON.stringify({ space: space?.id, graph: graph?.id, sheet: sheet?.id, model: model?.id, result }, null, 2)}
|
|
77
|
+
</SyntaxHighlighter>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
title: 'plugin-sheet/functions',
|
|
84
|
+
decorators: [
|
|
85
|
+
withClientProvider({ types: [FunctionType, SheetType], createIdentity: true, createSpace: true }),
|
|
86
|
+
withGraphDecorator,
|
|
87
|
+
withTheme,
|
|
88
|
+
],
|
|
89
|
+
render: (args: any) => <Story {...args} />,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const Default = {};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type FunctionPluginDefinition } from 'hyperformula';
|
|
6
|
+
import { type ConfigParams } from 'hyperformula/typings/ConfigParams';
|
|
7
|
+
import { type FunctionTranslationsPackage } from 'hyperformula/typings/interpreter';
|
|
8
|
+
|
|
9
|
+
import { Event } from '@dxos/async';
|
|
10
|
+
import { type SpaceId, type Space, Filter, fullyQualifiedId } from '@dxos/client/echo';
|
|
11
|
+
import { Resource } from '@dxos/context';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { PublicKey } from '@dxos/keys';
|
|
14
|
+
import { log } from '@dxos/log';
|
|
15
|
+
import { FunctionType } from '@dxos/plugin-script/types';
|
|
16
|
+
import { nonNullable } from '@dxos/util';
|
|
17
|
+
|
|
18
|
+
import { HyperFormula } from '#hyperformula';
|
|
19
|
+
import { FunctionContext, type FunctionContextOptions } from './async-function';
|
|
20
|
+
import { ComputeNode } from './compute-node';
|
|
21
|
+
import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './edge-function';
|
|
22
|
+
import { defaultFunctions, type FunctionDefinition } from './function-defs';
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// NOTE: The package.json file defines the packaged #hyperformula module.
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
// TODO(wittjosiah): Factor out.
|
|
29
|
+
const OBJECT_ID_LENGTH = 60; // 33 (space id) + 26 (object id) + 1 (separator).
|
|
30
|
+
|
|
31
|
+
// TODO(burdon): Change to "DX".
|
|
32
|
+
const CUSTOM_FUNCTION = 'ECHO';
|
|
33
|
+
|
|
34
|
+
export type ComputeGraphPlugin = {
|
|
35
|
+
plugin: FunctionPluginDefinition;
|
|
36
|
+
translations: FunctionTranslationsPackage;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ComputeGraphOptions = {
|
|
40
|
+
plugins?: ComputeGraphPlugin[];
|
|
41
|
+
} & Partial<FunctionContextOptions> &
|
|
42
|
+
Partial<ConfigParams>;
|
|
43
|
+
|
|
44
|
+
export const defaultOptions: ComputeGraphOptions = {
|
|
45
|
+
licenseKey: 'gpl-v3',
|
|
46
|
+
plugins: [
|
|
47
|
+
{
|
|
48
|
+
plugin: EdgeFunctionPlugin,
|
|
49
|
+
translations: EdgeFunctionPluginTranslations,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Marker for sheets that are managed by an ECHO object.
|
|
56
|
+
*/
|
|
57
|
+
const PREFIX = '__';
|
|
58
|
+
export const createSheetName = (id: string) => `${PREFIX}${id}`;
|
|
59
|
+
export const getSheetId = (name: string): string | undefined =>
|
|
60
|
+
name.startsWith(PREFIX) ? name.slice(PREFIX.length) : undefined;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* NOTE: Async imports to decouple hyperformula deps.
|
|
64
|
+
*/
|
|
65
|
+
export const createComputeGraphRegistry = (options: Partial<FunctionContextOptions> = {}) => {
|
|
66
|
+
return new ComputeGraphRegistry({
|
|
67
|
+
...defaultOptions,
|
|
68
|
+
...options,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Manages a collection of ComputeGraph instances for each space.
|
|
74
|
+
*
|
|
75
|
+
* [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
|
|
76
|
+
*/
|
|
77
|
+
// TODO(burdon): Move graph into separate plugin; isolate HF deps.
|
|
78
|
+
export class ComputeGraphRegistry extends Resource {
|
|
79
|
+
private readonly _registry = new Map<SpaceId, ComputeGraph>();
|
|
80
|
+
|
|
81
|
+
constructor(private readonly _options: ComputeGraphOptions = defaultOptions) {
|
|
82
|
+
super();
|
|
83
|
+
this._options.plugins?.forEach(({ plugin, translations }) => {
|
|
84
|
+
HyperFormula.registerFunctionPlugin(plugin, translations);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getGraph(spaceId: SpaceId): ComputeGraph | undefined {
|
|
89
|
+
return this._registry.get(spaceId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getOrCreateGraph(space: Space): Promise<ComputeGraph> {
|
|
93
|
+
let graph = this.getGraph(space.id);
|
|
94
|
+
if (!graph) {
|
|
95
|
+
log.info('create graph', { space: space.id });
|
|
96
|
+
graph = await this.createGraph(space);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return graph;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async createGraph(space: Space): Promise<ComputeGraph> {
|
|
103
|
+
invariant(!this._registry.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
|
|
104
|
+
const hf = HyperFormula.buildEmpty(this._options);
|
|
105
|
+
const graph = new ComputeGraph(hf, space, this._options);
|
|
106
|
+
await graph.open(this._ctx);
|
|
107
|
+
this._registry.set(space.id, graph);
|
|
108
|
+
return graph;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Per-space compute and dependency graph.
|
|
114
|
+
* Consists of multiple ComputeNode (corresponding to a HyperFormula sheet).
|
|
115
|
+
* Manages the set of custom functions.
|
|
116
|
+
* HyperFormula manages the dependency graph.
|
|
117
|
+
*/
|
|
118
|
+
// TODO(burdon): Tests.
|
|
119
|
+
export class ComputeGraph extends Resource {
|
|
120
|
+
public readonly id = `graph-${PublicKey.random().truncate()}`;
|
|
121
|
+
|
|
122
|
+
// Map of nodes indexed by sheet number.
|
|
123
|
+
private readonly _nodes = new Map<number, ComputeNode>();
|
|
124
|
+
|
|
125
|
+
// Cached function objects.
|
|
126
|
+
private _functions: FunctionType[] = [];
|
|
127
|
+
|
|
128
|
+
// The context is passed to all functions.
|
|
129
|
+
public readonly context = new FunctionContext(this._hf, this._space, this.refresh.bind(this), this._options);
|
|
130
|
+
|
|
131
|
+
// TODO(burdon): Typed events.
|
|
132
|
+
// TODO(burdon): Tie into HyperFormula dependency graph.
|
|
133
|
+
// TODO(burdon): Event propagation.
|
|
134
|
+
public readonly update = new Event();
|
|
135
|
+
|
|
136
|
+
constructor(
|
|
137
|
+
private readonly _hf: HyperFormula,
|
|
138
|
+
private readonly _space?: Space,
|
|
139
|
+
private readonly _options?: Partial<FunctionContextOptions>,
|
|
140
|
+
) {
|
|
141
|
+
super();
|
|
142
|
+
|
|
143
|
+
this._hf.updateConfig({ context: this.context });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// TODO(burdon): Remove.
|
|
147
|
+
get hf() {
|
|
148
|
+
return this._hf;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
refresh() {
|
|
152
|
+
log('refresh', { id: this.id });
|
|
153
|
+
this.update.emit();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getFunctions({ standard = true, echo = true }: { standard?: boolean; echo?: boolean } = {}): FunctionDefinition[] {
|
|
157
|
+
return [
|
|
158
|
+
...(standard
|
|
159
|
+
? this._hf
|
|
160
|
+
.getRegisteredFunctionNames()
|
|
161
|
+
.map((name) => defaultFunctions.find((fn) => fn.name === name) ?? { name })
|
|
162
|
+
: []),
|
|
163
|
+
...(echo ? this._functions.map((fn) => ({ name: fn.binding! })) : []),
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get or create cell representing a sheet.
|
|
169
|
+
*/
|
|
170
|
+
// TODO(burdon): Async (open node).
|
|
171
|
+
// The graph should be an extensible factory that plugins extend with model constructors.
|
|
172
|
+
// This would enable on-the-fly instantiation of new models when then are referenced.
|
|
173
|
+
// E.g., Cross-object reference would be stored as "ObjectId!A1"
|
|
174
|
+
// The graph would then load the object and create a ComputeNode (model) of the appropriate type.
|
|
175
|
+
getOrCreateNode(name: string): ComputeNode {
|
|
176
|
+
invariant(name.length);
|
|
177
|
+
if (!this._hf.doesSheetExist(name)) {
|
|
178
|
+
log.info('created node', { space: this._space?.id, name });
|
|
179
|
+
this._hf.addSheet(name);
|
|
180
|
+
this.update.emit();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const sheetId = this._hf.getSheetId(name);
|
|
184
|
+
invariant(sheetId !== undefined);
|
|
185
|
+
|
|
186
|
+
// TODO(burdon): Chain context?
|
|
187
|
+
const node = new ComputeNode(this, sheetId);
|
|
188
|
+
this._nodes.set(sheetId, node);
|
|
189
|
+
return node;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Map bound value to custom function invocation.
|
|
194
|
+
* E.g., "HELLO(...args)" => "EDGE("HELLO", ...args)".
|
|
195
|
+
*/
|
|
196
|
+
mapFormulaToNative(formula: string): string {
|
|
197
|
+
return (
|
|
198
|
+
formula
|
|
199
|
+
// Sheet references.
|
|
200
|
+
// https://hyperformula.handsontable.com/guide/cell-references.html#cell-references
|
|
201
|
+
.replace(/['"]?([ \w]+)['"]?!/, (_match, name) => {
|
|
202
|
+
if (name) {
|
|
203
|
+
// TODO(burdon): What if not loaded?
|
|
204
|
+
const objects = this._hf
|
|
205
|
+
.getSheetNames()
|
|
206
|
+
.map((name) => {
|
|
207
|
+
const id = getSheetId(name);
|
|
208
|
+
return id ? this._space?.db.getObjectById(id) : undefined;
|
|
209
|
+
})
|
|
210
|
+
.filter(nonNullable);
|
|
211
|
+
|
|
212
|
+
for (const obj of objects) {
|
|
213
|
+
if (obj.name === name || obj.title === name) {
|
|
214
|
+
return `${createSheetName(obj.id)}!`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return `${name}!`;
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Functions.
|
|
223
|
+
.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
|
|
224
|
+
const fn = this._functions.find((fn) => fn.binding === binding);
|
|
225
|
+
if (!fn) {
|
|
226
|
+
return match;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (args.trim() === '') {
|
|
230
|
+
return `${CUSTOM_FUNCTION}("${binding}")`;
|
|
231
|
+
} else {
|
|
232
|
+
return `${CUSTOM_FUNCTION}("${binding}", ${args})`;
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Map from binding to fully qualified ECHO ID (to store).
|
|
240
|
+
* E.g., HELLO() => spaceId:objectId()
|
|
241
|
+
*/
|
|
242
|
+
mapFunctionBindingToId(formula: string) {
|
|
243
|
+
return formula.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
|
|
244
|
+
if (binding === CUSTOM_FUNCTION || defaultFunctions.find((fn) => fn.name === binding)) {
|
|
245
|
+
return match;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const fn = this._functions.find((fn) => fn.binding === binding);
|
|
249
|
+
if (fn) {
|
|
250
|
+
const id = fullyQualifiedId(fn);
|
|
251
|
+
return `${id}(${args})`;
|
|
252
|
+
} else {
|
|
253
|
+
return match;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Map from fully qualified ECHO ID to binding (from store).
|
|
260
|
+
* E.g., spaceId:objectId() => HELLO()
|
|
261
|
+
*/
|
|
262
|
+
mapFunctionBindingFromId(formula: string) {
|
|
263
|
+
return formula.replace(/(\w+):([a-zA-Z0-9]+)\((.*)\)/g, (match, spaceId, objectId, args) => {
|
|
264
|
+
const id = `${spaceId}:${objectId}`;
|
|
265
|
+
if (id.length !== OBJECT_ID_LENGTH) {
|
|
266
|
+
return match;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const fn = this._functions.find((fn) => fullyQualifiedId(fn) === id);
|
|
270
|
+
if (fn?.binding) {
|
|
271
|
+
return `${fn.binding}(${args})`;
|
|
272
|
+
} else {
|
|
273
|
+
return match;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected override async _open() {
|
|
279
|
+
if (this._space) {
|
|
280
|
+
// Subscribe to remote function definitions.
|
|
281
|
+
const query = this._space.db.query(Filter.schema(FunctionType));
|
|
282
|
+
const unsubscribe = query.subscribe(({ objects }) => {
|
|
283
|
+
this._functions = objects.filter(({ binding }) => binding);
|
|
284
|
+
this.update.emit();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
this._ctx.onDispose(unsubscribe);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Event } from '@dxos/async';
|
|
6
|
+
import { Resource } from '@dxos/context';
|
|
7
|
+
|
|
8
|
+
import { DetailedCellError } from '#hyperformula';
|
|
9
|
+
import { type ComputeGraph } from './compute-graph';
|
|
10
|
+
import { type CellAddress } from '../defs';
|
|
11
|
+
import { type CellScalarValue } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Individual "sheet" (typically corresponds to an ECHO object).
|
|
15
|
+
*/
|
|
16
|
+
// TODO(burdon): Factor out common HF wrapper from from SheetModel.
|
|
17
|
+
export class ComputeNode extends Resource {
|
|
18
|
+
// TODO(burdon): Chaing events.
|
|
19
|
+
public readonly update = new Event();
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly _graph: ComputeGraph,
|
|
23
|
+
public readonly sheetId: number,
|
|
24
|
+
) {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// TODO(burdon): Remove?
|
|
29
|
+
get graph() {
|
|
30
|
+
return this._graph;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get hf() {
|
|
34
|
+
return this._graph.hf;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getValue(cell: CellAddress): CellScalarValue {
|
|
38
|
+
const value = this._graph.hf.getCellValue({ sheet: this.sheetId, row: cell.row, col: cell.col });
|
|
39
|
+
if (value instanceof DetailedCellError) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setValue(cell: CellAddress, value: CellScalarValue) {
|
|
47
|
+
const mappedValue =
|
|
48
|
+
typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value;
|
|
49
|
+
this._graph.hf.setCellContents({ sheet: this.sheetId, row: cell.row, col: cell.col }, [[mappedValue]]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -2,18 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { FunctionArgumentType } from 'hyperformula';
|
|
6
5
|
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
7
6
|
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
8
7
|
|
|
9
8
|
import { getDeep } from '@dxos/util';
|
|
10
9
|
|
|
10
|
+
import { FunctionArgumentType } from '#hyperformula';
|
|
11
11
|
import { type AsyncFunction, FunctionPluginAsync } from './async-function';
|
|
12
|
-
|
|
13
|
-
// TODO(burdon): Factor out.
|
|
14
|
-
const parseNumberString = (str: string): number => {
|
|
15
|
-
return parseFloat(str.replace(/[^\d.]/g, ''));
|
|
16
|
-
};
|
|
12
|
+
import { parseNumberString } from './util';
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
15
|
* https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
|