@webstudio-is/react-sdk 0.78.0 → 0.80.0
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/lib/cjs/context.js +8 -2
- package/lib/cjs/css/normalize.js +23 -43
- package/lib/cjs/css/presets.js +1 -111
- package/lib/cjs/embed-template.js +45 -16
- package/lib/cjs/expression.js +137 -22
- package/lib/cjs/index.js +6 -2
- package/lib/cjs/props.js +32 -2
- package/lib/cjs/tree/create-elements-tree.js +7 -2
- package/lib/cjs/tree/root.js +15 -7
- package/lib/context.js +8 -2
- package/lib/css/normalize.js +23 -33
- package/lib/css/presets.js +1 -111
- package/lib/embed-template.js +45 -16
- package/lib/expression.js +137 -22
- package/lib/index.js +13 -5
- package/lib/props.js +32 -2
- package/lib/tree/create-elements-tree.js +10 -3
- package/lib/tree/root.js +16 -8
- package/lib/types/components/component-meta.d.ts +30 -0
- package/lib/types/context.d.ts +5 -2
- package/lib/types/css/normalize.d.ts +0 -520
- package/lib/types/css/presets.d.ts +0 -282
- package/lib/types/embed-template.d.ts +38 -0
- package/lib/types/expression.d.ts +12 -4
- package/lib/types/index.d.ts +1 -1
- package/lib/types/props.d.ts +10 -0
- package/lib/types/tree/create-elements-tree.d.ts +6 -5
- package/lib/types/tree/root.d.ts +5 -5
- package/package.json +17 -16
- package/src/context.tsx +17 -4
- package/src/css/normalize.ts +23 -32
- package/src/css/presets.ts +0 -110
- package/src/embed-template.test.ts +84 -4
- package/src/embed-template.ts +51 -18
- package/src/expression.test.ts +106 -12
- package/src/expression.ts +157 -26
- package/src/index.ts +6 -2
- package/src/props.ts +33 -3
- package/src/tree/create-elements-tree.tsx +19 -10
- package/src/tree/root.ts +24 -13
package/src/expression.ts
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import jsep from "jsep";
|
|
2
|
+
import jsepAssignment from "@jsep-plugin/assignment";
|
|
3
|
+
import type {
|
|
4
|
+
UpdateExpression,
|
|
5
|
+
AssignmentExpression,
|
|
6
|
+
} from "@jsep-plugin/assignment";
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
jsep.plugins.register(jsepAssignment);
|
|
4
9
|
|
|
5
|
-
type
|
|
10
|
+
type TransformIdentifier = (id: string, assignee: boolean) => string;
|
|
11
|
+
|
|
12
|
+
type Node = jsep.CoreExpression | UpdateExpression | AssignmentExpression;
|
|
6
13
|
|
|
7
14
|
const generateCode = (
|
|
8
15
|
node: Node,
|
|
9
16
|
failOnForbidden: boolean,
|
|
17
|
+
effectful: boolean,
|
|
10
18
|
transformIdentifier: TransformIdentifier
|
|
11
19
|
): string => {
|
|
12
20
|
if (node.type === "Identifier") {
|
|
13
|
-
return transformIdentifier(node.name);
|
|
21
|
+
return transformIdentifier(node.name, false);
|
|
14
22
|
}
|
|
15
23
|
if (node.type === "MemberExpression") {
|
|
16
24
|
if (failOnForbidden) {
|
|
17
25
|
const object = generateCode(
|
|
18
26
|
node.object as Node,
|
|
19
27
|
false,
|
|
28
|
+
effectful,
|
|
20
29
|
transformIdentifier
|
|
21
30
|
);
|
|
22
31
|
const property = generateCode(
|
|
23
32
|
node.property as Node,
|
|
24
33
|
false,
|
|
34
|
+
effectful,
|
|
25
35
|
transformIdentifier
|
|
26
36
|
);
|
|
27
37
|
throw Error(`Cannot access "${property}" of "${object}"`);
|
|
@@ -29,11 +39,13 @@ const generateCode = (
|
|
|
29
39
|
const object = generateCode(
|
|
30
40
|
node.object as Node,
|
|
31
41
|
failOnForbidden,
|
|
42
|
+
effectful,
|
|
32
43
|
transformIdentifier
|
|
33
44
|
);
|
|
34
45
|
const property = generateCode(
|
|
35
46
|
node.property as Node,
|
|
36
47
|
failOnForbidden,
|
|
48
|
+
effectful,
|
|
37
49
|
transformIdentifier
|
|
38
50
|
);
|
|
39
51
|
return `${object}.${property}`;
|
|
@@ -45,6 +57,7 @@ const generateCode = (
|
|
|
45
57
|
const arg = generateCode(
|
|
46
58
|
node.argument as Node,
|
|
47
59
|
failOnForbidden,
|
|
60
|
+
effectful,
|
|
48
61
|
transformIdentifier
|
|
49
62
|
);
|
|
50
63
|
return `${node.operator}${arg}`;
|
|
@@ -53,18 +66,25 @@ const generateCode = (
|
|
|
53
66
|
const left = generateCode(
|
|
54
67
|
node.left as Node,
|
|
55
68
|
failOnForbidden,
|
|
69
|
+
effectful,
|
|
56
70
|
transformIdentifier
|
|
57
71
|
);
|
|
58
72
|
const right = generateCode(
|
|
59
73
|
node.right as Node,
|
|
60
74
|
failOnForbidden,
|
|
75
|
+
effectful,
|
|
61
76
|
transformIdentifier
|
|
62
77
|
);
|
|
63
78
|
return `${left} ${node.operator} ${right}`;
|
|
64
79
|
}
|
|
65
80
|
if (node.type === "ArrayExpression") {
|
|
66
81
|
const elements = node.elements.map((element) =>
|
|
67
|
-
generateCode(
|
|
82
|
+
generateCode(
|
|
83
|
+
element as Node,
|
|
84
|
+
failOnForbidden,
|
|
85
|
+
effectful,
|
|
86
|
+
transformIdentifier
|
|
87
|
+
)
|
|
68
88
|
);
|
|
69
89
|
return `[${elements.join(", ")}]`;
|
|
70
90
|
}
|
|
@@ -73,6 +93,7 @@ const generateCode = (
|
|
|
73
93
|
const callee = generateCode(
|
|
74
94
|
node.callee as Node,
|
|
75
95
|
false,
|
|
96
|
+
effectful,
|
|
76
97
|
transformIdentifier
|
|
77
98
|
);
|
|
78
99
|
throw Error(`Cannot call "${callee}"`);
|
|
@@ -80,10 +101,11 @@ const generateCode = (
|
|
|
80
101
|
const callee = generateCode(
|
|
81
102
|
node.callee as Node,
|
|
82
103
|
failOnForbidden,
|
|
104
|
+
effectful,
|
|
83
105
|
transformIdentifier
|
|
84
106
|
);
|
|
85
107
|
const args = node.arguments.map((arg) =>
|
|
86
|
-
generateCode(arg as Node, failOnForbidden, transformIdentifier)
|
|
108
|
+
generateCode(arg as Node, failOnForbidden, effectful, transformIdentifier)
|
|
87
109
|
);
|
|
88
110
|
return `${callee}(${args.join(", ")})`;
|
|
89
111
|
}
|
|
@@ -99,16 +121,43 @@ const generateCode = (
|
|
|
99
121
|
if (node.type === "Compound") {
|
|
100
122
|
throw Error("Cannot use multiple expressions");
|
|
101
123
|
}
|
|
124
|
+
if (node.type === "AssignmentExpression") {
|
|
125
|
+
if (node.operator !== "=") {
|
|
126
|
+
throw Error(`Only "=" assignment operator is supported`);
|
|
127
|
+
}
|
|
128
|
+
if (effectful === false) {
|
|
129
|
+
throw Error(`Cannot use assignment in this expression`);
|
|
130
|
+
}
|
|
131
|
+
const left = generateCode(
|
|
132
|
+
node.left as Node,
|
|
133
|
+
failOnForbidden,
|
|
134
|
+
effectful,
|
|
135
|
+
// override and mark all identifiers inside of left expression as assignee
|
|
136
|
+
(id) => transformIdentifier(id, true)
|
|
137
|
+
);
|
|
138
|
+
const right = generateCode(
|
|
139
|
+
node.right as Node,
|
|
140
|
+
failOnForbidden,
|
|
141
|
+
effectful,
|
|
142
|
+
transformIdentifier
|
|
143
|
+
);
|
|
144
|
+
return `${left} ${node.operator} ${right}`;
|
|
145
|
+
}
|
|
146
|
+
if (node.type === "UpdateExpression") {
|
|
147
|
+
throw Error(`"${node.operator}" operator is not supported`);
|
|
148
|
+
}
|
|
102
149
|
node satisfies never;
|
|
103
150
|
return "";
|
|
104
151
|
};
|
|
105
152
|
|
|
106
153
|
export const validateExpression = (
|
|
107
154
|
code: string,
|
|
108
|
-
|
|
155
|
+
options?: { effectful?: boolean; transformIdentifier?: TransformIdentifier }
|
|
109
156
|
) => {
|
|
157
|
+
const { effectful = false, transformIdentifier = (id: string) => id } =
|
|
158
|
+
options ?? {};
|
|
110
159
|
const expression = jsep(code) as Node;
|
|
111
|
-
return generateCode(expression, true, transformIdentifier);
|
|
160
|
+
return generateCode(expression, true, effectful, transformIdentifier);
|
|
112
161
|
};
|
|
113
162
|
|
|
114
163
|
const sortTopologically = (
|
|
@@ -135,22 +184,26 @@ const sortTopologically = (
|
|
|
135
184
|
* Generates a function body expecting map as _variables argument
|
|
136
185
|
* and outputing map of results
|
|
137
186
|
*/
|
|
138
|
-
export const
|
|
139
|
-
|
|
140
|
-
|
|
187
|
+
export const generateComputingExpressions = (
|
|
188
|
+
expressions: Map<string, string>,
|
|
189
|
+
allowedVariables: Set<string>
|
|
141
190
|
) => {
|
|
142
191
|
const depsById = new Map<string, Set<string>>();
|
|
192
|
+
const inputVariables = new Set<string>();
|
|
143
193
|
for (const [id, code] of expressions) {
|
|
144
194
|
const deps = new Set<string>();
|
|
145
|
-
validateExpression(code,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
validateExpression(code, {
|
|
196
|
+
transformIdentifier: (identifier) => {
|
|
197
|
+
if (allowedVariables.has(identifier)) {
|
|
198
|
+
inputVariables.add(identifier);
|
|
199
|
+
return identifier;
|
|
200
|
+
}
|
|
201
|
+
if (expressions.has(identifier)) {
|
|
202
|
+
deps.add(identifier);
|
|
203
|
+
return identifier;
|
|
204
|
+
}
|
|
205
|
+
throw Error(`Unknown dependency "${identifier}"`);
|
|
206
|
+
},
|
|
154
207
|
});
|
|
155
208
|
depsById.set(id, deps);
|
|
156
209
|
}
|
|
@@ -163,7 +216,7 @@ export const generateExpressionsComputation = (
|
|
|
163
216
|
// generate code computing all expressions
|
|
164
217
|
let generatedCode = "";
|
|
165
218
|
|
|
166
|
-
for (const id of
|
|
219
|
+
for (const id of inputVariables) {
|
|
167
220
|
generatedCode += `const ${id} = _variables.get('${id}');\n`;
|
|
168
221
|
}
|
|
169
222
|
|
|
@@ -184,19 +237,78 @@ export const generateExpressionsComputation = (
|
|
|
184
237
|
return generatedCode;
|
|
185
238
|
};
|
|
186
239
|
|
|
187
|
-
export const
|
|
188
|
-
|
|
189
|
-
|
|
240
|
+
export const executeComputingExpressions = (
|
|
241
|
+
expressions: Map<string, string>,
|
|
242
|
+
variables: Map<string, unknown>
|
|
190
243
|
) => {
|
|
191
|
-
const generatedCode =
|
|
192
|
-
|
|
193
|
-
|
|
244
|
+
const generatedCode = generateComputingExpressions(
|
|
245
|
+
expressions,
|
|
246
|
+
new Set(variables.keys())
|
|
194
247
|
);
|
|
195
248
|
const executeFn = new Function("_variables", generatedCode);
|
|
196
249
|
const values = executeFn(variables) as Map<string, unknown>;
|
|
197
250
|
return values;
|
|
198
251
|
};
|
|
199
252
|
|
|
253
|
+
export const generateEffectfulExpression = (
|
|
254
|
+
code: string,
|
|
255
|
+
allowedVariables: Set<string>
|
|
256
|
+
) => {
|
|
257
|
+
const inputVariables = new Set<string>();
|
|
258
|
+
const outputVariables = new Set<string>();
|
|
259
|
+
validateExpression(code, {
|
|
260
|
+
effectful: true,
|
|
261
|
+
transformIdentifier: (identifier, assignee) => {
|
|
262
|
+
if (allowedVariables.has(identifier)) {
|
|
263
|
+
if (assignee) {
|
|
264
|
+
outputVariables.add(identifier);
|
|
265
|
+
} else {
|
|
266
|
+
inputVariables.add(identifier);
|
|
267
|
+
}
|
|
268
|
+
return identifier;
|
|
269
|
+
}
|
|
270
|
+
throw Error(`Unknown dependency "${identifier}"`);
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// generate code computing all expressions
|
|
275
|
+
let generatedCode = "";
|
|
276
|
+
|
|
277
|
+
for (const id of inputVariables) {
|
|
278
|
+
generatedCode += `let ${id} = _variables.get('${id}');\n`;
|
|
279
|
+
}
|
|
280
|
+
for (const id of outputVariables) {
|
|
281
|
+
if (inputVariables.has(id) === false) {
|
|
282
|
+
generatedCode += `let ${id};\n`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
generatedCode += `${code};\n`;
|
|
287
|
+
|
|
288
|
+
generatedCode += `return new Map([\n`;
|
|
289
|
+
for (const id of outputVariables) {
|
|
290
|
+
generatedCode += ` ['${id}', ${id}],\n`;
|
|
291
|
+
}
|
|
292
|
+
generatedCode += `]);`;
|
|
293
|
+
|
|
294
|
+
return generatedCode;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export const executeEffectfulExpression = (
|
|
298
|
+
code: string,
|
|
299
|
+
variables: Map<string, unknown>
|
|
300
|
+
) => {
|
|
301
|
+
const generatedCode = generateEffectfulExpression(
|
|
302
|
+
code,
|
|
303
|
+
new Set(variables.keys())
|
|
304
|
+
);
|
|
305
|
+
const executeFn = new Function("_variables", generatedCode);
|
|
306
|
+
const values = executeFn(variables) as Map<string, unknown>;
|
|
307
|
+
return values;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
type Values = Map<string, unknown>;
|
|
311
|
+
|
|
200
312
|
const dataSourceVariablePrefix = "$ws$dataSource$";
|
|
201
313
|
|
|
202
314
|
// data source id is generated with nanoid which has "-" in alphabeta
|
|
@@ -208,6 +320,14 @@ export const encodeDataSourceVariable = (id: string) => {
|
|
|
208
320
|
return `${dataSourceVariablePrefix}${encoded}`;
|
|
209
321
|
};
|
|
210
322
|
|
|
323
|
+
export const encodeVariablesMap = (values: Values) => {
|
|
324
|
+
const encodedValues: Values = new Map();
|
|
325
|
+
for (const [id, value] of values) {
|
|
326
|
+
encodedValues.set(encodeDataSourceVariable(id), value);
|
|
327
|
+
}
|
|
328
|
+
return encodedValues;
|
|
329
|
+
};
|
|
330
|
+
|
|
211
331
|
export const decodeDataSourceVariable = (name: string) => {
|
|
212
332
|
if (name.startsWith(dataSourceVariablePrefix)) {
|
|
213
333
|
const encoded = name.slice(dataSourceVariablePrefix.length);
|
|
@@ -215,3 +335,14 @@ export const decodeDataSourceVariable = (name: string) => {
|
|
|
215
335
|
}
|
|
216
336
|
return;
|
|
217
337
|
};
|
|
338
|
+
|
|
339
|
+
export const decodeVariablesMap = (values: Values) => {
|
|
340
|
+
const decodedValues: Values = new Map();
|
|
341
|
+
for (const [name, value] of values) {
|
|
342
|
+
const id = decodeDataSourceVariable(name);
|
|
343
|
+
if (id !== undefined) {
|
|
344
|
+
decodedValues.set(id, value);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return decodedValues;
|
|
348
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -22,8 +22,12 @@ export {
|
|
|
22
22
|
export { type Params, ReactSdkContext } from "./context";
|
|
23
23
|
export {
|
|
24
24
|
validateExpression,
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
generateComputingExpressions,
|
|
26
|
+
executeComputingExpressions,
|
|
27
|
+
generateEffectfulExpression,
|
|
28
|
+
executeEffectfulExpression,
|
|
27
29
|
encodeDataSourceVariable,
|
|
30
|
+
encodeVariablesMap,
|
|
28
31
|
decodeDataSourceVariable,
|
|
32
|
+
decodeVariablesMap,
|
|
29
33
|
} from "./expression";
|
package/src/props.ts
CHANGED
|
@@ -26,8 +26,13 @@ export const getPropsByInstanceId = (props: Props) => {
|
|
|
26
26
|
// this utility is be used only for preview with static props
|
|
27
27
|
// so there is no need to use computed to optimize rerenders
|
|
28
28
|
export const useInstanceProps = (instanceId: Instance["id"]) => {
|
|
29
|
-
const {
|
|
30
|
-
|
|
29
|
+
const {
|
|
30
|
+
propsByInstanceIdStore,
|
|
31
|
+
dataSourceValuesStore,
|
|
32
|
+
executeEffectfulExpression,
|
|
33
|
+
setDataSourceValues,
|
|
34
|
+
renderer,
|
|
35
|
+
} = useContext(ReactSdkContext);
|
|
31
36
|
const instancePropsObjectStore = useMemo(() => {
|
|
32
37
|
return computed(
|
|
33
38
|
[propsByInstanceIdStore, dataSourceValuesStore],
|
|
@@ -49,12 +54,37 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
|
|
|
49
54
|
}
|
|
50
55
|
continue;
|
|
51
56
|
}
|
|
57
|
+
if (prop.type === "action") {
|
|
58
|
+
instancePropsObject[prop.name] = () => {
|
|
59
|
+
// prevent all actions in canvas mode
|
|
60
|
+
if (renderer === "canvas") {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const value of prop.value) {
|
|
64
|
+
if (value.type === "execute") {
|
|
65
|
+
const newValues = executeEffectfulExpression(
|
|
66
|
+
value.code,
|
|
67
|
+
dataSourceValues
|
|
68
|
+
);
|
|
69
|
+
setDataSourceValues(newValues);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
52
75
|
instancePropsObject[prop.name] = prop.value;
|
|
53
76
|
}
|
|
54
77
|
return instancePropsObject;
|
|
55
78
|
}
|
|
56
79
|
);
|
|
57
|
-
}, [
|
|
80
|
+
}, [
|
|
81
|
+
propsByInstanceIdStore,
|
|
82
|
+
dataSourceValuesStore,
|
|
83
|
+
instanceId,
|
|
84
|
+
renderer,
|
|
85
|
+
executeEffectfulExpression,
|
|
86
|
+
setDataSourceValues,
|
|
87
|
+
]);
|
|
58
88
|
const instancePropsObject = useStore(instancePropsObjectStore);
|
|
59
89
|
return instancePropsObject;
|
|
60
90
|
};
|
|
@@ -2,13 +2,13 @@ import { type ComponentProps, Fragment } from "react";
|
|
|
2
2
|
import type { ReadableAtom } from "nanostores";
|
|
3
3
|
import { Scripts, ScrollRestoration } from "@remix-run/react";
|
|
4
4
|
import type { Assets } from "@webstudio-is/asset-uploader";
|
|
5
|
-
import type {
|
|
6
|
-
DataSource,
|
|
7
|
-
Instance,
|
|
8
|
-
Instances,
|
|
9
|
-
} from "@webstudio-is/project-build";
|
|
5
|
+
import type { Instance, Instances } from "@webstudio-is/project-build";
|
|
10
6
|
import type { Components } from "../components/components-utils";
|
|
11
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type Params,
|
|
9
|
+
type DataSourceValues,
|
|
10
|
+
ReactSdkContext,
|
|
11
|
+
} from "../context";
|
|
12
12
|
import type { Pages, PropsByInstanceId } from "../props";
|
|
13
13
|
import type { WebstudioComponent } from "./webstudio-component";
|
|
14
14
|
|
|
@@ -24,6 +24,7 @@ export const createElementsTree = ({
|
|
|
24
24
|
assetsStore,
|
|
25
25
|
pagesStore,
|
|
26
26
|
dataSourceValuesStore,
|
|
27
|
+
executeEffectfulExpression,
|
|
27
28
|
onDataSourceUpdate,
|
|
28
29
|
Component,
|
|
29
30
|
components,
|
|
@@ -33,8 +34,12 @@ export const createElementsTree = ({
|
|
|
33
34
|
propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
|
|
34
35
|
assetsStore: ReadableAtom<Assets>;
|
|
35
36
|
pagesStore: ReadableAtom<Pages>;
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
executeEffectfulExpression: (
|
|
38
|
+
expression: string,
|
|
39
|
+
values: DataSourceValues
|
|
40
|
+
) => DataSourceValues;
|
|
41
|
+
dataSourceValuesStore: ReadableAtom<DataSourceValues>;
|
|
42
|
+
onDataSourceUpdate: (newValues: DataSourceValues) => void;
|
|
38
43
|
Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
|
|
39
44
|
components: Components;
|
|
40
45
|
}) => {
|
|
@@ -74,7 +79,9 @@ export const createElementsTree = ({
|
|
|
74
79
|
renderer,
|
|
75
80
|
imageBaseUrl,
|
|
76
81
|
assetBaseUrl,
|
|
77
|
-
|
|
82
|
+
executeEffectfulExpression,
|
|
83
|
+
setDataSourceValues: onDataSourceUpdate,
|
|
84
|
+
setBoundDataSourceValue: (instanceId, propName, value) => {
|
|
78
85
|
const propsByInstanceId = propsByInstanceIdStore.get();
|
|
79
86
|
const props = propsByInstanceId.get(instanceId);
|
|
80
87
|
const prop = props?.find((prop) => prop.name === propName);
|
|
@@ -82,7 +89,9 @@ export const createElementsTree = ({
|
|
|
82
89
|
throw Error(`${propName} is not data source`);
|
|
83
90
|
}
|
|
84
91
|
const dataSourceId = prop.value;
|
|
85
|
-
|
|
92
|
+
const newValues = new Map();
|
|
93
|
+
newValues.set(dataSourceId, value);
|
|
94
|
+
onDataSourceUpdate(newValues);
|
|
86
95
|
},
|
|
87
96
|
}}
|
|
88
97
|
>
|
package/src/tree/root.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { useRef, type ComponentProps } from "react";
|
|
1
|
+
import { useRef, type ComponentProps, useCallback } from "react";
|
|
2
2
|
import {
|
|
3
3
|
atom,
|
|
4
4
|
computed,
|
|
5
5
|
type ReadableAtom,
|
|
6
6
|
type WritableAtom,
|
|
7
7
|
} from "nanostores";
|
|
8
|
-
import { type Build, type Page
|
|
8
|
+
import { type Build, type Page } from "@webstudio-is/project-build";
|
|
9
9
|
import type { Asset } from "@webstudio-is/asset-uploader";
|
|
10
10
|
import { createElementsTree } from "./create-elements-tree";
|
|
11
11
|
import { WebstudioComponent } from "./webstudio-component";
|
|
12
12
|
import { getPropsByInstanceId } from "../props";
|
|
13
13
|
import type { Components } from "../components/components-utils";
|
|
14
|
-
import type { Params } from "../context";
|
|
14
|
+
import type { Params, DataSourceValues } from "../context";
|
|
15
15
|
|
|
16
16
|
export type Data = {
|
|
17
17
|
page: Page;
|
|
@@ -25,18 +25,21 @@ export type RootPropsData = Omit<Data, "build"> & {
|
|
|
25
25
|
build: Pick<Data["build"], "instances" | "props" | "dataSources">;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
type DataSourceValues = Map<DataSource["id"], unknown>;
|
|
29
|
-
|
|
30
28
|
type RootProps = {
|
|
31
29
|
data: RootPropsData;
|
|
32
|
-
|
|
30
|
+
executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
|
|
31
|
+
executeEffectfulExpression: (
|
|
32
|
+
expression: string,
|
|
33
|
+
values: DataSourceValues
|
|
34
|
+
) => DataSourceValues;
|
|
33
35
|
Component?: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
|
|
34
36
|
components: Components;
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
export const InstanceRoot = ({
|
|
38
40
|
data,
|
|
39
|
-
|
|
41
|
+
executeComputingExpressions,
|
|
42
|
+
executeEffectfulExpression,
|
|
40
43
|
Component,
|
|
41
44
|
components,
|
|
42
45
|
}: RootProps): JSX.Element | null => {
|
|
@@ -67,7 +70,7 @@ export const InstanceRoot = ({
|
|
|
67
70
|
|
|
68
71
|
// set expression values
|
|
69
72
|
try {
|
|
70
|
-
const result =
|
|
73
|
+
const result = executeComputingExpressions(dataSourceValues);
|
|
71
74
|
for (const [id, value] of result) {
|
|
72
75
|
dataSourceValues.set(id, value);
|
|
73
76
|
}
|
|
@@ -82,6 +85,17 @@ export const InstanceRoot = ({
|
|
|
82
85
|
}
|
|
83
86
|
const dataSourceValuesStore = dataSourceValuesStoreRef.current;
|
|
84
87
|
|
|
88
|
+
const onDataSourceUpdate = useCallback(
|
|
89
|
+
(newValues: DataSourceValues) => {
|
|
90
|
+
const dataSourceVariables = new Map(dataSourceVariablesStore.get());
|
|
91
|
+
for (const [dataSourceId, value] of newValues) {
|
|
92
|
+
dataSourceVariables.set(dataSourceId, value);
|
|
93
|
+
}
|
|
94
|
+
dataSourceVariablesStore.set(dataSourceVariables);
|
|
95
|
+
},
|
|
96
|
+
[dataSourceVariablesStore]
|
|
97
|
+
);
|
|
98
|
+
|
|
85
99
|
return createElementsTree({
|
|
86
100
|
imageBaseUrl: data.params?.imageBaseUrl ?? "/",
|
|
87
101
|
assetBaseUrl: data.params?.assetBaseUrl ?? "/",
|
|
@@ -92,12 +106,9 @@ export const InstanceRoot = ({
|
|
|
92
106
|
),
|
|
93
107
|
assetsStore: atom(new Map(data.assets.map((asset) => [asset.id, asset]))),
|
|
94
108
|
pagesStore: atom(new Map(data.pages.map((page) => [page.id, page]))),
|
|
109
|
+
executeEffectfulExpression,
|
|
95
110
|
dataSourceValuesStore,
|
|
96
|
-
onDataSourceUpdate
|
|
97
|
-
const dataSourceVariables = new Map(dataSourceVariablesStore.get());
|
|
98
|
-
dataSourceVariables.set(dataSourceId, value);
|
|
99
|
-
dataSourceVariablesStore.set(dataSourceVariables);
|
|
100
|
-
},
|
|
111
|
+
onDataSourceUpdate,
|
|
101
112
|
Component: Component ?? WebstudioComponent,
|
|
102
113
|
components,
|
|
103
114
|
});
|