From 8d95c14f28bb228afe60152ee5cd0c3ea68a177a Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sat, 28 Jan 2023 19:28:07 +0300 Subject: [PATCH] Fix copy paste of props and styles (#885) Copy/paste of props and styles was broken while converting to normalized data. Here fixed both by providing new instanceId. --- apps/designer/app/canvas/canvas.tsx | 9 +++-- .../wrapper-component/wrapper-component.tsx | 11 ++---- apps/designer/app/canvas/shared/instance.ts | 20 ++++++++--- apps/designer/app/designer/designer.tsx | 9 +++-- .../app/shared/copy-paste/serialize.ts | 5 +-- .../copy-paste/use-instance-copy-paste.ts | 36 ++++++++++++------- .../app/shared/nano-states/nano-states.ts | 12 +++++++ packages/project/src/index.ts | 2 -- .../src/shared/props/clone-user-props.ts | 5 --- packages/project/src/shared/props/index.ts | 1 - 10 files changed, 68 insertions(+), 42 deletions(-) delete mode 100644 packages/project/src/shared/props/clone-user-props.ts delete mode 100644 packages/project/src/shared/props/index.ts diff --git a/apps/designer/app/canvas/canvas.tsx b/apps/designer/app/canvas/canvas.tsx index 8c512da926..f2500670e7 100644 --- a/apps/designer/app/canvas/canvas.tsx +++ b/apps/designer/app/canvas/canvas.tsx @@ -30,6 +30,7 @@ import { selectedInstanceStore, useBreakpoints, useInstanceProps, + useInstanceStyles, useRootInstance, useSetBreakpoints, useSetDesignTokens, @@ -101,15 +102,17 @@ const useAssets = (initialAssets: Array) => { const useCopyPaste = () => { const selectedInstance = useStore(selectedInstanceStore); const instanceProps = useInstanceProps(selectedInstance?.id); + const instanceStyles = useInstanceStyles(selectedInstance?.id); const selectedInstanceData = useMemo(() => { if (selectedInstance) { return { instance: selectedInstance, props: instanceProps, + styles: instanceStyles, }; } - }, [selectedInstance, instanceProps]); + }, [selectedInstance, instanceProps, instanceStyles]); // We need to initialize this in both canvas and designer, // because the events will fire in either one, depending on where the focus is @@ -119,10 +122,10 @@ const useCopyPaste = () => { onCut: (instance) => { publish({ type: "deleteInstance", payload: { id: instance.id } }); }, - onPaste: (instance, props) => { + onPaste: ({ instance, props, styles }) => { publish({ type: "insertInstance", - payload: { instance, props }, + payload: { instance, props, styles }, }); }, }); diff --git a/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx b/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx index b520943dea..0717c52b98 100644 --- a/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx +++ b/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx @@ -9,11 +9,10 @@ import { getComponent, idAttribute, } from "@webstudio-is/react-sdk"; -import { shallowComputed } from "~/shared/store-utils"; import { selectedInstanceIdStore, - stylesIndexStore, useInstanceProps, + useInstanceStyles, useTextEditingInstanceId, } from "~/shared/nano-states"; import { useCssRules } from "~/canvas/shared/styles"; @@ -57,13 +56,7 @@ export const WrapperComponentDev = ({ }: WrapperComponentDevProps) => { const instanceId = instance.id; - const instanceStylesStore = useMemo(() => { - return shallowComputed( - [stylesIndexStore], - (stylesIndex) => stylesIndex.stylesByInstanceId.get(instanceId) ?? [] - ); - }, [instanceId]); - const instanceStyles = useStore(instanceStylesStore); + const instanceStyles = useInstanceStyles(instanceId); useCssRules({ instanceId: instance.id, instanceStyles }); const [editingInstanceId, setTextEditingInstanceId] = diff --git a/apps/designer/app/canvas/shared/instance.ts b/apps/designer/app/canvas/shared/instance.ts index f2c7a40e57..6fda42e3dd 100644 --- a/apps/designer/app/canvas/shared/instance.ts +++ b/apps/designer/app/canvas/shared/instance.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import store from "immerhin"; -import type { Instance, PropsItem } from "@webstudio-is/project-build"; +import type { Instance, Props, Styles } from "@webstudio-is/project-build"; import { getComponentMeta } from "@webstudio-is/react-sdk"; import { utils, type InstanceInsertionSpec } from "@webstudio-is/project"; import { useSubscribe } from "~/shared/pubsub"; @@ -8,6 +8,7 @@ import { propsStore, rootInstanceContainer, selectedInstanceIdStore, + stylesContainer, useRootInstance, useTextEditingInstanceId, } from "~/shared/nano-states"; @@ -19,7 +20,8 @@ declare module "~/shared/pubsub" { insertInstance: { instance: Instance; dropTarget?: { parentId: Instance["id"]; position: number }; - props?: Array; + props?: Props; + styles?: Styles; }; } } @@ -57,11 +59,16 @@ export const findInsertLocation = ( export const useInsertInstance = () => { useSubscribe( "insertInstance", - ({ instance, dropTarget, props: insertedProps }) => { + ({ + instance, + dropTarget, + props: insertedProps, + styles: insertedStyles, + }) => { const selectedInstanceId = selectedInstanceIdStore.get(); store.createTransaction( - [rootInstanceContainer, propsStore], - (rootInstance, props) => { + [rootInstanceContainer, propsStore, stylesContainer], + (rootInstance, props, styles) => { if (rootInstance === undefined) { return; } @@ -76,6 +83,9 @@ export const useInsertInstance = () => { if (insertedProps !== undefined) { props.push(...insertedProps); } + if (insertedStyles !== undefined) { + styles.push(...insertedStyles); + } } ); } diff --git a/apps/designer/app/designer/designer.tsx b/apps/designer/app/designer/designer.tsx index c4d55caa39..94a0349260 100644 --- a/apps/designer/app/designer/designer.tsx +++ b/apps/designer/app/designer/designer.tsx @@ -32,6 +32,7 @@ import { selectedInstanceStore, useDragAndDropState, useInstanceProps, + useInstanceStyles, useIsPreviewMode, } from "~/shared/nano-states"; import { useClientSettings } from "./shared/client-settings"; @@ -85,15 +86,17 @@ const useSubscribeCanvasReady = (publish: Publish) => { const useCopyPaste = (publish: Publish) => { const selectedInstance = useStore(selectedInstanceStore); const instanceProps = useInstanceProps(selectedInstance?.id); + const instanceStyles = useInstanceStyles(selectedInstance?.id); const selectedInstanceData = useMemo(() => { if (selectedInstance) { return { instance: selectedInstance, props: instanceProps, + styles: instanceStyles, }; } - }, [selectedInstance, instanceProps]); + }, [selectedInstance, instanceProps, instanceStyles]); // We need to initialize this in both canvas and designer, // because the events will fire in either one, depending on where the focus is @@ -102,10 +105,10 @@ const useCopyPaste = (publish: Publish) => { onCut: (instance) => { publish({ type: "deleteInstance", payload: { id: instance.id } }); }, - onPaste: (instance, props) => { + onPaste: ({ instance, props, styles }) => { publish({ type: "insertInstance", - payload: { instance, props }, + payload: { instance, props, styles }, }); }, }); diff --git a/apps/designer/app/shared/copy-paste/serialize.ts b/apps/designer/app/shared/copy-paste/serialize.ts index 1a0033749e..ecab6e090b 100644 --- a/apps/designer/app/shared/copy-paste/serialize.ts +++ b/apps/designer/app/shared/copy-paste/serialize.ts @@ -1,11 +1,12 @@ import { z } from "zod"; -import { Instance, Props } from "@webstudio-is/project-build"; +import { Instance, Props, Styles } from "@webstudio-is/project-build"; const TYPE = "@webstudio/instance/v0.1" as const; const InstanceCopyData = z.object({ instance: Instance, - props: Props.optional(), + props: Props, + styles: Styles, }); export type InstanceCopyData = z.infer; diff --git a/apps/designer/app/shared/copy-paste/use-instance-copy-paste.ts b/apps/designer/app/shared/copy-paste/use-instance-copy-paste.ts index c98094bc1c..b33e4035e9 100644 --- a/apps/designer/app/shared/copy-paste/use-instance-copy-paste.ts +++ b/apps/designer/app/shared/copy-paste/use-instance-copy-paste.ts @@ -1,6 +1,7 @@ -import { utils } from "@webstudio-is/project"; +import ObjectId from "bson-objectid"; import { useEffect, useMemo } from "react"; -import type { Instance, PropsItem } from "@webstudio-is/project-build"; +import { utils } from "@webstudio-is/project"; +import type { Instance, Props, Styles } from "@webstudio-is/project-build"; import { type InstanceCopyData, serialize, deserialize } from "./serialize"; const isInstanceClipboardEvent = ( @@ -35,15 +36,15 @@ const isInstanceClipboardEvent = ( return true; }; -type Props = { +type InstanceCopyPasteProps = { selectedInstanceData?: InstanceCopyData | undefined; allowAnyTarget?: boolean; - onPaste: (instance: Instance, props?: PropsItem[]) => void; + onPaste: (data: InstanceCopyData) => void; onCut: (instance: Instance) => void; }; const createEventsHandler = () => { - let currentProps: Props | undefined; + let currentProps: InstanceCopyPasteProps | undefined; const handleCopy = (event: ClipboardEvent) => { if (currentProps === undefined) { @@ -121,12 +122,23 @@ const createEventsHandler = () => { const instance = utils.tree.cloneInstance(data.instance); - const props = - data.props && data.props.length > 0 - ? utils.props.cloneUserProps(data.props) - : undefined; + // copy props with new ids and link to new instance + const props: Props = data.props.map((prop) => { + return { + ...prop, + id: ObjectId().toString(), + instanceId: instance.id, + }; + }); - onPaste(instance, props); + const styles: Styles = data.styles.map((styleDecl) => { + return { + ...styleDecl, + instanceId: instance.id, + }; + }); + + onPaste({ instance, props, styles }); }; return { @@ -140,7 +152,7 @@ const createEventsHandler = () => { document.removeEventListener("cut", handleCut); document.removeEventListener("paste", handlePaste); }, - setProps(props: Props) { + setProps(props: InstanceCopyPasteProps) { currentProps = props; }, }; @@ -148,7 +160,7 @@ const createEventsHandler = () => { // Everything is extrcated from hook, // to make it easier to remove React from canvas if we need to -export const useInstanceCopyPaste = (props: Props): void => { +export const useInstanceCopyPaste = (props: InstanceCopyPasteProps): void => { const eventsHandler = useMemo(createEventsHandler, []); useEffect(() => { diff --git a/apps/designer/app/shared/nano-states/nano-states.ts b/apps/designer/app/shared/nano-states/nano-states.ts index b00d23dd15..6e1dbb1fa9 100644 --- a/apps/designer/app/shared/nano-states/nano-states.ts +++ b/apps/designer/app/shared/nano-states/nano-states.ts @@ -110,6 +110,18 @@ export const useSetStyles = (styles: Styles) => { stylesContainer.set(styles); }); }; +export const useInstanceStyles = (instanceId: undefined | Instance["id"]) => { + const instanceStylesStore = useMemo(() => { + return shallowComputed([stylesIndexStore], (stylesIndex) => { + if (instanceId === undefined) { + return []; + } + return stylesIndex.stylesByInstanceId.get(instanceId) ?? []; + }); + }, [instanceId]); + const instanceStyles = useStore(instanceStylesStore); + return instanceStyles; +}; export const breakpointsContainer = atom([]); export const useBreakpoints = () => useValue(breakpointsContainer); diff --git a/packages/project/src/index.ts b/packages/project/src/index.ts index e7638d14f8..07766df5db 100644 --- a/packages/project/src/index.ts +++ b/packages/project/src/index.ts @@ -1,13 +1,11 @@ import * as breakpointsUtils from "./shared/breakpoints"; import * as treeUtils from "./shared/tree-utils"; import * as pagesUtils from "./shared/pages"; -import * as propsUtils from "./shared/props"; export const utils = { breakpoints: breakpointsUtils, tree: treeUtils, pages: pagesUtils, - props: propsUtils, } as const; export * from "./shared/styles"; diff --git a/packages/project/src/shared/props/clone-user-props.ts b/packages/project/src/shared/props/clone-user-props.ts deleted file mode 100644 index 3a459060de..0000000000 --- a/packages/project/src/shared/props/clone-user-props.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ObjectId from "bson-objectid"; -import type { PropsItem } from "@webstudio-is/project-build"; - -export const cloneUserProps = (props: Array): Array => - props.map((prop) => ({ ...prop, id: ObjectId().toString() })); diff --git a/packages/project/src/shared/props/index.ts b/packages/project/src/shared/props/index.ts deleted file mode 100644 index 8dfba76240..0000000000 --- a/packages/project/src/shared/props/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./clone-user-props";