Skip to content

Commit

Permalink
refactor: insert new component copy (#4648)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
TrySound authored Dec 24, 2024
1 parent e3baeec commit 1315731
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 432 deletions.
22 changes: 6 additions & 16 deletions apps/builder/app/builder/features/ai/apply-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) => {
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -154,7 +154,7 @@ const ComponentOptionsGroup = ({ options }: { options: ComponentOption[] }) => {
if (fragment) {
const insertable = findClosestInsertable(fragment);
if (insertable) {
insertTemplateData(fragment, insertable);
insertWebstudioFragmentAt(fragment, insertable);
}
}
}}
Expand Down
4 changes: 2 additions & 2 deletions apps/builder/app/builder/features/components/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -191,7 +191,7 @@ export const ComponentsPanel = ({
if (fragment) {
const insertable = findClosestInsertable(fragment);
if (insertable) {
insertTemplateData(fragment, insertable);
insertWebstudioFragmentAt(fragment, insertable);
}
}
};
Expand Down
8 changes: 6 additions & 2 deletions apps/builder/app/builder/features/marketplace/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 = ({
Expand Down
4 changes: 2 additions & 2 deletions apps/builder/app/canvas/shared/use-drag-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { publish, useSubscribe } from "~/shared/pubsub";
import {
getComponentTemplateData,
insertTemplateData,
insertWebstudioFragmentAt,
reparentInstance,
} from "~/shared/instance-utils";
import {
Expand Down Expand Up @@ -323,7 +323,7 @@ export const useDragAndDrop = () => {
if (templateData === undefined) {
return;
}
insertTemplateData(templateData, {
insertWebstudioFragmentAt(templateData, {
parentSelector: dropTarget.itemSelector,
position: dropTarget.indexWithinChildren,
});
Expand Down
22 changes: 7 additions & 15 deletions apps/builder/app/shared/copy-paste/plugin-embed-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
};
7 changes: 5 additions & 2 deletions apps/builder/app/shared/copy-paste/plugin-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -220,7 +223,7 @@ export const onPaste = async (clipboardData: string) => {
if (insertable === undefined) {
return false;
}
insertTemplateData(fragment, insertable);
insertWebstudioFragmentAt(fragment, insertable);
return true;
};

Expand Down
185 changes: 0 additions & 185 deletions apps/builder/app/shared/instance-utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Instance>([
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<Instance>([
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
Expand Down
Loading

0 comments on commit 1315731

Please sign in to comment.