From a76c29fc05d71acefa59f3f293eec82be697afef Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Thu, 18 Jan 2024 19:37:50 +0300 Subject: [PATCH] feat: inline variables outside current component/slot (#2778) Ref https://github.com/webstudio-is/webstudio/issues/2532 Important constrain on the way to custom components to not use data sources defined in other components when copy paste. ## Steps for reproduction 1. Test covers it --- apps/builder/app/builder/shared/commands.ts | 1 + .../shared/copy-paste/plugin-instance.test.ts | 65 ++++++++++++++++++- .../app/shared/copy-paste/plugin-instance.ts | 1 + apps/builder/app/shared/instance-utils.ts | 11 +++- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/apps/builder/app/builder/shared/commands.ts b/apps/builder/app/builder/shared/commands.ts index 25378a6616..2b4144590d 100644 --- a/apps/builder/app/builder/shared/commands.ts +++ b/apps/builder/app/builder/shared/commands.ts @@ -187,6 +187,7 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({ slice, availableDataSources: findAvailableDataSources( $dataSources.get(), + $instances.get(), parentInstanceSelector ), beforeTransactionEnd: (rootInstanceId, draft) => { diff --git a/apps/builder/app/shared/copy-paste/plugin-instance.test.ts b/apps/builder/app/shared/copy-paste/plugin-instance.test.ts index fbfa57b213..fbf3e75cb7 100644 --- a/apps/builder/app/shared/copy-paste/plugin-instance.test.ts +++ b/apps/builder/app/shared/copy-paste/plugin-instance.test.ts @@ -27,7 +27,7 @@ import { $registeredComponentMetas, $selectedPageId, } from "../nano-states"; -import { onCopy, onPaste } from "./plugin-instance"; +import { onCopy, onCut, onPaste } from "./plugin-instance"; import { createDefaultPages } from "@webstudio-is/project-build"; const expectString = expect.any(String) as unknown as string; @@ -629,3 +629,66 @@ test("wrap siblings with span when instance is rich text container", () => { ]) ); }); + +test("inline data source not available in portal when copy paste inside the portal", () => { + const instances = toMap([ + createInstance("body", "Body", [ + { type: "id", value: "box" }, + { type: "id", value: "portal" }, + ]), + createInstance("box", "Box", []), + createInstance("portal", portalComponent, [ + { type: "id", value: "fragment" }, + ]), + createInstance("fragment", "Fragment", []), + ]); + $instances.set(instances); + const dataSources = toMap([ + { + id: "variableId", + scopeInstanceId: "body", + name: "variableName", + type: "variable", + value: { type: "string", value: "value" }, + }, + ]); + $dataSources.set(dataSources); + const props = toMap([ + { + id: "propId", + instanceId: "box", + name: "data-value", + type: "expression", + value: "$ws$dataSource$variableId", + }, + ]); + $props.set(props); + $selectedInstanceSelector.set(["box", "body"]); + const clipboardData = onCut() ?? ""; + $selectedInstanceSelector.set(["fragment", "portal", "body"]); + onPaste(clipboardData); + expect($instances.get()).toEqual( + toMap([ + createInstance("body", "Body", [{ type: "id", value: "portal" }]), + createInstance("portal", portalComponent, [ + { type: "id", value: "fragment" }, + ]), + createInstance("fragment", "Fragment", [ + { type: "id", value: expectString }, + ]), + createInstance(expectString, "Box", []), + ]) + ); + expect($dataSources.get()).toEqual(dataSources); + expect($props.get()).toEqual( + toMap([ + { + id: expectString, + instanceId: expectString, + name: "data-value", + type: "expression", + value: `"value"`, + }, + ]) + ); +}); diff --git a/apps/builder/app/shared/copy-paste/plugin-instance.ts b/apps/builder/app/shared/copy-paste/plugin-instance.ts index 0bd5ec4d47..9afdabd38c 100644 --- a/apps/builder/app/shared/copy-paste/plugin-instance.ts +++ b/apps/builder/app/shared/copy-paste/plugin-instance.ts @@ -206,6 +206,7 @@ export const onPaste = (clipboardData: string): boolean => { slice: data, availableDataSources: findAvailableDataSources( $dataSources.get(), + $instances.get(), instanceSelector ), beforeTransactionEnd: (rootInstanceId, draft) => { diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index f7ad38ddfd..a5d3f0f413 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -722,9 +722,18 @@ export const getInstancesSlice = (rootInstanceId: string) => { export const findAvailableDataSources = ( dataSources: DataSources, + instances: Instances, instanceSelector: InstanceSelector ) => { - const instanceIds = new Set(instanceSelector); + // inline data sources not scoped to current portal + const instanceIds = new Set(); + for (const instanceId of instanceSelector) { + const instance = instances.get(instanceId); + if (instance?.component === portalComponent) { + break; + } + instanceIds.add(instanceId); + } const availableDataSources = new Set(); for (const { id, scopeInstanceId } of dataSources.values()) { if (scopeInstanceId && instanceIds.has(scopeInstanceId)) {