diff --git a/plugins/ui/src/js/src/DashboardPlugin.tsx b/plugins/ui/src/js/src/DashboardPlugin.tsx index 0b22fc62e..614dfe17f 100644 --- a/plugins/ui/src/js/src/DashboardPlugin.tsx +++ b/plugins/ui/src/js/src/DashboardPlugin.tsx @@ -76,13 +76,7 @@ export function DashboardPlugin( >(new Map()); const handleWidgetOpen = useCallback( - ({ - widgetId = nanoid(), - widget, - }: { - widgetId: string; - widget: WidgetDescriptor; - }) => { + ({ widgetId, widget }: { widgetId: string; widget: WidgetDescriptor }) => { log.debug('Opening widget with ID', widgetId, widget); setWidgetMap(prevWidgetMap => { const newWidgetMap = new Map(prevWidgetMap); diff --git a/plugins/ui/src/js/src/layout/ReactPanel.tsx b/plugins/ui/src/js/src/layout/ReactPanel.tsx index 6ff46f70f..9ebdc1180 100644 --- a/plugins/ui/src/js/src/layout/ReactPanel.tsx +++ b/plugins/ui/src/js/src/layout/ReactPanel.tsx @@ -7,7 +7,13 @@ import { useLayoutManager, useListener, } from '@deephaven/dashboard'; -import { View, ViewProps, Flex, FlexProps } from '@deephaven/components'; +import { + View, + ViewProps, + Flex, + FlexProps, + LoadingOverlay, +} from '@deephaven/components'; import Log from '@deephaven/log'; import PortalPanel from './PortalPanel'; import { ReactPanelControl, useReactPanel } from './ReactPanelManager'; @@ -186,6 +192,20 @@ function ReactPanel({ ); const widgetStatus = useWidgetStatus(); + let renderedChildren: React.ReactNode; + if (widgetStatus.status === 'loading') { + renderedChildren = ( + <> + + {children} + + ); + } else if (widgetStatus.status === 'error') { + renderedChildren = ; + } else { + renderedChildren = children; + } + return portal ? ReactDOM.createPortal( @@ -224,11 +244,7 @@ function ReactPanel({ * Don't render the children if there's an error with the widget. If there's an error with the widget, we can assume the children won't render properly, * but we still want the panels to appear so things don't disappear/jump around. */} - {widgetStatus.status === 'error' ? ( - - ) : ( - children - )} + {renderedChildren} diff --git a/plugins/ui/src/js/src/widget/WidgetHandler.tsx b/plugins/ui/src/js/src/widget/WidgetHandler.tsx index e4184cfd1..121d9f666 100644 --- a/plugins/ui/src/js/src/widget/WidgetHandler.tsx +++ b/plugins/ui/src/js/src/widget/WidgetHandler.tsx @@ -64,6 +64,14 @@ function WidgetHandler({ initialData: initialDataProp, }: WidgetHandlerProps): JSX.Element | null { const { widget, error: widgetError } = useWidget(widgetDescriptor); + const [isLoading, setIsLoading] = useState(true); + const [prevWidget, setPrevWidget] = useState(widget); + // Cannot use usePrevious to change setIsLoading + // Since usePrevious runs in an effect, the value never gets updated if setIsLoading is called during render + if (widget !== prevWidget) { + setPrevWidget(widget); + setIsLoading(true); + } const [document, setDocument] = useState(); @@ -226,6 +234,7 @@ function WidgetHandler({ const newDocument = parseDocument(documentParam); setInternalError(undefined); setDocument(newDocument); + setIsLoading(false); // Must go after setDocument since setters are not batched in effects if (stateParam != null) { try { const newState = JSON.parse(stateParam); @@ -339,11 +348,14 @@ function WidgetHandler({ if (error != null) { return { status: 'error', descriptor: widgetDescriptor, error }; } + if (isLoading) { + return { status: 'loading', descriptor: widgetDescriptor }; + } if (renderedDocument != null) { return { status: 'ready', descriptor: widgetDescriptor }; } return { status: 'loading', descriptor: widgetDescriptor }; - }, [error, renderedDocument, widgetDescriptor]); + }, [error, renderedDocument, widgetDescriptor, isLoading]); return useMemo( () =>