Canvas: Tree View Navigation (#51855)

* tree navigation using rc-tree library
This commit is contained in:
Adela Almasan
2022-07-12 08:31:02 -05:00
committed by GitHub
parent db9c9b5354
commit c73d78eaac
13 changed files with 621 additions and 23 deletions

View File

@@ -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);
}
};

View File

@@ -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}
/>
</>
);
};

View 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 },
});
},
};
}