experimental: Allow json in template literals (#3886)

## Description
closes #3872


- [x] - Builder support
- [x] - Publish support

## Steps for reproduction

Example

https://add-json.staging.webstudio.is/builder/8160e16d-5ab4-4249-86de-0c98a64526a3

https://test-json-ki9yj.wstd.work/

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
Ivan Starkov authored and GitHub committed 2024-08-11 11:50:18 +03:00
1 parent 403d7b2328
commit 13da170ef4
38 files changed
+888 -173

No files matched your search

@@ -472,7 +472,7 @@ test("generate component with variables and actions", () => {
).toEqual(
clear(`
const Page = () => {
let [variableName, set$variableName] = useState<any>("initial")
let [variableName, set$variableName] = useVariableState<any>("initial")
return <Body>
<Input
value={variableName}
@@ -545,7 +545,7 @@ test("avoid generating collection parameter variable as state", () => {
).toEqual(
clear(`
const Page = () => {
let [data, set$data] = useState<any>(["apple","orange","mango"])
let [data, set$data] = useVariableState<any>(["apple","orange","mango"])
return <Body>
{data?.map((element: any, index: number) =>
<Fragment key={index}>
@@ -637,7 +637,7 @@ test("generate resources loading", () => {
).toEqual(
clear(`
const Page = () => {
let [data, set$data] = useState<any>("data")
let [data, set$data] = useVariableState<any>("data")
let data_1 = useResource("data_2")
return <Body
data-data={data}
@@ -702,7 +702,7 @@ test("avoid generating unused variables", () => {
})
).toMatchInlineSnapshot(`
"const Page = ({ }: { system: any; }) => {
let [UsedVariableName, set$UsedVariableName] = useState<any>("initial")
let [UsedVariableName, set$UsedVariableName] = useVariableState<any>("initial")
return <Body
data-data={UsedVariableName} />
}
@@ -771,21 +771,21 @@ test("generate conditional collection", () => {
),
})
).toMatchInlineSnapshot(`
"const Page = () => {
let [conditionName, set$conditionName] = useState<any>(false)
return <Body>
{(conditionName) &&
<>
{[]?.map((collectionItemName: any, index: number) =>
<Fragment key={index}>
</Fragment>
)}
</>
}
</Body>
}
"
`);
"const Page = () => {
let [conditionName, set$conditionName] = useVariableState<any>(false)
return <Body>
{(conditionName) &&
<>
{[]?.map((collectionItemName: any, index: number) =>
<Fragment key={index}>
</Fragment>
)}
</>
}
</Body>
}
"
`);
});
test("generate resource prop", () => {
@@ -424,7 +424,7 @@ export const generateWebstudioComponent = ({
);
const initialValue = dataSource.value.value;
const initialValueString = JSON.stringify(initialValue);
generatedDataSources += `let [${valueName}, ${setterName}] = useState<any>(${initialValueString})\n`;
generatedDataSources += `let [${valueName}, ${setterName}] = useVariableState<any>(${initialValueString})\n`;
}
if (dataSource.type === "resource") {
const valueName = scope.getName(dataSource.id, dataSource.name);
+11 -2
View File
@@ -1,5 +1,6 @@
import { createContext, useContext } from "react";
import { createContext, useContext, useMemo } from "react";
import type { ImageLoader } from "@webstudio-is/image";
import { createJsonStringifyProxy, isPlainObject } from "@webstudio-is/sdk";
export type Params = {
/**
@@ -49,5 +50,13 @@ export const ReactSdkContext = createContext<
export const useResource = (name: string) => {
const { resources } = useContext(ReactSdkContext);
return resources[name];
const resource = resources[name];
const resourceMemozied = useMemo(
() =>
isPlainObject(resource) ? createJsonStringifyProxy(resource) : resource,
[resource]
);
return resourceMemozied;
};
+1
View File
@@ -1,5 +1,6 @@
export * from "./context";
export * from "./hook";
export * from "./variable-state";
export const getIndexWithinAncestorFromComponentProps = (
props: Record<string, unknown>
+15
View File
@@ -0,0 +1,15 @@
import { createJsonStringifyProxy, isPlainObject } from "@webstudio-is/sdk";
import { useState, useMemo, type Dispatch, type SetStateAction } from "react";
export const useVariableState = <S,>(
initialState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>] => {
const [state, setState] = useState(initialState);
const value = useMemo(
() => (isPlainObject(state) ? createJsonStringifyProxy(state) : state),
[state]
);
return [value, setState];
};