mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-05 03:28:59 +09:00
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:
1 parent
403d7b2328
commit
13da170ef4
38 files changed
+888
-173
No files matched your search
@@ -496,6 +496,7 @@ export const prebuild = async (options: {
|
||||
"useState",
|
||||
"Fragment",
|
||||
"useResource",
|
||||
"useVariableState",
|
||||
"Page",
|
||||
"_props",
|
||||
]);
|
||||
@@ -615,7 +616,7 @@ export const prebuild = async (options: {
|
||||
|
||||
import { Fragment, useState } from "react";
|
||||
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
|
||||
import { useResource } from "@webstudio-is/react-sdk/runtime";
|
||||
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
|
||||
${componentImports}
|
||||
|
||||
export const siteName = ${JSON.stringify(projectMeta?.siteName)};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
export * from "./context";
|
||||
export * from "./hook";
|
||||
export * from "./variable-state";
|
||||
|
||||
export const getIndexWithinAncestorFromComponentProps = (
|
||||
props: Record<string, unknown>
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
@@ -20,3 +20,4 @@ export * from "./resources-generator";
|
||||
export * from "./page-meta-generator";
|
||||
export * from "./url-pattern";
|
||||
export * from "./form-fields";
|
||||
export * from "./to-string";
|
||||
@@ -0,0 +1,27 @@
|
||||
export const createJsonStringifyProxy = <T extends object>(target: T): T => {
|
||||
return new Proxy(target, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "toString") {
|
||||
return function () {
|
||||
return JSON.stringify(target);
|
||||
};
|
||||
}
|
||||
|
||||
const value = Reflect.get(target, prop, receiver);
|
||||
|
||||
if (typeof value === "object" && value !== null) {
|
||||
return createJsonStringifyProxy(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const isPlainObject = (value: unknown): value is object => {
|
||||
return (
|
||||
Object.prototype.toString.call(value) === "[object Object]" &&
|
||||
(Object.getPrototypeOf(value) === null ||
|
||||
Object.getPrototypeOf(value) === Object.prototype)
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user