diff --git a/web/src/hooks/useDebounce.ts b/web/src/hooks/useDebounce.ts index 80cb00f60..add7b7c0d 100644 --- a/web/src/hooks/useDebounce.ts +++ b/web/src/hooks/useDebounce.ts @@ -49,13 +49,15 @@ export function useDebounceFn(fn: T, options?: DebounceOptions) flush: debounced.flush }; } + export function useDebounce(value: T, options: DebounceOptions = {}) { - const { wait = 1000, maxWait } = options; + const { wait = 1000, maxWait, leading = false, trailing = true } = options; const [debouncedValue, setDebouncedValue] = useState(value); const timeoutRef = useRef | undefined>(undefined); const maxTimeoutRef = useRef | undefined>(undefined); const lastUpdateTime = useRef(Date.now()); const valueRef = useRef(value); + const isFirstCallRef = useRef(true); // Keep latest value in ref useEffect(() => { @@ -70,17 +72,29 @@ export function useDebounce(value: T, options: DebounceOptions = {}) { if (maxTimeoutRef.current) { clearTimeout(maxTimeoutRef.current); } + + isFirstCallRef.current = true; }, []); useEffect(() => { clearTimeouts(); const now = Date.now(); - // Set up the regular debounce timeout - timeoutRef.current = setTimeout(() => { - setDebouncedValue(valueRef.current); - lastUpdateTime.current = Date.now(); - }, wait); + // Handle leading edge + if (leading && isFirstCallRef.current) { + setDebouncedValue(value); + lastUpdateTime.current = now; + isFirstCallRef.current = false; + return; + } + + // Only set up trailing timeout if trailing is true + if (trailing) { + timeoutRef.current = setTimeout(() => { + setDebouncedValue(valueRef.current); + lastUpdateTime.current = Date.now(); + }, wait); + } // Handle maxWait if (maxWait) { @@ -96,8 +110,9 @@ export function useDebounce(value: T, options: DebounceOptions = {}) { }, maxWaitTimeRemaining); } + isFirstCallRef.current = false; return () => clearTimeouts(); - }, [value, wait, maxWait, clearTimeouts]); + }, [value, wait, maxWait, leading, trailing, clearTimeouts]); return debouncedValue; } diff --git a/web/src/layouts/ChatLayout/ChatLayoutContext/useSelectedFile/useSelectedFile.ts b/web/src/layouts/ChatLayout/ChatLayoutContext/useSelectedFile/useSelectedFile.ts index d4b3a7573..f72c8c9bf 100644 --- a/web/src/layouts/ChatLayout/ChatLayoutContext/useSelectedFile/useSelectedFile.ts +++ b/web/src/layouts/ChatLayout/ChatLayoutContext/useSelectedFile/useSelectedFile.ts @@ -44,13 +44,6 @@ export const useSelectedFile = ({ return false; }, [selectedFile?.type, metricVersionNumber, dashboardVersionNumber]); - console.log( - 'isVersionHistoryMode', - isVersionHistoryMode, - metricVersionNumber, - dashboardVersionNumber - ); - const [renderViewLayoutKey, setRenderViewLayoutKey] = useState( selectedLayout || 'chat' ); diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainer.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainer.tsx index 25f343c00..637cdb100 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainer.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainer.tsx @@ -6,7 +6,8 @@ import { AppPageLayout, AppSplitter, AppSplitterRef } from '@/components/ui/layo import { useChatLayoutContextSelector } from '../ChatLayoutContext'; import { createAutoSaveId } from '@/components/ui/layouts/AppSplitter/helper'; import Cookies from 'js-cookie'; -import { useMemoizedFn, useUpdateLayoutEffect } from '@/hooks'; +import { useDebounce, useMemoizedFn, useUpdateLayoutEffect } from '@/hooks'; +import { FileContainerSecondary } from './FileContainerSecondary'; interface FileContainerProps { children: React.ReactNode; @@ -18,15 +19,22 @@ const autoSaveId = 'file-container-splitter'; export const FileContainer: React.FC = ({ children }) => { const appSplitterRef = useRef(null); - + const selectedFile = useChatLayoutContextSelector((x) => x.selectedFile); const selectedFileViewSecondary = useChatLayoutContextSelector( (x) => x.selectedFileViewSecondary ); const selectedFileViewRenderSecondary = useChatLayoutContextSelector( (x) => x.selectedFileViewRenderSecondary ); + const isOpenSecondary = selectedFileViewRenderSecondary; + //we need to debounce the selectedFileViewSecondary to avoid flickering + const debouncedSelectedFileViewSecondary = useDebounce(selectedFileViewSecondary, { + wait: 350, + leading: selectedFileViewRenderSecondary + }); + const secondaryLayoutDimensions: [string, string] = useMemo(() => { const cookieKey = createAutoSaveId(autoSaveId); const cookieValue = Cookies.get(cookieKey); @@ -58,6 +66,15 @@ export const FileContainer: React.FC = ({ children }) => { } }); + const rightChildren = useMemo(() => { + return ( + + ); + }, [debouncedSelectedFileViewSecondary, selectedFile?.id, selectedFile?.type]); + useUpdateLayoutEffect(() => { animateOpenSplitter(isOpenSecondary ? 'open' : 'closed'); }, [isOpenSecondary]); @@ -70,7 +87,7 @@ export const FileContainer: React.FC = ({ children }) => { defaultLayout={defaultLayout} initialReady={false} leftChildren={children} - rightChildren={
Right {selectedFileViewSecondary}
} + rightChildren={rightChildren} allowResize={selectedFileViewRenderSecondary} preserveSide={'right'} rightPanelMinSize={250} diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerVersionHistory.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerVersionHistory.tsx index b24349061..5f09e9d36 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerVersionHistory.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerVersionHistory.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Button } from '@/components/ui/buttons'; import { ArrowLeft, History } from '@/components/ui/icons'; import React, { useMemo, useTransition } from 'react'; @@ -8,6 +10,7 @@ import { useGetDashboard } from '@/api/buster_rest/dashboards'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useMemoizedFn } from '@/hooks'; import { timeout } from '@/lib'; +import { AnimatePresence, motion } from 'framer-motion'; export const FileContainerVersionHistory = React.memo(() => { return ( diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/FileContainerSecondary.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/FileContainerSecondary.tsx index 636e5021f..5b3bdf407 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/FileContainerSecondary.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/FileContainerSecondary.tsx @@ -1,31 +1,29 @@ -import type { FileType } from '@/api/asset_interfaces/chat'; import type { FileContainerSecondaryProps } from './interfaces'; -import type { - DashboardFileViewSecondary, - MetricFileViewSecondary -} from '../../ChatLayoutContext/useLayoutConfig/interfaces'; +import { SelectedFileSecondaryRecord } from './secondaryPanelsConfig'; +import { useMemo } from 'react'; -const MetricSecondaryRecord: Record< - MetricFileViewSecondary, - React.FC -> = { - 'chart-edit': () => null, - 'sql-edit': () => null, - 'version-history': () => null -}; +export const FileContainerSecondary: React.FC = ({ + selectedFile, + selectedFileViewSecondary +}) => { + const Component = useMemo(() => { + if (!selectedFile || !selectedFileViewSecondary) return null; -const DashboardSecondaryRecord: Record< - DashboardFileViewSecondary, - React.FC -> = { - 'version-history': () => null -}; + const assosciatedType = SelectedFileSecondaryRecord[selectedFile?.type]; -const SelectedFileSecondaryRecord: Record< - FileType, - Record> -> = { - metric: MetricSecondaryRecord, - dashboard: DashboardSecondaryRecord, - reasoning: {} + if (!assosciatedType) return null; + + return assosciatedType[selectedFileViewSecondary]; + }, [selectedFileViewSecondary, selectedFile?.id, selectedFile?.type]); + + return ( + <> + {Component && ( + + )} + + ); }; diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/interfaces.ts b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/interfaces.ts index f56543cea..60333c631 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/interfaces.ts +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/interfaces.ts @@ -1,6 +1,7 @@ import type { FileViewSecondary } from '@/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig'; +import { SelectedFile } from '../../interfaces'; export type FileContainerSecondaryProps = { - selectedFileId: string | undefined; selectedFileViewSecondary: FileViewSecondary | undefined; + selectedFile: SelectedFile | null; }; diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/dashboardPanels.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/dashboardPanels.tsx index 39fd5c21c..29f994319 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/dashboardPanels.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/dashboardPanels.tsx @@ -15,7 +15,7 @@ export const DashboardSecondaryRecord: Record< DashboardFileViewSecondary, React.FC > = { - 'version-history': ({ selectedFileId }) => ( - + 'version-history': ({ selectedFile }) => ( + ) }; diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/loading.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/loading.tsx index 5c06cb184..4e2d81af4 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/loading.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/loading.tsx @@ -2,7 +2,7 @@ import { CircleSpinnerLoaderContainer } from '@/components/ui/loaders/CircleSpin export const loading = () => { return ( -
+
); diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/metricPanels.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/metricPanels.tsx index 68bd27ea0..8ac26d630 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/metricPanels.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/metricPanels.tsx @@ -26,9 +26,9 @@ export const MetricSecondaryRecord: Record< MetricFileViewSecondary, React.FC > = { - 'chart-edit': ({ selectedFileId }) => , - 'sql-edit': ({ selectedFileId }) => , - 'version-history': ({ selectedFileId }) => ( - + 'chart-edit': ({ selectedFile }) => , + 'sql-edit': ({ selectedFile }) => , + 'version-history': ({ selectedFile }) => ( + ) };