From bc2779d8251eb9328b717bb7dc9f200ed89df232 Mon Sep 17 00:00:00 2001 From: Roman Pominov Date: Wed, 24 Aug 2022 12:02:34 +0300 Subject: [PATCH] restore storybook demo & more cleanup --- .../components/tree/horizontal-shift.test.ts | 44 ++--- .../components/tree/placement-indicator.tsx | 2 +- .../src/components/tree/test-tree-data.ts | 90 ++++++++++ .../src/components/tree/tree-node.tsx | 2 +- .../src/components/tree/tree.stories.tsx | 159 ++++++++++-------- 5 files changed, 189 insertions(+), 108 deletions(-) create mode 100644 packages/design-system/src/components/tree/test-tree-data.ts diff --git a/packages/design-system/src/components/tree/horizontal-shift.test.ts b/packages/design-system/src/components/tree/horizontal-shift.test.ts index 5e4b0faf6b..9b04a4ed19 100644 --- a/packages/design-system/src/components/tree/horizontal-shift.test.ts +++ b/packages/design-system/src/components/tree/horizontal-shift.test.ts @@ -1,12 +1,13 @@ import { renderHook, act } from "@testing-library/react-hooks"; import { DropTarget, Placement } from "@webstudio-is/design-system"; import { useHorizontalShift } from "./horizontal-shift"; - -type Item = { - id: string; - canAcceptChildren: boolean; - children: Item[]; -}; +import { + canAcceptChild, + getItemChildren, + getItemPath, + getItemPathWithPositions, + Item, +} from "./test-tree-data"; const box1: Item = { canAcceptChildren: true, id: "box1", children: [] }; const box2: Item = { canAcceptChildren: true, id: "box2", children: [] }; @@ -90,30 +91,6 @@ const makeDrop = ({ rect: null as unknown as DOMRect, }); -const getItemPathWithPositions = (tree: Item, itemId: string) => { - const path = []; - - const find = (item: Item) => { - if (item.id === itemId) return true; - const children = item.children; - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if (typeof child === "string") continue; - const found = find(child); - if (found) { - path.push({ item: child, position: i }); - return true; - } - } - }; - - if (find(tree)) { - path.push({ item: tree, position: 0 }); - } - - return path.reverse(); -}; - const render = ( { dragItem, @@ -130,10 +107,9 @@ const render = ( dropTarget, root: tree, getIsExpanded: (item: Item) => item.children.length > 0, - canAcceptChild: (item: Item) => item.canAcceptChildren, - getItemChildren: (item: Item) => item.children, - getItemPath: (tree: Item, id: string) => - getItemPathWithPositions(tree, id).map(({ item }) => item), + canAcceptChild, + getItemChildren, + getItemPath, getItemPathWithPositions, }, }); diff --git a/packages/design-system/src/components/tree/placement-indicator.tsx b/packages/design-system/src/components/tree/placement-indicator.tsx index 0031e7bcb9..c467c72476 100644 --- a/packages/design-system/src/components/tree/placement-indicator.tsx +++ b/packages/design-system/src/components/tree/placement-indicator.tsx @@ -1,4 +1,4 @@ -import { styled } from "~/stitches.config"; +import { styled } from "../../stitches.config"; import { Box } from "../box"; import { Placement } from "../primitives/dnd"; diff --git a/packages/design-system/src/components/tree/test-tree-data.ts b/packages/design-system/src/components/tree/test-tree-data.ts new file mode 100644 index 0000000000..35075be9c6 --- /dev/null +++ b/packages/design-system/src/components/tree/test-tree-data.ts @@ -0,0 +1,90 @@ +import produce from "immer"; + +export type Item = { + id: string; + canAcceptChildren: boolean; + children: Item[]; +}; + +export const getItemPathWithPositions = (tree: Item, itemId: string) => { + const path = []; + + const find = (item: Item) => { + if (item.id === itemId) return true; + const children = item.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (typeof child === "string") continue; + const found = find(child); + if (found) { + path.push({ item: child, position: i }); + return true; + } + } + }; + + if (find(tree)) { + path.push({ item: tree, position: 0 }); + } + + return path.reverse(); +}; + +export const getItemPath = (tree: Item, id: string) => + getItemPathWithPositions(tree, id).map(({ item }) => item); + +export const canAcceptChild = (item: Item) => item.canAcceptChildren; + +export const getItemChildren = (item: Item) => item.children; + +export const findItemById = (root: Item, id: string) => { + const path = getItemPath(root, id); + return path.length > 0 ? path[path.length - 1] : undefined; +}; + +export const reparent = ( + root: Item, + { + itemId, + dropTarget, + }: { + itemId: string; + dropTarget: { + itemId: string; + position: number | "end"; + }; + } +) => + produce(root, (draft) => { + const path = getItemPath(draft, itemId); + const item = path[path.length - 1]; + const currentParent = path[path.length - 2]; + const newParent = findItemById(draft, dropTarget.itemId); + + if ( + item === undefined || + currentParent === undefined || + newParent === undefined + ) { + return; + } + + const currentIndex = currentParent.children.findIndex( + (child) => child.id === itemId + ); + + currentParent.children.splice(currentIndex, 1); + + if (dropTarget.position === "end") { + newParent.children.push(item); + } else { + let newIndex = dropTarget.position; + + // dropTarget.position does not take into account the fact that the drag item will be removed + if (currentParent.id === newParent.id && currentIndex < newIndex) { + newIndex--; + } + + newParent.children.splice(newIndex, 0, item); + } + }); diff --git a/packages/design-system/src/components/tree/tree-node.tsx b/packages/design-system/src/components/tree/tree-node.tsx index 91f6c2c38d..41efd044fc 100644 --- a/packages/design-system/src/components/tree/tree-node.tsx +++ b/packages/design-system/src/components/tree/tree-node.tsx @@ -1,10 +1,10 @@ import { useCallback, useRef, useEffect } from "react"; import { TriangleRightIcon, TriangleDownIcon } from "@webstudio-is/icons"; -import { keyframes, styled } from "~/stitches.config"; import { Box } from "../box"; import * as Collapsible from "@radix-ui/react-collapsible"; import { Flex } from "../flex"; import { Text } from "../text"; +import { keyframes, styled } from "../../stitches.config"; export const INDENT = 16; const ITEM_HEIGHT = 32; diff --git a/packages/design-system/src/components/tree/tree.stories.tsx b/packages/design-system/src/components/tree/tree.stories.tsx index da26f63624..88faf15243 100644 --- a/packages/design-system/src/components/tree/tree.stories.tsx +++ b/packages/design-system/src/components/tree/tree.stories.tsx @@ -1,76 +1,91 @@ -// import { ComponentMeta } from "@storybook/react"; -// import { Instance } from "@webstudio-is/react-sdk"; -// import { useState } from "react"; -// import produce from "immer"; -// import { createInstance, reparentInstanceMutable } from "~/shared/tree-utils"; -// import { Tree } from "./tree"; -// import { Flex } from "@webstudio-is/design-system"; +import { ComponentMeta } from "@storybook/react"; +import { useState } from "react"; +import { Tree } from "./tree"; +import { + canAcceptChild, + findItemById, + getItemChildren, + getItemPath, + getItemPathWithPositions, + Item, + reparent, +} from "./test-tree-data"; +import { Flex } from "../flex"; +import { TreeNodeLabel } from "./tree-node"; -// export const StressTest = ({ animate }: { animate: boolean }) => { -// const [root, setRoot] = useState(() => { -// return createInstance({ -// component: "Body", -// children: Array.from({ length: 100 }, () => -// createInstance({ -// component: "Box", -// children: [ -// createInstance({ component: "Heading" }), -// createInstance({ component: "Paragraph" }), -// createInstance({ -// component: "Box", -// children: [ -// createInstance({ -// component: "Box", -// children: [ -// createInstance({ -// component: "Box", -// children: [ -// createInstance({ component: "Heading" }), -// createInstance({ component: "Paragraph" }), -// ], -// }), -// ], -// }), -// createInstance({ component: "Box" }), -// ], -// }), -// ], -// }) -// ), -// }); -// }); +export const StressTest = ({ animate }: { animate: boolean }) => { + const [root, setRoot] = useState((): Item => { + return { + id: "root", + canAcceptChildren: true, + children: Array.from( + { length: 100 }, + (_, i): Item => ({ + id: `box-${i}`, + canAcceptChildren: true, + children: [ + { id: `heading-${i}.0`, canAcceptChildren: false, children: [] }, + { id: `paragraph-${i}.0`, canAcceptChildren: false, children: [] }, + { + id: `box-${i}.0`, + canAcceptChildren: true, + children: [ + { + id: `box-${i}.0.0`, + canAcceptChildren: true, + children: [ + { + id: `box-${i}.0.0.0`, + canAcceptChildren: true, + children: [ + { + id: `heading-${i}.1`, + canAcceptChildren: false, + children: [], + }, + { + id: `paragraph-${i}.1`, + canAcceptChildren: false, + children: [], + }, + ], + }, + ], + }, + { id: `box-${i}.0.1`, canAcceptChildren: true, children: [] }, + ], + }, + ], + }) + ), + }; + }); -// const [selectedInstanceId, setSelectedInstanceId] = useState< -// string | undefined -// >(); + const [selectedItemId, setSelectedItemId] = useState(); -// return ( -// -// setSelectedInstanceId(instanceId)} -// onDragEnd={(payload) => -// setRoot( -// produce((draft) => -// reparentInstanceMutable( -// draft, -// payload.instanceId, -// payload.dropTarget.instanceId, -// payload.dropTarget.position -// ) -// ) -// ) -// } -// onDelete={() => null} -// /> -// -// ); -// }; + return ( + + true} + getItemChildren={getItemChildren} + animate={animate} + root={root} + selectedItemId={selectedItemId} + onSelect={(instanceId) => setSelectedItemId(instanceId)} + renderItem={({ data, isSelected }) => ( + + )} + onDragEnd={(payload) => setRoot((root) => reparent(root, payload))} + onDelete={() => null} + /> + + ); +}; -// export default { -// args: { animate: true }, -// } as ComponentMeta; - -export const tmp = 1; +export default { + args: { animate: true }, +} as ComponentMeta;