From 441463b17205e386a147b407c164656638fb7dba Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 6 Jun 2024 17:58:37 -0700 Subject: [PATCH] Add placeholder for layout drag and drop (#26) --- frontend/faraday/lib/TileLayout.tsx | 107 ++++++++++++++++++++++++++- frontend/faraday/lib/tilelayout.less | 22 +----- 2 files changed, 109 insertions(+), 20 deletions(-) diff --git a/frontend/faraday/lib/TileLayout.tsx b/frontend/faraday/lib/TileLayout.tsx index 149b43d17..45cca293f 100644 --- a/frontend/faraday/lib/TileLayout.tsx +++ b/frontend/faraday/lib/TileLayout.tsx @@ -2,11 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 import clsx from "clsx"; -import { CSSProperties, RefObject, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { + CSSProperties, + ReactNode, + RefObject, + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; import { useDrag, useDragLayer, useDrop } from "react-dnd"; import useResizeObserver from "@react-hook/resize-observer"; import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js"; +import { findNode } from "./layoutNode.js"; import { ContentRenderer, LayoutNode, @@ -14,11 +25,12 @@ import { LayoutTreeActionType, LayoutTreeComputeMoveNodeAction, LayoutTreeDeleteNodeAction, + LayoutTreeMoveNodeAction, LayoutTreeState, WritableLayoutTreeStateAtom, } from "./model.js"; import "./tilelayout.less"; -import { setTransform as createTransform, debounce, determineDropDirection } from "./utils.js"; +import { FlexDirection, setTransform as createTransform, debounce, determineDropDirection } from "./utils.js"; export interface TileLayoutProps { layoutTreeStateAtom: WritableLayoutTreeStateAtom; @@ -178,6 +190,13 @@ export const TileLayout = ({ layoutTreeStateAtom, className, renderContent, ); })} +
({ layoutNode, layoutTreeState, dispatch, setRef, delete
); }; + +interface PlaceholderProps { + layoutTreeState: LayoutTreeState; + overlayContainerRef: React.RefObject; + nodeRefs: Map>; + style: React.CSSProperties; +} + +const Placeholder = ({ layoutTreeState, overlayContainerRef, nodeRefs, style }: PlaceholderProps) => { + const [placeholderOverlay, setPlaceholderOverlay] = useState(null); + + useEffect(() => { + let newPlaceholderOverlay: ReactNode; + if (layoutTreeState?.pendingAction?.type === LayoutTreeActionType.Move && overlayContainerRef?.current) { + const action = layoutTreeState.pendingAction as LayoutTreeMoveNodeAction; + let parentId: string; + if (action.insertAtRoot) { + parentId = layoutTreeState.rootNode.id; + } else { + parentId = action.parentId; + } + + const parentNode = findNode(layoutTreeState.rootNode, parentId); + if (action.index !== undefined && parentNode) { + const targetIndex = Math.min( + parentNode.children ? parentNode.children.length - 1 : 0, + Math.max(0, action.index - 1) + ); + let targetNode = parentNode?.children?.at(targetIndex); + let targetRef: React.RefObject; + if (targetNode) { + targetRef = nodeRefs.get(targetNode.id); + } else { + targetRef = nodeRefs.get(parentNode.id); + targetNode = parentNode; + } + if (targetRef?.current) { + const overlayBoundingRect = overlayContainerRef.current.getBoundingClientRect(); + const targetBoundingRect = targetRef.current.getBoundingClientRect(); + + let placeholderTransform: CSSProperties; + const placeholderHeight = + parentNode.flexDirection === FlexDirection.Column + ? targetBoundingRect.height / 2 + : targetBoundingRect.height; + const placeholderWidth = + parentNode.flexDirection === FlexDirection.Row + ? targetBoundingRect.width / 2 + : targetBoundingRect.width; + if (action.index > targetIndex) { + placeholderTransform = createTransform({ + top: + targetBoundingRect.top + + (parentNode.flexDirection === FlexDirection.Column && targetBoundingRect.height / 2) - + overlayBoundingRect.top, + left: + targetBoundingRect.left + + (parentNode.flexDirection === FlexDirection.Row && targetBoundingRect.width / 2) - + overlayBoundingRect.left, + width: placeholderWidth, + height: placeholderHeight, + }); + } else { + placeholderTransform = createTransform({ + top: targetBoundingRect.top - overlayBoundingRect.top, + left: targetBoundingRect.left - overlayBoundingRect.left, + width: placeholderWidth, + height: placeholderHeight, + }); + } + + newPlaceholderOverlay =
; + } + } + } + setPlaceholderOverlay(newPlaceholderOverlay); + }, [layoutTreeState, nodeRefs, overlayContainerRef]); + + return ( +
+ {placeholderOverlay} +
+ ); +}; diff --git a/frontend/faraday/lib/tilelayout.less b/frontend/faraday/lib/tilelayout.less index 7fc7d58ba..ab471efb5 100644 --- a/frontend/faraday/lib/tilelayout.less +++ b/frontend/faraday/lib/tilelayout.less @@ -24,8 +24,6 @@ .overlay-container { z-index: 2; - background-color: coral; - opacity: 0.5; } .tile-node, @@ -47,7 +45,8 @@ } &.animate { - .tile-node { + .tile-node, + .placeholder { transition-duration: 0.15s; transition-timing-function: ease-in; transition-property: transform, width, height; @@ -66,28 +65,15 @@ flex: 1 1 auto; max-height: 100%; max-width: 100%; - /* margin: 5px; */ } .tile-leaf { border: 1px solid black; } - .overlay-leaf { - border: 10px solid red; - } - - // .tile-leaf { - // border: 1px solid black; - // } - - // .overlay-leaf { - // margin: 1px; - // } - .placeholder { - display: flex; - flex: 1 1 auto; background-color: aqua; + opacity: 0.5; + border-radius: 5px; } }