Canvas: Merge tree view and layer element list UX (#52701)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Adela Almasan 2022-07-26 18:51:20 -05:00 committed by GitHub
parent e5d89ddb95
commit a5410063c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 268 additions and 362 deletions

View File

@ -8630,15 +8630,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"] [0, 0, 0, "Unexpected any. Specify a different type.", "4"]
], ],
"public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/plugins/panel/canvas/editor/PlacementEditor.tsx:5381": [ "public/app/plugins/panel/canvas/editor/PlacementEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/panel/canvas/editor/TreeNavigationEditor.tsx:5381": [ "public/app/plugins/panel/canvas/editor/TreeNavigationEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
], ],
"public/app/plugins/panel/canvas/editor/elementEditor.tsx:5381": [ "public/app/plugins/panel/canvas/editor/elementEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],

View File

@ -15,7 +15,6 @@ import { setOptionImmutably } from 'app/features/dashboard/components/PanelEdito
import { activePanelSubject, InstanceState } from './CanvasPanel'; import { activePanelSubject, InstanceState } from './CanvasPanel';
import { getElementEditor } from './editor/elementEditor'; import { getElementEditor } from './editor/elementEditor';
import { getLayerEditor } from './editor/layerEditor'; import { getLayerEditor } from './editor/layerEditor';
import { getTreeViewEditor } from './editor/treeViewEditor';
export const InlineEditBody = () => { export const InlineEditBody = () => {
const activePanel = useObservable(activePanelSubject); const activePanel = useObservable(activePanelSubject);
@ -30,7 +29,6 @@ export const InlineEditBody = () => {
} }
const supplier = (builder: PanelOptionsEditorBuilder<any>, context: StandardEditorContext<any>) => { const supplier = (builder: PanelOptionsEditorBuilder<any>, context: StandardEditorContext<any>) => {
builder.addNestedOptions(getTreeViewEditor(state));
builder.addNestedOptions(getLayerEditor(instanceState)); builder.addNestedOptions(getLayerEditor(instanceState));
const selection = state.selected; const selection = state.selected;

View File

@ -1,266 +0,0 @@
import React, { PureComponent } from 'react';
import { DropResult } from 'react-beautiful-dnd';
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';
import { LayerDragDropList } from 'app/core/components/Layers/LayerDragDropList';
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 { ShowConfirmModalEvent } from 'app/types/events';
import { PanelOptions } from '../models.gen';
import { LayerActionID } from '../types';
import { doSelect } from '../utils';
import { LayerEditorProps } from './layerEditor';
type Props = StandardEditorProps<any, LayerEditorProps, PanelOptions>;
export class LayerElementListEditor extends PureComponent<Props> {
getScene = () => {
const { settings } = this.props.item;
if (!settings?.layer) {
return;
}
return settings.layer.scene;
};
onAddItem = (sel: SelectableValue<string>) => {
const { settings } = this.props.item;
if (!settings?.layer) {
return;
}
const { layer } = settings;
const item = canvasElementRegistry.getIfExists(sel.value) ?? notFoundItem;
const newElementOptions = item.getNewOptions() as CanvasElementOptions;
newElementOptions.type = item.id;
const newElement = new ElementState(item, newElementOptions, layer);
newElement.updateData(layer.scene.context);
layer.elements.push(newElement);
layer.scene.save();
layer.reinitializeMoveable();
};
onSelect = (item: ElementState) => {
const { settings } = this.props.item;
if (settings?.scene) {
doSelect(settings.scene, item);
}
};
onClearSelection = () => {
const { settings } = this.props.item;
if (!settings?.layer) {
return;
}
const { layer } = settings;
layer.scene.clearCurrentSelection();
};
onDragEnd = (result: DropResult) => {
if (!result.destination) {
return;
}
const { settings } = this.props.item;
if (!settings?.layer) {
return;
}
const { layer } = settings;
const count = layer.elements.length - 1;
const src = (result.source.index - count) * -1;
const dst = (result.destination.index - count) * -1;
layer.reorder(src, dst);
};
goUpLayer = () => {
const settings = this.props.item.settings;
if (!settings?.layer || !settings?.scene) {
return;
}
const { scene, layer } = settings;
if (layer.parent) {
scene.updateCurrentLayer(layer.parent);
}
};
private decoupleFrame = () => {
const settings = this.props.item.settings;
if (!settings?.layer) {
return;
}
const { layer } = settings;
this.deleteFrame();
layer.elements.forEach((element: ElementState) => {
const elementContainer = element.div?.getBoundingClientRect();
element.setPlacementFromConstraint(elementContainer, layer.parent?.div?.getBoundingClientRect());
layer.parent?.doAction(LayerActionID.Duplicate, element, false, false);
});
};
private onDecoupleFrame = () => {
appEvents.publish(
new ShowConfirmModalEvent({
title: 'Decouple frame',
text: `Are you sure you want to decouple this frame?`,
text2: 'This will remove the frame and push nested elements in the next level up.',
confirmText: 'Yes',
yesText: 'Decouple',
onConfirm: async () => {
this.decoupleFrame();
},
})
);
};
private deleteFrame = () => {
const settings = this.props.item.settings;
if (!settings?.layer) {
return;
}
const { layer } = settings;
const scene = this.getScene();
scene?.byName.delete(layer.getName());
layer.elements.forEach((element) => scene?.byName.delete(element.getName()));
layer.parent?.doAction(LayerActionID.Delete, layer);
this.goUpLayer();
};
private onFrameSelection = () => {
const scene = this.getScene();
if (scene) {
scene.frameSelection();
} else {
console.warn('no scene!');
}
};
private onDeleteFrame = () => {
appEvents.publish(
new ShowConfirmModalEvent({
title: 'Delete frame',
text: `Are you sure you want to delete this frame?`,
text2: 'This will delete the frame and all nested elements.',
icon: 'trash-alt',
confirmText: 'Delete',
yesText: 'Delete',
onConfirm: async () => {
this.deleteFrame();
},
})
);
};
render() {
const settings = this.props.item.settings;
if (!settings) {
return <div>No settings</div>;
}
const layer = settings.layer;
if (!layer) {
return <div>Missing layer?</div>;
}
const onDelete = (element: ElementState) => {
layer.doAction(LayerActionID.Delete, element);
};
const onDuplicate = (element: ElementState) => {
layer.doAction(LayerActionID.Duplicate, element);
};
const getLayerInfo = (element: ElementState) => {
return element.options.type;
};
const onNameChange = (element: ElementState, name: string) => {
element.onChange({ ...element.options, name });
};
const showActions = (element: ElementState) => {
return !(element instanceof FrameState);
};
const verifyLayerNameUniqueness = (nameToVerify: string) => {
const scene = this.getScene();
return Boolean(scene?.canRename(nameToVerify));
};
const selection: string[] = settings.selected ? settings.selected.map((v) => v.getName()) : [];
return (
<>
{!layer.isRoot() && (
<>
<Button icon="angle-up" size="sm" variant="secondary" onClick={this.goUpLayer}>
Go up level
</Button>
<Button size="sm" variant="secondary" onClick={() => this.onSelect(layer)}>
Select frame
</Button>
<Button size="sm" variant="secondary" onClick={() => this.onDecoupleFrame()}>
Decouple frame
</Button>
<Button size="sm" variant="secondary" onClick={() => this.onDeleteFrame()}>
Delete frame
</Button>
</>
)}
<LayerDragDropList
onDragEnd={this.onDragEnd}
onSelect={this.onSelect}
onDelete={onDelete}
onDuplicate={onDuplicate}
getLayerInfo={getLayerInfo}
onNameChange={onNameChange}
verifyLayerNameUniqueness={verifyLayerNameUniqueness}
showActions={showActions}
layers={layer.elements}
selection={selection}
/>
<br />
<HorizontalGroup>
<AddLayerButton
onChange={this.onAddItem}
options={canvasElementRegistry.selectOptions().options}
label={'Add item'}
/>
{selection.length > 0 && (
<Button size="sm" variant="secondary" onClick={this.onClearSelection}>
Clear selection
</Button>
)}
{selection.length > 1 && config.featureToggles.canvasPanelNesting && (
<Button size="sm" variant="secondary" onClick={this.onFrameSelection}>
Frame selection
</Button>
)}
</HorizontalGroup>
</>
);
}
}

View File

@ -1,42 +1,65 @@
import { Global } from '@emotion/react'; import { Global } from '@emotion/react';
import Tree from 'rc-tree'; import Tree from 'rc-tree';
import React, { Key, useEffect, useState } from 'react'; import React, { Key, useEffect, useMemo, useState } from 'react';
import SVG from 'react-inlinesvg'; import SVG from 'react-inlinesvg';
import { StandardEditorProps } from '@grafana/data'; import { SelectableValue, StandardEditorProps } from '@grafana/data';
import { useTheme2 } from '@grafana/ui'; import { config } from '@grafana/runtime';
import { Button, HorizontalGroup, useTheme2 } from '@grafana/ui';
import { ElementState } from 'app/features/canvas/runtime/element'; import { ElementState } from 'app/features/canvas/runtime/element';
import { AddLayerButton } from '../../../../core/components/Layers/AddLayerButton';
import { CanvasElementOptions, canvasElementRegistry } from '../../../../features/canvas';
import { notFoundItem } from '../../../../features/canvas/elements/notFound';
import { getGlobalStyles } from '../globalStyles'; import { getGlobalStyles } from '../globalStyles';
import { PanelOptions } from '../models.gen'; import { PanelOptions } from '../models.gen';
import { getTreeData, onNodeDrop } from '../tree'; import { getTreeData, onNodeDrop, TreeElement } from '../tree';
import { DragNode, DropNode } from '../types'; import { DragNode, DropNode } from '../types';
import { doSelect } from '../utils'; import { doSelect } from '../utils';
import { TreeViewEditorProps } from './treeViewEditor'; import { TreeNodeTitle } from './TreeNodeTitle';
import { TreeViewEditorProps } from './elementEditor';
let allowSelection = true;
export const TreeNavigationEditor = ({ item }: StandardEditorProps<any, TreeViewEditorProps, PanelOptions>) => { export const TreeNavigationEditor = ({ item }: StandardEditorProps<any, TreeViewEditorProps, PanelOptions>) => {
const [treeData, setTreeData] = useState(getTreeData(item?.settings?.scene.root)); const [treeData, setTreeData] = useState(getTreeData(item?.settings?.scene.root));
const [autoExpandParent, setAutoExpandParent] = useState(true); const [autoExpandParent, setAutoExpandParent] = useState(true);
const [expandedKeys, setExpandedKeys] = useState<Key[]>([]); const [expandedKeys, setExpandedKeys] = useState<Key[]>([]);
const [selectedKeys, setSelectedKeys] = useState<Key[]>([]);
const theme = useTheme2(); const theme = useTheme2();
const globalCSS = getGlobalStyles(theme); const globalCSS = getGlobalStyles(theme);
const selectedBgColor = theme.colors.background.secondary;
const selectedBgColor = theme.v1.colors.formInputBorderActive;
const { settings } = item; const { settings } = item;
const selection = useMemo(
() => (settings?.selected ? settings.selected.map((v) => v.getName()) : []),
[settings?.selected]
);
const selectionByUID = useMemo(
() => (settings?.selected ? settings.selected.map((v) => v.UID) : []),
[settings?.selected]
);
useEffect(() => { useEffect(() => {
const selection: string[] = settings?.selected ? settings.selected.map((v) => v.getName()) : [];
setTreeData(getTreeData(item?.settings?.scene.root, selection, selectedBgColor)); setTreeData(getTreeData(item?.settings?.scene.root, selection, selectedBgColor));
}, [item?.settings?.scene.root, selectedBgColor, settings?.selected]); setSelectedKeys(selectionByUID);
setAllowSelection();
}, [item?.settings?.scene.root, selectedBgColor, selection, selectionByUID]);
if (!settings) { if (!settings) {
return <div>No settings</div>; return <div>No settings</div>;
} }
const layer = settings.layer;
if (!layer) {
return <div>Missing layer?</div>;
}
const onSelect = (selectedKeys: Key[], info: { node: { dataRef: ElementState } }) => { const onSelect = (selectedKeys: Key[], info: { node: { dataRef: ElementState } }) => {
if (item.settings?.scene) { if (allowSelection && item.settings?.scene) {
doSelect(item.settings.scene, info.node.dataRef); doSelect(item.settings.scene, info.node.dataRef);
} }
}; };
@ -77,6 +100,39 @@ export const TreeNavigationEditor = ({ item }: StandardEditorProps<any, TreeView
}); });
}; };
const setAllowSelection = (allow = true) => {
allowSelection = allow;
};
const onAddItem = (sel: SelectableValue<string>) => {
const newItem = canvasElementRegistry.getIfExists(sel.value) ?? notFoundItem;
const newElementOptions = newItem.getNewOptions() as CanvasElementOptions;
newElementOptions.type = newItem.id;
const newElement = new ElementState(newItem, newElementOptions, layer);
newElement.updateData(layer.scene.context);
layer.elements.push(newElement);
layer.scene.save();
layer.reinitializeMoveable();
};
const onClearSelection = () => {
layer.scene.clearCurrentSelection();
};
const onTitleRender = (nodeData: TreeElement) => {
return <TreeNodeTitle nodeData={nodeData} setAllowSelection={setAllowSelection} settings={settings} />;
};
// TODO: This functionality is currently kinda broken / no way to decouple / delete created frames at this time
const onFrameSelection = () => {
if (layer.scene) {
layer.scene.frameSelection();
} else {
console.warn('no scene!');
}
};
return ( return (
<> <>
<Global styles={globalCSS} /> <Global styles={globalCSS} />
@ -92,8 +148,31 @@ export const TreeNavigationEditor = ({ item }: StandardEditorProps<any, TreeView
expandedKeys={expandedKeys} expandedKeys={expandedKeys}
onExpand={onExpand} onExpand={onExpand}
treeData={treeData} treeData={treeData}
titleRender={onTitleRender}
switcherIcon={switcherIcon} switcherIcon={switcherIcon}
selectedKeys={selectedKeys}
multiple={true}
/> />
<HorizontalGroup>
<div style={{ marginLeft: '18px' }}>
<AddLayerButton
onChange={onAddItem}
options={canvasElementRegistry.selectOptions().options}
label={'Add item'}
/>
</div>
{selection.length > 0 && (
<Button size="sm" variant="secondary" onClick={onClearSelection}>
Clear selection
</Button>
)}
{selection.length > 1 && config.featureToggles.canvasPanelNesting && (
<Button size="sm" variant="secondary" onClick={onFrameSelection}>
Frame selection
</Button>
)}
</HorizontalGroup>
</> </>
); );
}; };

