From 1315731c76c545b0a67061e12abfafcbd2534bf2 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 24 Dec 2024 23:08:55 +0700 Subject: [PATCH] refactor: insert new component copy (#4648) Our copy paste is able to merge breakpoints so we can leverage it when generate new component template. Here instead of passing base breakpoint id I invoke insertWebstudioFragmentCopy with generated breakpoint which is merged into project one. Also switched plugin-embed, plugin-markdown and ai to use the same insertWebstudioFragmentCopy utility. --- .../builder/features/ai/apply-operations.ts | 22 +-- .../features/command-panel/command-panel.tsx | 4 +- .../features/components/components.tsx | 4 +- .../features/marketplace/templates.tsx | 8 +- .../app/canvas/shared/use-drag-drop.ts | 4 +- .../copy-paste/plugin-embed-template.ts | 22 +-- .../app/shared/copy-paste/plugin-markdown.ts | 7 +- .../app/shared/instance-utils.test.tsx | 185 ------------------ apps/builder/app/shared/instance-utils.ts | 89 ++------- apps/builder/app/shared/tree-utils.test.ts | 43 ---- apps/builder/app/shared/tree-utils.ts | 26 --- .../react-sdk/src/embed-template.test.tsx | 96 +++++---- packages/react-sdk/src/embed-template.ts | 14 +- packages/sdk-cli/src/generate-stories.ts | 9 +- 14 files changed, 101 insertions(+), 432 deletions(-) diff --git a/apps/builder/app/builder/features/ai/apply-operations.ts b/apps/builder/app/builder/features/ai/apply-operations.ts index 7b48428ec1e3..a39d674eb11a 100644 --- a/apps/builder/app/builder/features/ai/apply-operations.ts +++ b/apps/builder/app/builder/features/ai/apply-operations.ts @@ -6,9 +6,10 @@ import { serverSyncStore } from "~/shared/sync"; import { isBaseBreakpoint } from "~/shared/breakpoints"; import { deleteInstanceMutable, - insertTemplateData, + insertWebstudioFragmentAt, isInstanceDetachable, updateWebstudioData, + type Insertable, } from "~/shared/instance-utils"; import { $breakpoints, @@ -19,7 +20,7 @@ import { $styleSources, $styles, } from "~/shared/nano-states"; -import type { DroppableTarget, InstanceSelector } from "~/shared/tree-utils"; +import type { InstanceSelector } from "~/shared/tree-utils"; import { $selectedInstance } from "~/shared/awareness"; export const applyOperations = (operations: operations.WsOperations) => { @@ -45,18 +46,8 @@ export const applyOperations = (operations: operations.WsOperations) => { const insertTemplateByOp = ( operation: operations.generateInsertTemplateWsOperation ) => { - const breakpoints = $breakpoints.get(); - const breakpointValues = Array.from(breakpoints.values()); - const baseBreakpoint = breakpointValues.find(isBaseBreakpoint); - if (baseBreakpoint === undefined) { - return false; - } const metas = $registeredComponentMetas.get(); - const templateData = generateDataFromEmbedTemplate( - operation.template, - metas, - baseBreakpoint.id - ); + const templateData = generateDataFromEmbedTemplate(operation.template, metas); // @todo Find a way to avoid the workaround below, peharps improving the prompt. // Occasionally the LLM picks a component name or the entire data-ws-id attribute as the insertion point. @@ -87,12 +78,11 @@ const insertTemplateByOp = ( return; } - const dropTarget: DroppableTarget = { + const dropTarget: Insertable = { parentSelector: instanceSelector, position: operation.addAtIndex + 1, }; - - insertTemplateData(templateData, dropTarget); + insertWebstudioFragmentAt(templateData, dropTarget); return rootInstanceIds; } }; diff --git a/apps/builder/app/builder/features/command-panel/command-panel.tsx b/apps/builder/app/builder/features/command-panel/command-panel.tsx index 374ae2ca30c0..38c51d79096e 100644 --- a/apps/builder/app/builder/features/command-panel/command-panel.tsx +++ b/apps/builder/app/builder/features/command-panel/command-panel.tsx @@ -39,7 +39,7 @@ import { findClosestInsertable, getComponentTemplateData, getInstanceLabel, - insertTemplateData, + insertWebstudioFragmentAt, } from "~/shared/instance-utils"; import { humanizeString } from "~/shared/string-utils"; import { setCanvasWidth } from "~/builder/features/breakpoints"; @@ -154,7 +154,7 @@ const ComponentOptionsGroup = ({ options }: { options: ComponentOption[] }) => { if (fragment) { const insertable = findClosestInsertable(fragment); if (insertable) { - insertTemplateData(fragment, insertable); + insertWebstudioFragmentAt(fragment, insertable); } } }} diff --git a/apps/builder/app/builder/features/components/components.tsx b/apps/builder/app/builder/features/components/components.tsx index d040bb6e8911..5210a8cec780 100644 --- a/apps/builder/app/builder/features/components/components.tsx +++ b/apps/builder/app/builder/features/components/components.tsx @@ -38,7 +38,7 @@ import { findClosestInsertable, getComponentTemplateData, getInstanceLabel, - insertTemplateData, + insertWebstudioFragmentAt, } from "~/shared/instance-utils"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; import { matchSorter } from "match-sorter"; @@ -191,7 +191,7 @@ export const ComponentsPanel = ({ if (fragment) { const insertable = findClosestInsertable(fragment); if (insertable) { - insertTemplateData(fragment, insertable); + insertWebstudioFragmentAt(fragment, insertable); } } }; diff --git a/apps/builder/app/builder/features/marketplace/templates.tsx b/apps/builder/app/builder/features/marketplace/templates.tsx index 4a2ae194be74..6835c6698eaf 100644 --- a/apps/builder/app/builder/features/marketplace/templates.tsx +++ b/apps/builder/app/builder/features/marketplace/templates.tsx @@ -23,7 +23,8 @@ import { CollapsibleSection } from "~/builder/shared/collapsible-section"; import { builderUrl } from "~/shared/router-utils"; import { extractWebstudioFragment, - findTargetAndInsertFragment, + findClosestInsertable, + insertWebstudioFragmentAt, updateWebstudioData, } from "~/shared/instance-utils"; import { insertPageCopyMutable } from "~/shared/page-utils"; @@ -47,7 +48,10 @@ const insertSection = ({ fragment.instances = fragment.instances.filter( (instance) => instance.component !== "Body" ); - findTargetAndInsertFragment(fragment); + const insertable = findClosestInsertable(fragment); + if (insertable) { + insertWebstudioFragmentAt(fragment, insertable); + } }; const insertPage = ({ diff --git a/apps/builder/app/canvas/shared/use-drag-drop.ts b/apps/builder/app/canvas/shared/use-drag-drop.ts index 89840ee357c9..8852f8f50e66 100644 --- a/apps/builder/app/canvas/shared/use-drag-drop.ts +++ b/apps/builder/app/canvas/shared/use-drag-drop.ts @@ -16,7 +16,7 @@ import { import { publish, useSubscribe } from "~/shared/pubsub"; import { getComponentTemplateData, - insertTemplateData, + insertWebstudioFragmentAt, reparentInstance, } from "~/shared/instance-utils"; import { @@ -323,7 +323,7 @@ export const useDragAndDrop = () => { if (templateData === undefined) { return; } - insertTemplateData(templateData, { + insertWebstudioFragmentAt(templateData, { parentSelector: dropTarget.itemSelector, position: dropTarget.indexWithinChildren, }); diff --git a/apps/builder/app/shared/copy-paste/plugin-embed-template.ts b/apps/builder/app/shared/copy-paste/plugin-embed-template.ts index 3babb0bce82d..834e34363952 100644 --- a/apps/builder/app/shared/copy-paste/plugin-embed-template.ts +++ b/apps/builder/app/shared/copy-paste/plugin-embed-template.ts @@ -3,9 +3,11 @@ import { WsEmbedTemplate, generateDataFromEmbedTemplate, } from "@webstudio-is/react-sdk"; -import { $registeredComponentMetas, $breakpoints } from "../nano-states"; -import { findClosestInsertable, insertTemplateData } from "../instance-utils"; -import { isBaseBreakpoint } from "../breakpoints"; +import { $registeredComponentMetas } from "../nano-states"; +import { + findClosestInsertable, + insertWebstudioFragmentAt, +} from "../instance-utils"; const version = "@webstudio/template"; @@ -28,21 +30,11 @@ export const onPaste = (clipboardData: string) => { return false; } const metas = $registeredComponentMetas.get(); - const breakpoints = $breakpoints.get(); - const breakpointValues = Array.from(breakpoints.values()); - const baseBreakpoint = breakpointValues.find(isBaseBreakpoint); - if (baseBreakpoint === undefined) { - return false; - } - const fragment = generateDataFromEmbedTemplate( - template, - metas, - baseBreakpoint.id - ); + const fragment = generateDataFromEmbedTemplate(template, metas); const insertable = findClosestInsertable(fragment); if (insertable === undefined) { return false; } - insertTemplateData(fragment, insertable); + insertWebstudioFragmentAt(fragment, insertable); return true; }; diff --git a/apps/builder/app/shared/copy-paste/plugin-markdown.ts b/apps/builder/app/shared/copy-paste/plugin-markdown.ts index 31e7bc72fb2f..3a53f8fbec20 100644 --- a/apps/builder/app/shared/copy-paste/plugin-markdown.ts +++ b/apps/builder/app/shared/copy-paste/plugin-markdown.ts @@ -7,7 +7,10 @@ import type { Instance, WebstudioFragment, } from "@webstudio-is/sdk"; -import { findClosestInsertable, insertTemplateData } from "../instance-utils"; +import { + findClosestInsertable, + insertWebstudioFragmentAt, +} from "../instance-utils"; import { $breakpoints } from "../nano-states"; import { isBaseBreakpoint } from "../breakpoints"; import { denormalizeSrcProps } from "./asset-upload"; @@ -220,7 +223,7 @@ export const onPaste = async (clipboardData: string) => { if (insertable === undefined) { return false; } - insertTemplateData(fragment, insertable); + insertWebstudioFragmentAt(fragment, insertable); return true; }; diff --git a/apps/builder/app/shared/instance-utils.test.tsx b/apps/builder/app/shared/instance-utils.test.tsx index 84f6455579d3..cf785ec8e98b 100644 --- a/apps/builder/app/shared/instance-utils.test.tsx +++ b/apps/builder/app/shared/instance-utils.test.tsx @@ -33,7 +33,6 @@ import { encodeDataSourceVariable, getStyleDeclKey } from "@webstudio-is/sdk"; import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine"; import { findClosestEditableInstanceSelector, - insertTemplateData, deleteInstanceMutable, extractWebstudioFragment, insertWebstudioFragmentCopy, @@ -436,190 +435,6 @@ describe("insert instance children", () => { }); }); -test("insert template data with instances", () => { - $instances.set(toMap([createInstance("body", "Body", [])])); - $styleSourceSelections.set(new Map()); - $styleSources.set(toMap([{ type: "token", id: "1", name: "Zero" }])); - $styles.set( - new Map([ - [ - "1:base:color:", - { - breakpointId: "base", - styleSourceId: "1", - property: "color", - value: { type: "keyword", value: "red" }, - }, - ], - ]) - ); - insertTemplateData( - { - children: [{ type: "id", value: "box" }], - instances: [createInstance("box", "Box", [])], - props: [], - dataSources: [], - styleSourceSelections: [], - styleSources: [], - styles: [], - assets: [], - resources: [], - breakpoints: [], - }, - { parentSelector: ["body"], position: "end" } - ); - expect($instances.get()).toEqual( - toMap([ - createInstance("body", "Body", [{ type: "id", value: "box" }]), - createInstance("box", "Box", []), - ]) - ); -}); - -test("insert template inside text-only instance should wrap the text into Text instance", () => { - $instances.set( - toMap([ - createInstance("body", "Body", [ - { - type: "id", - value: "heading", - }, - ]), - createInstance("heading", "Heading", [ - { type: "text", value: "Heading text" }, - ]), - ]) - ); - insertTemplateData( - { - children: [{ type: "id", value: "box" }], - instances: [createInstance("box", "Box", [])], - props: [], - dataSources: [], - styleSourceSelections: [], - styleSources: [], - styles: [], - assets: [], - resources: [], - breakpoints: [], - }, - { parentSelector: ["heading", "body"], position: "end" } - ); - - expect($instances.get()).toEqual( - toMap([ - createInstance("body", "Body", [ - { - type: "id", - value: "heading", - }, - ]), - createInstance("heading", "Heading", [ - { - type: "id", - value: expect.not.stringMatching("text") as unknown as string, - }, - { type: "id", value: "box" }, - ]), - createInstance( - expect.not.stringMatching("text") as unknown as string, - "Text", - [{ type: "text", value: "Heading text" }] - ), - createInstance("box", "Box", []), - ]) - ); -}); - -test("insert template data with only new style sources", () => { - $instances.set(new Map([createInstancePair("body", "Body", [])])); - $styleSourceSelections.set(new Map()); - $styleSources.set(new Map([["1", { type: "token", id: "1", name: "Zero" }]])); - $styles.set( - new Map([ - [ - "1:base:color:", - { - breakpointId: "base", - styleSourceId: "1", - property: "color", - value: { type: "keyword", value: "red" }, - }, - ], - ]) - ); - insertTemplateData( - { - children: [{ type: "id", value: "box1" }], - instances: [ - { type: "instance", id: "box1", component: "Box", children: [] }, - ], - props: [], - dataSources: [], - styleSourceSelections: [{ instanceId: "box1", values: ["1", "2"] }], - styleSources: [ - { type: "token", id: "1", name: "One" }, - { type: "token", id: "2", name: "Two" }, - ], - styles: [ - { - breakpointId: "base", - styleSourceId: "1", - property: "color", - value: { type: "keyword", value: "black" }, - }, - { - breakpointId: "base", - styleSourceId: "1", - property: "backgroundColor", - value: { type: "keyword", value: "purple" }, - }, - { - breakpointId: "base", - styleSourceId: "2", - property: "color", - value: { type: "keyword", value: "green" }, - }, - ], - assets: [], - resources: [], - breakpoints: [], - }, - { parentSelector: ["body"], position: "end" } - ); - expect($styleSourceSelections.get()).toEqual( - new Map([["box1", { instanceId: "box1", values: ["1", "2"] }]]) - ); - expect($styleSources.get()).toEqual( - new Map([ - ["1", { type: "token", id: "1", name: "Zero" }], - ["2", { type: "token", id: "2", name: "Two" }], - ]) - ); - expect($styles.get()).toEqual( - new Map([ - [ - "1:base:color:", - { - breakpointId: "base", - styleSourceId: "1", - property: "color", - value: { type: "keyword", value: "red" }, - }, - ], - [ - "2:base:color:", - { - breakpointId: "base", - styleSourceId: "2", - property: "color", - value: { type: "keyword", value: "green" }, - }, - ], - ]) - ); -}); - describe("reparent instance", () => { test("between instances", () => { // body diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index 6b9e3e8b8df7..91666263a2b6 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -51,13 +51,11 @@ import { type InstanceSelector, findLocalStyleSourcesWithinInstances, getAncestorInstanceSelector, - insertPropsCopyMutable, getReparentDropTargetMutable, getInstanceOrCreateFragmentIfNecessary, wrapEditableChildrenAroundDropTargetMutable, } from "./tree-utils"; import { removeByMutable } from "./array-utils"; -import { isBaseBreakpoint } from "./breakpoints"; import { humanizeString } from "./string-utils"; import { serverSyncStore } from "./sync"; import { setDifference, setUnion } from "./shim"; @@ -157,7 +155,7 @@ const getLabelFromComponentName = (component: Instance["component"]) => { export const getInstanceLabel = ( instance: { component: string; label?: string }, - meta: WsComponentMeta + meta: { label?: string } ) => { return ( instance.label || @@ -314,13 +312,11 @@ export const insertInstanceChildrenMutable = ( } }; -export const findTargetAndInsertFragment = (fragment: WebstudioFragment) => { - let isSuccess = false; - const insertable = findClosestInsertable(fragment); - if (insertable === undefined) { - return isSuccess; - } - +export const insertWebstudioFragmentAt = ( + fragment: WebstudioFragment, + insertable: Insertable +) => { + let newRootInstanceId: undefined | Instance["id"]; updateWebstudioData((data) => { const { newInstanceIds } = insertWebstudioFragmentCopy({ data, @@ -331,75 +327,18 @@ export const findTargetAndInsertFragment = (fragment: WebstudioFragment) => { insertable.parentSelector ), }); - const newRootInstanceId = newInstanceIds.get(fragment.instances[0].id); + newRootInstanceId = newInstanceIds.get(fragment.instances[0].id); if (newRootInstanceId === undefined) { - isSuccess = false; return; } const children: Instance["children"] = [ { type: "id", value: newRootInstanceId }, ]; insertInstanceChildrenMutable(data, children, insertable); - isSuccess = true; }); - - return isSuccess; -}; - -export const insertTemplateData = ( - templateData: WebstudioFragment, - dropTarget: DroppableTarget -) => { - const { - children, - instances: insertedInstances, - props: insertedProps, - dataSources: insertedDataSources, - } = templateData; - const rootInstanceId = insertedInstances[0].id; - updateWebstudioData((data) => { - const { - instances, - dataSources, - props, - styleSourceSelections, - styleSources, - styles, - } = data; - for (const instance of insertedInstances) { - instances.set(instance.id, instance); - } - insertInstanceChildrenMutable(data, children, dropTarget); - insertPropsCopyMutable(props, insertedProps, new Map()); - for (const dataSource of insertedDataSources) { - dataSources.set(dataSource.id, dataSource); - } - - // insert only new style sources and their styles to support - // embed template tokens which have persistent id - // so when user changes these styles and then again add component with token - // nothing breaks visually - const insertedStyleSources = new Set(); - for (const styleSource of templateData.styleSources) { - if (styleSources.has(styleSource.id) === false) { - insertedStyleSources.add(styleSource.id); - styleSources.set(styleSource.id, styleSource); - } - } - for (const styleDecl of templateData.styles) { - if (insertedStyleSources.has(styleDecl.styleSourceId)) { - styles.set(getStyleDeclKey(styleDecl), styleDecl); - } - } - for (const styleSourceSelection of templateData.styleSourceSelections) { - styleSourceSelections.set( - styleSourceSelection.instanceId, - styleSourceSelection - ); - } - }); - - selectInstance([rootInstanceId, ...dropTarget.parentSelector]); + if (newRootInstanceId) { + selectInstance([newRootInstanceId, ...insertable.parentSelector]); + } }; export const getComponentTemplateData = (component: string) => { @@ -413,13 +352,7 @@ export const getComponentTemplateData = (component: string) => { children: [], }, ]; - const breakpoints = $breakpoints.get(); - const breakpointValues = Array.from(breakpoints.values()); - const baseBreakpoint = breakpointValues.find(isBaseBreakpoint); - if (baseBreakpoint === undefined) { - return; - } - return generateDataFromEmbedTemplate(template, metas, baseBreakpoint.id); + return generateDataFromEmbedTemplate(template, metas); }; export const reparentInstance = ( diff --git a/apps/builder/app/shared/tree-utils.test.ts b/apps/builder/app/shared/tree-utils.test.ts index 2eb71ac9f07b..b2ed7ad3fdb3 100644 --- a/apps/builder/app/shared/tree-utils.test.ts +++ b/apps/builder/app/shared/tree-utils.test.ts @@ -1,7 +1,6 @@ import { test, expect } from "vitest"; import type { Instance, - Prop, StyleDecl, Styles, StyleSource, @@ -13,22 +12,9 @@ import { cloneStyles, findLocalStyleSourcesWithinInstances, getAncestorInstanceSelector, - insertPropsCopyMutable, isDescendantOrSelf, } from "./tree-utils"; -const expectString = expect.any(String) as unknown as string; - -const createProp = (id: string, instanceId: string, value?: string): Prop => { - return { - id, - instanceId, - type: "string", - name: "prop", - value: value ?? "value", - }; -}; - const createStyleSource = ( type: StyleSource["type"], id: StyleSource["id"] @@ -101,35 +87,6 @@ test("get ancestor instance selector", () => { expect(getAncestorInstanceSelector(instanceSelector, "1")).toEqual(["1"]); }); -test("insert props copy with new ids and apply new instance ids", () => { - const props = new Map([ - ["prop1", createProp("prop1", "instance1")], - ["prop2", createProp("prop2", "instance2")], - ["prop3", createProp("prop3", "instance3")], - ["existingSharedProp", createProp("existingSharedProp", "instance3")], - ]); - const copiedProps = [ - createProp("prop2", "instance2"), - createProp("sharedProp", "instance3", "newValue"), - createProp("existingSharedProp", "instance3", "newValue"), - ]; - const clonedInstanceIds = new Map([ - ["instance2", "newInstance2"], - ]); - insertPropsCopyMutable(props, copiedProps, clonedInstanceIds); - expect(Array.from(props.entries())).toEqual([ - ["prop1", createProp("prop1", "instance1")], - ["prop2", createProp("prop2", "instance2")], - ["prop3", createProp("prop3", "instance3")], - // shared prop is not overriden - ["existingSharedProp", createProp("existingSharedProp", "instance3")], - // new props are copied - [expectString, createProp(expectString, "newInstance2")], - // shared prop inserted without changes - ["sharedProp", createProp("sharedProp", "instance3", "newValue")], - ]); -}); - test("clone styles with appled new style source ids", () => { const styles: Styles = new Map([ createStyleDeclPair("styleSource1", "bp1"), diff --git a/apps/builder/app/shared/tree-utils.ts b/apps/builder/app/shared/tree-utils.ts index 6a1e617e04e3..dd29736bc3c4 100644 --- a/apps/builder/app/shared/tree-utils.ts +++ b/apps/builder/app/shared/tree-utils.ts @@ -350,29 +350,3 @@ export const findLocalStyleSourcesWithinInstances = ( return subtreeLocalStyleSourceIds; }; - -export const insertPropsCopyMutable = ( - props: Props, - copiedProps: Prop[], - copiedInstanceIds: Map -) => { - for (const prop of copiedProps) { - const newInstanceId = copiedInstanceIds.get(prop.instanceId); - // insert without changes when instance does not have new id - if (newInstanceId === undefined) { - // prevent overriding shared props if already exist - if (props.has(prop.id) === false) { - props.set(prop.id, prop); - } - continue; - } - - // copy prop before inserting - const newPropId = nanoid(); - props.set(newPropId, { - ...prop, - id: newPropId, - instanceId: newInstanceId, - }); - } -}; diff --git a/packages/react-sdk/src/embed-template.test.tsx b/packages/react-sdk/src/embed-template.test.tsx index 780dad61759a..e9c57496831f 100644 --- a/packages/react-sdk/src/embed-template.test.tsx +++ b/packages/react-sdk/src/embed-template.test.tsx @@ -4,8 +4,6 @@ import { showAttribute } from "./props"; const expectString = expect.any(String); -const defaultBreakpointId = "base"; - test("generate data for embedding from instances and text", () => { expect( generateDataFromEmbedTemplate( @@ -20,8 +18,7 @@ test("generate data for embedding from instances and text", () => { ], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [ @@ -77,8 +74,7 @@ test("generate data for embedding from props", () => { ], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [{ type: "id", value: expectString }], @@ -130,35 +126,34 @@ test("generate data for embedding from props", () => { }); test("generate data for embedding from styles", () => { - expect( - generateDataFromEmbedTemplate( - [ - { - type: "instance", - component: "Box1", - styles: [ - { property: "width", value: { type: "keyword", value: "auto" } }, - { property: "height", value: { type: "keyword", value: "auto" } }, - ], - children: [ - { - type: "instance", - component: "Box2", - styles: [ - { - property: "color", - value: { type: "keyword", value: "black" }, - }, - ], - children: [], - }, - ], - }, - ], - new Map(), - defaultBreakpointId - ) - ).toEqual({ + const fragment = generateDataFromEmbedTemplate( + [ + { + type: "instance", + component: "Box1", + styles: [ + { property: "width", value: { type: "keyword", value: "auto" } }, + { property: "height", value: { type: "keyword", value: "auto" } }, + ], + children: [ + { + type: "instance", + component: "Box2", + styles: [ + { + property: "color", + value: { type: "keyword", value: "black" }, + }, + ], + children: [], + }, + ], + }, + ], + new Map() + ); + const baseBreakpointId = fragment.breakpoints[0].id; + expect(fragment).toEqual({ children: [{ type: "id", value: expectString }], instances: [ { @@ -198,21 +193,21 @@ test("generate data for embedding from styles", () => { ], styles: [ { - breakpointId: "base", + breakpointId: baseBreakpointId, styleSourceId: expectString, state: undefined, property: "width", value: { type: "keyword", value: "auto" }, }, { - breakpointId: "base", + breakpointId: baseBreakpointId, styleSourceId: expectString, state: undefined, property: "height", value: { type: "keyword", value: "auto" }, }, { - breakpointId: "base", + breakpointId: baseBreakpointId, styleSourceId: expectString, state: undefined, property: "color", @@ -220,7 +215,12 @@ test("generate data for embedding from styles", () => { }, ], assets: [], - breakpoints: [], + breakpoints: [ + { + id: baseBreakpointId, + label: "", + }, + ], resources: [], }); }); @@ -257,8 +257,7 @@ test("generate data for embedding from props bound to data source variables", () children: [], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [ @@ -319,8 +318,7 @@ test("generate variables with aliases instead of reference name", () => { children: [], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [{ type: "id", value: expectString }], @@ -381,8 +379,7 @@ test("generate data for embedding from props with complex expressions", () => { children: [], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [ @@ -474,8 +471,7 @@ test("generate data for embedding from action props", () => { ], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [{ type: "id", value: expectString }], @@ -566,8 +562,7 @@ test("generate data for embedding from parameter props", () => { children: [], }, ], - new Map(), - defaultBreakpointId + new Map() ); const instanceId = data.instances[0].id; const variableId = data.dataSources[0].id; @@ -634,8 +629,7 @@ test("generate data for embedding from instance child bound to variables", () => children: [{ type: "expression", value: "myValue" }], }, ], - new Map(), - defaultBreakpointId + new Map() ) ).toEqual({ children: [{ type: "id", value: expectString }], diff --git a/packages/react-sdk/src/embed-template.ts b/packages/react-sdk/src/embed-template.ts index 9d7546398d73..cb948f2c7f71 100644 --- a/packages/react-sdk/src/embed-template.ts +++ b/packages/react-sdk/src/embed-template.ts @@ -354,7 +354,6 @@ const createInstancesFromTemplate = ( export const generateDataFromEmbedTemplate = ( treeTemplate: WsEmbedTemplate, metas: Map, - defaultBreakpointId: Breakpoint["id"], generateId: () => string = nanoid ): WebstudioFragment => { const instances: Instance[] = []; @@ -363,6 +362,7 @@ export const generateDataFromEmbedTemplate = ( const styleSourceSelections: StyleSourceSelection[] = []; const styleSources: StyleSource[] = []; const styles: StyleDecl[] = []; + const baseBreakpointId = generateId(); const children = createInstancesFromTemplate( treeTemplate, @@ -373,9 +373,17 @@ export const generateDataFromEmbedTemplate = ( styleSources, styles, metas, - defaultBreakpointId, + baseBreakpointId, generateId ); + const breakpoints: Breakpoint[] = []; + // will be merged into project base breakpoint + if (styles.length > 0) { + breakpoints.push({ + id: baseBreakpointId, + label: "", + }); + } return { children, @@ -385,8 +393,8 @@ export const generateDataFromEmbedTemplate = ( styleSourceSelections, styleSources, styles, + breakpoints, assets: [], - breakpoints: [], resources: [], }; }; diff --git a/packages/sdk-cli/src/generate-stories.ts b/packages/sdk-cli/src/generate-stories.ts index 8d2db70a8e0b..9b79a3b486c7 100644 --- a/packages/sdk-cli/src/generate-stories.ts +++ b/packages/sdk-cli/src/generate-stories.ts @@ -108,13 +108,11 @@ export const generateStories = async () => { continue; } const rootInstanceId = "root"; - const baseBreakpointId = "base"; let id = 0; const generateStableId = () => (++id).toString(); const data = generateDataFromEmbedTemplate( meta.template, metas, - baseBreakpointId, generateStableId ); const instances: Instances = new Map([ @@ -133,6 +131,9 @@ export const generateStories = async () => { ), ]); const props = new Map(data.props.map((prop) => [prop.id, prop])); + const breakpoints = new Map( + data.breakpoints.map((breakpoint) => [breakpoint.id, breakpoint]) + ); const components = new Set(); const usedMetas = new Map(); const bodyMeta = baseMetas.get("Body"); @@ -152,9 +153,7 @@ export const generateStories = async () => { instances, props, assets: new Map(), - breakpoints: new Map([ - [baseBreakpointId, { id: baseBreakpointId, label: "base" }], - ]), + breakpoints, styles: new Map(data.styles.map((item) => [getStyleDeclKey(item), item])), styleSourceSelections: new Map( data.styleSourceSelections.map((item) => [item.instanceId, item])