2024-06-04 22:05:44 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
import clsx from "clsx";
|
2024-06-19 08:44:53 +02:00
|
|
|
import { toPng } from "html-to-image";
|
2024-06-12 02:42:10 +02:00
|
|
|
import React, {
|
2024-06-07 02:58:37 +02:00
|
|
|
CSSProperties,
|
|
|
|
ReactNode,
|
|
|
|
RefObject,
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
Suspense,
|
2024-06-07 02:58:37 +02:00
|
|
|
useCallback,
|
|
|
|
useEffect,
|
|
|
|
useLayoutEffect,
|
|
|
|
useMemo,
|
|
|
|
useRef,
|
|
|
|
useState,
|
|
|
|
} from "react";
|
2024-06-04 22:05:44 +02:00
|
|
|
import { useDrag, useDragLayer, useDrop } from "react-dnd";
|
|
|
|
|
2024-06-06 23:57:37 +02:00
|
|
|
import useResizeObserver from "@react-hook/resize-observer";
|
2024-06-12 02:42:10 +02:00
|
|
|
import { useLayoutTreeStateReducerAtom } from "./layoutAtom";
|
|
|
|
import { findNode } from "./layoutNode";
|
2024-06-04 22:05:44 +02:00
|
|
|
import {
|
|
|
|
ContentRenderer,
|
|
|
|
LayoutNode,
|
|
|
|
LayoutTreeAction,
|
|
|
|
LayoutTreeActionType,
|
|
|
|
LayoutTreeComputeMoveNodeAction,
|
|
|
|
LayoutTreeDeleteNodeAction,
|
2024-06-07 02:58:37 +02:00
|
|
|
LayoutTreeMoveNodeAction,
|
2024-06-04 22:05:44 +02:00
|
|
|
LayoutTreeState,
|
2024-06-17 23:14:09 +02:00
|
|
|
LayoutTreeSwapNodeAction,
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
PreviewRenderer,
|
2024-06-04 22:05:44 +02:00
|
|
|
WritableLayoutTreeStateAtom,
|
2024-06-12 02:42:10 +02:00
|
|
|
} from "./model";
|
2024-06-04 22:05:44 +02:00
|
|
|
import "./tilelayout.less";
|
2024-06-12 02:42:10 +02:00
|
|
|
import { Dimensions, FlexDirection, setTransform as createTransform, debounce, determineDropDirection } from "./utils";
|
2024-06-04 22:05:44 +02:00
|
|
|
|
|
|
|
export interface TileLayoutProps<T> {
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The atom containing the layout tree state.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that accepts the data from the leaf node and displays the leaf contents to the user.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
renderContent: ContentRenderer<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that accepts the data from the leaf node and returns a preview that can be shown when the user drags a node.
|
|
|
|
*/
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
renderPreview?: PreviewRenderer<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that is called when a node gets deleted from the LayoutTreeState.
|
|
|
|
* @param data The contents of the node that was deleted.
|
|
|
|
*/
|
2024-06-06 02:21:40 +02:00
|
|
|
onNodeDelete?: (data: T) => Promise<void>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The class name to use for the top-level div of the tile layout.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
className?: string;
|
|
|
|
}
|
|
|
|
|
2024-06-19 08:44:53 +02:00
|
|
|
const DragPreviewWidth = 300;
|
|
|
|
const DragPreviewHeight = 300;
|
|
|
|
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
export const TileLayout = <T,>({
|
|
|
|
layoutTreeStateAtom,
|
|
|
|
className,
|
|
|
|
renderContent,
|
|
|
|
renderPreview,
|
|
|
|
onNodeDelete,
|
|
|
|
}: TileLayoutProps<T>) => {
|
2024-06-04 22:05:44 +02:00
|
|
|
const overlayContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const displayContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
const [layoutTreeState, dispatch] = useLayoutTreeStateReducerAtom(layoutTreeStateAtom);
|
|
|
|
const [nodeRefs, setNodeRefs] = useState<Map<string, RefObject<HTMLDivElement>>>(new Map());
|
2024-06-11 00:16:29 +02:00
|
|
|
const [nodeRefsGen, setNodeRefsGen] = useState<number>(0);
|
2024-06-04 22:05:44 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
console.log("layoutTreeState changed", layoutTreeState);
|
|
|
|
}, [layoutTreeState]);
|
|
|
|
|
|
|
|
const setRef = useCallback(
|
|
|
|
(id: string, ref: RefObject<HTMLDivElement>) => {
|
|
|
|
setNodeRefs((prev) => {
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
// console.log("setRef", id, ref);
|
2024-06-04 22:05:44 +02:00
|
|
|
prev.set(id, ref);
|
|
|
|
return prev;
|
|
|
|
});
|
2024-06-11 00:16:29 +02:00
|
|
|
setNodeRefsGen((prev) => prev + 1);
|
2024-06-04 22:05:44 +02:00
|
|
|
},
|
|
|
|
[setNodeRefs]
|
|
|
|
);
|
|
|
|
|
|
|
|
const deleteRef = useCallback(
|
|
|
|
(id: string) => {
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
// console.log("deleteRef", id);
|
2024-06-04 22:05:44 +02:00
|
|
|
if (nodeRefs.has(id)) {
|
|
|
|
setNodeRefs((prev) => {
|
|
|
|
prev.delete(id);
|
|
|
|
return prev;
|
|
|
|
});
|
2024-06-11 00:16:29 +02:00
|
|
|
setNodeRefsGen((prev) => prev + 1);
|
2024-06-04 22:05:44 +02:00
|
|
|
} else {
|
|
|
|
console.log("deleteRef id not found", id);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[nodeRefs, setNodeRefs]
|
|
|
|
);
|
|
|
|
|
|
|
|
const [overlayTransform, setOverlayTransform] = useState<CSSProperties>();
|
|
|
|
const [layoutLeafTransforms, setLayoutLeafTransforms] = useState<Record<string, CSSProperties>>({});
|
|
|
|
|
|
|
|
const activeDrag = useDragLayer((monitor) => monitor.isDragging());
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback to update the transforms on the displayed leafs and move the overlay over the display layer when dragging.
|
|
|
|
*/
|
|
|
|
const updateTransforms = useCallback(
|
|
|
|
debounce(() => {
|
|
|
|
if (overlayContainerRef.current && displayContainerRef.current) {
|
|
|
|
const displayBoundingRect = displayContainerRef.current.getBoundingClientRect();
|
|
|
|
console.log("displayBoundingRect", displayBoundingRect);
|
|
|
|
const overlayBoundingRect = overlayContainerRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
const newLayoutLeafTransforms: Record<string, CSSProperties> = {};
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
"nodeRefs",
|
|
|
|
nodeRefs,
|
|
|
|
"layoutLeafs",
|
|
|
|
layoutTreeState.leafs,
|
|
|
|
"layoutTreeState",
|
|
|
|
layoutTreeState
|
|
|
|
);
|
|
|
|
|
|
|
|
for (const leaf of layoutTreeState.leafs) {
|
|
|
|
const leafRef = nodeRefs.get(leaf.id);
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
// console.log("current leafRef", leafRef.current);
|
2024-06-04 22:05:44 +02:00
|
|
|
if (leafRef?.current) {
|
|
|
|
const leafBounding = leafRef.current.getBoundingClientRect();
|
|
|
|
const transform = createTransform({
|
|
|
|
top: leafBounding.top - overlayBoundingRect.top,
|
|
|
|
left: leafBounding.left - overlayBoundingRect.left,
|
|
|
|
width: leafBounding.width,
|
|
|
|
height: leafBounding.height,
|
|
|
|
});
|
|
|
|
newLayoutLeafTransforms[leafRef.current.id] = transform;
|
|
|
|
} else {
|
|
|
|
console.warn("missing leaf", leaf.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setLayoutLeafTransforms(newLayoutLeafTransforms);
|
|
|
|
|
|
|
|
const newOverlayOffset = displayBoundingRect.top + 2 * displayBoundingRect.height;
|
|
|
|
console.log("overlayOffset", newOverlayOffset);
|
|
|
|
setOverlayTransform(
|
|
|
|
createTransform(
|
|
|
|
{
|
|
|
|
top: activeDrag ? 0 : newOverlayOffset,
|
|
|
|
left: 0,
|
|
|
|
width: overlayBoundingRect.width,
|
|
|
|
height: overlayBoundingRect.height,
|
|
|
|
},
|
|
|
|
false
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}, 30),
|
2024-06-11 00:16:29 +02:00
|
|
|
[activeDrag, overlayContainerRef, displayContainerRef, layoutTreeState.leafs, nodeRefsGen]
|
2024-06-04 22:05:44 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Update the transforms whenever we drag something and whenever the layout updates.
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
updateTransforms();
|
2024-06-11 00:16:29 +02:00
|
|
|
}, [updateTransforms]);
|
2024-06-04 22:05:44 +02:00
|
|
|
|
2024-06-06 23:57:37 +02:00
|
|
|
useResizeObserver(overlayContainerRef, () => updateTransforms());
|
2024-06-04 22:05:44 +02:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
useEffect(() => {
|
|
|
|
setTimeout(() => {
|
|
|
|
setAnimate(true);
|
|
|
|
}, 50);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onLeafClose = useCallback(
|
2024-06-06 02:21:40 +02:00
|
|
|
async (node: LayoutNode<T>) => {
|
2024-06-04 22:05:44 +02:00
|
|
|
console.log("onLeafClose", node);
|
|
|
|
const deleteAction: LayoutTreeDeleteNodeAction = {
|
|
|
|
type: LayoutTreeActionType.DeleteNode,
|
|
|
|
nodeId: node.id,
|
|
|
|
};
|
|
|
|
console.log("calling dispatch", deleteAction);
|
|
|
|
dispatch(deleteAction);
|
|
|
|
console.log("calling onNodeDelete", node);
|
2024-06-06 02:21:40 +02:00
|
|
|
await onNodeDelete?.(node.data);
|
2024-06-04 22:05:44 +02:00
|
|
|
console.log("node deleted");
|
|
|
|
},
|
|
|
|
[onNodeDelete, dispatch]
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
<Suspense>
|
|
|
|
<div className={clsx("tile-layout", className, { animate })}>
|
|
|
|
<div key="display" ref={displayContainerRef} className="display-container">
|
|
|
|
{layoutLeafTransforms &&
|
|
|
|
layoutTreeState.leafs.map((leaf) => {
|
|
|
|
return (
|
2024-06-14 04:36:06 +02:00
|
|
|
<DisplayNode
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
key={leaf.id}
|
|
|
|
layoutNode={leaf}
|
|
|
|
renderContent={renderContent}
|
|
|
|
renderPreview={renderPreview}
|
|
|
|
transform={layoutLeafTransforms[leaf.id]}
|
|
|
|
onLeafClose={onLeafClose}
|
|
|
|
ready={animate}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
<Placeholder
|
|
|
|
key="placeholder"
|
2024-06-04 22:05:44 +02:00
|
|
|
layoutTreeState={layoutTreeState}
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
overlayContainerRef={overlayContainerRef}
|
|
|
|
nodeRefs={nodeRefs}
|
|
|
|
style={{ top: 10000, ...overlayTransform }}
|
2024-06-04 22:05:44 +02:00
|
|
|
/>
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
<div
|
|
|
|
key="overlay"
|
|
|
|
ref={overlayContainerRef}
|
|
|
|
className="overlay-container"
|
|
|
|
style={{ top: 10000, ...overlayTransform }}
|
|
|
|
>
|
|
|
|
<OverlayNode
|
|
|
|
layoutNode={layoutTreeState.rootNode}
|
|
|
|
layoutTreeState={layoutTreeState}
|
|
|
|
dispatch={dispatch}
|
|
|
|
setRef={setRef}
|
|
|
|
deleteRef={deleteRef}
|
|
|
|
/>
|
|
|
|
</div>
|
2024-06-04 22:05:44 +02:00
|
|
|
</div>
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
</Suspense>
|
2024-06-04 22:05:44 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-06-14 04:36:06 +02:00
|
|
|
interface DisplayNodeProps<T> {
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The leaf node object, containing the data needed to display the leaf contents to the user.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
layoutNode: LayoutNode<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that accepts the data from the leaf node and displays the leaf contents to the user.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
renderContent: ContentRenderer<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that accepts the data from the leaf node and returns a preview that can be shown when the user drags a node.
|
|
|
|
*/
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
renderPreview?: PreviewRenderer<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback that is called when a leaf node gets closed.
|
|
|
|
* @param node The node that is closed.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
onLeafClose: (node: LayoutNode<T>) => void;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* Determines whether a leaf's contents should be displayed to the user.
|
|
|
|
*/
|
2024-06-05 20:56:04 +02:00
|
|
|
ready: boolean;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
transform: CSSProperties;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dragItemType = "TILE_ITEM";
|
|
|
|
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
2024-06-14 04:36:06 +02:00
|
|
|
* The draggable and displayable portion of a leaf node in a layout tree.
|
2024-06-14 04:33:06 +02:00
|
|
|
*/
|
2024-06-14 04:36:06 +02:00
|
|
|
const DisplayNode = <T,>({
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
layoutNode,
|
|
|
|
renderContent,
|
|
|
|
renderPreview,
|
|
|
|
transform,
|
|
|
|
onLeafClose,
|
|
|
|
ready,
|
2024-06-14 04:36:06 +02:00
|
|
|
}: DisplayNodeProps<T>) => {
|
2024-06-04 22:05:44 +02:00
|
|
|
const tileNodeRef = useRef<HTMLDivElement>(null);
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
const previewRef = useRef<HTMLDivElement>(null);
|
2024-06-19 08:44:53 +02:00
|
|
|
const hasImagePreviewSetRef = useRef(false);
|
2024-06-04 22:05:44 +02:00
|
|
|
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
// Register the node as a draggable item.
|
2024-06-11 23:51:35 +02:00
|
|
|
const [{ isDragging }, drag, dragPreview] = useDrag(
|
2024-06-04 22:05:44 +02:00
|
|
|
() => ({
|
|
|
|
type: dragItemType,
|
|
|
|
item: () => layoutNode,
|
|
|
|
collect: (monitor) => ({
|
|
|
|
isDragging: monitor.isDragging(),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
[layoutNode]
|
|
|
|
);
|
|
|
|
|
2024-06-19 08:44:53 +02:00
|
|
|
const previewElement = renderPreview?.(layoutNode.data);
|
|
|
|
const previewWidth = DragPreviewWidth;
|
|
|
|
const previewHeight = DragPreviewHeight;
|
|
|
|
const previewTransform = `scale(${1 / window.devicePixelRatio})`;
|
|
|
|
const [previewImage, setPreviewImage] = useState<HTMLImageElement>(null);
|
|
|
|
// we set the drag preview on load to be the HTML element
|
|
|
|
// later, on pointerenter, we generate a static png preview to use instead (for performance)
|
|
|
|
useEffect(() => {
|
|
|
|
if (!hasImagePreviewSetRef.current) {
|
|
|
|
dragPreview(previewRef.current);
|
|
|
|
}
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
}, []);
|
2024-06-11 23:28:17 +02:00
|
|
|
const generatePreviewImage = useCallback(() => {
|
2024-06-19 08:44:53 +02:00
|
|
|
let offsetX = (DragPreviewWidth * window.devicePixelRatio - DragPreviewWidth) / 2 + 10;
|
|
|
|
let offsetY = (DragPreviewHeight * window.devicePixelRatio - DragPreviewHeight) / 2 + 10;
|
|
|
|
if (previewImage != null) {
|
|
|
|
dragPreview(previewImage, { offsetY, offsetX });
|
2024-06-12 01:28:41 +02:00
|
|
|
} else if (previewRef.current) {
|
2024-06-11 23:28:17 +02:00
|
|
|
toPng(previewRef.current).then((url) => {
|
|
|
|
const img = new Image();
|
|
|
|
img.src = url;
|
2024-06-19 08:44:53 +02:00
|
|
|
img.onload = () => {
|
|
|
|
hasImagePreviewSetRef.current = true;
|
|
|
|
setPreviewImage(img);
|
|
|
|
dragPreview(img, { offsetY, offsetX });
|
|
|
|
};
|
2024-06-11 23:28:17 +02:00
|
|
|
});
|
|
|
|
}
|
2024-06-12 01:28:41 +02:00
|
|
|
}, [previewRef, previewImage, dragPreview]);
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
|
2024-06-04 22:05:44 +02:00
|
|
|
// Register the tile item as a draggable component
|
|
|
|
useEffect(() => {
|
|
|
|
drag(tileNodeRef);
|
|
|
|
}, [tileNodeRef]);
|
|
|
|
|
|
|
|
const onClose = useCallback(() => {
|
|
|
|
onLeafClose(layoutNode);
|
|
|
|
}, [layoutNode, onLeafClose]);
|
|
|
|
|
2024-06-05 20:56:04 +02:00
|
|
|
const leafContent = useMemo(() => {
|
|
|
|
return (
|
|
|
|
layoutNode.data && (
|
|
|
|
<div key="leaf" className="tile-leaf">
|
|
|
|
{renderContent(layoutNode.data, ready, onClose)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
);
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
}, [layoutNode.data, ready, onClose]);
|
2024-06-05 20:56:04 +02:00
|
|
|
|
2024-06-04 22:05:44 +02:00
|
|
|
return (
|
|
|
|
<div
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
className={clsx("tile-node", { dragging: isDragging })}
|
2024-06-04 22:05:44 +02:00
|
|
|
ref={tileNodeRef}
|
|
|
|
id={layoutNode.id}
|
|
|
|
style={{
|
|
|
|
flexDirection: layoutNode.flexDirection,
|
|
|
|
flexBasis: layoutNode.size,
|
|
|
|
...transform,
|
|
|
|
}}
|
2024-06-11 23:28:17 +02:00
|
|
|
onPointerEnter={generatePreviewImage}
|
2024-06-04 22:05:44 +02:00
|
|
|
>
|
2024-06-05 20:56:04 +02:00
|
|
|
{leafContent}
|
2024-06-19 08:44:53 +02:00
|
|
|
<div key="preview" className="tile-preview-container">
|
|
|
|
<div
|
|
|
|
className="tile-preview"
|
|
|
|
ref={previewRef}
|
|
|
|
style={{
|
|
|
|
width: previewWidth,
|
|
|
|
height: previewHeight,
|
|
|
|
transform: previewTransform,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{previewElement}
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-06-04 22:05:44 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
interface OverlayNodeProps<T> {
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The layout node object corresponding to the OverlayNode.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
layoutNode: LayoutNode<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The layout tree state.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
layoutTreeState: LayoutTreeState<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The reducer function for mutating the layout tree state.
|
|
|
|
* @param action The action to perform.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
dispatch: (action: LayoutTreeAction) => void;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback to update the RefObject mapping corresponding to the layout node. Used to inform the TileLayout of changes to the OverlayNode's position and size.
|
|
|
|
* @param id The id of the layout node being mounted.
|
|
|
|
* @param ref The reference to the mounted overlay node.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
setRef: (id: string, ref: RefObject<HTMLDivElement>) => void;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A callback to remove the RefObject mapping corresponding to the layout node when it gets unmounted.
|
|
|
|
* @param id The id of the layout node being unmounted.
|
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
deleteRef: (id: string) => void;
|
|
|
|
}
|
|
|
|
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* An overlay representing the true flexbox layout of the LayoutTreeState. This holds the drop targets for moving around nodes and is used to calculate the
|
2024-06-14 04:36:06 +02:00
|
|
|
* dimensions of the corresponding DisplayNode for each LayoutTreeState leaf.
|
2024-06-14 04:33:06 +02:00
|
|
|
*/
|
2024-06-04 22:05:44 +02:00
|
|
|
const OverlayNode = <T,>({ layoutNode, layoutTreeState, dispatch, setRef, deleteRef }: OverlayNodeProps<T>) => {
|
|
|
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
|
|
const leafRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
const [, drop] = useDrop(
|
|
|
|
() => ({
|
|
|
|
accept: dragItemType,
|
|
|
|
canDrop: (_, monitor) => {
|
|
|
|
const dragItem = monitor.getItem<LayoutNode<T>>();
|
|
|
|
if (monitor.isOver({ shallow: true }) && dragItem?.id !== layoutNode.id) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
drop: (_, monitor) => {
|
|
|
|
console.log("drop start", layoutNode.id, layoutTreeState.pendingAction);
|
|
|
|
if (!monitor.didDrop() && layoutTreeState.pendingAction) {
|
|
|
|
dispatch({
|
|
|
|
type: LayoutTreeActionType.CommitPendingAction,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
hover: (_, monitor) => {
|
2024-06-19 01:03:00 +02:00
|
|
|
if (monitor.isOver({ shallow: true })) {
|
|
|
|
if (monitor.canDrop()) {
|
|
|
|
const dragItem = monitor.getItem<LayoutNode<T>>();
|
|
|
|
console.log("computing operation", layoutNode, dragItem, layoutTreeState.pendingAction);
|
|
|
|
dispatch({
|
|
|
|
type: LayoutTreeActionType.ComputeMove,
|
|
|
|
node: layoutNode,
|
|
|
|
nodeToMove: dragItem,
|
|
|
|
direction: determineDropDirection(
|
|
|
|
overlayRef.current?.getBoundingClientRect(),
|
|
|
|
monitor.getClientOffset()
|
|
|
|
),
|
|
|
|
} as LayoutTreeComputeMoveNodeAction<T>);
|
|
|
|
} else {
|
|
|
|
dispatch({
|
|
|
|
type: LayoutTreeActionType.ClearPendingAction,
|
|
|
|
});
|
|
|
|
}
|
2024-06-04 22:05:44 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
[overlayRef.current, layoutNode, layoutTreeState, dispatch]
|
|
|
|
);
|
|
|
|
|
|
|
|
// Register the tile item as a draggable component
|
|
|
|
useEffect(() => {
|
|
|
|
const layoutNodeId = layoutNode?.id;
|
|
|
|
if (overlayRef?.current) {
|
|
|
|
drop(overlayRef);
|
|
|
|
setRef(layoutNodeId, overlayRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
deleteRef(layoutNodeId);
|
|
|
|
};
|
|
|
|
}, [overlayRef]);
|
|
|
|
|
|
|
|
const generateChildren = () => {
|
|
|
|
if (Array.isArray(layoutNode.children)) {
|
|
|
|
return layoutNode.children.map((childItem) => {
|
|
|
|
return (
|
|
|
|
<OverlayNode
|
|
|
|
key={childItem.id}
|
|
|
|
layoutNode={childItem}
|
|
|
|
layoutTreeState={layoutTreeState}
|
|
|
|
dispatch={dispatch}
|
|
|
|
setRef={setRef}
|
|
|
|
deleteRef={deleteRef}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return [<div ref={leafRef} key="leaf" className="overlay-leaf"></div>];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!layoutNode) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={overlayRef}
|
|
|
|
className="overlay-node"
|
|
|
|
id={layoutNode.id}
|
|
|
|
style={{
|
|
|
|
flexBasis: layoutNode.size,
|
|
|
|
flexDirection: layoutNode.flexDirection,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{generateChildren()}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
2024-06-07 02:58:37 +02:00
|
|
|
|
|
|
|
interface PlaceholderProps<T> {
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The layout tree state.
|
|
|
|
*/
|
2024-06-07 02:58:37 +02:00
|
|
|
layoutTreeState: LayoutTreeState<T>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* A reference to the div containing the overlay nodes. Used to normalize the position of the target node as the overlay container is moved in and out of view.
|
|
|
|
*/
|
2024-06-07 02:58:37 +02:00
|
|
|
overlayContainerRef: React.RefObject<HTMLElement>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* The mapping of all layout nodes to their corresponding mounted overlay node.
|
|
|
|
*/
|
2024-06-07 02:58:37 +02:00
|
|
|
nodeRefs: Map<string, React.RefObject<HTMLElement>>;
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* Any styling to apply to the placeholder container div.
|
|
|
|
*/
|
2024-06-07 02:58:37 +02:00
|
|
|
style: React.CSSProperties;
|
|
|
|
}
|
|
|
|
|
2024-06-14 04:33:06 +02:00
|
|
|
/**
|
|
|
|
* An overlay to preview pending actions on the layout tree.
|
|
|
|
*/
|
2024-06-07 02:58:37 +02:00
|
|
|
const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefs, style }: PlaceholderProps<T>) => {
|
|
|
|
const [placeholderOverlay, setPlaceholderOverlay] = useState<ReactNode>(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
let newPlaceholderOverlay: ReactNode;
|
2024-06-17 23:14:09 +02:00
|
|
|
if (overlayContainerRef?.current) {
|
|
|
|
switch (layoutTreeState?.pendingAction?.type) {
|
|
|
|
case LayoutTreeActionType.Move: {
|
|
|
|
const action = layoutTreeState.pendingAction as LayoutTreeMoveNodeAction<T>;
|
|
|
|
let parentId: string;
|
|
|
|
if (action.insertAtRoot) {
|
|
|
|
parentId = layoutTreeState.rootNode.id;
|
|
|
|
} else {
|
|
|
|
parentId = action.parentId;
|
|
|
|
}
|
2024-06-07 02:58:37 +02:00
|
|
|
|
2024-06-17 23:14:09 +02:00
|
|
|
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);
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
} else {
|
2024-06-17 23:14:09 +02:00
|
|
|
targetRef = nodeRefs.get(parentNode.id);
|
|
|
|
targetNode = parentNode;
|
|
|
|
}
|
|
|
|
if (targetRef?.current) {
|
|
|
|
const overlayBoundingRect = overlayContainerRef.current.getBoundingClientRect();
|
|
|
|
const targetBoundingRect = targetRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
// Placeholder should be either half the height or half the width of the targetNode, depending on the flex direction of the targetNode's parent.
|
|
|
|
// Default to placing the placeholder in the first half of the target node.
|
|
|
|
const placeholderDimensions: Dimensions = {
|
|
|
|
height:
|
|
|
|
parentNode.flexDirection === FlexDirection.Column
|
|
|
|
? targetBoundingRect.height / 2
|
|
|
|
: targetBoundingRect.height,
|
|
|
|
width:
|
|
|
|
parentNode.flexDirection === FlexDirection.Row
|
|
|
|
? targetBoundingRect.width / 2
|
|
|
|
: targetBoundingRect.width,
|
|
|
|
top: targetBoundingRect.top - overlayBoundingRect.top,
|
|
|
|
left: targetBoundingRect.left - overlayBoundingRect.left,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (action.index > targetIndex) {
|
|
|
|
if (action.index >= (parentNode.children?.length ?? 1)) {
|
|
|
|
// If there are no more nodes after the specified index, place the placeholder in the second half of the target node (either right or bottom).
|
|
|
|
placeholderDimensions.top +=
|
|
|
|
parentNode.flexDirection === FlexDirection.Column &&
|
|
|
|
targetBoundingRect.height / 2;
|
|
|
|
placeholderDimensions.left +=
|
|
|
|
parentNode.flexDirection === FlexDirection.Row && targetBoundingRect.width / 2;
|
|
|
|
} else {
|
|
|
|
// Otherwise, place the placeholder between the target node (the one after which it will be inserted) and the next node
|
|
|
|
placeholderDimensions.top +=
|
|
|
|
parentNode.flexDirection === FlexDirection.Column &&
|
|
|
|
(3 * targetBoundingRect.height) / 4;
|
|
|
|
placeholderDimensions.left +=
|
|
|
|
parentNode.flexDirection === FlexDirection.Row &&
|
|
|
|
(3 * targetBoundingRect.width) / 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const placeholderTransform = createTransform(placeholderDimensions);
|
|
|
|
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
Implement outer drop direction, add rudimentary drag preview image rendering (#29)
This PR adds support for Outer variants of each DropDirection.
When calculating the drop direction, the cursor position is calculated
relevant to the box over which it is hovering. The following diagram
shows how drop directions are calculated. The colored in center is
currently not supported, it is assigned to the top, bottom, left, right
direction for now, though it will ultimately be its own distinct
direction.
![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97)
When an outer drop direction is provided for a move operation, if the
reference node flexes in the same axis as the drop direction, the new
node will be inserted at the same level as the parent of the reference
node. If the reference node flexes in a different direction or the
reference node does not have a grandparent, the operation will fall back
to its non-Outer variant.
This also removes some chatty debug statements, adds a blur to the
currently-dragging node to indicate that it cannot be dropped onto, and
simplifies the deriving of the layout state atom from the tab atom so
there's no longer another intermediate derived atom for the layout node.
This also adds rudimentary support for rendering custom preview images
for any tile being dragged. Right now, this is a simple block containing
the block ID, but this can be anything. This resolves an issue where
letting React-DnD generate its own previews could take up to a half
second, and would block dragging until complete. For Monaco, this was
outright failing.
It also fixes an issue where the tile layout could animate on first
paint. Now, I use React Suspense to prevent the layout from displaying
until all the children have loaded.
2024-06-11 22:03:41 +02:00
|
|
|
}
|
2024-06-07 02:58:37 +02:00
|
|
|
}
|
2024-06-17 23:14:09 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LayoutTreeActionType.Swap: {
|
|
|
|
const action = layoutTreeState.pendingAction as LayoutTreeSwapNodeAction<T>;
|
|
|
|
console.log("placeholder for swap", action);
|
|
|
|
const targetNode = action.node1;
|
|
|
|
const targetRef = nodeRefs.get(targetNode?.id);
|
|
|
|
if (targetRef?.current) {
|
|
|
|
const overlayBoundingRect = overlayContainerRef.current.getBoundingClientRect();
|
|
|
|
const targetBoundingRect = targetRef.current.getBoundingClientRect();
|
|
|
|
const placeholderDimensions: Dimensions = {
|
|
|
|
top: targetBoundingRect.top - overlayBoundingRect.top,
|
|
|
|
left: targetBoundingRect.left - overlayBoundingRect.left,
|
|
|
|
height: targetBoundingRect.height,
|
|
|
|
width: targetBoundingRect.width,
|
|
|
|
};
|
|
|
|
|
|
|
|
const placeholderTransform = createTransform(placeholderDimensions);
|
|
|
|
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
|
|
|
}
|
|
|
|
break;
|
2024-06-07 02:58:37 +02:00
|
|
|
}
|
2024-06-17 23:14:09 +02:00
|
|
|
default:
|
|
|
|
// No-op
|
|
|
|
break;
|
2024-06-07 02:58:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
setPlaceholderOverlay(newPlaceholderOverlay);
|
|
|
|
}, [layoutTreeState, nodeRefs, overlayContainerRef]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="placeholder-container" style={style}>
|
|
|
|
{placeholderOverlay}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|