diff --git a/apps/designer/app/canvas/shared/use-drag-drop.ts b/apps/designer/app/canvas/shared/use-drag-drop.ts index 067169cb23..2b8e1ca0ba 100644 --- a/apps/designer/app/canvas/shared/use-drag-drop.ts +++ b/apps/designer/app/canvas/shared/use-drag-drop.ts @@ -20,8 +20,7 @@ import { export type Rect = Pick; -// data shared between iframe and main window -export type DropTargetSharedData = { +export type DropTargetChangePayload = { rect: Rect; placementRect: Rect; position: number; @@ -29,6 +28,15 @@ export type DropTargetSharedData = { instanceComponent: Instance["component"]; }; +export type DragStartPayload = { + origin: "panel" | "canvas"; + dragItem: Instance; +}; + +export type DragEndPayload = { origin: "panel" | "canvas" }; + +export type DragMovePayload = { canvasCoordinates: { x: number; y: number } }; + const initialState = { dropTarget: undefined as DropTarget | undefined, placement: undefined as { index: number; placementRect: Rect } | undefined, @@ -46,7 +54,7 @@ export const useDragAndDrop = () => { if (dropTarget === undefined || placement === undefined) { return; } - publish<"dropTargetChange", DropTargetSharedData>({ + publish<"dropTargetChange", DropTargetChangePayload>({ type: "dropTargetChange", payload: { rect: dropTarget.rect, @@ -157,12 +165,12 @@ export const useDragAndDrop = () => { autoScrollHandlers.setEnabled(true); - publish< - "dragStart", - { origin: "panel" | "canvas"; dragItem: { instanceId: Instance["id"] } } - >({ + publish<"dragStart", DragStartPayload>({ type: "dragStart", - payload: { origin: "canvas", dragItem: { instanceId: instance.id } }, + payload: { + origin: "canvas", + dragItem: instance, + }, }); }, onMove: (poiterCoordinate) => { @@ -175,7 +183,7 @@ export const useDragAndDrop = () => { autoScrollHandlers.setEnabled(false); placementHandlers.handleEnd(); - publish<"dragEnd", { origin: "panel" | "canvas" }>({ + publish<"dragEnd", DragEndPayload>({ type: "dragEnd", payload: { origin: "canvas" }, }); @@ -234,17 +242,17 @@ export const useDragAndDrop = () => { // Handle drag from the panel // ================================================================ - useSubscribe< + useSubscribe<"dragStart", DragStartPayload>( "dragStart", - { origin: "panel" | "canvas"; dragItem: { instance: Instance } } - >("dragStart", ({ origin, dragItem }) => { - if (origin === "panel") { - state.current.dragItem = dragItem.instance; - autoScrollHandlers.setEnabled(true); + ({ origin, dragItem }) => { + if (origin === "panel") { + state.current.dragItem = dragItem; + autoScrollHandlers.setEnabled(true); + } } - }); + ); - useSubscribe<"dragMove", { canvasCoordinates: { x: number; y: number } }>( + useSubscribe<"dragMove", DragMovePayload>( "dragMove", ({ canvasCoordinates }) => { dropTargetHandlers.handleMove(canvasCoordinates); @@ -253,37 +261,34 @@ export const useDragAndDrop = () => { } ); - useSubscribe<"dragEnd", { origin: "panel" | "canvas" }>( - "dragEnd", - ({ origin }) => { - if (origin === "panel") { - dropTargetHandlers.handleEnd(); - autoScrollHandlers.setEnabled(false); - placementHandlers.handleEnd(); + useSubscribe<"dragEnd", DragEndPayload>("dragEnd", ({ origin }) => { + if (origin === "panel") { + dropTargetHandlers.handleEnd(); + autoScrollHandlers.setEnabled(false); + placementHandlers.handleEnd(); - const { dropTarget, placement, dragItem } = state.current; + const { dropTarget, placement, dragItem } = state.current; - if (dropTarget && placement && dragItem) { - publish< - "insertInstance", - { - instance: Instance; - dropTarget: { instanceId: Instance["id"]; position: number }; - } - >({ - type: "insertInstance", - payload: { - instance: dragItem, - dropTarget: { - instanceId: dropTarget.data.id, - position: placement.index, - }, + if (dropTarget && placement && dragItem) { + publish< + "insertInstance", + { + instance: Instance; + dropTarget: { instanceId: Instance["id"]; position: number }; + } + >({ + type: "insertInstance", + payload: { + instance: dragItem, + dropTarget: { + instanceId: dropTarget.data.id, + position: placement.index, }, - }); - } - - state.current = { ...initialState }; + }, + }); } + + state.current = { ...initialState }; } - ); + }); }; diff --git a/apps/designer/app/designer/features/sidebar-left/panels/components/components.tsx b/apps/designer/app/designer/features/sidebar-left/panels/components/components.tsx index 37f87c28a1..7bd2d2347c 100644 --- a/apps/designer/app/designer/features/sidebar-left/panels/components/components.tsx +++ b/apps/designer/app/designer/features/sidebar-left/panels/components/components.tsx @@ -12,6 +12,11 @@ import { createInstance } from "~/shared/tree-utils"; import type { TabName } from "../../types"; import { ComponentThumb } from "./component-thumb"; import { useCanvasRect, useZoom } from "~/designer/shared/nano-states"; +import { + DragEndPayload, + DragMovePayload, + DragStartPayload, +} from "~/canvas/shared/use-drag-drop"; const componentNames = ( Object.keys(components) as Array @@ -101,28 +106,26 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => { return; } - const instance = createInstance({ component }); - setDragComponent(component); - publish< - "dragStart", - { origin: "panel" | "canvas"; dragItem: { instance: Instance } } - >({ + publish<"dragStart", DragStartPayload>({ type: "dragStart", - payload: { origin: "panel", dragItem: { instance } }, + payload: { + origin: "panel", + dragItem: createInstance({ component }), + }, }); }, onMove: (poiterCoordinate) => { setPointerPosition(poiterCoordinate); - publish<"dragMove", { canvasCoordinates: { x: number; y: number } }>({ + publish<"dragMove", DragMovePayload>({ type: "dragMove", payload: { canvasCoordinates: toCanvasCoordinates(poiterCoordinate) }, }); }, onEnd() { setDragComponent(undefined); - publish<"dragEnd", { origin: "panel" | "canvas" }>({ + publish<"dragEnd", DragEndPayload>({ type: "dragEnd", payload: { origin: "panel" }, }); diff --git a/apps/designer/app/designer/features/tree-preview/tree-preview.tsx b/apps/designer/app/designer/features/tree-preview/tree-preview.tsx index 9f479fb706..9acce49620 100644 --- a/apps/designer/app/designer/features/tree-preview/tree-preview.tsx +++ b/apps/designer/app/designer/features/tree-preview/tree-preview.tsx @@ -4,42 +4,58 @@ import { useMemo } from "react"; import { Tree } from "~/designer/shared/tree"; import { Flex } from "@webstudio-is/design-system"; import { useRootInstance, useDragAndDropState } from "~/shared/nano-states"; -import { getInstancePath, reparentInstanceMutable } from "~/shared/tree-utils"; +import { + getInstancePath, + reparentInstanceMutable, + insertInstanceMutable, + findInstanceById, +} from "~/shared/tree-utils"; export const TreePrevew = () => { const [rootInstance] = useRootInstance(); const [dragAndDropState] = useDragAndDropState(); - const dragItemInstanceId = dragAndDropState.dragItem?.instanceId; + const dragItemInstance = dragAndDropState.dragItem; const dropTargetInstanceId = dragAndDropState.dropTarget?.instanceId; const dropTargetPosition = dragAndDropState.dropTarget?.position; const treeProps = useMemo(() => { if ( - dragItemInstanceId === undefined || + dragItemInstance === undefined || dropTargetInstanceId === undefined || - dropTargetPosition === undefined + dropTargetPosition === undefined || + rootInstance === undefined ) { return null; } + const isNew = + findInstanceById(rootInstance, dragItemInstance.id) === undefined; + const instance: Instance = produce((draft) => { - reparentInstanceMutable( - draft, - dragItemInstanceId, - dropTargetInstanceId, - dropTargetPosition - ); + if (isNew) { + insertInstanceMutable(draft, dragItemInstance, { + parentId: dropTargetInstanceId, + position: dropTargetPosition, + }); + } else { + reparentInstanceMutable( + draft, + dragItemInstance.id, + dropTargetInstanceId, + dropTargetPosition + ); + } })(rootInstance); return { instance, - selectedInstanceId: dragItemInstanceId, - selectedInstancePath: getInstancePath(instance, dragItemInstanceId), + selectedInstanceId: dragItemInstance.id, + selectedInstancePath: getInstancePath(instance, dragItemInstance.id), }; }, [ rootInstance, - dragItemInstanceId, + dragItemInstance, dropTargetInstanceId, dropTargetPosition, ]); diff --git a/apps/designer/app/designer/features/workspace/canvas-tools/outline/drop-target-outline.tsx b/apps/designer/app/designer/features/workspace/canvas-tools/outline/drop-target-outline.tsx index 8362ff5a9b..9a3bc6e7f3 100644 --- a/apps/designer/app/designer/features/workspace/canvas-tools/outline/drop-target-outline.tsx +++ b/apps/designer/app/designer/features/workspace/canvas-tools/outline/drop-target-outline.tsx @@ -1,11 +1,11 @@ import { Outline } from "./outline"; -import { DropTargetSharedData } from "~/canvas/shared/use-drag-drop"; +import { DropTargetChangePayload } from "~/canvas/shared/use-drag-drop"; import { Label } from "./label"; export const DropTargetOutline = ({ dropTarget, }: { - dropTarget: DropTargetSharedData; + dropTarget: DropTargetChangePayload; }) => { return ( diff --git a/apps/designer/app/shared/nano-states/nano-states.ts b/apps/designer/app/shared/nano-states/nano-states.ts index b33cafd6ef..bc4389bd12 100644 --- a/apps/designer/app/shared/nano-states/nano-states.ts +++ b/apps/designer/app/shared/nano-states/nano-states.ts @@ -1,6 +1,9 @@ import { createValueContainer, useValue } from "react-nano-state"; import { type Breakpoint, type Instance } from "@webstudio-is/react-sdk"; -import { DropTargetSharedData } from "~/canvas/shared/use-drag-drop"; +import { + DropTargetChangePayload, + DragStartPayload, +} from "~/canvas/shared/use-drag-drop"; export const rootInstanceContainer = createValueContainer< Instance | undefined @@ -40,8 +43,8 @@ export const useTextEditingInstanceId = () => export type DragAndDropState = { isDragging: boolean; origin?: "canvas" | "panel"; - dropTarget?: DropTargetSharedData; - dragItem?: { instanceId: Instance["id"] }; + dropTarget?: DropTargetChangePayload; + dragItem?: DragStartPayload["dragItem"]; }; const dragAndDropStateContainer = createValueContainer({ isDragging: false, diff --git a/apps/designer/app/shared/nano-states/use-subscribe-drag-drop-state.ts b/apps/designer/app/shared/nano-states/use-subscribe-drag-drop-state.ts index a49a780a64..e77f7ec0b9 100644 --- a/apps/designer/app/shared/nano-states/use-subscribe-drag-drop-state.ts +++ b/apps/designer/app/shared/nano-states/use-subscribe-drag-drop-state.ts @@ -1,31 +1,29 @@ -import { useSubscribe, type Instance } from "@webstudio-is/react-sdk"; +import { useSubscribe } from "@webstudio-is/react-sdk"; import { useDragAndDropState } from "./nano-states"; -import { DropTargetSharedData } from "~/canvas/shared/use-drag-drop"; +import { + DropTargetChangePayload, + DragStartPayload, + DragEndPayload, +} from "~/canvas/shared/use-drag-drop"; export const useSubscribeDragAndDropState = () => { const [state, setState] = useDragAndDropState(); - useSubscribe<"dropTargetChange", DropTargetSharedData>( + useSubscribe<"dropTargetChange", DropTargetChangePayload>( "dropTargetChange", (dropTarget) => { - if (state.isDragging) { - setState({ ...state, dropTarget }); - } + setState({ ...state, dropTarget }); } ); - useSubscribe< + useSubscribe<"dragStart", DragStartPayload>( "dragStart", - { origin: "panel" | "canvas"; dragItem: { instanceId: Instance["id"] } } - >("dragStart", ({ origin, dragItem }) => { - setState({ - isDragging: true, - origin, - dragItem, - }); - }); + ({ origin, dragItem }) => { + setState({ isDragging: true, origin, dragItem }); + } + ); - useSubscribe<"dragEnd", { origin: "panel" | "canvas" }>("dragEnd", () => { + useSubscribe<"dragEnd", DragEndPayload>("dragEnd", () => { setState({ isDragging: false }); }); };