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.
This commit is contained in:
Bogdan Chadkin authored and GitHub committed 2023-01-28 19:28:07 +03:00
1 parent dbaa617dca
commit 8d95c14f28
10 files changed
+68 -42

No files matched your search

+6 -3
View File
@@ -30,6 +30,7 @@ import {
selectedInstanceStore,
useBreakpoints,
useInstanceProps,
useInstanceStyles,
useRootInstance,
useSetBreakpoints,
useSetDesignTokens,
@@ -101,15 +102,17 @@ const useAssets = (initialAssets: Array<Asset>) => {
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 },
});
},
});
@@ -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] =
+15 -5
View File
@@ -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<PropsItem>;
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);
}
}
);
}
+6 -3
View File
@@ -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 },
});
},
});
@@ -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<typeof InstanceCopyData>;
@@ -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(() => {
@@ -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<Breakpoint[]>([]);
export const useBreakpoints = () => useValue(breakpointsContainer);
-2
View File
@@ -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";
@@ -1,5 +0,0 @@
import ObjectId from "bson-objectid";
import type { PropsItem } from "@webstudio-is/project-build";
export const cloneUserProps = (props: Array<PropsItem>): Array<PropsItem> =>
props.map((prop) => ({ ...prop, id: ObjectId().toString() }));
@@ -1 +0,0 @@
export * from "./clone-user-props";