View File

@ -0,0 +1,118 @@
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { IconButton, useStyles2 } from '@grafana/ui';
import { ElementState } from 'app/features/canvas/runtime/element';
import { LayerName } from '../../../../core/components/Layers/LayerName';
import { TreeElement } from '../tree';
import { LayerActionID } from '../types';
import { TreeViewEditorProps } from './elementEditor';
interface Props {
settings: TreeViewEditorProps;
nodeData: TreeElement;
setAllowSelection: (allow: boolean) => void;
}
export const TreeNodeTitle = ({ settings, nodeData, setAllowSelection }: Props) => {
const element = nodeData.dataRef;
const name = nodeData.dataRef.getName();
const styles = useStyles2(getStyles);
const layer = settings.layer;
const getScene = () => {
if (!settings?.layer) {
return;
}
return settings.layer.scene;
};
const onDelete = (element: ElementState) => {
const elLayer = element.parent ?? layer;
elLayer.doAction(LayerActionID.Delete, element);
setAllowSelection(false);
};
const onDuplicate = (element: ElementState) => {
const elLayer = element.parent ?? layer;
elLayer.doAction(LayerActionID.Duplicate, element);
setAllowSelection(false);
};
const onNameChange = (element: ElementState, name: string) => {
element.onChange({ ...element.options, name });
};
const verifyLayerNameUniqueness = (nameToVerify: string) => {
const scene = getScene();
return Boolean(scene?.canRename(nameToVerify));
};
const getLayerInfo = (element: ElementState) => {
return element.options.type;
};
return (
<>
<LayerName
name={name}
onChange={(v) => onNameChange(element, v)}
verifyLayerNameUniqueness={verifyLayerNameUniqueness ?? undefined}
/>
<div className={styles.textWrapper}>&nbsp; {getLayerInfo(element)}</div>
{!nodeData.children && (
<div className={styles.actionButtonsWrapper}>
<IconButton
name="copy"
title={'Duplicate'}
className={styles.actionIcon}
onClick={() => onDuplicate(element)}
/>
<IconButton
name="trash-alt"
title={'remove'}
className={styles.actionIcon}
onClick={() => onDelete(element)}
/>
</div>
)}
</>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
actionButtonsWrapper: css`
display: flex;
align-items: flex-end;
`,
actionIcon: css`
color: ${theme.colors.text.secondary};
cursor: pointer;
&:hover {
color: ${theme.colors.text.primary};
}
`,
textWrapper: css`
display: flex;
align-items: center;
flex-grow: 1;
overflow: hidden;
margin-right: ${theme.v1.spacing.sm};
`,
layerName: css`
font-weight: ${theme.v1.typography.weight.semibold};
color: ${theme.v1.colors.textBlue};
cursor: pointer;
overflow: hidden;
margin-left: ${theme.v1.spacing.xs};
`,
});

