Fix drag-to-tabs edge cases and add RowsLayoutManager drop target
- Fix closure bug in _stopDraggingSync where drop targets were nulled before setTimeout callback - Add RowsLayoutManager as a drop target when empty (allows dropping grid items) - Fix dropped row not being added when converting grid layout to RowsLayoutManager - Remove visual drop target outline from RowsLayoutManager - Properly reset isDropTarget state after drag operations complete
This commit is contained in:
@@ -128,18 +128,21 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
|
||||
private _stopDraggingSync(_evt: PointerEvent) {
|
||||
const gridItem = this.state.draggingGridItem?.resolve();
|
||||
const wasDetached = this._itemDetachedFromSource;
|
||||
// Capture these before cleanup since setTimeout runs after cleanup
|
||||
const sourceDropTarget = this._sourceDropTarget;
|
||||
const lastDropTarget = this._lastDropTarget;
|
||||
|
||||
// Handle cross-layout or cross-tab drop
|
||||
if (this._sourceDropTarget !== this._lastDropTarget || wasDetached) {
|
||||
if (sourceDropTarget !== lastDropTarget || wasDetached) {
|
||||
// Wrapped in setTimeout to ensure that any event handlers are called
|
||||
// Useful for allowing react-grid-layout to remove placeholders, etc.
|
||||
setTimeout(() => {
|
||||
if (gridItem) {
|
||||
// Only remove from source if not already detached during tab switch
|
||||
if (!wasDetached) {
|
||||
this._sourceDropTarget?.draggedGridItemOutside?.(gridItem);
|
||||
sourceDropTarget?.draggedGridItemOutside?.(gridItem);
|
||||
}
|
||||
this._lastDropTarget?.draggedGridItemInside?.(gridItem);
|
||||
lastDropTarget?.draggedGridItemInside?.(gridItem);
|
||||
} else {
|
||||
const warningMessage = 'No grid item to drag';
|
||||
console.warn(warningMessage);
|
||||
@@ -153,6 +156,9 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
|
||||
|
||||
this._clearTabActivationTimer();
|
||||
this._clearDragPreview();
|
||||
this._lastDropTarget?.setIsDropTarget?.(false);
|
||||
this._lastDropTarget = null;
|
||||
this._sourceDropTarget = null;
|
||||
this._itemDetachedFromSource = false;
|
||||
this.setState({ draggingGridItem: undefined, sourceTabKey: undefined, hoverTabKey: undefined });
|
||||
}
|
||||
@@ -244,6 +250,9 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
|
||||
document.body.removeEventListener('pointerup', this._onRowDragPointerUp);
|
||||
this._clearTabActivationTimer();
|
||||
this._clearDragPreview();
|
||||
this._lastDropTarget?.setIsDropTarget?.(false);
|
||||
this._lastDropTarget = null;
|
||||
this._sourceDropTarget = null;
|
||||
this._itemDetachedFromSource = false;
|
||||
this._sourceRowsLayout = null;
|
||||
this._rowOffsetCaptured = false;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { t } from '@grafana/i18n';
|
||||
import {
|
||||
sceneGraph,
|
||||
SceneGridItemLike,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
@@ -13,6 +14,7 @@ import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboa
|
||||
import { dashboardEditActions, ObjectsReorderedOnCanvasEvent } from '../../edit-pane/shared';
|
||||
import { serializeRowsLayout } from '../../serialization/layoutSerializers/RowsLayoutSerializer';
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
import { AutoGridItem } from '../layout-auto-grid/AutoGridItem';
|
||||
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
|
||||
import { DashboardGridItem } from '../layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
|
||||
@@ -22,6 +24,7 @@ import { findAllGridTypes } from '../layouts-shared/findAllGridTypes';
|
||||
import { getRowFromClipboard } from '../layouts-shared/paste';
|
||||
import { showConvertMixedGridsModal, showUngroupConfirmation } from '../layouts-shared/ungroupConfirmation';
|
||||
import { generateUniqueTitle, ungroupLayout, GridLayoutType, mapIdToGridLayoutType } from '../layouts-shared/utils';
|
||||
import { DashboardDropTarget } from '../types/DashboardDropTarget';
|
||||
import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid';
|
||||
import { DashboardLayoutGroup, isDashboardLayoutGroup } from '../types/DashboardLayoutGroup';
|
||||
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
||||
@@ -33,11 +36,16 @@ import { RowLayoutManagerRenderer } from './RowsLayoutManagerRenderer';
|
||||
|
||||
interface RowsLayoutManagerState extends SceneObjectState {
|
||||
rows: RowItem[];
|
||||
isDropTarget?: boolean;
|
||||
}
|
||||
|
||||
export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> implements DashboardLayoutGroup {
|
||||
export class RowsLayoutManager
|
||||
extends SceneObjectBase<RowsLayoutManagerState>
|
||||
implements DashboardLayoutGroup, DashboardDropTarget
|
||||
{
|
||||
public static Component = RowLayoutManagerRenderer;
|
||||
public readonly isDashboardLayoutManager = true;
|
||||
public readonly isDashboardDropTarget = true as const;
|
||||
|
||||
public static readonly descriptor: LayoutRegistryItem = {
|
||||
get name() {
|
||||
@@ -62,6 +70,38 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
|
||||
this.state.rows[0]?.getLayout().addPanel(vizPanel);
|
||||
}
|
||||
|
||||
public setIsDropTarget(isDropTarget: boolean): void {
|
||||
this.setState({ isDropTarget });
|
||||
}
|
||||
|
||||
public draggedGridItemInside(gridItem: SceneGridItemLike): void {
|
||||
// Create a new row with a DefaultGridLayoutManager and add the grid item to it
|
||||
const newLayout = new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({ children: [], isDraggable: true, isResizable: true }),
|
||||
});
|
||||
const newRow = new RowItem({
|
||||
title: t('dashboard.rows-layout.new-row-title', 'New row'),
|
||||
layout: newLayout,
|
||||
collapse: false,
|
||||
});
|
||||
|
||||
// Convert AutoGridItem to DashboardGridItem if needed
|
||||
if (gridItem instanceof AutoGridItem) {
|
||||
const vizPanel = gridItem.state.body;
|
||||
const newGridItem = new DashboardGridItem({
|
||||
body: vizPanel.clone(),
|
||||
width: 12,
|
||||
height: 8,
|
||||
});
|
||||
newLayout.addGridItem(newGridItem);
|
||||
} else if (gridItem instanceof DashboardGridItem) {
|
||||
newLayout.addGridItem(gridItem);
|
||||
}
|
||||
|
||||
// Add the row to this layout
|
||||
this.setState({ rows: [...this.state.rows, newRow], isDropTarget: false });
|
||||
}
|
||||
|
||||
public getVizPanels(): VizPanel[] {
|
||||
const panels: VizPanel[] = [];
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayo
|
||||
const soloPanelContext = useSoloPanelContext();
|
||||
const orchestrator = getLayoutOrchestratorFor(model);
|
||||
|
||||
// Only act as a drop target when empty (no rows)
|
||||
const showAsDropTarget = rows.length === 0;
|
||||
|
||||
const handleBeforeCapture = useCallback(
|
||||
(before: BeforeCapture) => {
|
||||
const row = rows.find((r) => r.state.key === before.draggableId);
|
||||
@@ -75,7 +78,12 @@ export function RowLayoutManagerRenderer({ model }: SceneComponentProps<RowsLayo
|
||||
>
|
||||
<Droppable droppableId={key!} direction="vertical">
|
||||
{(dropProvided) => (
|
||||
<div className={styles.wrapper} ref={dropProvided.innerRef} {...dropProvided.droppableProps}>
|
||||
<div
|
||||
className={styles.wrapper}
|
||||
ref={dropProvided.innerRef}
|
||||
{...dropProvided.droppableProps}
|
||||
{...(showAsDropTarget ? { 'data-dashboard-drop-target-key': key } : {})}
|
||||
>
|
||||
{rows.map((row) => (
|
||||
<RowWrapper row={row} manager={model} key={row.state.key!} />
|
||||
))}
|
||||
|
||||
@@ -264,8 +264,10 @@ export class TabItem
|
||||
rowsLayout = new RowsLayoutManager({ rows: [row] });
|
||||
} else {
|
||||
// Convert existing layout and add the dropped row
|
||||
// Use direct state update instead of addNewRow because the rowsLayout
|
||||
// isn't connected to the scene yet, so dashboardEditActions won't work
|
||||
rowsLayout = RowsLayoutManager.createFromLayout(currentLayout);
|
||||
rowsLayout.addNewRow(row);
|
||||
rowsLayout.setState({ rows: [...rowsLayout.state.rows, row] });
|
||||
}
|
||||
|
||||
// Clear the parent reference from the old layout
|
||||
|
||||
Reference in New Issue
Block a user