diff --git a/src/hooks/use-debounce-v2.ts b/src/hooks/use-debounce-v2.ts new file mode 100644 index 00000000..592aa607 --- /dev/null +++ b/src/hooks/use-debounce-v2.ts @@ -0,0 +1,47 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { debounce as utilsDebounce } from '@/lib/utils'; + +interface DebouncedFunction { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (...args: any[]): void; + cancel?: () => void; +} + +/** + * A hook that returns a debounced version of the provided function. + * The debounced function will only be called after the specified delay + * has passed without the function being called again. + * + * @param callback The function to debounce + * @param delay The delay in milliseconds + * @returns A debounced version of the callback + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function useDebounce any>( + callback: T, + delay: number +): (...args: Parameters) => void { + // Use a ref to store the debounced function + const debouncedFnRef = useRef(); + + // Update the debounced function when dependencies change + useEffect(() => { + // Create the debounced function + debouncedFnRef.current = utilsDebounce(callback, delay); + + // Clean up when component unmounts or dependencies change + return () => { + if (debouncedFnRef.current?.cancel) { + debouncedFnRef.current.cancel(); + } + }; + }, [callback, delay]); + + // Create a stable callback that uses the ref + const debouncedCallback = useCallback((...args: Parameters) => { + debouncedFnRef.current?.(...args); + }, []); + + return debouncedCallback; +} diff --git a/src/pages/editor-page/canvas/canvas.tsx b/src/pages/editor-page/canvas/canvas.tsx index dbe4894e..90a86895 100644 --- a/src/pages/editor-page/canvas/canvas.tsx +++ b/src/pages/editor-page/canvas/canvas.tsx @@ -682,7 +682,7 @@ export const Canvas: React.FC = ({ initialTables, readonly }) => { return ( -
+
{ + const { getNodes, getViewport } = useReactFlow(); + const [noTablesVisible, setNoTablesVisible] = useState(false); + + // Check if any tables are visible in the current viewport + const checkVisibleTables = useCallback(() => { + const nodes = getNodes(); + const viewport = getViewport(); + + // If there are no nodes at all, don't highlight the button + if (nodes.length === 0) { + setNoTablesVisible(false); + return; + } + + // Count visible (not hidden) nodes + const visibleNodes = nodes.filter((node) => !node.hidden); + + // If there are no visible nodes at all, don't highlight the button + if (visibleNodes.length === 0) { + setNoTablesVisible(false); + return; + } + + // Calculate viewport boundaries + const viewportLeft = -viewport.x / viewport.zoom; + const viewportTop = -viewport.y / viewport.zoom; + + const width = + document.getElementById('canvas')?.clientWidth || window.innerWidth; + const height = + document.getElementById('canvas')?.clientHeight || + window.innerHeight; + + const viewportRight = viewportLeft + width / viewport.zoom; + const viewportBottom = viewportTop + height / viewport.zoom; + + // Check if any node is visible in the viewport + const anyNodeVisible = visibleNodes.some((node) => { + let nodeWidth = node.width || 0; + let nodeHeight = node.height || 0; + + if (node.type === 'table' && node.data?.table) { + const tableNodeType = node as TableNodeType; + const dimensions = getTableDimensions(tableNodeType.data.table); + nodeWidth = dimensions.width; + nodeHeight = dimensions.height; + } + + // Node boundaries + const nodeLeft = node.position.x; + const nodeTop = node.position.y; + const nodeRight = nodeLeft + nodeWidth; + const nodeBottom = nodeTop + nodeHeight; + + return ( + nodeRight >= viewportLeft && + nodeLeft <= viewportRight && + nodeBottom >= viewportTop && + nodeTop <= viewportBottom + ); + }); + + // Only set to true if there are tables but none are visible + setNoTablesVisible(!anyNodeVisible); + }, [getNodes, getViewport]); + + // Create a debounced version of checkVisibleTables + const debouncedCheckVisibleTables = useDebounce(checkVisibleTables, 1000); + + useOnViewportChange({ + onEnd: () => { + debouncedCheckVisibleTables(); + }, + }); + + return { + isLostInCanvas: noTablesVisible, + }; +}; diff --git a/src/pages/editor-page/canvas/toolbar/toolbar-button.tsx b/src/pages/editor-page/canvas/toolbar/toolbar-button.tsx index 5e26bd48..be0e0f34 100644 --- a/src/pages/editor-page/canvas/toolbar/toolbar-button.tsx +++ b/src/pages/editor-page/canvas/toolbar/toolbar-button.tsx @@ -1,17 +1,22 @@ import React from 'react'; import type { ButtonProps } from '@/components/button/button'; import { Button } from '@/components/button/button'; +import { cn } from '@/lib/utils'; export const ToolbarButton = React.forwardRef< React.ElementRef, ButtonProps >((props, ref) => { + const { className, ...rest } = props; return (