mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-04 18:59:08 +01:00
Add placeholder for layout drag and drop (#26)
This commit is contained in:
parent
2b456f9725
commit
441463b172
@ -2,11 +2,22 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import clsx from "clsx";
|
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 { useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||||
|
|
||||||
import useResizeObserver from "@react-hook/resize-observer";
|
import useResizeObserver from "@react-hook/resize-observer";
|
||||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js";
|
import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js";
|
||||||
|
import { findNode } from "./layoutNode.js";
|
||||||
import {
|
import {
|
||||||
ContentRenderer,
|
ContentRenderer,
|
||||||
LayoutNode,
|
LayoutNode,
|
||||||
@ -14,11 +25,12 @@ import {
|
|||||||
LayoutTreeActionType,
|
LayoutTreeActionType,
|
||||||
LayoutTreeComputeMoveNodeAction,
|
LayoutTreeComputeMoveNodeAction,
|
||||||
LayoutTreeDeleteNodeAction,
|
LayoutTreeDeleteNodeAction,
|
||||||
|
LayoutTreeMoveNodeAction,
|
||||||
LayoutTreeState,
|
LayoutTreeState,
|
||||||
WritableLayoutTreeStateAtom,
|
WritableLayoutTreeStateAtom,
|
||||||
} from "./model.js";
|
} from "./model.js";
|
||||||
import "./tilelayout.less";
|
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<T> {
|
export interface TileLayoutProps<T> {
|
||||||
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
||||||
@ -178,6 +190,13 @@ export const TileLayout = <T,>({ layoutTreeStateAtom, className, renderContent,
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<Placeholder
|
||||||
|
key="placeholder"
|
||||||
|
layoutTreeState={layoutTreeState}
|
||||||
|
overlayContainerRef={overlayContainerRef}
|
||||||
|
nodeRefs={nodeRefs}
|
||||||
|
style={{ top: 10000, ...overlayTransform }}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
key="overlay"
|
key="overlay"
|
||||||
ref={overlayContainerRef}
|
ref={overlayContainerRef}
|
||||||
@ -362,3 +381,87 @@ const OverlayNode = <T,>({ layoutNode, layoutTreeState, dispatch, setRef, delete
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface PlaceholderProps<T> {
|
||||||
|
layoutTreeState: LayoutTreeState<T>;
|
||||||
|
overlayContainerRef: React.RefObject<HTMLElement>;
|
||||||
|
nodeRefs: Map<string, React.RefObject<HTMLElement>>;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefs, style }: PlaceholderProps<T>) => {
|
||||||
|
const [placeholderOverlay, setPlaceholderOverlay] = useState<ReactNode>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let newPlaceholderOverlay: ReactNode;
|
||||||
|
if (layoutTreeState?.pendingAction?.type === LayoutTreeActionType.Move && overlayContainerRef?.current) {
|
||||||
|
const action = layoutTreeState.pendingAction as LayoutTreeMoveNodeAction<T>;
|
||||||
|
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<HTMLElement>;
|
||||||
|
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 = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPlaceholderOverlay(newPlaceholderOverlay);
|
||||||
|
}, [layoutTreeState, nodeRefs, overlayContainerRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="placeholder-container" style={style}>
|
||||||
|
{placeholderOverlay}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
.overlay-container {
|
.overlay-container {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
background-color: coral;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-node,
|
.tile-node,
|
||||||
@ -47,7 +45,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.animate {
|
&.animate {
|
||||||
.tile-node {
|
.tile-node,
|
||||||
|
.placeholder {
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
transition-timing-function: ease-in;
|
transition-timing-function: ease-in;
|
||||||
transition-property: transform, width, height;
|
transition-property: transform, width, height;
|
||||||
@ -66,28 +65,15 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
/* margin: 5px; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-leaf {
|
.tile-leaf {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-leaf {
|
|
||||||
border: 10px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
// .tile-leaf {
|
|
||||||
// border: 1px solid black;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .overlay-leaf {
|
|
||||||
// margin: 1px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
display: flex;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
background-color: aqua;
|
background-color: aqua;
|
||||||
|
opacity: 0.5;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user