diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx
index 23c6f1597b0..c2c80322cc2 100644
--- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx
+++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx
@@ -346,6 +346,8 @@ export function PanelChrome({
onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter}
ref={ref}
+ draggable={true}
+ unselectable="on"
>
{loadingState === LoadingState.Loading ? (
@@ -390,8 +392,6 @@ export function PanelChrome({
onMouseEnter={isSelectable ? onHeaderEnter : undefined}
onMouseLeave={isSelectable ? onHeaderLeave : undefined}
onPointerUp={onPointerUp}
- draggable={true}
- unselectable="on"
onDragStart={(evt) => evt.dataTransfer.setData('text/plain', '')}
>
{statusMessage && (
diff --git a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx
index 4629156a28f..284a0dacd1e 100644
--- a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx
@@ -1,27 +1,19 @@
import { PointerEvent as ReactPointerEvent } from 'react';
-import { logWarning } from '@grafana/runtime';
-import {
- sceneGraph,
- SceneObjectBase,
- SceneObjectRef,
- SceneObjectState,
- VizPanel,
- SceneGridItemLike,
-} from '@grafana/scenes';
+import { sceneGraph, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
import { createPointerDistance } from '@grafana/ui';
import { DashboardScene } from './DashboardScene';
import { DashboardLayoutGrid, isDashboardLayoutGrid } from './types/DashboardLayoutGrid';
+import { DashboardLayoutItem } from './types/DashboardLayoutItem';
import { isDashboardLayoutManager } from './types/DashboardLayoutManager';
-interface DashboardLayoutOrchestratorState extends SceneObjectState {
- draggingGridItem?: SceneObjectRef
;
-}
+interface DashboardLayoutOrchestratorState extends SceneObjectState {}
export class DashboardLayoutOrchestrator extends SceneObjectBase {
private _sourceGrid: DashboardLayoutGrid | null = null;
- private _lastGrid: DashboardLayoutGrid | null = null;
+ private _currentGrid: DashboardLayoutGrid | null = null;
+ private _layoutItem: DashboardLayoutItem | null = null;
private _pointerDistance = createPointerDistance();
private _isSelectedObject = false;
@@ -38,79 +30,11 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase {
document.body.removeEventListener('pointermove', this._onPointerMove);
document.body.removeEventListener('pointerup', this._stopDraggingSync);
+ document.body.classList.remove('dashboard-draggable-transparent-selection');
};
}
- public startDraggingSync(evt: ReactPointerEvent, gridItem: SceneGridItemLike, layoutGrid: DashboardLayoutGrid): void {
- this._pointerDistance.set(evt);
- this._isSelectedObject = false;
-
- (sceneGraph.findAllObjects(
- this._getDashboard(),
- (obj) => isDashboardLayoutManager(obj) && isDashboardLayoutGrid(obj)
- ) as DashboardLayoutGrid[]).forEach((layout) => layout.startOrchestratorSync?.());
-
- this._sourceGrid = layoutGrid;
- this._lastGrid = layoutGrid;
-
- document.body.addEventListener('pointermove', this._onPointerMove);
- document.body.addEventListener('pointerup', this._stopDraggingSync);
-
- this.setState({ draggingGridItem: gridItem.getRef() });
- }
-
- private _stopDraggingSync(_evt: PointerEvent) {
- const gridItem = this.state.draggingGridItem?.resolve();
-
- if (this._sourceGrid !== this._lastGrid) {
- // Wrapped in setTimeout to ensure that any event handlers are called
- // Useful for allowing react-grid-layout to remove placeholders, etc.
- setTimeout(() => {
- if (gridItem) {
- // Always use grid item dragging
- this._sourceGrid?.draggedItemOutside?.(gridItem);
- this._lastGrid?.draggedItemInside?.(gridItem);
- } else {
- const warningMessage = 'No grid item to drag';
- console.warn(warningMessage);
- logWarning(warningMessage);
- }
- });
- }
-
- document.body.removeEventListener('pointermove', this._onPointerMove);
- document.body.removeEventListener('pointerup', this._stopDraggingSync);
-
- this.setState({ draggingGridItem: undefined });
- }
-
- private _onPointerMove(evt: PointerEvent) {
- if (!this._isSelectedObject && this.state.draggingGridItem && this._pointerDistance.check(evt)) {
- this._isSelectedObject = true;
- const gridItem = this.state.draggingGridItem?.resolve();
- if (gridItem && 'state' in gridItem && 'body' in gridItem.state && gridItem.state.body instanceof VizPanel) {
- const panel = gridItem.state.body;
- this._getDashboard().state.editPane.selectObject(panel, panel.state.key!, { force: true, multi: false });
- }
- }
-
- const dropTarget = this._getLayoutGridUnderMouse(evt) ?? this._sourceGrid;
-
- if (!dropTarget) {
- return;
- }
-
- if (dropTarget !== this._lastGrid) {
- this._lastGrid?.setIsDropTarget?.(false);
- this._lastGrid = dropTarget;
-
- if (dropTarget !== this._sourceGrid) {
- dropTarget.setIsDropTarget?.(true);
- }
- }
- }
-
- private _getDashboard(): DashboardScene {
+ public getDashboard(): DashboardScene {
if (!(this.parent instanceof DashboardScene)) {
throw new Error('Parent is not a DashboardScene');
}
@@ -118,10 +42,74 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase el.getAttribute('data-grid-manager-key') ===
- this._sourceGrid?.state.key);
+ const cursorIsInSourceTarget = elementsUnderPoint.some(
+ (el) => el.getAttribute('data-grid-manager-key') === this._sourceGrid?.state.key
+ );
if (cursorIsInSourceTarget) {
return null;
@@ -135,7 +123,7 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase {
+ if (!this._sourceGrid || !this._currentGrid || !this._layoutItem) {
+ return;
+ }
+
+ sceneGraph.findAllObjects(this.getDashboard(), (obj) => isDashboardLayoutManager(obj) && isDashboardLayoutGrid(obj)).forEach((obj) => {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+ (obj as DashboardLayoutGrid).stopOrchestratorSync?.(this._sourceGrid!, this._currentGrid!, this._layoutItem!);
+ });
+
+ this._isSelectedObject = false;
+ this._sourceGrid = null;
+ this._currentGrid = null;
+ this._layoutItem = null;
+ });
+ }
}
diff --git a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx
index 45353082b40..d0bea2c9a4d 100644
--- a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayout.tsx
@@ -88,7 +88,6 @@ export class AutoGridLayout extends SceneObjectBase impleme
this._resetPanelPositionAndSize();
document.body.removeEventListener('pointermove', this._onDrag);
document.body.removeEventListener('pointerup', this._onDragEnd);
- document.body.classList.remove('dashboard-draggable-transparent-selection');
};
}
diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
index fc5cc9e968d..ebdd5fd3579 100644
--- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
@@ -15,6 +15,8 @@ import {
SceneGridItemLike,
useSceneObjectState,
SceneObject,
+ SceneGridLayoutDragStartEvent,
+ SceneGridPlaceholderItem
} from '@grafana/scenes';
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2';
import { useStyles2 } from '@grafana/ui';
@@ -48,6 +50,7 @@ import { clearClipboard, getDashboardGridItemFromClipboard } from '../layouts-sh
import { dashboardCanvasAddButtonHoverStyles } from '../layouts-shared/styles';
import { getIsLazy } from '../layouts-shared/utils';
import { DashboardLayoutGrid } from '../types/DashboardLayoutGrid';
+import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
@@ -127,7 +130,8 @@ export class DefaultGridLayoutManager
this.subscribeToEvent(SceneGridLayoutDragStartEvent, ({ payload: { evt, panel }}) => {
const gridItem = panel.parent;
if (gridItem instanceof DashboardGridItem) {
- getLayoutOrchestratorFor(this)?.startDraggingSync(evt, gridItem, this.state.grid);
+ this.state.grid.setPlaceholderSize(gridItem.state.width ?? NEW_PANEL_WIDTH, gridItem.state.height ?? NEW_PANEL_HEIGHT);
+ getLayoutOrchestratorFor(this)?.startDraggingSync(evt, gridItem, this);
}
})
)
@@ -562,6 +566,36 @@ export class DefaultGridLayoutManager
this.state.grid.setState({ children: [...this.state.grid.state.children, gridItem] });
}
+ public setIsDropTarget(flag: boolean, sourceGrid: DashboardLayoutGrid) {
+ const newState = {
+ isDragging: flag,
+ isOutsideDragging: sourceGrid !== this,
+ };
+
+ if (newState.isDragging !== this.state.grid.state.isDragging || newState.isOutsideDragging !== this.state.grid.state.isOutsideDragging) {
+ this.state.grid.setState(newState);
+ }
+ }
+
+ public stopOrchestratorSync(sourceGrid: DashboardLayoutGrid, targetGrid: DashboardLayoutGrid, layoutItem: DashboardLayoutItem) {
+ console.log('stop orchestrator sync');
+
+ const isSourceGrid = sourceGrid === this;
+ const isTargetGrid = targetGrid === this;
+
+ if (!isSourceGrid && !isTargetGrid) {
+ return;
+ }
+
+ if (isSourceGrid && !isTargetGrid) {
+ this.state.grid.setState({ children: this.state.grid.state.children.filter((child) => child !== layoutItem) });
+ } else if (!isSourceGrid && isTargetGrid) {
+ // From outside drag
+ } else {
+ // Inside drag
+ }
+ }
+
public static createFromLayout(currentLayout: DashboardLayoutManager): DefaultGridLayoutManager {
const panels = currentLayout.getVizPanels();
const isLazy = getIsLazy(getDashboardSceneFor(currentLayout).state.preload)!;
diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts
index a0271fb03ca..b17eafe0af5 100644
--- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts
+++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutGrid.ts
@@ -1,5 +1,6 @@
import { SceneGridItemLike } from '@grafana/scenes';
+import { DashboardLayoutItem } from './DashboardLayoutItem';
import { DashboardLayoutManager } from './DashboardLayoutManager';
export interface DashboardLayoutGrid extends DashboardLayoutManager {
@@ -12,10 +13,18 @@ export interface DashboardLayoutGrid extends DashboardLayoutManager {
*/
addGridItem(gridItem: SceneGridItemLike): void;
+ /**
+ * Start the synchronization of the orchestrator with the grid drag
+ */
startOrchestratorSync?(): void;
- draggedItemOutside?(gridItem: SceneGridItemLike): void;
- draggedItemInside?(gridItem: SceneGridItemLike): void;
- setIsDropTarget?(flag: boolean): void;
+
+ stopOrchestratorSync?(sourceGrid: DashboardLayoutGrid, targetGrid: DashboardLayoutGrid, layoutItem: DashboardLayoutItem): void;
+
+ /**
+ * Toggle the grid as the current drop target
+ * Useful for toggling between inner drag and outer drag
+ */
+ setIsDropTarget?(flag: boolean, sourceGrid: DashboardLayoutGrid, layoutItem: DashboardLayoutItem): void;
}
export function isDashboardLayoutGrid(obj: DashboardLayoutManager): obj is DashboardLayoutGrid {