View File

@ -6,6 +6,8 @@ import { ElementState } from 'app/features/canvas/runtime/element';
import { Scene } from 'app/features/canvas/runtime/scene'; import { Scene } from 'app/features/canvas/runtime/scene';
import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils';
import { FrameState } from '../../../../features/canvas/runtime/frame';
import { PlacementEditor } from './PlacementEditor'; import { PlacementEditor } from './PlacementEditor';
import { optionBuilder } from './options'; import { optionBuilder } from './options';
@ -15,6 +17,12 @@ export interface CanvasEditorOptions {
category?: string[]; category?: string[];
} }
export interface TreeViewEditorProps {
scene: Scene;
layer: FrameState;
selected: ElementState[];
}
export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<CanvasElementOptions> { export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<CanvasElementOptions> {
return { return {
category: opts.category, category: opts.category,

View File

@ -8,8 +8,8 @@ import { setOptionImmutably } from 'app/features/dashboard/components/PanelEdito
import { InstanceState } from '../CanvasPanel'; import { InstanceState } from '../CanvasPanel';
import { LayerElementListEditor } from './LayerElementListEditor';
import { PlacementEditor } from './PlacementEditor'; import { PlacementEditor } from './PlacementEditor';
import { TreeNavigationEditor } from './TreeNavigationEditor';
import { optionBuilder } from './options'; import { optionBuilder } from './options';
export interface LayerEditorProps { export interface LayerEditorProps {
@ -72,7 +72,7 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions<LayerEdi
id: 'content', id: 'content',
path: 'root', path: 'root',
name: 'Elements', name: 'Elements',
editor: LayerElementListEditor, editor: TreeNavigationEditor,
settings: { scene, layer: scene.currentLayer, selected }, settings: { scene, layer: scene.currentLayer, selected },
}); });

View File

@ -1,47 +0,0 @@
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 },
});
},
};
}

