mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Fix greedy rendering of drag preview (#68)
This commit is contained in:
parent
b8b03ea817
commit
2c6f6d917f
@ -10,6 +10,7 @@ import { TileLayout } from "@/faraday/index";
|
||||
import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { getApi } from "../store/global";
|
||||
import "./tabcontent.less";
|
||||
|
||||
const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
@ -27,7 +28,6 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
onClose: () => void,
|
||||
dragHandleRef: React.RefObject<HTMLDivElement>
|
||||
) => {
|
||||
// console.log("renderBlock", tabData);
|
||||
if (!tabData.blockId || !ready) {
|
||||
return null;
|
||||
}
|
||||
@ -37,15 +37,17 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
);
|
||||
|
||||
const renderPreview = useCallback((tabData: TabLayoutData) => {
|
||||
console.log("renderPreview", tabData);
|
||||
return <BlockFrame blockId={tabData.blockId} preview={true} />;
|
||||
}, []);
|
||||
|
||||
const onNodeDelete = useCallback((data: TabLayoutData) => {
|
||||
console.log("onNodeDelete", data);
|
||||
return services.ObjectService.DeleteBlock(data.blockId);
|
||||
}, []);
|
||||
|
||||
const getCursorPoint = useCallback(() => {
|
||||
return getApi().getCursorPoint();
|
||||
}, []);
|
||||
|
||||
if (tabLoading) {
|
||||
return <CenteredLoadingDiv />;
|
||||
}
|
||||
@ -66,6 +68,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
renderPreview={renderPreview}
|
||||
layoutTreeStateAtom={layoutStateAtom}
|
||||
onNodeDelete={onNodeDelete}
|
||||
getCursorPoint={getCursorPoint}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -26,7 +26,16 @@ const meta = {
|
||||
name: "Hello world!",
|
||||
})
|
||||
),
|
||||
renderContent: renderTestData,
|
||||
renderContent: (
|
||||
data: TestData,
|
||||
_ready: boolean,
|
||||
_onClose: () => void,
|
||||
dragHandleRef: React.RefObject<HTMLDivElement>
|
||||
) => (
|
||||
<div ref={dragHandleRef} className="test-content" style={{ width: "100%", height: "100%" }}>
|
||||
{renderTestData(data)}
|
||||
</div>
|
||||
),
|
||||
renderPreview: renderTestData,
|
||||
},
|
||||
component: TileLayout<TestData>,
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { getApi } from "@/app/store/global";
|
||||
import useResizeObserver from "@react-hook/resize-observer";
|
||||
import clsx from "clsx";
|
||||
import { toPng } from "html-to-image";
|
||||
@ -19,6 +18,7 @@ import React, {
|
||||
} from "react";
|
||||
import { DropTargetMonitor, useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import { useDevicePixelRatio } from "use-device-pixel-ratio";
|
||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom";
|
||||
import { findNode } from "./layoutNode";
|
||||
import {
|
||||
@ -59,6 +59,12 @@ export interface TileLayoutProps<T> {
|
||||
* The class name to use for the top-level div of the tile layout.
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* A callback for getting the cursor point in reference to the current window. This removes Electron as a runtime dependency, allowing for better integration with Storybook.
|
||||
* @returns The cursor position relative to the current window.
|
||||
*/
|
||||
getCursorPoint?: () => Point;
|
||||
}
|
||||
|
||||
const DragPreviewWidth = 300;
|
||||
@ -70,6 +76,7 @@ export const TileLayout = <T,>({
|
||||
renderContent,
|
||||
renderPreview,
|
||||
onNodeDelete,
|
||||
getCursorPoint,
|
||||
}: TileLayoutProps<T>) => {
|
||||
const overlayContainerRef = useRef<HTMLDivElement>(null);
|
||||
const displayContainerRef = useRef<HTMLDivElement>(null);
|
||||
@ -121,7 +128,7 @@ export const TileLayout = <T,>({
|
||||
// because that conflicts with the DnD layer.
|
||||
useEffect(
|
||||
debounce(100, () => {
|
||||
const cursorPoint = getApi().getCursorPoint();
|
||||
const cursorPoint = getCursorPoint?.() ?? dragClientOffset;
|
||||
if (cursorPoint && displayContainerRef.current) {
|
||||
const displayContainerRect = displayContainerRef.current.getBoundingClientRect();
|
||||
const normalizedX = cursorPoint.x - displayContainerRect.x;
|
||||
@ -324,9 +331,9 @@ const DisplayNode = <T,>({
|
||||
const tileNodeRef = useRef<HTMLDivElement>(null);
|
||||
const dragHandleRef = useRef<HTMLDivElement>(null);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
const hasImagePreviewSetRef = useRef(false);
|
||||
|
||||
// Register the node as a draggable item.
|
||||
const devicePixelRatio = useDevicePixelRatio();
|
||||
|
||||
const [{ isDragging }, drag, dragPreview] = useDrag(
|
||||
() => ({
|
||||
type: dragItemType,
|
||||
@ -338,42 +345,55 @@ const DisplayNode = <T,>({
|
||||
[layoutNode]
|
||||
);
|
||||
|
||||
const previewElement = renderPreview?.(layoutNode.data);
|
||||
const previewWidth = DragPreviewWidth;
|
||||
const previewHeight = DragPreviewHeight;
|
||||
const previewTransform = `scale(${1 / window.devicePixelRatio})`;
|
||||
const [previewElementGeneration, setPreviewElementGeneration] = useState(0);
|
||||
const previewElement = useMemo(() => {
|
||||
setPreviewElementGeneration(previewElementGeneration + 1);
|
||||
return (
|
||||
<div key="preview" className="tile-preview-container">
|
||||
<div
|
||||
className="tile-preview"
|
||||
ref={previewRef}
|
||||
style={{
|
||||
width: DragPreviewWidth,
|
||||
height: DragPreviewHeight,
|
||||
transform: `scale(${1 / devicePixelRatio})`,
|
||||
}}
|
||||
>
|
||||
{renderPreview?.(layoutNode.data)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [renderPreview, 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);
|
||||
}
|
||||
}, []);
|
||||
const [previewImageGeneration, setPreviewImageGeneration] = useState(0);
|
||||
const generatePreviewImage = useCallback(() => {
|
||||
let offsetX = (DragPreviewWidth * window.devicePixelRatio - DragPreviewWidth) / 2 + 10;
|
||||
let offsetY = (DragPreviewHeight * window.devicePixelRatio - DragPreviewHeight) / 2 + 10;
|
||||
if (previewImage != null) {
|
||||
const offsetX = (DragPreviewWidth * devicePixelRatio - DragPreviewWidth) / 2 + 10;
|
||||
const offsetY = (DragPreviewHeight * devicePixelRatio - DragPreviewHeight) / 2 + 10;
|
||||
if (previewImage !== null && previewElementGeneration === previewImageGeneration) {
|
||||
dragPreview(previewImage, { offsetY, offsetX });
|
||||
} else if (previewRef.current) {
|
||||
setPreviewImageGeneration(previewElementGeneration);
|
||||
toPng(previewRef.current).then((url) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => {
|
||||
hasImagePreviewSetRef.current = true;
|
||||
setPreviewImage(img);
|
||||
dragPreview(img, { offsetY, offsetX });
|
||||
};
|
||||
setPreviewImage(img);
|
||||
dragPreview(img, { offsetY, offsetX });
|
||||
});
|
||||
}
|
||||
}, [previewRef, previewImage, dragPreview]);
|
||||
}, [
|
||||
dragPreview,
|
||||
previewRef.current,
|
||||
previewElementGeneration,
|
||||
previewImageGeneration,
|
||||
previewImage,
|
||||
devicePixelRatio,
|
||||
]);
|
||||
|
||||
// Register the tile item as a draggable component
|
||||
useEffect(() => {
|
||||
if (dragHandleRef.current) {
|
||||
drag(dragHandleRef);
|
||||
}
|
||||
}, [ready]);
|
||||
drag(dragHandleRef);
|
||||
}, [drag, dragHandleRef.current]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
onLeafClose(layoutNode);
|
||||
@ -402,19 +422,7 @@ const DisplayNode = <T,>({
|
||||
onPointerEnter={generatePreviewImage}
|
||||
>
|
||||
{leafContent}
|
||||
<div key="preview" className="tile-preview-container">
|
||||
<div
|
||||
className="tile-preview"
|
||||
ref={previewRef}
|
||||
style={{
|
||||
width: previewWidth,
|
||||
height: previewHeight,
|
||||
transform: previewTransform,
|
||||
}}
|
||||
>
|
||||
{previewElement}
|
||||
</div>
|
||||
</div>
|
||||
{previewElement}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -76,6 +76,7 @@
|
||||
"remark-gfm": "^4.0.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"use-device-pixel-ratio": "^1.1.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"packageManager": "yarn@4.3.0"
|
||||
|
10
yarn.lock
10
yarn.lock
@ -12330,6 +12330,7 @@ __metadata:
|
||||
tsx: "npm:^4.15.4"
|
||||
typescript: "npm:^5.4.5"
|
||||
typescript-eslint: "npm:^7.8.0"
|
||||
use-device-pixel-ratio: "npm:^1.1.2"
|
||||
uuid: "npm:^9.0.1"
|
||||
vite: "npm:^5.0.0"
|
||||
vite-plugin-static-copy: "npm:^1.0.5"
|
||||
@ -12886,6 +12887,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-device-pixel-ratio@npm:^1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "use-device-pixel-ratio@npm:1.1.2"
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
checksum: 10c0/125d3f75b82de0dd754ad2c930a4441e2845cfbacfa6d818857b6991da60dc4b41d33a791ff5b84725a10616d84c7beb6a75ef489c63b3f24fa910779a284099
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sidecar@npm:^1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "use-sidecar@npm:1.1.2"
|
||||
|
Loading…
Reference in New Issue
Block a user