@webstudio-is/react-sdk 0.85.0 → 0.87.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/component-renderer.js +48 -30
- package/lib/cjs/components/component-meta.js +1 -1
- package/lib/cjs/generator.js +128 -0
- package/lib/cjs/hook.js +2 -2
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/tree/root.js +6 -3
- package/lib/component-renderer.js +52 -31
- package/lib/components/component-meta.js +1 -1
- package/lib/generator.js +114 -0
- package/lib/hook.js +2 -2
- package/lib/index.js +2 -0
- package/lib/tree/root.js +6 -3
- package/lib/types/component-renderer.d.ts +2 -1
- package/lib/types/components/component-meta.d.ts +3 -3
- package/lib/types/embed-template.d.ts +1 -1
- package/lib/types/generator.d.ts +23 -0
- package/lib/types/generator.test.d.ts +1 -0
- package/lib/types/hook.d.ts +13 -3
- package/lib/types/hook.test.d.ts +1 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/tree/root.d.ts +4 -6
- package/package.json +9 -9
- package/src/component-renderer.tsx +60 -31
- package/src/components/component-meta.ts +1 -1
- package/src/generator.test.ts +169 -0
- package/src/generator.ts +147 -0
- package/src/hook.test.ts +15 -0
- package/src/hook.ts +14 -4
- package/src/index.ts +1 -0
- package/src/tree/root.ts +8 -11
package/lib/types/hook.d.ts
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import type { Instance, Prop } from "@webstudio-is/project-build";
|
|
2
|
+
import type { IndexesWithinAncestors } from "./instance-utils";
|
|
2
3
|
/**
|
|
3
4
|
* Hooks are subscriptions to builder events
|
|
4
5
|
* with limited way to interact with it.
|
|
5
6
|
* Called independently from components.
|
|
6
7
|
*/
|
|
7
8
|
export type HookContext = {
|
|
9
|
+
indexesWithinAncestors: IndexesWithinAncestors;
|
|
10
|
+
getPropValue: (instanceId: Instance["id"], propName: Prop["name"]) => unknown;
|
|
8
11
|
setPropVariable: (instanceId: Instance["id"], propName: Prop["name"], value: unknown) => void;
|
|
9
12
|
};
|
|
10
|
-
export type
|
|
13
|
+
export type InstancePath = Instance[];
|
|
11
14
|
type NavigatorEvent = {
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* List of instances from selected to the root
|
|
17
|
+
*/
|
|
18
|
+
instancePath: InstancePath;
|
|
13
19
|
};
|
|
14
20
|
export type Hook = {
|
|
15
21
|
onNavigatorSelect?: (context: HookContext, event: NavigatorEvent) => void;
|
|
16
22
|
onNavigatorUnselect?: (context: HookContext, event: NavigatorEvent) => void;
|
|
17
23
|
};
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Find closest matching instance by component name
|
|
26
|
+
* by lookup in instance path
|
|
27
|
+
*/
|
|
28
|
+
export declare const getClosestInstance: (instancePath: InstancePath, currentInstance: Instance, closestComponent: Instance["component"]) => {
|
|
19
29
|
type: "instance";
|
|
20
30
|
id: string;
|
|
21
31
|
component: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { validateExpression, generateComputingExpressions, executeComputingExpre
|
|
|
11
11
|
export { renderComponentTemplate } from "./component-renderer";
|
|
12
12
|
export { getIndexesWithinAncestors } from "./instance-utils";
|
|
13
13
|
export * from "./hook";
|
|
14
|
+
export { generateUtilsExport } from "./generator";
|
package/lib/types/tree/root.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { type Build, type Page } from "@webstudio-is/project-build";
|
|
|
3
3
|
import type { Asset } from "@webstudio-is/asset-uploader";
|
|
4
4
|
import { type WebstudioComponentProps } from "./webstudio-component";
|
|
5
5
|
import type { Components } from "../components/components-utils";
|
|
6
|
-
import type { Params
|
|
7
|
-
import type {
|
|
6
|
+
import type { Params } from "../context";
|
|
7
|
+
import type { GeneratedUtils } from "../generator";
|
|
8
8
|
export type Data = {
|
|
9
9
|
page: Page;
|
|
10
10
|
pages: Array<Page>;
|
|
@@ -17,12 +17,10 @@ export type RootPropsData = Omit<Data, "build"> & {
|
|
|
17
17
|
};
|
|
18
18
|
type RootProps = {
|
|
19
19
|
data: RootPropsData;
|
|
20
|
-
|
|
21
|
-
executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
|
|
22
|
-
executeEffectfulExpression: (expression: string, args: DataSourceValues, values: DataSourceValues) => DataSourceValues;
|
|
20
|
+
utils: GeneratedUtils;
|
|
23
21
|
Component?: ForwardRefExoticComponent<WebstudioComponentProps & RefAttributes<HTMLElement>>;
|
|
24
22
|
components: Components;
|
|
25
23
|
scripts?: ReactNode;
|
|
26
24
|
};
|
|
27
|
-
export declare const InstanceRoot: ({ data,
|
|
25
|
+
export declare const InstanceRoot: ({ data, utils, Component, components, scripts, }: RootProps) => JSX.Element | null;
|
|
28
26
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "Webstudio JavaScript / TypeScript API",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@jest/globals": "^29.6.2",
|
|
10
10
|
"@remix-run/react": "^1.19.2",
|
|
11
|
-
"@types/react": "^18.2.
|
|
11
|
+
"@types/react": "^18.2.20",
|
|
12
12
|
"@types/react-dom": "^18.2.7",
|
|
13
13
|
"jest": "^29.6.2",
|
|
14
14
|
"react": "^18.2.0",
|
|
15
15
|
"react-dom": "^18.2.0",
|
|
16
|
-
"type-fest": "^
|
|
16
|
+
"type-fest": "^4.2.0",
|
|
17
17
|
"typescript": "5.1.6",
|
|
18
18
|
"zod": "^3.21.4",
|
|
19
19
|
"@webstudio-is/jest-config": "^1.0.7",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"nanoevents": "^8.0.0",
|
|
35
35
|
"nanoid": "^4.0.2",
|
|
36
36
|
"nanostores": "^0.9.3",
|
|
37
|
-
"@webstudio-is/asset-uploader": "^0.
|
|
38
|
-
"@webstudio-is/css-data": "^0.
|
|
39
|
-
"@webstudio-is/css-engine": "^0.
|
|
40
|
-
"@webstudio-is/fonts": "^0.
|
|
41
|
-
"@webstudio-is/generate-arg-types": "^0.
|
|
42
|
-
"@webstudio-is/project-build": "^0.
|
|
37
|
+
"@webstudio-is/asset-uploader": "^0.87.0",
|
|
38
|
+
"@webstudio-is/css-data": "^0.87.0",
|
|
39
|
+
"@webstudio-is/css-engine": "^0.87.0",
|
|
40
|
+
"@webstudio-is/fonts": "^0.87.0",
|
|
41
|
+
"@webstudio-is/generate-arg-types": "^0.87.0",
|
|
42
|
+
"@webstudio-is/project-build": "^0.87.0"
|
|
43
43
|
},
|
|
44
44
|
"exports": {
|
|
45
45
|
".": {
|
|
@@ -2,7 +2,10 @@ import type { ExoticComponent } from "react";
|
|
|
2
2
|
import type { Instance } from "@webstudio-is/project-build";
|
|
3
3
|
import { getStyleDeclKey } from "@webstudio-is/project-build";
|
|
4
4
|
import type { WsComponentMeta } from "./components/component-meta";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
WsEmbedTemplate,
|
|
7
|
+
generateDataFromEmbedTemplate,
|
|
8
|
+
} from "./embed-template";
|
|
6
9
|
import { generateCssText } from "./css";
|
|
7
10
|
import { InstanceRoot, WebstudioComponent } from "./tree";
|
|
8
11
|
import {
|
|
@@ -18,23 +21,43 @@ export const renderComponentTemplate = ({
|
|
|
18
21
|
name,
|
|
19
22
|
metas: metasRecord,
|
|
20
23
|
components,
|
|
24
|
+
props,
|
|
21
25
|
}: {
|
|
22
26
|
name: Instance["component"];
|
|
23
27
|
metas: Record<string, WsComponentMeta>;
|
|
28
|
+
props?: Record<string, unknown>;
|
|
24
29
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
30
|
components: Record<string, ExoticComponent<any>>;
|
|
26
31
|
}) => {
|
|
27
32
|
const metas = new Map(Object.entries(metasRecord));
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
33
|
+
|
|
34
|
+
const template: WsEmbedTemplate = metas.get(name)?.template ?? [
|
|
35
|
+
{
|
|
36
|
+
type: "instance",
|
|
37
|
+
component: name,
|
|
38
|
+
children: [],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (template[0].type === "instance" && props !== undefined) {
|
|
43
|
+
template[0].props = Object.entries(props).map(([prop, value]) => {
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
return { type: "string", name: prop, value: value };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof value === "number") {
|
|
49
|
+
return { type: "number", name: prop, value: value };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof value === "boolean") {
|
|
53
|
+
return { type: "boolean", name: prop, value: value };
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Unsupported prop ${props} with value ${value}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const data = generateDataFromEmbedTemplate(template, "base");
|
|
60
|
+
|
|
38
61
|
const instances: [Instance["id"], Instance][] = [
|
|
39
62
|
[
|
|
40
63
|
"root",
|
|
@@ -49,6 +72,7 @@ export const renderComponentTemplate = ({
|
|
|
49
72
|
(instance) => [instance.id, instance] satisfies [Instance["id"], Instance]
|
|
50
73
|
),
|
|
51
74
|
];
|
|
75
|
+
|
|
52
76
|
return (
|
|
53
77
|
<>
|
|
54
78
|
<style>
|
|
@@ -87,30 +111,35 @@ export const renderComponentTemplate = ({
|
|
|
87
111
|
]),
|
|
88
112
|
},
|
|
89
113
|
}}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
utils={{
|
|
115
|
+
indexesWithinAncestors: getIndexesWithinAncestors(
|
|
116
|
+
metas,
|
|
117
|
+
new Map(instances),
|
|
118
|
+
["root"]
|
|
119
|
+
),
|
|
120
|
+
executeComputingExpressions: (values) => {
|
|
121
|
+
const expressions = new Map<string, string>();
|
|
122
|
+
for (const dataSource of data.dataSources) {
|
|
123
|
+
const name = encodeDataSourceVariable(dataSource.id);
|
|
124
|
+
if (dataSource.type === "expression") {
|
|
125
|
+
expressions.set(name, dataSource.code);
|
|
126
|
+
}
|
|
96
127
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
return decodeVariablesMap(
|
|
129
|
+
executeComputingExpressions(
|
|
130
|
+
expressions,
|
|
131
|
+
encodeVariablesMap(values)
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
executeEffectfulExpression: (code, args, values) => {
|
|
136
|
+
return decodeVariablesMap(
|
|
137
|
+
executeEffectfulExpression(code, args, encodeVariablesMap(values))
|
|
138
|
+
);
|
|
139
|
+
},
|
|
106
140
|
}}
|
|
107
141
|
Component={WebstudioComponent}
|
|
108
142
|
components={new Map(Object.entries(components))}
|
|
109
|
-
indexesWithinAncestors={getIndexesWithinAncestors(
|
|
110
|
-
metas,
|
|
111
|
-
new Map(instances),
|
|
112
|
-
["root"]
|
|
113
|
-
)}
|
|
114
143
|
/>
|
|
115
144
|
</>
|
|
116
145
|
);
|
|
@@ -63,7 +63,7 @@ const WsComponentMeta = z.object({
|
|
|
63
63
|
// copied or dragged out of its parent instance
|
|
64
64
|
// true by default
|
|
65
65
|
detachable: z.optional(z.boolean()),
|
|
66
|
-
label: z.string(),
|
|
66
|
+
label: z.optional(z.string()),
|
|
67
67
|
description: z.string().optional(),
|
|
68
68
|
icon: z.string(),
|
|
69
69
|
presetStyle: z.optional(z.record(z.string(), EmbedTemplateStyleDecl)),
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { expect, test } from "@jest/globals";
|
|
2
|
+
import { generateUtilsExport } from "./generator";
|
|
3
|
+
|
|
4
|
+
test("generates utils", () => {
|
|
5
|
+
expect(
|
|
6
|
+
generateUtilsExport({
|
|
7
|
+
page: {
|
|
8
|
+
id: "",
|
|
9
|
+
path: "",
|
|
10
|
+
name: "",
|
|
11
|
+
title: "",
|
|
12
|
+
meta: {},
|
|
13
|
+
rootInstanceId: "tabs",
|
|
14
|
+
},
|
|
15
|
+
metas: new Map([
|
|
16
|
+
["Tabs", { type: "container", label: "", icon: "" }],
|
|
17
|
+
[
|
|
18
|
+
"TabsContent",
|
|
19
|
+
{
|
|
20
|
+
type: "container",
|
|
21
|
+
label: "",
|
|
22
|
+
icon: "",
|
|
23
|
+
indexWithinAncestor: "Tabs",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
]),
|
|
27
|
+
instances: new Map([
|
|
28
|
+
[
|
|
29
|
+
"tabs",
|
|
30
|
+
{
|
|
31
|
+
id: "tabs",
|
|
32
|
+
type: "instance",
|
|
33
|
+
component: "Tabs",
|
|
34
|
+
children: [
|
|
35
|
+
{ type: "id", value: "content1" },
|
|
36
|
+
{ type: "id", value: "content2" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
|
|
41
|
+
[
|
|
42
|
+
"content1",
|
|
43
|
+
{
|
|
44
|
+
id: "content1",
|
|
45
|
+
type: "instance",
|
|
46
|
+
component: "TabsContent",
|
|
47
|
+
children: [],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
[
|
|
52
|
+
"content2",
|
|
53
|
+
{
|
|
54
|
+
id: "content2",
|
|
55
|
+
type: "instance",
|
|
56
|
+
component: "TabsContent",
|
|
57
|
+
children: [],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
]),
|
|
61
|
+
props: new Map([
|
|
62
|
+
[
|
|
63
|
+
"open",
|
|
64
|
+
{
|
|
65
|
+
type: "dataSource",
|
|
66
|
+
id: "open",
|
|
67
|
+
instanceId: "tabs",
|
|
68
|
+
name: "open",
|
|
69
|
+
value: "tabsOpen",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
|
|
73
|
+
[
|
|
74
|
+
"onOpenChange",
|
|
75
|
+
{
|
|
76
|
+
type: "action",
|
|
77
|
+
id: "onOpenChange",
|
|
78
|
+
instanceId: "tabs",
|
|
79
|
+
name: "onOpenChange",
|
|
80
|
+
value: [
|
|
81
|
+
{
|
|
82
|
+
type: "execute",
|
|
83
|
+
args: ["open"],
|
|
84
|
+
code: `$ws$dataSource$tabsOpen = open`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
]),
|
|
90
|
+
dataSources: new Map([
|
|
91
|
+
[
|
|
92
|
+
"tabsOpen",
|
|
93
|
+
{
|
|
94
|
+
id: "tabsOpen",
|
|
95
|
+
name: "tabsOpen",
|
|
96
|
+
scopeInstanceId: "tabs",
|
|
97
|
+
type: "variable",
|
|
98
|
+
value: { type: "string", value: "0" },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
]),
|
|
102
|
+
})
|
|
103
|
+
).toMatchInlineSnapshot(`
|
|
104
|
+
"
|
|
105
|
+
/* eslint-disable */
|
|
106
|
+
|
|
107
|
+
const indexesWithinAncestors = new Map<string, number>([
|
|
108
|
+
["content1", 0],
|
|
109
|
+
["content2", 1],
|
|
110
|
+
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const rawExecuteComputingExpressions = (
|
|
114
|
+
_variables: Map<string, unknown>
|
|
115
|
+
): Map<string, unknown> => {
|
|
116
|
+
return new Map([
|
|
117
|
+
]);
|
|
118
|
+
};
|
|
119
|
+
const executeComputingExpressions = (variables: Map<string, unknown>) => {
|
|
120
|
+
const encodedvariables = sdk.encodeVariablesMap(variables);
|
|
121
|
+
const encodedResult = rawExecuteComputingExpressions(encodedvariables);
|
|
122
|
+
return sdk.decodeVariablesMap(encodedResult);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const generatedEffectfulExpressions = new Map<
|
|
126
|
+
string,
|
|
127
|
+
(args: Map<string, any>, variables: Map<string, any>) => Map<string, unknown>
|
|
128
|
+
>([
|
|
129
|
+
["$ws$dataSource$tabsOpen = open", (_args: Map<string, any>, _variables: Map<string, any>) => { let open = _args.get('open');
|
|
130
|
+
let $ws$dataSource$tabsOpen;
|
|
131
|
+
$ws$dataSource$tabsOpen = open;
|
|
132
|
+
return new Map([
|
|
133
|
+
['$ws$dataSource$tabsOpen', $ws$dataSource$tabsOpen],
|
|
134
|
+
]); })],
|
|
135
|
+
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const rawExecuteEffectfulExpression = (
|
|
139
|
+
code: string,
|
|
140
|
+
args: Map<string, unknown>,
|
|
141
|
+
variables: Map<string, unknown>
|
|
142
|
+
): Map<string, unknown> => {
|
|
143
|
+
if(generatedEffectfulExpressions.has(code)) {
|
|
144
|
+
return generatedEffectfulExpressions.get(code)!(args, variables);
|
|
145
|
+
}
|
|
146
|
+
console.error("Effectful expression not found", code);
|
|
147
|
+
throw new Error("Effectful expression not found");
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const executeEffectfulExpression = (
|
|
151
|
+
code: string,
|
|
152
|
+
args: Map<string, unknown>,
|
|
153
|
+
variables: Map<string, unknown>
|
|
154
|
+
) => {
|
|
155
|
+
const encodedvariables = sdk.encodeVariablesMap(variables);
|
|
156
|
+
const encodedResult = rawExecuteEffectfulExpression(code, args, encodedvariables);
|
|
157
|
+
return sdk.decodeVariablesMap(encodedResult);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const utils = {
|
|
161
|
+
indexesWithinAncestors,
|
|
162
|
+
executeComputingExpressions,
|
|
163
|
+
executeEffectfulExpression,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/* eslint-enable */
|
|
167
|
+
"
|
|
168
|
+
`);
|
|
169
|
+
});
|
package/src/generator.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataSources,
|
|
3
|
+
Instance,
|
|
4
|
+
Instances,
|
|
5
|
+
Page,
|
|
6
|
+
Props,
|
|
7
|
+
} from "@webstudio-is/project-build";
|
|
8
|
+
import type { WsComponentMeta } from "./components/component-meta";
|
|
9
|
+
import {
|
|
10
|
+
getIndexesWithinAncestors,
|
|
11
|
+
type IndexesWithinAncestors,
|
|
12
|
+
} from "./instance-utils";
|
|
13
|
+
import {
|
|
14
|
+
encodeDataSourceVariable,
|
|
15
|
+
generateComputingExpressions,
|
|
16
|
+
generateEffectfulExpression,
|
|
17
|
+
} from "./expression";
|
|
18
|
+
import type { DataSourceValues } from "./context";
|
|
19
|
+
|
|
20
|
+
type PageData = {
|
|
21
|
+
page: Page;
|
|
22
|
+
metas: Map<Instance["component"], WsComponentMeta>;
|
|
23
|
+
instances: Instances;
|
|
24
|
+
props: Props;
|
|
25
|
+
dataSources: DataSources;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type GeneratedUtils = {
|
|
29
|
+
indexesWithinAncestors: IndexesWithinAncestors;
|
|
30
|
+
executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
|
|
31
|
+
executeEffectfulExpression: (
|
|
32
|
+
expression: string,
|
|
33
|
+
args: DataSourceValues,
|
|
34
|
+
values: DataSourceValues
|
|
35
|
+
) => DataSourceValues;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates data based utilities at build time
|
|
40
|
+
* Requires this import statement in scope
|
|
41
|
+
* import * as sdk from "@webstudio-is/react-sdk";
|
|
42
|
+
*/
|
|
43
|
+
export const generateUtilsExport = (siteData: PageData) => {
|
|
44
|
+
const indexesWithinAncestors = getIndexesWithinAncestors(
|
|
45
|
+
siteData.metas,
|
|
46
|
+
siteData.instances,
|
|
47
|
+
[siteData.page.rootInstanceId]
|
|
48
|
+
);
|
|
49
|
+
let indexesWithinAncestorsEntries = "";
|
|
50
|
+
for (const [key, value] of indexesWithinAncestors) {
|
|
51
|
+
const keyString = JSON.stringify(key);
|
|
52
|
+
const valueString = JSON.stringify(value);
|
|
53
|
+
indexesWithinAncestorsEntries += `[${keyString}, ${valueString}],\n`;
|
|
54
|
+
}
|
|
55
|
+
const generatedIndexesWithinAncestors = `
|
|
56
|
+
const indexesWithinAncestors = new Map<string, number>([
|
|
57
|
+
${indexesWithinAncestorsEntries}
|
|
58
|
+
]);
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const variables = new Set<string>();
|
|
62
|
+
const expressions = new Map<string, string>();
|
|
63
|
+
for (const dataSource of siteData.dataSources.values()) {
|
|
64
|
+
if (dataSource.type === "variable") {
|
|
65
|
+
variables.add(encodeDataSourceVariable(dataSource.id));
|
|
66
|
+
}
|
|
67
|
+
if (dataSource.type === "expression") {
|
|
68
|
+
expressions.set(encodeDataSourceVariable(dataSource.id), dataSource.code);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const generatedExecuteComputingExpressions = `
|
|
72
|
+
const rawExecuteComputingExpressions = (
|
|
73
|
+
_variables: Map<string, unknown>
|
|
74
|
+
): Map<string, unknown> => {
|
|
75
|
+
${generateComputingExpressions(expressions, variables)}
|
|
76
|
+
};
|
|
77
|
+
const executeComputingExpressions = (variables: Map<string, unknown>) => {
|
|
78
|
+
const encodedvariables = sdk.encodeVariablesMap(variables);
|
|
79
|
+
const encodedResult = rawExecuteComputingExpressions(encodedvariables);
|
|
80
|
+
return sdk.decodeVariablesMap(encodedResult);
|
|
81
|
+
};
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
let effectfulExpressionsEntries = "";
|
|
85
|
+
for (const prop of siteData.props.values()) {
|
|
86
|
+
if (prop.type === "action") {
|
|
87
|
+
for (const executableValue of prop.value) {
|
|
88
|
+
const codeString = JSON.stringify(executableValue.code);
|
|
89
|
+
const generatedCode = generateEffectfulExpression(
|
|
90
|
+
executableValue.code,
|
|
91
|
+
new Set(executableValue.args),
|
|
92
|
+
variables
|
|
93
|
+
);
|
|
94
|
+
const generatedFunction = `(_args: Map<string, any>, _variables: Map<string, any>) => { ${generatedCode} })`;
|
|
95
|
+
|
|
96
|
+
effectfulExpressionsEntries += `[${codeString}, ${generatedFunction}],\n`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const generatedExecuteEffectfulExpression = `const generatedEffectfulExpressions = new Map<
|
|
101
|
+
string,
|
|
102
|
+
(args: Map<string, any>, variables: Map<string, any>) => Map<string, unknown>
|
|
103
|
+
>([
|
|
104
|
+
${effectfulExpressionsEntries}
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
const rawExecuteEffectfulExpression = (
|
|
108
|
+
code: string,
|
|
109
|
+
args: Map<string, unknown>,
|
|
110
|
+
variables: Map<string, unknown>
|
|
111
|
+
): Map<string, unknown> => {
|
|
112
|
+
if(generatedEffectfulExpressions.has(code)) {
|
|
113
|
+
return generatedEffectfulExpressions.get(code)!(args, variables);
|
|
114
|
+
}
|
|
115
|
+
console.error("Effectful expression not found", code);
|
|
116
|
+
throw new Error("Effectful expression not found");
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const executeEffectfulExpression = (
|
|
120
|
+
code: string,
|
|
121
|
+
args: Map<string, unknown>,
|
|
122
|
+
variables: Map<string, unknown>
|
|
123
|
+
) => {
|
|
124
|
+
const encodedvariables = sdk.encodeVariablesMap(variables);
|
|
125
|
+
const encodedResult = rawExecuteEffectfulExpression(code, args, encodedvariables);
|
|
126
|
+
return sdk.decodeVariablesMap(encodedResult);
|
|
127
|
+
};
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
return `
|
|
131
|
+
/* eslint-disable */
|
|
132
|
+
|
|
133
|
+
${generatedIndexesWithinAncestors.trim()}
|
|
134
|
+
|
|
135
|
+
${generatedExecuteComputingExpressions.trim()}
|
|
136
|
+
|
|
137
|
+
${generatedExecuteEffectfulExpression.trim()}
|
|
138
|
+
|
|
139
|
+
export const utils = {
|
|
140
|
+
indexesWithinAncestors,
|
|
141
|
+
executeComputingExpressions,
|
|
142
|
+
executeEffectfulExpression,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/* eslint-enable */
|
|
146
|
+
`;
|
|
147
|
+
};
|
package/src/hook.test.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect, test } from "@jest/globals";
|
|
2
|
+
import { getClosestInstance, type InstancePath } from ".";
|
|
3
|
+
|
|
4
|
+
test("get closest instance", () => {
|
|
5
|
+
const instancePath: InstancePath = [
|
|
6
|
+
{ type: "instance", id: "4", component: "Content", children: [] },
|
|
7
|
+
{ type: "instance", id: "3", component: "Tabs", children: [] },
|
|
8
|
+
{ type: "instance", id: "2", component: "Content", children: [] },
|
|
9
|
+
{ type: "instance", id: "1", component: "Tabs", children: [] },
|
|
10
|
+
{ type: "instance", id: "0", component: "Body", children: [] },
|
|
11
|
+
];
|
|
12
|
+
const [content2, tabs2, content1, tabs1, _body] = instancePath;
|
|
13
|
+
expect(getClosestInstance(instancePath, content2, "Tabs")).toBe(tabs2);
|
|
14
|
+
expect(getClosestInstance(instancePath, content1, "Tabs")).toBe(tabs1);
|
|
15
|
+
});
|
package/src/hook.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Instance, Prop } from "@webstudio-is/project-build";
|
|
2
|
+
import type { IndexesWithinAncestors } from "./instance-utils";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Hooks are subscriptions to builder events
|
|
@@ -7,6 +8,8 @@ import type { Instance, Prop } from "@webstudio-is/project-build";
|
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
export type HookContext = {
|
|
11
|
+
indexesWithinAncestors: IndexesWithinAncestors;
|
|
12
|
+
getPropValue: (instanceId: Instance["id"], propName: Prop["name"]) => unknown;
|
|
10
13
|
setPropVariable: (
|
|
11
14
|
instanceId: Instance["id"],
|
|
12
15
|
propName: Prop["name"],
|
|
@@ -14,10 +17,13 @@ export type HookContext = {
|
|
|
14
17
|
) => void;
|
|
15
18
|
};
|
|
16
19
|
|
|
17
|
-
export type
|
|
20
|
+
export type InstancePath = Instance[];
|
|
18
21
|
|
|
19
22
|
type NavigatorEvent = {
|
|
20
|
-
|
|
23
|
+
/**
|
|
24
|
+
* List of instances from selected to the root
|
|
25
|
+
*/
|
|
26
|
+
instancePath: InstancePath;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
export type Hook = {
|
|
@@ -25,13 +31,17 @@ export type Hook = {
|
|
|
25
31
|
onNavigatorUnselect?: (context: HookContext, event: NavigatorEvent) => void;
|
|
26
32
|
};
|
|
27
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Find closest matching instance by component name
|
|
36
|
+
* by lookup in instance path
|
|
37
|
+
*/
|
|
28
38
|
export const getClosestInstance = (
|
|
29
|
-
|
|
39
|
+
instancePath: InstancePath,
|
|
30
40
|
currentInstance: Instance,
|
|
31
41
|
closestComponent: Instance["component"]
|
|
32
42
|
) => {
|
|
33
43
|
let matched = false;
|
|
34
|
-
for (const instance of
|
|
44
|
+
for (const instance of instancePath) {
|
|
35
45
|
if (currentInstance === instance) {
|
|
36
46
|
matched = true;
|
|
37
47
|
}
|
package/src/index.ts
CHANGED
package/src/tree/root.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import { getPropsByInstanceId } from "../props";
|
|
22
22
|
import type { Components } from "../components/components-utils";
|
|
23
23
|
import type { Params, DataSourceValues } from "../context";
|
|
24
|
-
import type {
|
|
24
|
+
import type { GeneratedUtils } from "../generator";
|
|
25
25
|
|
|
26
26
|
export type Data = {
|
|
27
27
|
page: Page;
|
|
@@ -37,13 +37,7 @@ export type RootPropsData = Omit<Data, "build"> & {
|
|
|
37
37
|
|
|
38
38
|
type RootProps = {
|
|
39
39
|
data: RootPropsData;
|
|
40
|
-
|
|
41
|
-
executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
|
|
42
|
-
executeEffectfulExpression: (
|
|
43
|
-
expression: string,
|
|
44
|
-
args: DataSourceValues,
|
|
45
|
-
values: DataSourceValues
|
|
46
|
-
) => DataSourceValues;
|
|
40
|
+
utils: GeneratedUtils;
|
|
47
41
|
Component?: ForwardRefExoticComponent<
|
|
48
42
|
WebstudioComponentProps & RefAttributes<HTMLElement>
|
|
49
43
|
>;
|
|
@@ -53,13 +47,16 @@ type RootProps = {
|
|
|
53
47
|
|
|
54
48
|
export const InstanceRoot = ({
|
|
55
49
|
data,
|
|
56
|
-
|
|
57
|
-
executeComputingExpressions,
|
|
58
|
-
executeEffectfulExpression,
|
|
50
|
+
utils,
|
|
59
51
|
Component,
|
|
60
52
|
components,
|
|
61
53
|
scripts,
|
|
62
54
|
}: RootProps): JSX.Element | null => {
|
|
55
|
+
const {
|
|
56
|
+
indexesWithinAncestors,
|
|
57
|
+
executeComputingExpressions,
|
|
58
|
+
executeEffectfulExpression,
|
|
59
|
+
} = utils;
|
|
63
60
|
const dataSourceVariablesStoreRef = useRef<
|
|
64
61
|
undefined | WritableAtom<DataSourceValues>
|
|
65
62
|
>(undefined);
|