Dashboard: Scroll element into view when selecting from outline (#102966)
* Dashboard: Scroll element into view when selecting from outline * fix import * Switch to tab when selected via outline * Update --------- Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
This commit is contained in:
co-authored by
Oscar Kilhed
parent
73e0db452b
commit
5a916db4de
@@ -37,6 +37,7 @@ function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneO
|
||||
|
||||
const children = collectEditableElementChildren(sceneObject);
|
||||
const elementInfo = editableElement.getEditableElementInfo();
|
||||
const instanceName = elementInfo.instanceName === '' ? '<empty title>' : elementInfo.instanceName;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -46,11 +47,12 @@ function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneO
|
||||
onPointerDown={(evt) => {
|
||||
onSelect?.(evt);
|
||||
setIsExpanded(!isExpanded);
|
||||
editableElement.scrollIntoView?.();
|
||||
}}
|
||||
>
|
||||
{expandable && <Icon name={isExpanded ? 'angle-down' : 'angle-right'} />}
|
||||
<Icon size="sm" name={elementInfo.icon} />
|
||||
<span>{elementInfo.instanceName}</span>
|
||||
<span>{instanceName}</span>
|
||||
</button>
|
||||
|
||||
{expandable && isExpanded && (
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
PanelDescriptionTextArea,
|
||||
PanelFrameTitleInput,
|
||||
} from '../panel-edit/getPanelFrameOptions';
|
||||
import { AutoGridItem } from '../scene/layout-responsive-grid/ResponsiveGridItem';
|
||||
import { BulkActionElement } from '../scene/types/BulkActionElement';
|
||||
import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
|
||||
@@ -96,6 +97,12 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
|
||||
public createMultiSelectedElement(items: VizPanelEditableElement[]) {
|
||||
return new MultiSelectedVizPanelsEditableElement(items);
|
||||
}
|
||||
|
||||
public scrollIntoView() {
|
||||
if (this.panel.parent instanceof AutoGridItem) {
|
||||
this.panel.parent.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OpenPanelEditVizProps = {
|
||||
|
||||
+10
-10
@@ -1,4 +1,5 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
CustomVariable,
|
||||
@@ -18,6 +19,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
|
||||
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
|
||||
import { getCloneKey } from '../../utils/clone';
|
||||
import { getMultiVariableValues } from '../../utils/utils';
|
||||
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
||||
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
||||
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
||||
|
||||
@@ -43,9 +45,7 @@ export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements
|
||||
});
|
||||
|
||||
public readonly isDashboardLayoutItem = true;
|
||||
|
||||
public _containerRef: HTMLDivElement | null = null;
|
||||
|
||||
public containerRef = React.createRef<HTMLDivElement>();
|
||||
private _prevRepeatValues?: VariableValueSingle[];
|
||||
|
||||
public constructor(state: AutoGridItemState) {
|
||||
@@ -153,18 +153,18 @@ export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public setRef(ref: HTMLDivElement | null) {
|
||||
this._containerRef = ref;
|
||||
}
|
||||
|
||||
public getBoundingBox(): { width: number; height: number; top: number; left: number } {
|
||||
const rect = this._containerRef!.getBoundingClientRect();
|
||||
const rect = this.containerRef.current!.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
top: this._containerRef!.offsetTop,
|
||||
left: this._containerRef!.offsetLeft,
|
||||
top: this.containerRef.current!.offsetTop,
|
||||
left: this.containerRef.current!.offsetLeft,
|
||||
};
|
||||
}
|
||||
|
||||
public scrollIntoView() {
|
||||
scrollCanvasElementIntoView(this, this.containerRef);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div ref={(ref) => model.setRef(ref)} data-auto-grid-item-drop-target={isDragging ? key : undefined}>
|
||||
<div ref={model.containerRef} data-auto-grid-item-drop-target={isDragging ? key : undefined}>
|
||||
{isDragged && <div className={styles.draggedPlaceholder} />}
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
sceneGraph,
|
||||
SceneObject,
|
||||
@@ -14,6 +16,7 @@ import { ConditionalRendering } from '../../conditional-rendering/ConditionalRen
|
||||
import { getDefaultVizPanel } from '../../utils/utils';
|
||||
import { AutoGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
import { LayoutRestorer } from '../layouts-shared/LayoutRestorer';
|
||||
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
||||
import { BulkActionElement } from '../types/BulkActionElement';
|
||||
import { DashboardDropTarget } from '../types/DashboardDropTarget';
|
||||
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
||||
@@ -50,6 +53,7 @@ export class RowItem
|
||||
public readonly isEditableDashboardElement = true;
|
||||
public readonly isDashboardDropTarget = true;
|
||||
private _layoutRestorer = new LayoutRestorer();
|
||||
public containerRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
public constructor(state?: Partial<RowItemState>) {
|
||||
super({
|
||||
@@ -201,4 +205,8 @@ export class RowItem
|
||||
private _getRepeatBehavior(): RowItemRepeaterBehavior | undefined {
|
||||
return this.state.$behaviors?.find((b) => b instanceof RowItemRepeaterBehavior);
|
||||
}
|
||||
|
||||
public scrollIntoView() {
|
||||
scrollCanvasElementIntoView(this, this.containerRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={model.containerRef}
|
||||
data-dashboard-drop-target-key={model.state.key}
|
||||
className={cx(
|
||||
styles.wrapper,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
SceneObjectState,
|
||||
SceneObjectBase,
|
||||
@@ -13,6 +15,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
|
||||
import { getDefaultVizPanel } from '../../utils/utils';
|
||||
import { AutoGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
import { LayoutRestorer } from '../layouts-shared/LayoutRestorer';
|
||||
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
||||
import { BulkActionElement } from '../types/BulkActionElement';
|
||||
import { DashboardDropTarget } from '../types/DashboardDropTarget';
|
||||
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
||||
@@ -48,6 +51,7 @@ export class TabItem
|
||||
public readonly isDashboardDropTarget = true;
|
||||
|
||||
private _layoutRestorer = new LayoutRestorer();
|
||||
public containerRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
constructor(state?: Partial<TabItemState>) {
|
||||
super({
|
||||
@@ -160,4 +164,13 @@ export class TabItem
|
||||
private _getParentLayout(): TabsLayoutManager {
|
||||
return sceneGraph.getAncestor(this, TabsLayoutManager);
|
||||
}
|
||||
|
||||
public scrollIntoView(): void {
|
||||
const tabsLayout = sceneGraph.getAncestor(this, TabsLayoutManager);
|
||||
if (tabsLayout.getCurrentTab() !== this) {
|
||||
tabsLayout.switchToTab(this);
|
||||
}
|
||||
|
||||
scrollCanvasElementIntoView(this, this.containerRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export function TabItemRenderer({ model }: SceneComponentProps<TabItem>) {
|
||||
|
||||
return (
|
||||
<Tab
|
||||
ref={model.containerRef}
|
||||
truncate
|
||||
className={cx(
|
||||
isSelected && 'dashboard-selected-element',
|
||||
|
||||
@@ -87,6 +87,10 @@ export class TabsLayoutManager extends SceneObjectBase<TabsLayoutManagerState> i
|
||||
}
|
||||
}
|
||||
|
||||
public switchToTab(tab: TabItem) {
|
||||
this.setState({ currentTabIndex: this.state.tabs.indexOf(tab) });
|
||||
}
|
||||
|
||||
public getCurrentTab(): TabItem {
|
||||
return this.state.tabs.length > this.state.currentTabIndex
|
||||
? this.state.tabs[this.state.currentTabIndex]
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
|
||||
import { RowItem } from '../layout-rows/RowItem';
|
||||
import { TabItem } from '../layout-tabs/TabItem';
|
||||
import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
|
||||
|
||||
/**
|
||||
* Will scroll element into view. If element is not connected yet, it will try to expand rows
|
||||
* and switch tabs to make it visible.
|
||||
*/
|
||||
export function scrollCanvasElementIntoView(sceneObject: SceneObject, ref: React.RefObject<HTMLElement>) {
|
||||
if (ref.current?.isConnected) {
|
||||
scrollIntoView(ref.current);
|
||||
return;
|
||||
}
|
||||
|
||||
// try expanding rows and switching tabs
|
||||
let parent = sceneObject.parent;
|
||||
while (parent) {
|
||||
if (parent instanceof RowItem && parent.state.collapse) {
|
||||
parent.onCollapseToggle();
|
||||
}
|
||||
|
||||
if (parent instanceof TabItem) {
|
||||
const tabsManager = parent.parent;
|
||||
if (tabsManager instanceof TabsLayoutManager && tabsManager.getCurrentTab() !== parent) {
|
||||
tabsManager.switchToTab(parent);
|
||||
}
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
// now try to scroll into view
|
||||
setTimeout(() => {
|
||||
if (ref.current?.isConnected) {
|
||||
scrollIntoView(ref.current);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function scrollIntoView(element: HTMLElement) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
|
||||
}
|
||||
@@ -44,6 +44,11 @@ export interface EditableDashboardElement {
|
||||
* creates a new multi-selection element from a list of selected items
|
||||
*/
|
||||
createMultiSelectedElement?(elements: this[]): EditableDashboardElement;
|
||||
|
||||
/**
|
||||
* scroll element into view (when selected from outline)
|
||||
*/
|
||||
scrollIntoView?(): void;
|
||||
}
|
||||
|
||||
export interface EditableDashboardElementInfo {
|
||||
|
||||
Reference in New Issue
Block a user