@dxos/plugin-sheet 0.6.12-main.ed7cda7 → 0.6.12-main.f9d0246

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.
Files changed (119) hide show
  1. package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs → SheetContainer-VISF3VUB.mjs} +6 -6
  2. package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs.map → SheetContainer-VISF3VUB.mjs.map} +3 -3
  3. package/dist/lib/browser/{chunk-T3NJFTD4.mjs → chunk-WZMOZKQZ.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-WZMOZKQZ.mjs.map} +3 -3
  5. package/dist/lib/browser/{chunk-6ZMQVB4Z.mjs → chunk-Z2XOOC2R.mjs} +81 -62
  6. package/dist/lib/browser/chunk-Z2XOOC2R.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-ZLJ2GRE2.mjs} +173 -42
  8. package/dist/lib/browser/chunk-ZLJ2GRE2.mjs.map +7 -0
  9. package/dist/lib/browser/{graph-T27BOBOV.mjs → graph-4XFKIHRL.mjs} +4 -4
  10. package/dist/lib/browser/index.mjs +15 -13
  11. package/dist/lib/browser/index.mjs.map +3 -3
  12. package/dist/lib/browser/meta.json +1 -1
  13. package/dist/lib/browser/types.mjs +1 -1
  14. package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs → SheetContainer-2MEALQWW.cjs} +14 -14
  15. package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs.map → SheetContainer-2MEALQWW.cjs.map} +3 -3
  16. package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-6DQABRGJ.cjs} +192 -60
  17. package/dist/lib/node/chunk-6DQABRGJ.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-Q3HBHPRL.cjs → chunk-AOP42UAA.cjs} +5 -5
  19. package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-AOP42UAA.cjs.map} +3 -3
  20. package/dist/lib/node/{chunk-DD6FIXWC.cjs → chunk-P5QYYEHQ.cjs} +86 -67
  21. package/dist/lib/node/chunk-P5QYYEHQ.cjs.map +7 -0
  22. package/dist/lib/node/{graph-SPKGX7W4.cjs → graph-2LRDUXBZ.cjs} +14 -14
  23. package/dist/lib/node/graph-2LRDUXBZ.cjs.map +7 -0
  24. package/dist/lib/node/index.cjs +25 -24
  25. package/dist/lib/node/index.cjs.map +3 -3
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types.cjs +8 -8
  28. package/dist/lib/node/types.cjs.map +1 -1
  29. package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs → SheetContainer-RPSUSXWS.mjs} +6 -6
  30. package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs.map → SheetContainer-RPSUSXWS.mjs.map} +3 -3
  31. package/dist/lib/node-esm/{chunk-D6KU5MI7.mjs → chunk-4MM7THJW.mjs} +81 -62
  32. package/dist/lib/node-esm/chunk-4MM7THJW.mjs.map +7 -0
  33. package/dist/lib/node-esm/{chunk-7HVSOTGA.mjs → chunk-5RLTCIE2.mjs} +173 -42
  34. package/dist/lib/node-esm/chunk-5RLTCIE2.mjs.map +7 -0
  35. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-RR2AO4SM.mjs} +2 -2
  36. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-RR2AO4SM.mjs.map} +3 -3
  37. package/dist/lib/node-esm/{graph-U67IO4UC.mjs → graph-WG5EKOMO.mjs} +4 -4
  38. package/dist/lib/node-esm/index.mjs +15 -13
  39. package/dist/lib/node-esm/index.mjs.map +3 -3
  40. package/dist/lib/node-esm/meta.json +1 -1
  41. package/dist/lib/node-esm/types.mjs +1 -1
  42. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/GridSheet/GridSheet.d.ts +3 -3
  44. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  45. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
  46. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  48. package/dist/types/src/components/Sheet/sheet-context.d.ts +3 -3
  49. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
  50. package/dist/types/src/components/SheetContainer.d.ts +1 -1
  51. package/dist/types/src/components/index.d.ts +1 -1
  52. package/dist/types/src/defs/types.d.ts.map +1 -1
  53. package/dist/types/src/defs/util.d.ts +1 -1
  54. package/dist/types/src/defs/util.d.ts.map +1 -1
  55. package/dist/types/src/extensions/compute.d.ts +5 -1
  56. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  57. package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
  58. package/dist/types/src/graph/async-function.d.ts +7 -1
  59. package/dist/types/src/graph/async-function.d.ts.map +1 -1
  60. package/dist/types/src/graph/compute-graph.d.ts +12 -9
  61. package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
  62. package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -1
  63. package/dist/types/src/graph/compute-graph.test.d.ts +2 -0
  64. package/dist/types/src/graph/compute-graph.test.d.ts.map +1 -0
  65. package/dist/types/src/graph/compute-node.d.ts +9 -2
  66. package/dist/types/src/graph/compute-node.d.ts.map +1 -1
  67. package/dist/types/src/graph/edge-function.d.ts.map +1 -1
  68. package/dist/types/src/graph/{custom-function.d.ts → testing/custom-function.d.ts} +3 -1
  69. package/dist/types/src/graph/testing/custom-function.d.ts.map +1 -0
  70. package/dist/types/src/graph/testing/index.d.ts +2 -0
  71. package/dist/types/src/graph/testing/index.d.ts.map +1 -0
  72. package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useSheetModel.d.ts +2 -2
  74. package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -1
  75. package/dist/types/src/model/sheet-model.d.ts +3 -3
  76. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  77. package/dist/types/src/testing/testing.d.ts +4 -5
  78. package/dist/types/src/testing/testing.d.ts.map +1 -1
  79. package/dist/types/src/types.d.ts +4 -3
  80. package/dist/types/src/types.d.ts.map +1 -1
  81. package/package.json +33 -33
  82. package/src/SheetPlugin.tsx +9 -7
  83. package/src/components/CellEditor/CellEditor.stories.tsx +1 -1
  84. package/src/components/GridSheet/GridSheet.stories.tsx +5 -4
  85. package/src/components/GridSheet/GridSheet.tsx +4 -4
  86. package/src/components/Sheet/Sheet.stories.tsx +21 -20
  87. package/src/components/Sheet/sheet-context.tsx +4 -4
  88. package/src/components/SheetContainer.tsx +2 -2
  89. package/src/defs/types.ts +1 -0
  90. package/src/defs/util.ts +19 -3
  91. package/src/extensions/compute.stories.tsx +18 -16
  92. package/src/extensions/compute.ts +72 -39
  93. package/src/graph/async-function.ts +13 -6
  94. package/src/graph/compute-graph.stories.tsx +4 -3
  95. package/src/graph/compute-graph.test.ts +127 -0
  96. package/src/graph/compute-graph.ts +64 -41
  97. package/src/graph/compute-node.ts +16 -5
  98. package/src/graph/edge-function.ts +1 -2
  99. package/src/graph/{custom-function.ts → testing/custom-function.ts} +10 -2
  100. package/src/graph/testing/index.ts +5 -0
  101. package/src/hooks/hooks.stories.tsx +3 -3
  102. package/src/hooks/useComputeGraph.ts +2 -1
  103. package/src/hooks/useSheetModel.ts +4 -7
  104. package/src/model/sheet-model.ts +44 -29
  105. package/src/testing/testing.tsx +17 -15
  106. package/src/types.ts +3 -3
  107. package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +0 -7
  108. package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +0 -7
  109. package/dist/lib/node/chunk-DD6FIXWC.cjs.map +0 -7
  110. package/dist/lib/node/chunk-OTTD7FBK.cjs.map +0 -7
  111. package/dist/lib/node/graph-SPKGX7W4.cjs.map +0 -7
  112. package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +0 -7
  113. package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +0 -7
  114. package/dist/types/src/graph/compute-graph.browser.test.d.ts +0 -2
  115. package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +0 -1
  116. package/dist/types/src/graph/custom-function.d.ts.map +0 -1
  117. package/src/graph/compute-graph.browser.test.ts +0 -104
  118. /package/dist/lib/browser/{graph-T27BOBOV.mjs.map → graph-4XFKIHRL.mjs.map} +0 -0
  119. /package/dist/lib/node-esm/{graph-U67IO4UC.mjs.map → graph-WG5EKOMO.mjs.map} +0 -0
