@dxos/plugin-sheet 0.6.8-main.046e6cf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +14 -0
- package/dist/lib/browser/SheetContainer-H22IDJ43.mjs +3740 -0
- package/dist/lib/browser/SheetContainer-H22IDJ43.mjs.map +7 -0
- package/dist/lib/browser/chunk-6VPEAUG6.mjs +82 -0
- package/dist/lib/browser/chunk-6VPEAUG6.mjs.map +7 -0
- package/dist/lib/browser/chunk-AT2FJXQX.mjs +861 -0
- package/dist/lib/browser/chunk-AT2FJXQX.mjs.map +7 -0
- package/dist/lib/browser/chunk-JRL5LGCE.mjs +18 -0
- package/dist/lib/browser/chunk-JRL5LGCE.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +213 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/meta.mjs +9 -0
- package/dist/lib/browser/meta.mjs.map +7 -0
- package/dist/lib/browser/types.mjs +22 -0
- package/dist/lib/browser/types.mjs.map +7 -0
- package/dist/lib/node/SheetContainer-S32KTNZ6.cjs +3731 -0
- package/dist/lib/node/SheetContainer-S32KTNZ6.cjs.map +7 -0
- package/dist/lib/node/chunk-4CE6FK5Z.cjs +108 -0
- package/dist/lib/node/chunk-4CE6FK5Z.cjs.map +7 -0
- package/dist/lib/node/chunk-BJ6ZD7MN.cjs +51 -0
- package/dist/lib/node/chunk-BJ6ZD7MN.cjs.map +7 -0
- package/dist/lib/node/chunk-FCKJ4QRM.cjs +881 -0
- package/dist/lib/node/chunk-FCKJ4QRM.cjs.map +7 -0
- package/dist/lib/node/index.cjs +226 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.cjs +30 -0
- package/dist/lib/node/meta.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/lib/node/types.cjs +44 -0
- package/dist/lib/node/types.cjs.map +7 -0
- package/dist/types/src/SheetPlugin.d.ts +4 -0
- package/dist/types/src/SheetPlugin.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/CellEditor.d.ts +14 -0
- package/dist/types/src/components/CellEditor/CellEditor.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts +29 -0
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/extension.d.ts +18 -0
- package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/extension.test.d.ts +2 -0
- package/dist/types/src/components/CellEditor/extension.test.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/functions.d.ts +66 -0
- package/dist/types/src/components/CellEditor/functions.d.ts.map +1 -0
- package/dist/types/src/components/CellEditor/index.d.ts +3 -0
- package/dist/types/src/components/CellEditor/index.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/async-function.d.ts +52 -0
- package/dist/types/src/components/ComputeGraph/async-function.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/custom.d.ts +21 -0
- package/dist/types/src/components/ComputeGraph/custom.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/edge-function.d.ts +20 -0
- package/dist/types/src/components/ComputeGraph/edge-function.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/graph-context.d.ts +11 -0
- package/dist/types/src/components/ComputeGraph/graph-context.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts +2 -0
- package/dist/types/src/components/ComputeGraph/graph.browser.test.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/graph.d.ts +21 -0
- package/dist/types/src/components/ComputeGraph/graph.d.ts.map +1 -0
- package/dist/types/src/components/ComputeGraph/index.d.ts +4 -0
- package/dist/types/src/components/ComputeGraph/index.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/Sheet.d.ts +55 -0
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts +54 -0
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/formatting.d.ts +14 -0
- package/dist/types/src/components/Sheet/formatting.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/grid.d.ts +52 -0
- package/dist/types/src/components/Sheet/grid.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/index.d.ts +2 -0
- package/dist/types/src/components/Sheet/index.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/nav.d.ts +29 -0
- package/dist/types/src/components/Sheet/nav.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/sheet-context.d.ts +24 -0
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -0
- package/dist/types/src/components/Sheet/util.d.ts +18 -0
- package/dist/types/src/components/Sheet/util.d.ts.map +1 -0
- package/dist/types/src/components/SheetContainer.d.ts +9 -0
- package/dist/types/src/components/SheetContainer.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +21 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +35 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/common.d.ts +20 -0
- package/dist/types/src/components/Toolbar/common.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/index.d.ts +2 -0
- package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +7 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +15 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/model/index.d.ts +3 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/model.browser.test.d.ts +2 -0
- package/dist/types/src/model/model.browser.test.d.ts.map +1 -0
- package/dist/types/src/model/model.d.ts +142 -0
- package/dist/types/src/model/model.d.ts.map +1 -0
- package/dist/types/src/model/types.d.ts +17 -0
- package/dist/types/src/model/types.d.ts.map +1 -0
- package/dist/types/src/model/types.test.d.ts +2 -0
- package/dist/types/src/model/types.test.d.ts.map +1 -0
- package/dist/types/src/model/util.d.ts +15 -0
- package/dist/types/src/model/util.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +16 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +94 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/package.json +122 -0
- package/src/SheetPlugin.tsx +150 -0
- package/src/components/CellEditor/CellEditor.stories.tsx +88 -0
- package/src/components/CellEditor/CellEditor.tsx +113 -0
- package/src/components/CellEditor/extension.test.ts +42 -0
- package/src/components/CellEditor/extension.ts +286 -0
- package/src/components/CellEditor/functions.ts +2017 -0
- package/src/components/CellEditor/index.ts +6 -0
- package/src/components/ComputeGraph/async-function.ts +148 -0
- package/src/components/ComputeGraph/custom.ts +70 -0
- package/src/components/ComputeGraph/edge-function.ts +60 -0
- package/src/components/ComputeGraph/graph-context.tsx +37 -0
- package/src/components/ComputeGraph/graph.browser.test.ts +49 -0
- package/src/components/ComputeGraph/graph.ts +52 -0
- package/src/components/ComputeGraph/index.ts +7 -0
- package/src/components/Sheet/Sheet.stories.tsx +329 -0
- package/src/components/Sheet/Sheet.tsx +1164 -0
- package/src/components/Sheet/formatting.ts +106 -0
- package/src/components/Sheet/grid.ts +191 -0
- package/src/components/Sheet/index.ts +5 -0
- package/src/components/Sheet/nav.ts +157 -0
- package/src/components/Sheet/sheet-context.tsx +101 -0
- package/src/components/Sheet/util.ts +56 -0
- package/src/components/SheetContainer.tsx +30 -0
- package/src/components/Toolbar/Toolbar.stories.tsx +36 -0
- package/src/components/Toolbar/Toolbar.tsx +198 -0
- package/src/components/Toolbar/common.tsx +72 -0
- package/src/components/Toolbar/index.ts +5 -0
- package/src/components/index.ts +10 -0
- package/src/index.ts +9 -0
- package/src/meta.tsx +18 -0
- package/src/model/index.ts +6 -0
- package/src/model/model.browser.test.ts +100 -0
- package/src/model/model.ts +480 -0
- package/src/model/types.test.ts +92 -0
- package/src/model/types.ts +71 -0
- package/src/model/util.ts +36 -0
- package/src/translations.ts +22 -0
- package/src/types.ts +110 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { CellError, ErrorType, EmptyValue, FunctionPlugin, type HyperFormula } from 'hyperformula';
|
|
6
|
+
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
7
|
+
import { type InterpreterValue } from 'hyperformula/typings/interpreter/InterpreterValue';
|
|
8
|
+
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
9
|
+
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import { type Space } from '@dxos/client/echo';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
|
|
14
|
+
// TODO(burdon): API gateways!
|
|
15
|
+
// https://publicapis.io
|
|
16
|
+
// https://api-ninjas.com/api/cryptoprice
|
|
17
|
+
// https://developers.google.com/apis-explorer
|
|
18
|
+
// https://publicapis.io/coin-desk-api
|
|
19
|
+
|
|
20
|
+
// TODO(burdon): Create wrapper.
|
|
21
|
+
export type AsyncFunction = (...args: any) => Promise<InterpreterValue>;
|
|
22
|
+
|
|
23
|
+
export type FunctionOptions = {
|
|
24
|
+
ttl?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type FunctionContextOptions = {
|
|
28
|
+
defaultTtl: number;
|
|
29
|
+
recalculationDelay: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const defaultFunctionContextOptions: FunctionContextOptions = {
|
|
33
|
+
defaultTtl: 5_000,
|
|
34
|
+
recalculationDelay: 200,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The context singleton for the model is passed into custom functions.
|
|
39
|
+
*
|
|
40
|
+
* HyperFormula does not support async functions.
|
|
41
|
+
* - https://hyperformula.handsontable.com/guide/custom-functions.html
|
|
42
|
+
* - https://hyperformula.handsontable.com/guide/known-limitations.html#known-limitations
|
|
43
|
+
* - https://github.com/handsontable/hyperformula/issues/892
|
|
44
|
+
*/
|
|
45
|
+
export class FunctionContext {
|
|
46
|
+
// Mangle name with params.
|
|
47
|
+
static createInvocationKey(name: string, ...args: any) {
|
|
48
|
+
return JSON.stringify({ name, ...args });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Cached values for cell.
|
|
52
|
+
private readonly _cache = new Map<string, { value: InterpreterValue; ts: number }>();
|
|
53
|
+
|
|
54
|
+
// Active requests.
|
|
55
|
+
private readonly _pending = new Map<string, number>();
|
|
56
|
+
|
|
57
|
+
// Invocation count.
|
|
58
|
+
private _invocations: Record<string, number> = {};
|
|
59
|
+
|
|
60
|
+
private readonly _onUpdate: () => void;
|
|
61
|
+
|
|
62
|
+
constructor(
|
|
63
|
+
private readonly _hf: HyperFormula,
|
|
64
|
+
private readonly _space: Space | undefined,
|
|
65
|
+
onUpdate: (context: FunctionContext) => void,
|
|
66
|
+
private readonly _options = defaultFunctionContextOptions,
|
|
67
|
+
) {
|
|
68
|
+
this._onUpdate = debounce(() => {
|
|
69
|
+
// TODO(burdon): Better way to trigger recalculation?
|
|
70
|
+
// NOTE: rebuildAndRecalculate resets the undo history.
|
|
71
|
+
this._hf.resumeEvaluation();
|
|
72
|
+
onUpdate(this);
|
|
73
|
+
}, this._options.recalculationDelay);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get space() {
|
|
77
|
+
return this._space;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get info() {
|
|
81
|
+
return { cache: this._cache.size, invocations: this._invocations };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
flush() {
|
|
85
|
+
this._cache.clear();
|
|
86
|
+
this._invocations = {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Exec the function if TTL has expired.
|
|
91
|
+
* Return the cached value.
|
|
92
|
+
*/
|
|
93
|
+
invokeFunction(
|
|
94
|
+
name: string,
|
|
95
|
+
state: InterpreterState,
|
|
96
|
+
args: any[],
|
|
97
|
+
cb: AsyncFunction,
|
|
98
|
+
options?: FunctionOptions,
|
|
99
|
+
): InterpreterValue | undefined {
|
|
100
|
+
const ttl = options?.ttl ?? this._options.defaultTtl;
|
|
101
|
+
|
|
102
|
+
const { formulaAddress: cell } = state;
|
|
103
|
+
const invocationKey = FunctionContext.createInvocationKey(name, ...args);
|
|
104
|
+
const { value = undefined, ts = 0 } = this._cache.get(invocationKey) ?? {};
|
|
105
|
+
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const delta = now - ts;
|
|
108
|
+
if ((!ts || delta > ttl) && !this._pending.has(invocationKey)) {
|
|
109
|
+
this._pending.set(invocationKey, now);
|
|
110
|
+
setTimeout(async () => {
|
|
111
|
+
this._invocations[name] = (this._invocations[name] ?? 0) + 1;
|
|
112
|
+
try {
|
|
113
|
+
// Exec function.
|
|
114
|
+
const value = await cb(...args);
|
|
115
|
+
this._cache.set(invocationKey, { value, ts: Date.now() });
|
|
116
|
+
log('set', { cell, value });
|
|
117
|
+
this._onUpdate();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// TODO(burdon): Show error to user.
|
|
120
|
+
log.warn('failed', { cell, err });
|
|
121
|
+
this._cache.set(invocationKey, { value: new CellError(ErrorType.ERROR, 'Function failed.'), ts: Date.now() });
|
|
122
|
+
} finally {
|
|
123
|
+
this._pending.delete(invocationKey);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
log('invoke', { cell, name, args, cache: value });
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Base class for async functions.
|
|
135
|
+
*/
|
|
136
|
+
export class FunctionPluginAsync extends FunctionPlugin {
|
|
137
|
+
get context() {
|
|
138
|
+
return this.config.context as FunctionContext;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
runAsyncFunction(ast: ProcedureAst, state: InterpreterState, cb: AsyncFunction, options?: FunctionOptions) {
|
|
142
|
+
const { procedureName } = ast;
|
|
143
|
+
const metadata = this.metadata(procedureName);
|
|
144
|
+
return this.runFunction(ast.args, state, metadata, (...args: any) => {
|
|
145
|
+
return this.context.invokeFunction(procedureName, state, args, cb, options) ?? EmptyValue;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { FunctionArgumentType } from 'hyperformula';
|
|
6
|
+
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
7
|
+
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
8
|
+
|
|
9
|
+
import { getDeep } from '@dxos/util';
|
|
10
|
+
|
|
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
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
|
|
20
|
+
*/
|
|
21
|
+
export class CustomPlugin extends FunctionPluginAsync {
|
|
22
|
+
test(ast: ProcedureAst, state: InterpreterState) {
|
|
23
|
+
const handler: AsyncFunction = async () => {
|
|
24
|
+
return Math.random();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return this.runAsyncFunction(ast, state, handler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
crypto(ast: ProcedureAst, state: InterpreterState) {
|
|
31
|
+
const handler: AsyncFunction = async (_currency) => {
|
|
32
|
+
const currency = (_currency || 'USD').toUpperCase();
|
|
33
|
+
const result = await fetch(`https://api.coindesk.com/v1/bpi/currentprice/${currency}.json`);
|
|
34
|
+
const data = await result.json();
|
|
35
|
+
const rate = getDeep<string>(data, ['bpi', currency, 'rate']);
|
|
36
|
+
if (!rate) {
|
|
37
|
+
return NaN;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return parseNumberString(rate);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return this.runAsyncFunction(ast, state, handler, { ttl: 10_000 });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
CustomPlugin.implementedFunctions = {
|
|
48
|
+
TEST: {
|
|
49
|
+
method: 'test',
|
|
50
|
+
parameters: [],
|
|
51
|
+
isVolatile: true,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
CRYPTO: {
|
|
55
|
+
method: 'crypto',
|
|
56
|
+
parameters: [{ argumentType: FunctionArgumentType.STRING, optionalArg: true }],
|
|
57
|
+
isVolatile: true,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const CustomPluginTranslations = {
|
|
62
|
+
enGB: {
|
|
63
|
+
TEST: 'TEST',
|
|
64
|
+
CRYPTO: 'CRYPTO',
|
|
65
|
+
},
|
|
66
|
+
enUS: {
|
|
67
|
+
TEST: 'TEST',
|
|
68
|
+
CRYPTO: 'CRYPTO',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { CellError, ErrorType, FunctionArgumentType } from 'hyperformula';
|
|
6
|
+
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
7
|
+
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
8
|
+
|
|
9
|
+
import { Filter, getMeta } from '@dxos/client/echo';
|
|
10
|
+
import { getUserFunctionUrlInMetadata } from '@dxos/plugin-script/edge';
|
|
11
|
+
import { ScriptType } from '@dxos/plugin-script/types';
|
|
12
|
+
|
|
13
|
+
import { type AsyncFunction, FunctionPluginAsync } from './async-function';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A hyperformula function plugin for calling EDGE functions.
|
|
17
|
+
*
|
|
18
|
+
* https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
|
|
19
|
+
*/
|
|
20
|
+
export class EdgeFunctionPlugin extends FunctionPluginAsync {
|
|
21
|
+
edge(ast: ProcedureAst, state: InterpreterState) {
|
|
22
|
+
const handler: AsyncFunction = async (binding: string) => {
|
|
23
|
+
const space = this.context.space;
|
|
24
|
+
if (!space) {
|
|
25
|
+
return new CellError(ErrorType.VALUE, 'No space');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
objects: [script],
|
|
30
|
+
} = await space.db.query(Filter.schema(ScriptType, { binding })).run();
|
|
31
|
+
if (!script) {
|
|
32
|
+
return new CellError(ErrorType.VALUE, 'No script');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const path = getUserFunctionUrlInMetadata(getMeta(script));
|
|
36
|
+
// TODO(wittjosiah): Get base url from client config.
|
|
37
|
+
const result = await fetch(`https://functions-staging.dxos.workers.dev${path}`, { method: 'POST' });
|
|
38
|
+
return await result.text();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return this.runAsyncFunction(ast, state, handler, { ttl: 10_000 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
EdgeFunctionPlugin.implementedFunctions = {
|
|
46
|
+
EDGE: {
|
|
47
|
+
method: 'edge',
|
|
48
|
+
parameters: [{ argumentType: FunctionArgumentType.STRING }],
|
|
49
|
+
isVolatile: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const EdgeFunctionPluginTranslations = {
|
|
54
|
+
enGB: {
|
|
55
|
+
EDGE: 'EDGE',
|
|
56
|
+
},
|
|
57
|
+
enUS: {
|
|
58
|
+
EDGE: 'EDGE',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { createContext, type PropsWithChildren, useContext, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
8
|
+
|
|
9
|
+
import { createComputeGraph, type ComputeGraph } from './graph';
|
|
10
|
+
|
|
11
|
+
export type ComputeGraphContextType = {
|
|
12
|
+
graphs: Record<string, ComputeGraph>;
|
|
13
|
+
setGraph: (key: string, graph: ComputeGraph) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ComputeGraphContext = createContext<ComputeGraphContextType>({ graphs: {}, setGraph: () => {} });
|
|
17
|
+
|
|
18
|
+
export const ComputeGraphContextProvider = ({
|
|
19
|
+
children,
|
|
20
|
+
graphs,
|
|
21
|
+
setGraph,
|
|
22
|
+
}: PropsWithChildren<ComputeGraphContextType>) => {
|
|
23
|
+
return <ComputeGraphContext.Provider value={{ graphs, setGraph }}>{children}</ComputeGraphContext.Provider>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const useComputeGraph = (space: Space): ComputeGraph => {
|
|
27
|
+
const { graphs, setGraph } = useContext(ComputeGraphContext);
|
|
28
|
+
const graph = graphs[space.id] ?? createComputeGraph(space);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!graphs[space.id]) {
|
|
32
|
+
setGraph(space.id, graph);
|
|
33
|
+
}
|
|
34
|
+
}, [space]);
|
|
35
|
+
|
|
36
|
+
return graph;
|
|
37
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Trigger } from '@dxos/async';
|
|
9
|
+
|
|
10
|
+
import { createComputeGraph } from './graph';
|
|
11
|
+
import { addressFromA1Notation, SheetModel } from '../../model';
|
|
12
|
+
import { type CellScalarValue, createSheet } from '../../types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* VITEST_ENV=chrome p vitest --watch
|
|
16
|
+
* NOTE: Browser test required for hyperformula due to raw translation files.
|
|
17
|
+
*/
|
|
18
|
+
describe('compute graph', () => {
|
|
19
|
+
const createModel = async () => {
|
|
20
|
+
const graph = createComputeGraph();
|
|
21
|
+
const sheet = createSheet();
|
|
22
|
+
const model = new SheetModel(graph, sheet, { rows: 5, columns: 5 });
|
|
23
|
+
graph.update.on(() => model.update.emit());
|
|
24
|
+
return { graph, model };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
test('async function', async () => {
|
|
28
|
+
const { graph, model } = await createModel();
|
|
29
|
+
|
|
30
|
+
// Triggers function.
|
|
31
|
+
model.setValue(addressFromA1Notation('A1'), '=TEST()');
|
|
32
|
+
|
|
33
|
+
const trigger = new Trigger<CellScalarValue>();
|
|
34
|
+
model.update.on(() => {
|
|
35
|
+
const value = model.getValue(addressFromA1Notation('A1'));
|
|
36
|
+
trigger.wake(value);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Get initial value (i.e., null).
|
|
40
|
+
const v1 = model.getValue(addressFromA1Notation('A1'));
|
|
41
|
+
expect(v1).to.be.null;
|
|
42
|
+
expect(graph.context.info.invocations.TEST).to.eq(undefined);
|
|
43
|
+
|
|
44
|
+
// Wait until async update triggered.
|
|
45
|
+
const v2 = await trigger.wait();
|
|
46
|
+
expect(v2).not.to.be.null;
|
|
47
|
+
expect(graph.context.info.invocations.TEST).to.eq(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { HyperFormula } from 'hyperformula';
|
|
6
|
+
|
|
7
|
+
import { Event } from '@dxos/async';
|
|
8
|
+
import { type Space } from '@dxos/client/echo';
|
|
9
|
+
import { PublicKey } from '@dxos/keys';
|
|
10
|
+
import { log } from '@dxos/log';
|
|
11
|
+
|
|
12
|
+
import { FunctionContext } from './async-function';
|
|
13
|
+
import { CustomPlugin, CustomPluginTranslations } from './custom';
|
|
14
|
+
import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './edge-function';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create root graph for space.
|
|
18
|
+
*/
|
|
19
|
+
export const createComputeGraph = (space?: Space): ComputeGraph => {
|
|
20
|
+
// TODO(burdon): Configure.
|
|
21
|
+
HyperFormula.registerFunctionPlugin(CustomPlugin, CustomPluginTranslations);
|
|
22
|
+
HyperFormula.registerFunctionPlugin(EdgeFunctionPlugin, EdgeFunctionPluginTranslations);
|
|
23
|
+
|
|
24
|
+
const hf = HyperFormula.buildEmpty({ licenseKey: 'gpl-v3' });
|
|
25
|
+
return new ComputeGraph(hf, space);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Per-space compute and dependency graph.
|
|
30
|
+
*/
|
|
31
|
+
// TODO(burdon): Create instance for each space.
|
|
32
|
+
export class ComputeGraph {
|
|
33
|
+
public readonly id = `graph-${PublicKey.random().truncate()}`;
|
|
34
|
+
public readonly update = new Event();
|
|
35
|
+
|
|
36
|
+
// The context is passed to all functions.
|
|
37
|
+
public readonly context = new FunctionContext(this.hf, this._space, () => {
|
|
38
|
+
this.refresh();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
public readonly hf: HyperFormula,
|
|
43
|
+
private readonly _space?: Space,
|
|
44
|
+
) {
|
|
45
|
+
this.hf.updateConfig({ context: this.context });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
refresh() {
|
|
49
|
+
log('refresh', { id: this.id });
|
|
50
|
+
this.update.emit();
|
|
51
|
+
}
|
|
52
|
+
}
|