mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 05:26:24 +09:00
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:
1 parent
dbaa617dca
commit
8d95c14f28
10 files changed
+68
-42
No files matched your search
@@ -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] =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
Reference in new issue
Block a user