@@ -4,7 +4,9 @@
4
4
 
5
5
  import { type FunctionPluginDefinition } from 'hyperformula';
6
6
  import { type ConfigParams } from 'hyperformula/typings/ConfigParams';
7
+ import { type Listeners } from 'hyperformula/typings/Emitter';
7
8
  import { type FunctionTranslationsPackage } from 'hyperformula/typings/interpreter';
9
+ import defaultsDeep from 'lodash.defaultsdeep';
8
10
 
9
11
  import { Event } from '@dxos/async';
10
12
  import { type SpaceId, type Space, Filter, fullyQualifiedId } from '@dxos/client/echo';
@@ -15,7 +17,7 @@ import { log } from '@dxos/log';
15
17
  import { FunctionType } from '@dxos/plugin-script/types';
16
18
  import { nonNullable } from '@dxos/util';
17
19
 
18
- import { HyperFormula } from '#hyperformula';
20
+ import { ExportedCellChange, HyperFormula } from '#hyperformula';
19
21
  import { FunctionContext, type FunctionContextOptions } from './async-function';
20
22
  import { ComputeNode } from './compute-node';
21
23
  import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './edge-function';
@@ -43,14 +45,15 @@ export type ComputeGraphOptions = {
43
45
 
44
46
  export const defaultOptions: ComputeGraphOptions = {
45
47
  licenseKey: 'gpl-v3',
46
- plugins: [
47
- {
48
- plugin: EdgeFunctionPlugin,
49
- translations: EdgeFunctionPluginTranslations,
50
- },
51
- ],
52
48
  };
53
49
 
50
+ export const defaultPlugins: ComputeGraphPlugin[] = [
51
+ {
52
+ plugin: EdgeFunctionPlugin,
53
+ translations: EdgeFunctionPluginTranslations,
54
+ },
55
+ ];
56
+
54
57
  /**
55
58
  * Marker for sheets that are managed by an ECHO object.
56
59
  */
@@ -59,34 +62,29 @@ export const createSheetName = (id: string) => `${PREFIX}${id}`;
59
62
  export const getSheetId = (name: string): string | undefined =>
60
63
  name.startsWith(PREFIX) ? name.slice(PREFIX.length) : undefined;
61
64
 
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
65
  /**
73
66
  * Manages a collection of ComputeGraph instances for each space.
74
67
  *
75
68
  * [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
69
+ *
70
+ * NOTE: The ComputeGraphRegistry manages the hierarchy of resources via its root Context.
76
71
  */
77
72
  // TODO(burdon): Move graph into separate plugin; isolate HF deps.
78
73
  export class ComputeGraphRegistry extends Resource {
79
- private readonly _registry = new Map<SpaceId, ComputeGraph>();
74
+ private readonly _graphs = new Map<SpaceId, ComputeGraph>();
80
75
 
81
- constructor(private readonly _options: ComputeGraphOptions = defaultOptions) {
76
+ private readonly _options: ComputeGraphOptions;
77
+
78
+ constructor(options: ComputeGraphOptions = { plugins: defaultPlugins }) {
82
79
  super();
80
+ this._options = defaultsDeep({}, options, defaultOptions);
83
81
  this._options.plugins?.forEach(({ plugin, translations }) => {
84
82
  HyperFormula.registerFunctionPlugin(plugin, translations);
85
83
  });
86
84
  }
87
85
 
88
86
  getGraph(spaceId: SpaceId): ComputeGraph | undefined {
89
- return this._registry.get(spaceId);
87
+ return this._graphs.get(spaceId);
90
88
  }
91
89
 
92
90
  async getOrCreateGraph(space: Space): Promise<ComputeGraph> {
@@ -100,22 +98,29 @@ export class ComputeGraphRegistry extends Resource {
100
98
  }
101
99
 
102
100
  async createGraph(space: Space): Promise<ComputeGraph> {
103
- invariant(!this._registry.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
101
+ invariant(!this._graphs.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
104
102
  const hf = HyperFormula.buildEmpty(this._options);
105
103
  const graph = new ComputeGraph(hf, space, this._options);
106
- await graph.open(this._ctx);
107
- this._registry.set(space.id, graph);
104
+ this._graphs.set(space.id, graph);
105
+ await graph.open();
108
106
  return graph;
109
107
  }
108
+
109
+ protected override async _close() {
110
+ for (const graph of this._graphs.values()) {
111
+ await graph.close();
112
+ }
113
+ }
110
114
  }
111
115
 
116
+ export type ComputeGraphEvent = 'functionsUpdated';
117
+
112
118
  /**
113
119
  * Per-space compute and dependency graph.
114
120
  * Consists of multiple ComputeNode (corresponding to a HyperFormula sheet).
115
121
  * Manages the set of custom functions.
116
122
  * HyperFormula manages the dependency graph.
117
123
  */
118
- // TODO(burdon): Tests.
119
124
  export class ComputeGraph extends Resource {
120
125
  public readonly id = `graph-${PublicKey.random().truncate()}`;
121
126
 
@@ -125,13 +130,10 @@ export class ComputeGraph extends Resource {
125
130
  // Cached function objects.
126
131
  private _functions: FunctionType[] = [];
127
132
 
128
- // The context is passed to all functions.
129
- public readonly context = new FunctionContext(this._hf, this._space, this.refresh.bind(this), this._options);
133
+ public readonly update = new Event<{ type: ComputeGraphEvent }>();
130
134
 
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
+ // The context is passed to all functions.
136
+ public readonly context = new FunctionContext(this._hf, this._space, this._options);
135
137
 
136
138
  constructor(
137
139
  private readonly _hf: HyperFormula,
@@ -139,21 +141,36 @@ export class ComputeGraph extends Resource {
139
141
  private readonly _options?: Partial<FunctionContextOptions>,
140
142
  ) {
141
143
  super();
142
-
143
144
  this._hf.updateConfig({ context: this.context });
145
+ // TODO(burdon): If debounce then aggregate changes.
146
+ const onValuesUpdate: Listeners['valuesUpdated'] = (changes) => {
147
+ for (const change of changes) {
148
+ if (change instanceof ExportedCellChange) {
149
+ const { sheet } = change;
150
+ const node = this._nodes.get(sheet);
151
+ if (node) {
152
+ node.update.emit({ type: 'valuesUpdated', change });
153
+ }
154
+ }
155
+ }
156
+ };
157
+
158
+ this._hf.on('valuesUpdated', onValuesUpdate);
159
+ this._ctx.onDispose(() => this._hf.off('valuesUpdated', onValuesUpdate));
144
160
  }
145
161
 
146
- // TODO(burdon): Remove.
147
162
  get hf() {
148
163
  return this._hf;
149
164
  }
150
165
 
151
- refresh() {
152
- log('refresh', { id: this.id });
153
- this.update.emit();
154
- }
166
+ // refresh() {
167
+ // log('refresh', { id: this.id });
168
+ // this.update.emit();
169
+ // }
155
170
 
156
- getFunctions({ standard = true, echo = true }: { standard?: boolean; echo?: boolean } = {}): FunctionDefinition[] {
171
+ getFunctions(
172
+ { standard, echo }: { standard?: boolean; echo?: boolean } = { standard: true, echo: true },
173
+ ): FunctionDefinition[] {
157
174
  return [
158
175
  ...(standard
159
176
  ? this._hf
@@ -172,19 +189,19 @@ export class ComputeGraph extends Resource {
172
189
  // This would enable on-the-fly instantiation of new models when then are referenced.
173
190
  // E.g., Cross-object reference would be stored as "ObjectId!A1"
174
191
  // The graph would then load the object and create a ComputeNode (model) of the appropriate type.
175
- getOrCreateNode(name: string): ComputeNode {
192
+ async getOrCreateNode(name: string): Promise<ComputeNode> {
176
193
  invariant(name.length);
177
194
  if (!this._hf.doesSheetExist(name)) {
178
195
  log.info('created node', { space: this._space?.id, name });
179
196
  this._hf.addSheet(name);
180
- this.update.emit();
197
+ // this.update.emit();
181
198
  }
182
199
 
183
200
  const sheetId = this._hf.getSheetId(name);
184
201
  invariant(sheetId !== undefined);
185
202
 
186
- // TODO(burdon): Chain context?
187
203
  const node = new ComputeNode(this, sheetId);
204
+ await node.open();
188
205
  this._nodes.set(sheetId, node);
189
206
  return node;
190
207
  }
@@ -281,10 +298,16 @@ export class ComputeGraph extends Resource {
281
298
  const query = this._space.db.query(Filter.schema(FunctionType));
282
299
  const unsubscribe = query.subscribe(({ objects }) => {
283
300
  this._functions = objects.filter(({ binding }) => binding);
284
- this.update.emit();
301
+ this.update.emit({ type: 'functionsUpdated' });
285
302
  });
286
303
 
287
304
  this._ctx.onDispose(unsubscribe);
288
305
  }
289
306
  }
307
+
308
+ protected override async _close() {
309
+ for (const node of this._nodes.values()) {
310
+ await node.close();
311
+ }
312
+ }
290
313
  }
@@ -2,6 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Listeners } from 'hyperformula/typings/Emitter';
6
+ import { type ExportedCellChange } from 'hyperformula/typings/Exporter';
7
+
5
8
  import { Event } from '@dxos/async';
6
9
  import { Resource } from '@dxos/context';
7
10
 
@@ -10,13 +13,17 @@ import { type ComputeGraph } from './compute-graph';
10
13
  import { type CellAddress } from '../defs';
11
14
  import { type CellScalarValue } from '../types';
12
15
 
16
+ export type ComputeNodeEvent = {
17
+ type: keyof Listeners;
18
+ change?: ExportedCellChange;
19
+ };
20
+
13
21
  /**
14
22
  * Individual "sheet" (typically corresponds to an ECHO object).
15
23
  */
16
24
  // TODO(burdon): Factor out common HF wrapper from from SheetModel.
17
25
  export class ComputeNode extends Resource {
18
- // TODO(burdon): Chaing events.
19
- public readonly update = new Event();
26
+ public readonly update = new Event<ComputeNodeEvent>();
20
27
 
21
28
  constructor(
22
29
  private readonly _graph: ComputeGraph,
@@ -25,13 +32,12 @@ export class ComputeNode extends Resource {
25
32
  super();
26
33
  }
27
34
 
28
- // TODO(burdon): Remove?
29
35
  get graph() {
30
36
  return this._graph;
31
37
  }
32
38
 
33
- get hf() {
34
- return this._graph.hf;
39
+ clear() {
40
+ this._graph.hf.clearSheet(this.sheetId);
35
41
  }
36
42
 
37
43
  getValue(cell: CellAddress): CellScalarValue {
@@ -48,4 +54,9 @@ export class ComputeNode extends Resource {
48
54
  typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value;
49
55
  this._graph.hf.setCellContents({ sheet: this.sheetId, row: cell.row, col: cell.col }, [[mappedValue]]);
50
56
  }
57
+
58
+ protected override async _open() {
59
+ // const unsubscribe = this._graph.update.on(this.update.emit);
60
+ // this._ctx.onDispose(unsubscribe);
61
+ }
51
62
  }
@@ -74,8 +74,7 @@ EdgeFunctionPlugin.implementedFunctions = {
74
74
  // Binding
75
75
  { argumentType: FunctionArgumentType.STRING },
76
76
 
77
- // Remote function arguments (currently supporting up to 9).
78
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
77
+ // Remote function arguments (currently supporting up to 8).
79
78
  { argumentType: FunctionArgumentType.ANY, optionalArg: true },
80
79
  { argumentType: FunctionArgumentType.ANY, optionalArg: true },
81
80
  { argumentType: FunctionArgumentType.ANY, optionalArg: true },
@@ -8,8 +8,9 @@ import { type ProcedureAst } from 'hyperformula/typings/parser';
8
8
  import { getDeep } from '@dxos/util';
9
9
 
10
10
  import { FunctionArgumentType } from '#hyperformula';
11
- import { type AsyncFunction, FunctionPluginAsync } from './async-function';
12
- import { parseNumberString } from './util';
11
+ import { type AsyncFunction, FunctionPluginAsync } from '../async-function';
12
+ import { type ComputeGraphPlugin } from '../compute-graph';
13
+ import { parseNumberString } from '../util';
13
14
 
14
15
  /**
15
16
  * https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
@@ -64,3 +65,10 @@ export const CustomPluginTranslations = {
64
65
  CRYPTO: 'CRYPTO',
65
66
  },
66
67
  };
68
+
69
+ export const testPlugins: ComputeGraphPlugin[] = [
70
+ {
71
+ plugin: CustomPlugin,
72
+ translations: CustomPluginTranslations,
73
+ },
74
+ ];
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './custom-function';
@@ -14,14 +14,14 @@ import { withTheme } from '@dxos/storybook-utils';
14
14
  import { ComputeGraphContextProvider } from '../components';
15
15
  import { createSheet } from '../defs';
16
16
  import { useComputeGraph, useSheetModel } from '../hooks';
17
- import { withGraphDecorator } from '../testing';
17
+ import { withComputeGraphDecorator } from '../testing';
18
18
  import { SheetType } from '../types';
19
19
 
20
20
  const Story = () => {
21
21
  const space = useSpace();
22
22
  const graph = useComputeGraph(space);
23
23
  const [sheet, setSheet] = useState<SheetType>();
24
- const model = useSheetModel(space, sheet);
24
+ const model = useSheetModel(graph, sheet);
25
25
  useEffect(() => {
26
26
  if (space) {
27
27
  const sheet = space.db.add(createSheet());
@@ -41,7 +41,7 @@ export default {
41
41
  component: ComputeGraphContextProvider,
42
42
  decorators: [
43
43
  withClientProvider({ types: [SheetType], createIdentity: true, createSpace: true }),
44
- withGraphDecorator,
44
+ withComputeGraphDecorator(),
45
45
  withTheme,
46
46
  ],
47
47
  render: (args: any) => <Story {...args} />,
@@ -16,5 +16,6 @@ import { type ComputeGraph } from '../graph';
16
16
  */
17
17
  export const useComputeGraph = (space?: Space): ComputeGraph | undefined => {
18
18
  const { registry } = useContext(ComputeGraphContext) ?? raise(new Error('Missing ComputeGraphContext'));
19
- return useAsyncState(async () => space && registry.getOrCreateGraph(space), [space, registry]);
19
+ const [graph] = useAsyncState(async () => space && registry.getOrCreateGraph(space), [space, registry]);
20
+ return graph;
20
21
  };
@@ -4,9 +4,7 @@
4
4
 
5
5
  import { useEffect, useState } from 'react';
6
6
 
7
- import { type Space } from '@dxos/react-client/echo';
8
-
9
- import { useComputeGraph } from './useComputeGraph';
7
+ import { type ComputeGraph } from '../graph';
10
8
  import { SheetModel } from '../model';
11
9
  import { type SheetType } from '../types';
12
10
 
@@ -15,14 +13,13 @@ export type UseSheetModelOptions = {
15
13
  };
16
14
 
17
15
  export const useSheetModel = (
18
- space?: Space,
16
+ graph?: ComputeGraph,
19
17
  sheet?: SheetType,
20
18
  { readonly }: UseSheetModelOptions = {},
21
19
  ): SheetModel | undefined => {
22
- const graph = useComputeGraph(space);
23
20
  const [model, setModel] = useState<SheetModel>();
24
21
  useEffect(() => {
25
- if (!space || !graph || !sheet) {
22
+ if (!graph || !sheet) {
26
23
  return;
27
24
  }
28
25
 
@@ -37,7 +34,7 @@ export const useSheetModel = (
37
34
  clearTimeout(t);
38
35
  void model?.close();
39
36
  };
40
- }, [space, sheet, graph, readonly]);
37
+ }, [graph, sheet, readonly]);
41
38
 
42
39
  return model;
43
40
  };
@@ -22,7 +22,7 @@ import {
22
22
  MAX_ROWS,
23
23
  } from '../defs';
24
24
  import { addressFromIndex, addressToIndex, initialize, insertIndices, ReadonlyException } from '../defs';
25
- import { type ComputeNode, type ComputeGraph, createSheetName } from '../graph';
25
+ import { type ComputeNode, type ComputeGraph, createSheetName, type ComputeNodeEvent } from '../graph';
26
26
  import { type CellScalarValue, type CellValue, type SheetType, ValueTypeEnum } from '../types';
27
27
 
28
28
  const typeMap: Record<string, ValueTypeEnum> = {
@@ -64,9 +64,10 @@ export type SheetModelOptions = {
64
64
  export class SheetModel extends Resource {
65
65
  public readonly id = `model-${PublicKey.random().truncate()}`;
66
66
 
67
- public readonly update = new Event();
67
+ // Wraps compute node.
68
+ public readonly update = new Event<ComputeNodeEvent>();
68
69
 
69
- private readonly _node: ComputeNode;
70
+ private _node?: ComputeNode;
70
71
 
71
72
  constructor(
72
73
  private readonly _graph: ComputeGraph,
@@ -74,9 +75,6 @@ export class SheetModel extends Resource {
74
75
  private readonly _options: SheetModelOptions = {},
75
76
  ) {
76
77
  super();
77
- // TODO(burdon): SheetModel should extend ComputeNode and be constructed via the graph.
78
- this._node = this._graph.getOrCreateNode(createSheetName(this._sheet.id));
79
- this.reset();
80
78
  }
81
79
 
82
80
  get graph() {
@@ -104,12 +102,15 @@ export class SheetModel extends Resource {
104
102
  protected override async _open() {
105
103
  log('initialize', { id: this.id });
106
104
  initialize(this._sheet);
107
- this.reset();
108
105
 
109
- // TODO(burdon): Event hierarchy?
106
+ // TODO(burdon): SheetModel should extend ComputeNode and be constructed via the graph.
107
+ this._node = await this._graph.getOrCreateNode(createSheetName(this._sheet.id));
108
+
110
109
  // Listen for model updates (e.g., async calculations).
111
- const unsubscribe = this._graph.update.on(() => this.update.emit());
110
+ const unsubscribe = this._node.update.on((event) => this.update.emit(event));
112
111
  this._ctx.onDispose(unsubscribe);
112
+
113
+ this.reset();
113
114
  }
114
115
 
115
116
  /**
@@ -118,8 +119,10 @@ export class SheetModel extends Resource {
118
119
  * @deprecated
119
120
  */
120
121
  reset() {
121
- this._node.hf.clearSheet(this._node.sheetId);
122
+ invariant(this._node);
123
+ this._node.graph.hf.clearSheet(this._node.sheetId);
122
124
  Object.entries(this._sheet.cells).forEach(([key, { value }]) => {
125
+ invariant(this._node);
123
126
  const { col, row } = addressFromIndex(this._sheet, key);
124
127
  if (typeof value === 'string' && value.charAt(0) === '=') {
125
128
  value = this._graph.mapFormulaToNative(
@@ -127,7 +130,7 @@ export class SheetModel extends Resource {
127
130
  );
128
131
  }
129
132
 
130
- this._node.hf.setCellContents({ sheet: this._node.sheetId, row, col }, value);
133
+ this._node.graph.hf.setCellContents({ sheet: this._node.sheetId, row, col }, value);
131
134
  });
132
135
  }
133
136
 
@@ -139,7 +142,7 @@ export class SheetModel extends Resource {
139
142
  */
140
143
  // TODO(burdon): Remove.
141
144
  recalculate() {
142
- this._node.hf.rebuildAndRecalculate();
145
+ this._node?.graph.hf.rebuildAndRecalculate();
143
146
  }
144
147
 
145
148
  insertRows(i: number, n = 1) {
@@ -161,9 +164,10 @@ export class SheetModel extends Resource {
161
164
  * Clear range of values.
162
165
  */
163
166
  clear(range: CellRange) {
167
+ invariant(this._node);
164
168
  const topLeft = getTopLeft(range);
165
169
  const values = this._iterRange(range, () => null);
166
- this._node.hf.setCellContents(toSimpleCellAddress(this._node.sheetId, topLeft), values);
170
+ this._node.graph.hf.setCellContents(toSimpleCellAddress(this._node.sheetId, topLeft), values);
167
171
  this._iterRange(range, (cell) => {
168
172
  const idx = addressToIndex(this._sheet, cell);
169
173
  delete this._sheet.cells[idx];
@@ -171,7 +175,8 @@ export class SheetModel extends Resource {
171
175
  }
172
176
 
173
177
  cut(range: CellRange) {
174
- this._node.hf.cut(toModelRange(this._node.sheetId, range));
178
+ invariant(this._node);
179
+ this._node.graph.hf.cut(toModelRange(this._node.sheetId, range));
175
180
  this._iterRange(range, (cell) => {
176
181
  const idx = addressToIndex(this._sheet, cell);
177
182
  delete this._sheet.cells[idx];
@@ -179,12 +184,14 @@ export class SheetModel extends Resource {
179
184
  }
180
185
 
181
186
  copy(range: CellRange) {
182
- this._node.hf.copy(toModelRange(this._node.sheetId, range));
187
+ invariant(this._node);
188
+ this._node.graph.hf.copy(toModelRange(this._node.sheetId, range));
183
189
  }
184
190
 
185
191
  paste(cell: CellAddress) {
186
- if (!this._node.hf.isClipboardEmpty()) {
187
- const changes = this._node.hf.paste(toSimpleCellAddress(this._node.sheetId, cell));
192
+ invariant(this._node);
193
+ if (!this._node.graph.hf.isClipboardEmpty()) {
194
+ const changes = this._node.graph.hf.paste(toSimpleCellAddress(this._node.sheetId, cell));
188
195
  for (const change of changes) {
189
196
  if (change instanceof ExportedCellChange) {
190
197
  const { address, newValue } = change;
@@ -197,16 +204,18 @@ export class SheetModel extends Resource {
197
204
 
198
205
  // TODO(burdon): Display undo/redo state.
199
206
  undo() {
200
- if (this._node.hf.isThereSomethingToUndo()) {
201
- this._node.hf.undo();
202
- this.update.emit();
207
+ invariant(this._node);
208
+ if (this._node.graph.hf.isThereSomethingToUndo()) {
209
+ this._node.graph.hf.undo();
210
+ // this.update.emit();
203
211
  }
204
212
  }
205
213
 
206
214
  redo() {
207
- if (this._node.hf.isThereSomethingToRedo()) {
208
- this._node.hf.redo();
209
- this.update.emit();
215
+ invariant(this._node);
216
+ if (this._node.graph.hf.isThereSomethingToRedo()) {
217
+ this._node.graph.hf.redo();
218
+ // this.update.emit();
210
219
  }
211
220
  }
212
221
 
@@ -246,7 +255,8 @@ export class SheetModel extends Resource {
246
255
  */
247
256
  getValue(cell: CellAddress): CellScalarValue {
248
257
  // Applies rounding and post-processing.
249
- const value = this._node.hf.getCellValue(toSimpleCellAddress(this._node.sheetId, cell));
258
+ invariant(this._node);
259
+ const value = this._node.graph.hf.getCellValue(toSimpleCellAddress(this._node.sheetId, cell));
250
260
  if (value instanceof DetailedCellError) {
251
261
  return value.toString();
252
262
  }
@@ -258,8 +268,9 @@ export class SheetModel extends Resource {
258
268
  * Get value type.
259
269
  */
260
270
  getValueType(cell: CellAddress): ValueTypeEnum {
271
+ invariant(this._node);
261
272
  const addr = toSimpleCellAddress(this._node.sheetId, cell);
262
- const type = this._node.hf.getCellValueDetailedType(addr);
273
+ const type = this._node.graph.hf.getCellValueDetailedType(addr);
263
274
  return typeMap[type];
264
275
  }
265
276
 
@@ -267,6 +278,7 @@ export class SheetModel extends Resource {
267
278
  * Sets the value, updating the sheet and engine.
268
279
  */
269
280
  setValue(cell: CellAddress, value: CellScalarValue) {
281
+ invariant(this._node);
270
282
  if (this._options.readonly) {
271
283
  throw new ReadonlyException();
272
284
  }
@@ -288,7 +300,7 @@ export class SheetModel extends Resource {
288
300
  }
289
301
 
290
302
  // Insert into engine.
291
- this._node.hf.setCellContents({ sheet: this._node.sheetId, row: cell.row, col: cell.col }, [
303
+ this._node.graph.hf.setCellContents({ sheet: this._node.sheetId, row: cell.row, col: cell.col }, [
292
304
  [typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value],
293
305
  ]);
294
306
 
@@ -386,14 +398,17 @@ export class SheetModel extends Resource {
386
398
  }
387
399
 
388
400
  toDateTime(num: number): SimpleDateTime {
389
- return this._node.hf.numberToDateTime(num) as SimpleDateTime;
401
+ invariant(this._node);
402
+ return this._node.graph.hf.numberToDateTime(num) as SimpleDateTime;
390
403
  }
391
404
 
392
405
  toDate(num: number): SimpleDate {
393
- return this._node.hf.numberToDate(num) as SimpleDate;
406
+ invariant(this._node);
407
+ return this._node.graph.hf.numberToDate(num) as SimpleDate;
394
408
  }
395
409
 
396
410
  toTime(num: number): SimpleDate {
397
- return this._node.hf.numberToTime(num) as SimpleDate;
411
+ invariant(this._node);
412
+ return this._node.graph.hf.numberToTime(num) as SimpleDate;
398
413
  }
399
414
  }
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import type { Decorator } from '@storybook/react';
5
+ import { type Decorator } from '@storybook/react';
6
6
  import React, { useState } from 'react';
7
7
 
8
8
  import { type Space } from '@dxos/react-client/echo';
@@ -10,14 +10,13 @@ import { useAsyncState } from '@dxos/react-hooks';
10
10
 
11
11
  import { ComputeGraphContextProvider } from '../components';
12
12
  import { createSheet } from '../defs';
13
- import { type ComputeGraph, ComputeGraphRegistry } from '../graph';
13
+ import { type ComputeGraph, type ComputeGraphOptions, ComputeGraphRegistry } from '../graph';
14
14
  import { type CellValue, type CreateSheetOptions } from '../types';
15
15
 
16
- export const testSheetName = 'test';
16
+ const testSheetName = 'test';
17
17
 
18
- // TODO(thure): Remove this from the `/testing` entrypoint.
19
- export const createCells = (): Record<string, CellValue> => ({
20
- B1: { value: 'Qty' },
18
+ export const createTestCells = (): Record<string, CellValue> => ({
19
+ B1: { value: 'Qty2' },
21
20
  B3: { value: 1 },
22
21
  B4: { value: 2 },
23
22
  B5: { value: 3 },
@@ -45,7 +44,7 @@ export const createCells = (): Record<string, CellValue> => ({
45
44
  });
46
45
 
47
46
  export const useTestSheet = (space?: Space, graph?: ComputeGraph, options?: CreateSheetOptions) => {
48
- return useAsyncState(async () => {
47
+ const [sheet] = useAsyncState(async () => {
49
48
  if (!space || !graph) {
50
49
  return;
51
50
  }
@@ -54,13 +53,16 @@ export const useTestSheet = (space?: Space, graph?: ComputeGraph, options?: Crea
54
53
  space.db.add(sheet);
55
54
  return sheet;
56
55
  }, [space, graph]);
56
+ return sheet;
57
57
  };
58
58
 
59
- export const withGraphDecorator: Decorator = (Story) => {
60
- const [registry] = useState(new ComputeGraphRegistry());
61
- return (
62
- <ComputeGraphContextProvider registry={registry}>
63
- <Story />
64
- </ComputeGraphContextProvider>
65
- );
66
- };
59
+ export const withComputeGraphDecorator =
60
+ (options?: ComputeGraphOptions): Decorator =>
61
+ (Story) => {
62
+ const [registry] = useState(new ComputeGraphRegistry(options));
63
+ return (
64
+ <ComputeGraphContextProvider registry={registry}>
65
+ <Story />
66
+ </ComputeGraphContextProvider>
67
+ );
68
+ };
package/src/types.ts CHANGED
@@ -102,7 +102,7 @@ export const RowColumnMeta = S.Struct({
102
102
  // TODO(wittjosiah): Migrate typename to remove `Type` suffix.
103
103
  // TODO(wittjosiah): Rename title to name to align with other schemas.
104
104
  export class SheetType extends TypedObject({ typename: 'dxos.org/type/SheetType', version: '0.1.0' })({
105
- title: S.optional(S.String),
105
+ name: S.optional(S.String),
106
106
 
107
107
  // Sparse map of cells referenced by index.
108
108
  cells: S.mutable(S.Record(S.String, S.mutable(CellValue))),
@@ -132,6 +132,6 @@ export type SheetSize = {
132
132
  };
133
133
 
134
134
  export type CreateSheetOptions = {
135
- // TODO(burdon): Standardize as name.
136
- title?: string;
135
+ name?: string;
136
+ cells?: Record<string, CellValue>;
137
137
  } & Partial<SheetSize>;