AutoGridItemRenderer: Fix repeated panel lazy loading and drag and drop (#109334)

* wip: wrap individual grid items with lazy loader

* fix repeated panel lazy loading and drag and drop in auto grid item
This commit is contained in:
Sergej-Vlasov
2025-08-08 13:43:13 +01:00
committed by GitHub
parent e1e9a570af
commit 67140e9850
2 changed files with 63 additions and 59 deletions
@@ -1,11 +1,13 @@
import { css, cx } from '@emotion/css';
import { memo, useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data/';
import { SceneComponentProps } from '@grafana/scenes';
import { LazyLoader, SceneComponentProps, VizPanel } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { useIsConditionallyHidden } from '../../conditional-rendering/useIsConditionallyHidden';
import { useDashboardState } from '../../utils/utils';
import { getIsLazy } from '../layouts-shared/utils';
import { AutoGridItem } from './AutoGridItem';
import { DRAGGED_ITEM_HEIGHT, DRAGGED_ITEM_LEFT, DRAGGED_ITEM_TOP, DRAGGED_ITEM_WIDTH } from './const';
@@ -13,11 +15,13 @@ import { DRAGGED_ITEM_HEIGHT, DRAGGED_ITEM_LEFT, DRAGGED_ITEM_TOP, DRAGGED_ITEM_
export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem>) {
const { body, repeatedPanels, key } = model.useState();
const { draggingKey } = model.getParentGrid().useState();
const { isEditing } = useDashboardState(model);
const { isEditing, preload } = useDashboardState(model);
const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] =
useIsConditionallyHidden(model);
const styles = useStyles2(getStyles);
const isLazy = useMemo(() => getIsLazy(preload), [preload]);
if (isConditionallyHidden && !isEditing) {
return null;
}
@@ -25,37 +29,65 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
const isDragging = !!draggingKey;
const isDragged = draggingKey === key;
const Wrapper = useMemo(
() =>
memo(
({
item,
addDndContainer,
isDragged,
isDragging,
}: {
item: VizPanel;
addDndContainer: boolean;
isDragged: boolean;
isDragging: boolean;
}) => (
<div
{...(addDndContainer
? { ref: model.containerRef, ['data-auto-grid-item-drop-target']: isDragging ? key : undefined }
: {})}
>
{isDragged && <div className={styles.draggedPlaceholder} />}
{isLazy ? (
<LazyLoader
key={item.state.key!}
className={cx(conditionalRenderingClass, styles.wrapper, isDragged && styles.draggedWrapper)}
>
<item.Component model={item} />
{conditionalRenderingOverlay}
</LazyLoader>
) : (
<div className={cx(conditionalRenderingClass, styles.wrapper, isDragged && styles.draggedWrapper)}>
<item.Component model={item} />
{conditionalRenderingOverlay}
</div>
)}
</div>
)
),
[conditionalRenderingClass, conditionalRenderingOverlay, isLazy, key, model.containerRef, styles]
);
return repeatedPanels ? (
<>
{repeatedPanels.map((item) => (
<div className={cx(conditionalRenderingClass, styles.wrapper)} key={item.state.key}>
<item.Component model={item} />
{conditionalRenderingOverlay}
</div>
{repeatedPanels.map((item, index) => (
<Wrapper
item={item}
addDndContainer={index === 0}
key={item.state.key!}
isDragged={isDragged}
isDragging={isDragging}
/>
))}
</>
) : (
<div ref={model.containerRef} data-auto-grid-item-drop-target={isDragging ? key : undefined}>
{isDragged && <div className={styles.draggedPlaceholder} />}
<div className={cx(!isDragged && conditionalRenderingClass, styles.wrapper, isDragged && styles.draggedWrapper)}>
<body.Component model={body} />
{conditionalRenderingOverlay}
</div>
</div>
<Wrapper item={body} addDndContainer key={body.state.key!} isDragged={isDragged} isDragging={isDragging} />
);
}
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
width: '100%',
height: '100%',
}),
wrapper: css({
width: '100%',
height: '100%',
position: 'relative',
}),
wrapper: css({ width: '100%', height: '100%', position: 'relative' }),
draggedWrapper: css({
position: 'absolute',
zIndex: 1000,
@@ -1,15 +1,13 @@
import { css, cx } from '@emotion/css';
import { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { LazyLoader, SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { useHasClonedParents } from '../../utils/clone';
import { useDashboardState } from '../../utils/utils';
import { CanvasGridAddActions } from '../layouts-shared/CanvasGridAddActions';
import { dashboardCanvasAddButtonHoverStyles } from '../layouts-shared/styles';
import { getIsLazy } from '../layouts-shared/utils';
import { AutoGridLayout, AutoGridLayoutState } from './AutoGridLayout';
import { AutoGridLayoutManager } from './AutoGridLayoutManager';
@@ -18,12 +16,10 @@ export function AutoGridLayoutRenderer({ model }: SceneComponentProps<AutoGridLa
const { children, isHidden } = model.useState();
const hasClonedParents = useHasClonedParents(model);
const styles = useStyles2(getStyles, model.state);
const { layoutOrchestrator, isEditing, preload } = useDashboardState(model);
const { layoutOrchestrator, isEditing } = useDashboardState(model);
const layoutManager = sceneGraph.getAncestor(model, AutoGridLayoutManager);
const { fillScreen } = layoutManager.useState();
const isLazy = useMemo(() => getIsLazy(preload), [preload]);
if (isHidden || !layoutOrchestrator) {
return null;
}
@@ -35,15 +31,9 @@ export function AutoGridLayoutRenderer({ model }: SceneComponentProps<AutoGridLa
className={cx(styles.container, fillScreen && styles.containerFillScreen, isEditing && styles.containerEditing)}
ref={model.containerRef}
>
{children.map((item) =>
isLazy ? (
<LazyLoader key={item.state.key!} className={styles.container}>
<item.Component key={item.state.key} model={item} />
</LazyLoader>
) : (
<item.Component key={item.state.key} model={item} />
)
)}
{children.map((item) => (
<item.Component key={item.state.key} model={item} />
))}
{showCanvasActions && <CanvasGridAddActions layoutManager={layoutManager} />}
</div>
);
@@ -75,24 +65,6 @@ const getStyles = (theme: GrafanaTheme2, state: AutoGridLayoutState) => ({
// Show add action when hovering over the grid
...dashboardCanvasAddButtonHoverStyles,
}),
containerFillScreen: css({
flexGrow: 1,
}),
containerEditing: css({
paddingBottom: theme.spacing(5),
position: 'relative',
}),
wrapper: css({
display: 'grid',
position: 'relative',
width: '100%',
height: '100%',
}),
dragging: css({
position: 'fixed',
top: 0,
left: 0,
zIndex: theme.zIndex.portal + 1,
pointerEvents: 'none',
}),
containerFillScreen: css({ flexGrow: 1 }),
containerEditing: css({ paddingBottom: theme.spacing(5), position: 'relative' }),
});