From 4a7d2e4e7d9909d35fab908df9755c211a004659 Mon Sep 17 00:00:00 2001 From: Roman Pominov Date: Thu, 21 Jul 2022 12:11:52 +0300 Subject: [PATCH] new canvas playground --- .../primitives/dnd/canvas.stories.tsx | 234 ++++++++++++++++++ .../primitives/dnd/handlers.stories.tsx | 136 ---------- .../primitives/dnd/sortable-list.stories.tsx | 2 +- 3 files changed, 235 insertions(+), 137 deletions(-) create mode 100644 apps/designer/app/shared/design-system/components/primitives/dnd/canvas.stories.tsx delete mode 100644 apps/designer/app/shared/design-system/components/primitives/dnd/handlers.stories.tsx diff --git a/apps/designer/app/shared/design-system/components/primitives/dnd/canvas.stories.tsx b/apps/designer/app/shared/design-system/components/primitives/dnd/canvas.stories.tsx new file mode 100644 index 0000000000..0877e02305 --- /dev/null +++ b/apps/designer/app/shared/design-system/components/primitives/dnd/canvas.stories.tsx @@ -0,0 +1,234 @@ +import { ComponentMeta } from "@storybook/react"; +import React, { useState, useRef } from "react"; +import { Box } from "../../box"; +import { useDropTarget } from "./use-drop-target"; +import { useDrag } from "./use-drag"; +import { usePlacement, PlacementIndicator, type Rect } from "./placement"; +import { useAutoScroll } from "./use-auto-scroll"; + +const ROOT_ID = "root"; + +type ItemData = { + id: string; + style: React.CSSProperties; + acceptsChildren: boolean; + children: ItemData[]; +}; + +const Item = ({ + data, + dragItemId, +}: { + data: ItemData; + dragItemId: string | undefined; +}) => { + return ( + + + + ); +}; + +const Items = ({ + data, + dragItemId, +}: { + data: ItemData[]; + dragItemId: string | undefined; +}) => { + return ( + <> + {data.map((child) => ( + + ))} + + ); +}; + +const findItem = (data: ItemData[], id: string): ItemData | undefined => { + for (const item of data) { + if (item.id === id) { + return item; + } + const found = findItem(item.children, id); + if (found) { + return found; + } + } + return undefined; +}; + +const mapItems = ( + data: ItemData[], + fn: (item: ItemData) => ItemData +): ItemData[] => { + return fn({ + id: ROOT_ID, + style: {}, + acceptsChildren: true, + children: data.map((item) => { + return fn({ + ...item, + children: mapItems(item.children, fn), + }); + }), + }).children; +}; + +export const Canvas = () => { + const [data, setData] = useState([ + { + id: "0", + style: {}, + children: [], + acceptsChildren: true, + }, + { id: "1", style: {}, children: [], acceptsChildren: true }, + { id: "2", style: {}, children: [], acceptsChildren: true }, + { id: "3", style: {}, children: [], acceptsChildren: true }, + { id: "4", style: {}, children: [], acceptsChildren: true }, + { id: "5", style: {}, children: [], acceptsChildren: true }, + ]); + + const [placement, setPalcement] = useState<{ + index: number; + placementRect: Rect; + }>(); + + const [dragItemId, setDragItemId] = useState(); + + const dropTargetId = useRef(); + + const dropTargetHandlers = useDropTarget({ + isDropTarget(element: HTMLElement) { + const id = element.dataset.id; + return ( + id != null && + (id === ROOT_ID || findItem(data, id)?.acceptsChildren === true) + ); + }, + onDropTargetChange(event) { + dropTargetId.current = event.target.dataset.id; + placementHandlers.handleTargetChange(event.target); + }, + }); + + const autoScrollHandlers = useAutoScroll(); + + const dragProps = useDrag({ + onStart(event) { + const id = event.target.dataset.id; + if (id == null) { + event.cancel(); + return; + } + setDragItemId(id); + autoScrollHandlers.setEnabled(true); + }, + onMove: (poiterCoordinate) => { + dropTargetHandlers.handleMove(poiterCoordinate); + autoScrollHandlers.handleMove(poiterCoordinate); + placementHandlers.handleMove(poiterCoordinate); + }, + onEnd() { + const currentDropTargetId = dropTargetId.current; + if ( + placement !== undefined && + dragItemId !== undefined && + currentDropTargetId !== undefined + ) { + setData((current) => { + const dragItem = findItem(current, dragItemId); + + // shouldn't happen, for TypeScript + if (dragItem === undefined) { + return current; + } + + return mapItems(current, (item) => { + let children = item.children; + + const oldIndex = children.findIndex( + (child) => child.id === dragItemId + ); + + if (oldIndex !== -1) { + children = children.slice(); + children.splice(oldIndex, 1); + } + + if (item.id === currentDropTargetId) { + // placement.index does not take into account the fact that the drag item will be removed. + // we need to do this to account for it. + const newIndex = + oldIndex !== -1 && oldIndex < placement.index + ? placement.index - 1 + : placement.index; + + children = children.slice(); + children.splice(newIndex, 0, dragItem); + } + + return children === item.children ? item : { ...item, children }; + }); + }); + } + + dropTargetHandlers.handleEnd(); + autoScrollHandlers.setEnabled(false); + placementHandlers.handleEnd(); + setDragItemId(undefined); + setPalcement(undefined); + dropTargetId.current = undefined; + }, + }); + + const placementHandlers = usePlacement({ + onPalcementChange: (event) => { + setPalcement(event); + }, + }); + + return ( + <> + { + dropTargetHandlers.rootRef(element); + autoScrollHandlers.targetRef(element); + }} + onScroll={() => { + dropTargetHandlers.handleScroll(); + placementHandlers.handleScroll(); + }} + data-id={ROOT_ID} + {...dragProps} + > + + + {placement && } + + ); +}; + +export default {} as ComponentMeta; diff --git a/apps/designer/app/shared/design-system/components/primitives/dnd/handlers.stories.tsx b/apps/designer/app/shared/design-system/components/primitives/dnd/handlers.stories.tsx deleted file mode 100644 index 62640500c5..0000000000 --- a/apps/designer/app/shared/design-system/components/primitives/dnd/handlers.stories.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { ComponentMeta } from "@storybook/react"; -import React, { useRef, useState } from "react"; -import { Box } from "../../box"; -import { useDropTarget } from "./use-drop-target"; -import { useDrag } from "./use-drag"; -import { getPlacement, PlacementIndicator, type Rect } from "./placement"; -import { useAutoScroll } from "./use-auto-scroll"; - -export const Playground = () => { - const [dropTargetRect, setDropTargetRect] = useState(); - const [placementIndicatorRect, setPlacementIndicatorRect] = useState(); - const holdRef = useRef(); - const dragItemRef = useRef(); - - const handleHoldEnd = () => { - holdRef.current?.style.removeProperty("background"); - }; - - const dropTargetHandlers = useDropTarget({ - isDropTarget(element: HTMLElement) { - return element.dataset.draggable === "true"; - }, - onDropTargetChange({ rect, target }) { - setDropTargetRect(rect); - const placementRect = getPlacement({ target }); - setPlacementIndicatorRect(placementRect); - }, - onHold({ target }: { target: HTMLElement }) { - handleHoldEnd(); - holdRef.current = target; - target.style.background = "red"; - }, - }); - - const autoScrollHandlers = useAutoScroll(); - - const dragProps = useDrag({ - onStart(event) { - if (event.target.dataset.draggable === "false") { - event.cancel(); - return; - } - dragItemRef.current = event.target; - autoScrollHandlers.setEnabled(true); - }, - onMove: (poiterCoordinate) => { - dropTargetHandlers.handleMove(poiterCoordinate); - autoScrollHandlers.handleMove(poiterCoordinate); - }, - onEnd() { - dropTargetHandlers.handleEnd(); - setDropTargetRect(undefined); - setPlacementIndicatorRect(undefined); - handleHoldEnd(); - autoScrollHandlers.setEnabled(false); - }, - onShiftChange({ shifts }) { - if (dragItemRef.current) { - dragItemRef.current.textContent = `shifted ${shifts}`; - } - }, - }); - - return ( - { - dropTargetHandlers.rootRef(el); - autoScrollHandlers.targetRef(el); - }} - css={{ display: "block", overflow: "auto", height: 500 }} - onScroll={dropTargetHandlers.handleScroll} - > - - - - - - - - - - - - - - - - - Not Draggable - - - - - ); -}; - -export default {} as ComponentMeta; - -const Item = ({ - background, - draggable = true, - children, -}: { - background: string; - draggable?: boolean; - children?: React.ReactNode; -}) => ( - - {children} - -); - -const Outline = ({ rect }: { rect?: Rect }) => { - if (rect === undefined) return null; - const style = { - top: rect.top, - left: rect.left, - width: rect.width, - height: rect.height, - }; - return ( - - ); -}; diff --git a/apps/designer/app/shared/design-system/components/primitives/dnd/sortable-list.stories.tsx b/apps/designer/app/shared/design-system/components/primitives/dnd/sortable-list.stories.tsx index 6882ef62a4..6fed0dc20f 100644 --- a/apps/designer/app/shared/design-system/components/primitives/dnd/sortable-list.stories.tsx +++ b/apps/designer/app/shared/design-system/components/primitives/dnd/sortable-list.stories.tsx @@ -102,7 +102,7 @@ export const SortableList = ({ let newIndex = placement.index; // placement.index does not take into account the fact that the drag item will be removed. - // we need to do this to account for that + // we need to do this to account for it. if (oldIndex < newIndex) { newIndex = Math.max(0, newIndex - 1); }