Add drag & drop for new button
This commit is contained in:
@@ -46,7 +46,7 @@ export function SidebarComp({ children, contextValue }: Props) {
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={contextValue}>
|
||||
<div ref={ref} className={className} style={style}>
|
||||
<div ref={ref} className={className} style={style} data-testid="dashboard-edit-pane-sidebar">
|
||||
{!tabsMode && <SidebarResizer />}
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,11 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) {
|
||||
const hasUid = Boolean(uid);
|
||||
const selectedObject = selection?.getFirstObject();
|
||||
const isNewElement = selection?.isNewElement() ?? false;
|
||||
const [selectedLayoutElement, setSelectedLayoutElement] = useState(selectedObject);
|
||||
// the layout element that was selected when opening the 'add' pane
|
||||
// used when adding new panel from the sidebar
|
||||
const [selectedLayoutElement, setSelectedLayoutElement] = useState<DashboardScene | SceneObject<SceneObjectState>>(
|
||||
dashboard
|
||||
);
|
||||
|
||||
const editableElement = useMemo(() => {
|
||||
if (selection) {
|
||||
@@ -46,7 +50,7 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) {
|
||||
return undefined;
|
||||
}, [selection]);
|
||||
|
||||
// saves the row or tab selected when opening the 'add' pane, so new panels can later be added to it
|
||||
|
||||
const onSetLayoutElement = (obj: SceneObject<SceneObjectState> | undefined) => {
|
||||
if (obj instanceof RowItem || obj instanceof TabItem) {
|
||||
setSelectedLayoutElement(obj);
|
||||
@@ -80,7 +84,7 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) {
|
||||
)}
|
||||
{openPane === 'add' && (
|
||||
<Sidebar.OpenPane>
|
||||
<DashboardSidePaneNew onAddPanel={onAddNewPanel} />
|
||||
<DashboardSidePaneNew onAddPanel={onAddNewPanel} dashboard={dashboard} />
|
||||
</Sidebar.OpenPane>
|
||||
)}
|
||||
{openPane === 'outline' && (
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { Sidebar, Text, useStyles2 } from '@grafana/ui';
|
||||
import addPanelImg from 'img/dashboards/add-panel.png';
|
||||
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
export function DashboardSidePaneNew({ onAddPanel }: {onAddPanel: () => void}) {
|
||||
export function DashboardSidePaneNew({ onAddPanel, dashboard }: { onAddPanel: () => void; dashboard: SceneObject }) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const orchestrator = getDashboardSceneFor(dashboard).state.layoutOrchestrator;
|
||||
|
||||
return (
|
||||
<div className={styles.sidePanel}>
|
||||
<Sidebar.PaneHeader title={t('dashboard.add.pane-header', 'Add')} />
|
||||
<div className={styles.sidePanelContainer}>
|
||||
<Text weight="medium">{t('dashboard.add.new-panel.title', 'Panel')}</Text>
|
||||
<Text variant="bodySmall">
|
||||
{t('dashboard.add.new-panel.description', 'Drag or click to add a panel')}
|
||||
</Text>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<button className={styles.noStyles} onClick={onAddPanel} aria-label={t('dashboard.add.new-panel.title', 'Panel')}>
|
||||
<img alt="Add panel click area" src={addPanelImg} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DragDropContext onDragStart={() => orchestrator?.startDraggingNewPanel()} onDragEnd={() => {}}>
|
||||
<Droppable droppableId="side-drop-id" isDropDisabled>
|
||||
{(dropProvided) => (
|
||||
<div className={styles.sidePanel} ref={dropProvided.innerRef} {...dropProvided.droppableProps}>
|
||||
<Sidebar.PaneHeader title={t('dashboard.add.pane-header', 'Add')} />
|
||||
<div className={styles.sidePanelContainer}>
|
||||
<Text weight="medium">{t('dashboard.add.new-panel.title', 'Panel')}</Text>
|
||||
<Text variant="bodySmall">
|
||||
{t('dashboard.add.new-panel.description', 'Drag or click to add a panel')}
|
||||
</Text>
|
||||
<div className={styles.dragContainer}>
|
||||
<Draggable draggableId="new-panel-drag" index={0}>
|
||||
{(dragProvided, dragSnapshot) => {
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
ref={dragProvided.innerRef}
|
||||
{...dragProvided.draggableProps}
|
||||
{...dragProvided.dragHandleProps}
|
||||
className={cx(styles.imageContainer, dragSnapshot.isDragging && styles.dragging)}
|
||||
onClick={onAddPanel}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onAddPanel();
|
||||
}
|
||||
}}
|
||||
aria-label={t('dashboard.add.new-panel.title', 'Panel')}
|
||||
>
|
||||
<img alt="Add panel click area" src={addPanelImg} draggable={false} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,24 +63,28 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
sidePanel: css({
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
dragging: css({
|
||||
cursor: 'move',
|
||||
}),
|
||||
sidePanelContainer: css({
|
||||
padding: theme.spacing(2),
|
||||
span: {
|
||||
display: 'block',
|
||||
},
|
||||
}),
|
||||
dragContainer: css({
|
||||
height: 150,
|
||||
}),
|
||||
imageContainer: css({
|
||||
opacity: 0.8,
|
||||
cursor: 'pointer',
|
||||
opacity: 0.8,
|
||||
overflow: 'hidden',
|
||||
padding: theme.spacing(2, 0),
|
||||
borderRadius: theme.shape.radius.sm,
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
'&:hover': {
|
||||
opacity: 1,
|
||||
}
|
||||
}),
|
||||
noStyles: css({
|
||||
all: 'unset',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,11 +11,16 @@ import {
|
||||
} from '@grafana/scenes';
|
||||
import { createPointerDistance } from '@grafana/ui';
|
||||
|
||||
import { getDefaultVizPanel } from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { RowItem } from './layout-rows/RowItem';
|
||||
import { TabItem } from './layout-tabs/TabItem';
|
||||
import { DashboardDropTarget, isDashboardDropTarget } from './types/DashboardDropTarget';
|
||||
|
||||
interface DashboardLayoutOrchestratorState extends SceneObjectState {
|
||||
draggingGridItem?: SceneObjectRef<SceneGridItemLike>;
|
||||
isDraggingNewPanel?: boolean;
|
||||
}
|
||||
|
||||
export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayoutOrchestratorState> {
|
||||
@@ -59,10 +64,19 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
|
||||
this.setState({ draggingGridItem: gridItem.getRef() });
|
||||
}
|
||||
|
||||
public startDraggingNewPanel(): void {
|
||||
document.body.addEventListener('pointermove', this._onPointerMove);
|
||||
document.body.addEventListener('pointerup', this._stopDraggingSync, true);
|
||||
|
||||
this.setState({ isDraggingNewPanel: true });
|
||||
}
|
||||
|
||||
private _stopDraggingSync(_evt: PointerEvent) {
|
||||
const gridItem = this.state.draggingGridItem?.resolve();
|
||||
|
||||
if (this._sourceDropTarget !== this._lastDropTarget) {
|
||||
if (this.state.isDraggingNewPanel) {
|
||||
this._dropNewPanel(_evt);
|
||||
} else if (this._sourceDropTarget !== this._lastDropTarget) {
|
||||
// Wrapped in setTimeout to ensure that any event handlers are called
|
||||
// Useful for allowing react-grid-layout to remove placeholders, etc.
|
||||
setTimeout(() => {
|
||||
@@ -76,12 +90,35 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase<DashboardLayout
|
||||
logWarning(warningMessage);
|
||||
}
|
||||
});
|
||||
document.body.removeEventListener('pointermove', this._onPointerMove);
|
||||
document.body.removeEventListener('pointerup', this._stopDraggingSync);
|
||||
|
||||
this.setState({ draggingGridItem: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
private _dropNewPanel(_evt: PointerEvent) {
|
||||
const elementsUnderPoint = document.elementsFromPoint(_evt.clientX, _evt.clientY);
|
||||
|
||||
// if the cursor is in the sidebar, don't add panel
|
||||
const isInSidebar = elementsUnderPoint.some((el) => el.closest('[data-testid="dashboard-edit-pane-sidebar"]') !== null);
|
||||
|
||||
if (isInSidebar) {
|
||||
return;
|
||||
}
|
||||
const panel = getDefaultVizPanel();
|
||||
if (!this._lastDropTarget) {
|
||||
// if no lastDropTarget and not in Sidebar, treat the dashboard itself as the drop target
|
||||
this._getDashboard().addPanel(panel);
|
||||
}
|
||||
|
||||
if (this._lastDropTarget instanceof RowItem || this._lastDropTarget instanceof TabItem) {
|
||||
this._lastDropTarget.getLayout().addPanel(panel);
|
||||
}
|
||||
document.body.removeEventListener('pointermove', this._onPointerMove);
|
||||
document.body.removeEventListener('pointerup', this._stopDraggingSync);
|
||||
document.body.removeEventListener('pointerup', this._stopDraggingSync, true);
|
||||
|
||||
this.setState({ draggingGridItem: undefined });
|
||||
this.setState({ isDraggingNewPanel: false });
|
||||
}
|
||||
|
||||
private _onPointerMove(evt: PointerEvent) {
|
||||
|
||||
Reference in New Issue
Block a user