View File

@ -10,19 +10,32 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
, ,
.rc-tree { .rc-tree {
margin: 0; margin: 0;
margin-bottom: 15px;
border: 1px solid transparent; border: 1px solid transparent;
&-focused:not(&-active-focused) { &-focused:not(&-active-focused) {
border-color: cyan; border-color: cyan;
} }
.rc-tree-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.rc-tree-treenode { .rc-tree-treenode {
margin: 0; margin: 0;
padding: 5px; padding: 1px;
line-height: 24px; line-height: 24px;
white-space: nowrap; white-space: nowrap;
list-style: none; list-style: none;
outline: 0; outline: 0;
display: flex;
margin-bottom: 3px;
cursor: pointer;
.draggable { .draggable {
color: #333; color: #333;
-moz-user-select: none; -moz-user-select: none;
@ -34,14 +47,6 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
// -webkit-user-drag: element; // -webkit-user-drag: element;
} }
&:hover {
background-color: ${theme.colors.background.secondary};
}
&.dragging {
background: rgba(100, 100, 255, 0.1);
}
&.drop-container { &.drop-container {
> .draggable::after { > .draggable::after {
position: absolute; position: absolute;
@ -49,15 +54,14 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
box-shadow: inset 0 0 0 2px red; box-shadow: inset 0 0 0 2px blue;
content: ''; content: '';
} }
& ~ .rc-tree-treenode { & ~ .rc-tree-treenode {
border-left: 2px solid chocolate; border-left: 2px solid ${theme.v1.colors.formInputBorder};
} }
} }
&.drop-target { &.drop-target {
border: 1px solid ${theme.colors.border.strong};
& ~ .rc-tree-treenode { & ~ .rc-tree-treenode {
border-left: none; border-left: none;
} }
@ -80,10 +84,26 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
padding: 0; padding: 0;
text-decoration: none; text-decoration: none;
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: grab;
flex-grow: 1;
display: flex;
border: 1px solid ${theme.v1.colors.formInputBorder};
border-radius: ${theme.v1.border.radius.sm};
background: ${theme.v1.colors.bg2};
min-height: ${theme.v1.spacing.formInputHeight}px;
&:hover {
border: 1px solid ${theme.v1.colors.formInputBorderHover};
}
&.rc-tree-node-selected {
border: 1px solid ${theme.v1.colors.formInputBorderActive};
opacity: 1;
}
} }
span { span {
&.rc-tree-switcher,
&.rc-tree-checkbox, &.rc-tree-checkbox,
&.rc-tree-iconEle { &.rc-tree-iconEle {
display: inline-block; display: inline-block;
@ -103,6 +123,21 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
background-image: none; background-image: none;
} }
} }
&.rc-tree-switcher {
display: flex;
align-items: center;
width: 16px;
background-color: transparent;
background-repeat: no-repeat;
background-attachment: scroll;
border: 0 none;
outline: none;
cursor: pointer;
&.rc-tree-icon__customize {
background-image: none;
}
}
&.rc-tree-icon_loading { &.rc-tree-icon_loading {
margin-right: 2px; margin-right: 2px;
vertical-align: top; vertical-align: top;

View File

@ -4,7 +4,6 @@ import { FrameState } from 'app/features/canvas/runtime/frame';
import { CanvasPanel, InstanceState } from './CanvasPanel'; import { CanvasPanel, InstanceState } from './CanvasPanel';
import { getElementEditor } from './editor/elementEditor'; import { getElementEditor } from './editor/elementEditor';
import { getLayerEditor } from './editor/layerEditor'; import { getLayerEditor } from './editor/layerEditor';
import { getTreeViewEditor } from './editor/treeViewEditor';
import { PanelOptions } from './models.gen'; import { PanelOptions } from './models.gen';
export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel) export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
@ -21,7 +20,6 @@ export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
}); });
if (state) { if (state) {
builder.addNestedOptions(getTreeViewEditor(state));
builder.addNestedOptions(getLayerEditor(state)); builder.addNestedOptions(getLayerEditor(state));
const selection = state.selected; const selection = state.selected;

View File

@ -27,18 +27,8 @@ export function getTreeData(root?: RootElement | FrameState, selection?: string[
dataRef: item, dataRef: item,
}; };
const isSelected = isItemSelected(item, selection);
if (isSelected) {
element.style = { backgroundColor: selectedColor };
}
if (item instanceof FrameState) { if (item instanceof FrameState) {
element.children = getTreeData(item, selection, selectedColor); element.children = getTreeData(item, selection, selectedColor);
if (isSelected) {
element.children.map((child) => {
child.style = { backgroundColor: selectedColor };
});
}
} }
elements.push(element); elements.push(element);
} }
@ -47,10 +37,6 @@ export function getTreeData(root?: RootElement | FrameState, selection?: string[
return elements; return elements;
} }
function isItemSelected(item: ElementState, selection: string[] | undefined) {
return Boolean(selection?.includes(item.getName()));
}
export function onNodeDrop( export function onNodeDrop(
info: { node: DropNode; dragNode: DragNode; dropPosition: number; dropToGap: boolean }, info: { node: DropNode; dragNode: DragNode; dropPosition: number; dropToGap: boolean },
treeData: TreeElement[] treeData: TreeElement[]