mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Clear a drag placeholder if the user drags an item out of the layout's hit trap (#61)
This commit is contained in:
parent
9c8ab4f555
commit
bfa4bb259e
@ -272,6 +272,17 @@ electron.ipcMain.on("isDevServer", (event) => {
|
||||
event.returnValue = isDevServer;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getCursorPoint", (event) => {
|
||||
const window = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
const screenPoint = electron.screen.getCursorScreenPoint();
|
||||
const windowRect = window.getContentBounds();
|
||||
const retVal: Point = {
|
||||
x: screenPoint.x - windowRect.x,
|
||||
y: screenPoint.y - windowRect.y,
|
||||
};
|
||||
event.returnValue = retVal;
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const startTs = Date.now();
|
||||
const instanceLock = electronApp.requestSingleInstanceLock();
|
||||
|
@ -6,4 +6,5 @@ let { contextBridge, ipcRenderer } = require("electron");
|
||||
contextBridge.exposeInMainWorld("api", {
|
||||
isDev: () => ipcRenderer.sendSync("isDev"),
|
||||
isDevServer: () => ipcRenderer.sendSync("isDevServer"),
|
||||
getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"),
|
||||
});
|
||||
|
@ -15,9 +15,11 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||
import { DropTargetMonitor, useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||
|
||||
import { getApi } from "@/app/store/global";
|
||||
import useResizeObserver from "@react-hook/resize-observer";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom";
|
||||
import { findNode } from "./layoutNode";
|
||||
import {
|
||||
@ -34,7 +36,7 @@ import {
|
||||
WritableLayoutTreeStateAtom,
|
||||
} from "./model";
|
||||
import "./tilelayout.less";
|
||||
import { Dimensions, FlexDirection, setTransform as createTransform, debounce, determineDropDirection } from "./utils";
|
||||
import { Dimensions, FlexDirection, setTransform as createTransform, determineDropDirection } from "./utils";
|
||||
|
||||
export interface TileLayoutProps<T> {
|
||||
/**
|
||||
@ -77,9 +79,9 @@ export const TileLayout = <T,>({
|
||||
const [nodeRefs, setNodeRefs] = useState<Map<string, RefObject<HTMLDivElement>>>(new Map());
|
||||
const [nodeRefsGen, setNodeRefsGen] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("layoutTreeState changed", layoutTreeState);
|
||||
}, [layoutTreeState]);
|
||||
// useEffect(() => {
|
||||
// console.log("layoutTreeState changed", layoutTreeState);
|
||||
// }, [layoutTreeState]);
|
||||
|
||||
const setRef = useCallback(
|
||||
(id: string, ref: RefObject<HTMLDivElement>) => {
|
||||
@ -108,32 +110,57 @@ export const TileLayout = <T,>({
|
||||
},
|
||||
[nodeRefs, setNodeRefs]
|
||||
);
|
||||
|
||||
const [overlayTransform, setOverlayTransform] = useState<CSSProperties>();
|
||||
const [layoutLeafTransforms, setLayoutLeafTransforms] = useState<Record<string, CSSProperties>>({});
|
||||
|
||||
const activeDrag = useDragLayer((monitor) => monitor.isDragging());
|
||||
const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({
|
||||
activeDrag: monitor.isDragging(),
|
||||
dragClientOffset: monitor.getClientOffset(),
|
||||
}));
|
||||
|
||||
// Effect to detect when the cursor leaves the TileLayout hit trap so we can remove any placeholders. This cannot be done using pointer capture
|
||||
// because that conflicts with the DnD layer.
|
||||
useEffect(
|
||||
debounce(100, () => {
|
||||
const cursorPoint = getApi().getCursorPoint();
|
||||
console.log(cursorPoint);
|
||||
if (cursorPoint && displayContainerRef.current) {
|
||||
const displayContainerRect = displayContainerRef.current.getBoundingClientRect();
|
||||
const normalizedX = cursorPoint.x - displayContainerRect.x;
|
||||
const normalizedY = cursorPoint.y - displayContainerRect.y;
|
||||
if (
|
||||
normalizedX <= 0 ||
|
||||
normalizedX >= displayContainerRect.width ||
|
||||
normalizedY <= 0 ||
|
||||
normalizedY >= displayContainerRect.height
|
||||
) {
|
||||
dispatch({ type: LayoutTreeActionType.ClearPendingAction });
|
||||
}
|
||||
}
|
||||
}),
|
||||
[dragClientOffset]
|
||||
);
|
||||
|
||||
/**
|
||||
* Callback to update the transforms on the displayed leafs and move the overlay over the display layer when dragging.
|
||||
*/
|
||||
const updateTransforms = useCallback(
|
||||
debounce(() => {
|
||||
debounce(30, () => {
|
||||
if (overlayContainerRef.current && displayContainerRef.current) {
|
||||
const displayBoundingRect = displayContainerRef.current.getBoundingClientRect();
|
||||
console.log("displayBoundingRect", displayBoundingRect);
|
||||
// console.log("displayBoundingRect", displayBoundingRect);
|
||||
const overlayBoundingRect = overlayContainerRef.current.getBoundingClientRect();
|
||||
|
||||
const newLayoutLeafTransforms: Record<string, CSSProperties> = {};
|
||||
|
||||
console.log(
|
||||
"nodeRefs",
|
||||
nodeRefs,
|
||||
"layoutLeafs",
|
||||
layoutTreeState.leafs,
|
||||
"layoutTreeState",
|
||||
layoutTreeState
|
||||
);
|
||||
// console.log(
|
||||
// "nodeRefs",
|
||||
// nodeRefs,
|
||||
// "layoutLeafs",
|
||||
// layoutTreeState.leafs,
|
||||
// "layoutTreeState",
|
||||
// layoutTreeState
|
||||
// );
|
||||
|
||||
for (const leaf of layoutTreeState.leafs) {
|
||||
const leafRef = nodeRefs.get(leaf.id);
|
||||
@ -155,7 +182,7 @@ export const TileLayout = <T,>({
|
||||
setLayoutLeafTransforms(newLayoutLeafTransforms);
|
||||
|
||||
const newOverlayOffset = displayBoundingRect.top + 2 * displayBoundingRect.height;
|
||||
console.log("overlayOffset", newOverlayOffset);
|
||||
// console.log("overlayOffset", newOverlayOffset);
|
||||
setOverlayTransform(
|
||||
createTransform(
|
||||
{
|
||||
@ -168,7 +195,7 @@ export const TileLayout = <T,>({
|
||||
)
|
||||
);
|
||||
}
|
||||
}, 30),
|
||||
}),
|
||||
[activeDrag, overlayContainerRef, displayContainerRef, layoutTreeState.leafs, nodeRefsGen]
|
||||
);
|
||||
|
||||
@ -179,6 +206,12 @@ export const TileLayout = <T,>({
|
||||
|
||||
useResizeObserver(overlayContainerRef, () => updateTransforms());
|
||||
|
||||
const onPointerLeave = useCallback(() => {
|
||||
if (activeDrag) {
|
||||
dispatch({ type: LayoutTreeActionType.ClearPendingAction });
|
||||
}
|
||||
}, [activeDrag, dispatch]);
|
||||
|
||||
// Ensure that we don't see any jostling in the layout when we're rendering it the first time.
|
||||
// `animate` will be disabled until after the transforms have all applied the first time.
|
||||
const [animate, setAnimate] = useState(false);
|
||||
@ -190,23 +223,23 @@ export const TileLayout = <T,>({
|
||||
|
||||
const onLeafClose = useCallback(
|
||||
async (node: LayoutNode<T>) => {
|
||||
console.log("onLeafClose", node);
|
||||
// console.log("onLeafClose", node);
|
||||
const deleteAction: LayoutTreeDeleteNodeAction = {
|
||||
type: LayoutTreeActionType.DeleteNode,
|
||||
nodeId: node.id,
|
||||
};
|
||||
console.log("calling dispatch", deleteAction);
|
||||
// console.log("calling dispatch", deleteAction);
|
||||
dispatch(deleteAction);
|
||||
console.log("calling onNodeDelete", node);
|
||||
// console.log("calling onNodeDelete", node);
|
||||
await onNodeDelete?.(node.data);
|
||||
console.log("node deleted");
|
||||
// console.log("node deleted");
|
||||
},
|
||||
[onNodeDelete, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<div className={clsx("tile-layout", className, { animate })}>
|
||||
<div className={clsx("tile-layout", className, { animate })} onPointerOut={onPointerLeave}>
|
||||
<div key="display" ref={displayContainerRef} className="display-container">
|
||||
{layoutLeafTransforms &&
|
||||
layoutTreeState.leafs.map((leaf) => {
|
||||
@ -431,18 +464,18 @@ const OverlayNode = <T,>({ layoutNode, layoutTreeState, dispatch, setRef, delete
|
||||
return false;
|
||||
},
|
||||
drop: (_, monitor) => {
|
||||
console.log("drop start", layoutNode.id, layoutTreeState.pendingAction);
|
||||
// console.log("drop start", layoutNode.id, layoutTreeState.pendingAction);
|
||||
if (!monitor.didDrop() && layoutTreeState.pendingAction) {
|
||||
dispatch({
|
||||
type: LayoutTreeActionType.CommitPendingAction,
|
||||
});
|
||||
}
|
||||
},
|
||||
hover: (_, monitor) => {
|
||||
hover: throttle(30, (_, monitor: DropTargetMonitor<unknown, unknown>) => {
|
||||
if (monitor.isOver({ shallow: true })) {
|
||||
if (monitor.canDrop()) {
|
||||
const dragItem = monitor.getItem<LayoutNode<T>>();
|
||||
console.log("computing operation", layoutNode, dragItem, layoutTreeState.pendingAction);
|
||||
// console.log("computing operation", layoutNode, dragItem, layoutTreeState.pendingAction);
|
||||
dispatch({
|
||||
type: LayoutTreeActionType.ComputeMove,
|
||||
node: layoutNode,
|
||||
@ -458,7 +491,7 @@ const OverlayNode = <T,>({ layoutNode, layoutTreeState, dispatch, setRef, delete
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
[overlayRef.current, layoutNode, layoutTreeState, dispatch]
|
||||
);
|
||||
@ -612,7 +645,7 @@ const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefs, style
|
||||
}
|
||||
case LayoutTreeActionType.Swap: {
|
||||
const action = layoutTreeState.pendingAction as LayoutTreeSwapNodeAction<T>;
|
||||
console.log("placeholder for swap", action);
|
||||
// console.log("placeholder for swap", action);
|
||||
const targetNode = action.node1;
|
||||
const targetRef = nodeRefs.get(targetNode?.id);
|
||||
if (targetRef?.current) {
|
||||
|
@ -116,7 +116,7 @@ function computeMoveNode<T>(
|
||||
) {
|
||||
const rootNode = layoutTreeState.rootNode;
|
||||
const { node, nodeToMove, direction } = computeInsertAction;
|
||||
console.log("computeInsertOperation start", layoutTreeState.rootNode, node, nodeToMove, direction);
|
||||
// console.log("computeInsertOperation start", layoutTreeState.rootNode, node, nodeToMove, direction);
|
||||
if (direction === undefined) {
|
||||
console.warn("No direction provided for insertItemInDirection");
|
||||
return;
|
||||
@ -141,10 +141,8 @@ function computeMoveNode<T>(
|
||||
switch (direction) {
|
||||
case DropDirection.OuterTop:
|
||||
if (node.flexDirection === FlexDirection.Column) {
|
||||
console.log("outer top column");
|
||||
const grandparentNode = grandparent();
|
||||
if (grandparentNode) {
|
||||
console.log("has grandparent", grandparentNode);
|
||||
const index = indexInGrandparent();
|
||||
newMoveOperation = {
|
||||
parentId: grandparentNode.id,
|
||||
@ -176,10 +174,8 @@ function computeMoveNode<T>(
|
||||
break;
|
||||
case DropDirection.OuterBottom:
|
||||
if (node.flexDirection === FlexDirection.Column) {
|
||||
console.log("outer bottom column");
|
||||
const grandparentNode = grandparent();
|
||||
if (grandparentNode) {
|
||||
console.log("has grandparent", grandparentNode);
|
||||
const index = indexInGrandparent() + 1;
|
||||
newMoveOperation = {
|
||||
parentId: grandparentNode.id,
|
||||
@ -211,10 +207,8 @@ function computeMoveNode<T>(
|
||||
break;
|
||||
case DropDirection.OuterLeft:
|
||||
if (node.flexDirection === FlexDirection.Row) {
|
||||
console.log("outer left row");
|
||||
const grandparentNode = grandparent();
|
||||
if (grandparentNode) {
|
||||
console.log("has grandparent", grandparentNode);
|
||||
const index = indexInGrandparent();
|
||||
newMoveOperation = {
|
||||
parentId: grandparentNode.id,
|
||||
@ -239,10 +233,8 @@ function computeMoveNode<T>(
|
||||
break;
|
||||
case DropDirection.OuterRight:
|
||||
if (node.flexDirection === FlexDirection.Row) {
|
||||
console.log("outer right row");
|
||||
const grandparentNode = grandparent();
|
||||
if (grandparentNode) {
|
||||
console.log("has grandparent", grandparentNode);
|
||||
const index = indexInGrandparent() + 1;
|
||||
newMoveOperation = {
|
||||
parentId: grandparentNode.id,
|
||||
@ -266,14 +258,14 @@ function computeMoveNode<T>(
|
||||
}
|
||||
break;
|
||||
case DropDirection.Center:
|
||||
console.log("center drop", rootNode, node, nodeToMove);
|
||||
// console.log("center drop", rootNode, node, nodeToMove);
|
||||
if (node.id !== rootNode.id && nodeToMove.id !== rootNode.id) {
|
||||
const swapAction: LayoutTreeSwapNodeAction<T> = {
|
||||
type: LayoutTreeActionType.Swap,
|
||||
node1: node,
|
||||
node2: nodeToMove,
|
||||
};
|
||||
console.log("swapAction", swapAction);
|
||||
// console.log("swapAction", swapAction);
|
||||
layoutTreeState.pendingAction = swapAction;
|
||||
return;
|
||||
} else {
|
||||
@ -301,7 +293,7 @@ function clearPendingAction(layoutTreeState: LayoutTreeState<any>) {
|
||||
|
||||
function moveNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeMoveNodeAction<T>) {
|
||||
const rootNode = layoutTreeState.rootNode;
|
||||
console.log("moveNode", action, layoutTreeState.rootNode);
|
||||
// console.log("moveNode", action, layoutTreeState.rootNode);
|
||||
if (!action) {
|
||||
console.error("no move node action provided");
|
||||
return;
|
||||
@ -397,7 +389,7 @@ function swapNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeSwap
|
||||
}
|
||||
|
||||
function deleteNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeDeleteNodeAction) {
|
||||
console.log("deleteNode", layoutTreeState, action);
|
||||
// console.log("deleteNode", layoutTreeState, action);
|
||||
if (!action?.nodeId) {
|
||||
console.error("no delete node action provided");
|
||||
return;
|
||||
@ -415,7 +407,7 @@ function deleteNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeDe
|
||||
if (parent) {
|
||||
const node = parent.children.find((child) => child.id === action.nodeId);
|
||||
removeChild(parent, node);
|
||||
console.log("node deleted", parent, node);
|
||||
// console.log("node deleted", parent, node);
|
||||
} else {
|
||||
console.error("unable to delete node, not found in tree");
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export function reverseFlexDirection(flexDirection: FlexDirection): FlexDirectio
|
||||
}
|
||||
|
||||
export function determineDropDirection(dimensions?: Dimensions, offset?: XYCoord | null): DropDirection | undefined {
|
||||
console.log("determineDropDirection", dimensions, offset);
|
||||
// console.log("determineDropDirection", dimensions, offset);
|
||||
if (!offset || !dimensions) return undefined;
|
||||
const { width, height, left, top } = dimensions;
|
||||
let { x, y } = offset;
|
||||
|
13
frontend/types/custom.d.ts
vendored
13
frontend/types/custom.d.ts
vendored
@ -7,8 +7,21 @@ declare global {
|
||||
};
|
||||
|
||||
type ElectronApi = {
|
||||
/**
|
||||
* Determines whether the current app instance is a development build.
|
||||
* @returns True if the current app instance is a development build.
|
||||
*/
|
||||
isDev: () => boolean;
|
||||
/**
|
||||
* Determines whether the current app instance is hosted in a Vite dev server.
|
||||
* @returns True if the current app instance is hosted in a Vite dev server.
|
||||
*/
|
||||
isDevServer: () => boolean;
|
||||
/**
|
||||
* Get a point value representing the cursor's position relative to the calling BrowserWindow
|
||||
* @returns A point value.
|
||||
*/
|
||||
getCursorPoint: () => Electron.Point;
|
||||
};
|
||||
|
||||
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
||||
|
Loading…
Reference in New Issue
Block a user