mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Tree View Navigation (#51855)
* tree navigation using rc-tree library
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
import { AppEvents, SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime/src';
|
||||
import { SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, HorizontalGroup } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { AddLayerButton } from 'app/core/components/Layers/AddLayerButton';
|
||||
@@ -11,11 +11,11 @@ import { CanvasElementOptions, canvasElementRegistry } from 'app/features/canvas
|
||||
import { notFoundItem } from 'app/features/canvas/elements/notFound';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
import { FrameState } from 'app/features/canvas/runtime/frame';
|
||||
import { SelectionParams } from 'app/features/canvas/runtime/scene';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
|
||||
import { PanelOptions } from '../models.gen';
|
||||
import { LayerActionID } from '../types';
|
||||
import { doSelect } from '../utils';
|
||||
|
||||
import { LayerEditorProps } from './layerEditor';
|
||||
|
||||
@@ -50,24 +50,8 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
||||
|
||||
onSelect = (item: ElementState) => {
|
||||
const { settings } = this.props.item;
|
||||
|
||||
if (settings?.scene) {
|
||||
try {
|
||||
let selection: SelectionParams = { targets: [] };
|
||||
if (item instanceof FrameState) {
|
||||
const targetElements: HTMLDivElement[] = [];
|
||||
targetElements.push(item?.div!);
|
||||
selection.targets = targetElements;
|
||||
selection.frame = item;
|
||||
settings.scene.select(selection);
|
||||
} else if (item instanceof ElementState) {
|
||||
const targetElement = [item?.div!];
|
||||
selection.targets = targetElement;
|
||||
settings.scene.select(selection);
|
||||
}
|
||||
} catch (error) {
|
||||
appEvents.emit(AppEvents.alertError, ['Unable to select element, try selecting element in panel instead']);
|
||||
}
|
||||
doSelect(settings.scene, item);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Global } from '@emotion/react';
|
||||
import Tree from 'rc-tree';
|
||||
import React, { Key, useEffect, useState } from 'react';
|
||||
import SVG from 'react-inlinesvg';
|
||||
|
||||
import { StandardEditorProps } from '@grafana/data';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
|
||||
import { getGlobalStyles } from '../globalStyles';
|
||||
import { PanelOptions } from '../models.gen';
|
||||
import { getTreeData, onNodeDrop } from '../tree';
|
||||
import { DragNode, DropNode } from '../types';
|
||||
import { doSelect } from '../utils';
|
||||
|
||||
import { TreeViewEditorProps } from './treeViewEditor';
|
||||
|
||||
export const TreeNavigationEditor = ({ item }: StandardEditorProps<any, TreeViewEditorProps, PanelOptions>) => {
|
||||
const [treeData, setTreeData] = useState(getTreeData(item?.settings?.scene.root));
|
||||
const [autoExpandParent, setAutoExpandParent] = useState(true);
|
||||
const [expandedKeys, setExpandedKeys] = useState<Key[]>([]);
|
||||
|
||||
const theme = useTheme2();
|
||||
const globalCSS = getGlobalStyles(theme);
|
||||
const selectedBgColor = theme.colors.background.secondary;
|
||||
const { settings } = item;
|
||||
|
||||
useEffect(() => {
|
||||
const selection: string[] = settings?.selected ? settings.selected.map((v) => v.getName()) : [];
|
||||
|
||||
setTreeData(getTreeData(item?.settings?.scene.root, selection, selectedBgColor));
|
||||
}, [item?.settings?.scene.root, selectedBgColor, settings?.selected]);
|
||||
|
||||
if (!settings) {
|
||||
return <div>No settings</div>;
|
||||
}
|
||||
|
||||
const onSelect = (selectedKeys: Key[], info: { node: { dataRef: ElementState } }) => {
|
||||
if (item.settings?.scene) {
|
||||
doSelect(item.settings.scene, info.node.dataRef);
|
||||
}
|
||||
};
|
||||
|
||||
const allowDrop = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const onDrop = (info: { node: DropNode; dragNode: DragNode; dropPosition: number; dropToGap: boolean }) => {
|
||||
const destPos = info.node.pos.split('-');
|
||||
const destPosition = info.dropPosition - Number(destPos[destPos.length - 1]);
|
||||
|
||||
const srcEl = info.dragNode.dataRef;
|
||||
const destEl = info.node.dataRef;
|
||||
|
||||
const data = onNodeDrop(info, treeData);
|
||||
|
||||
setTreeData(data);
|
||||
destEl.parent?.scene.reorderElements(srcEl, destEl, info.dropToGap, destPosition);
|
||||
};
|
||||
|
||||
const onExpand = (expandedKeys: Key[]) => {
|
||||
setExpandedKeys(expandedKeys);
|
||||
setAutoExpandParent(false);
|
||||
};
|
||||
|
||||
const getSvgIcon = (path = '', style = {}) => <SVG src={path} title={'Node Icon'} style={{ ...style }} />;
|
||||
|
||||
const switcherIcon = (obj: { isLeaf: boolean; expanded: boolean }) => {
|
||||
if (obj.isLeaf) {
|
||||
// TODO: Implement element specific icons
|
||||
return getSvgIcon('');
|
||||
}
|
||||
|
||||
return getSvgIcon('public/img/icons/unicons/angle-right.svg', {
|
||||
transform: `rotate(${obj.expanded ? 90 : 0}deg)`,
|
||||
fill: theme.colors.text.primary,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Global styles={globalCSS} />
|
||||
<Tree
|
||||
selectable={true}
|
||||
onSelect={onSelect}
|
||||
draggable={true}
|
||||
defaultExpandAll={true}
|
||||
autoExpandParent={autoExpandParent}
|
||||
showIcon={false}
|
||||
allowDrop={allowDrop}
|
||||
onDrop={onDrop}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
treeData={treeData}
|
||||
switcherIcon={switcherIcon}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
47
public/app/plugins/panel/canvas/editor/treeViewEditor.tsx
Normal file
47
public/app/plugins/panel/canvas/editor/treeViewEditor.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NestedPanelOptions } from '@grafana/data/src/utils/OptionsUIBuilders';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
import { FrameState } from 'app/features/canvas/runtime/frame';
|
||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
|
||||
import { InstanceState } from '../CanvasPanel';
|
||||
|
||||
import { TreeNavigationEditor } from './TreeNavigationEditor';
|
||||
|
||||
export interface TreeViewEditorProps {
|
||||
scene: Scene;
|
||||
layer: FrameState;
|
||||
selected: ElementState[];
|
||||
}
|
||||
|
||||
export function getTreeViewEditor(opts: InstanceState): NestedPanelOptions<TreeViewEditorProps> {
|
||||
const { selected, scene } = opts;
|
||||
|
||||
if (selected) {
|
||||
for (const element of selected) {
|
||||
if (element instanceof FrameState) {
|
||||
scene.currentLayer = element;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element.parent) {
|
||||
scene.currentLayer = element.parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
category: ['Tree View'],
|
||||
path: '--',
|
||||
build: (builder, context) => {
|
||||
builder.addCustomEditor({
|
||||
category: [],
|
||||
id: 'treeView',
|
||||
path: '__', // not used
|
||||
name: '',
|
||||
editor: TreeNavigationEditor,
|
||||
settings: { scene, layer: scene.currentLayer, selected },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user