diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index 4d22c3cc730..354f5c99481 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -1,6 +1,7 @@ import React, { CSSProperties } from 'react'; import { css } from '@emotion/css'; import { ReplaySubject, Subject } from 'rxjs'; +import { first } from 'rxjs/operators'; import Moveable from 'moveable'; import Selecto from 'selecto'; @@ -24,6 +25,7 @@ import { import { ElementState } from './element'; import { RootElement } from './root'; import { GroupState } from './group'; +import { LayerActionID } from 'app/plugins/panel/canvas/types'; export interface SelectionParams { targets: Array; @@ -94,6 +96,30 @@ export class Scene { } } + groupSelection() { + this.selection.pipe(first()).subscribe((currentSelectedElements) => { + const currentLayer = currentSelectedElements[0].parent!; + + const newLayer = new GroupState( + { + type: 'group', + elements: [], + }, + this, + currentSelectedElements[0].parent + ); + + currentSelectedElements.forEach((element: ElementState) => { + newLayer.doAction(LayerActionID.Duplicate, element); + currentLayer.doAction(LayerActionID.Delete, element); + }); + + currentLayer.elements.push(newLayer); + + this.save(); + }); + } + clearCurrentSelection() { let event: MouseEvent = new MouseEvent('click'); this.selecto?.clickTarget(event, this.div); diff --git a/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx b/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx index 0538bc10505..7ee01464394 100644 --- a/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { css, cx } from '@emotion/css'; -import { Button, Container, Icon, IconButton, stylesFactory, ValuePicker } from '@grafana/ui'; +import { Button, HorizontalGroup, Icon, IconButton, stylesFactory, ValuePicker } from '@grafana/ui'; import { AppEvents, GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data'; import { config } from '@grafana/runtime'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; @@ -21,6 +21,14 @@ type Props = StandardEditorProps; export class LayerElementListEditor extends PureComponent { style = getLayerDragStyles(config.theme); + getScene = () => { + const { settings } = this.props.item; + if (!settings?.layer) { + return; + } + return settings.layer.scene; + }; + onAddItem = (sel: SelectableValue) => { const { settings } = this.props.item; if (!settings?.layer) { @@ -158,6 +166,15 @@ export class LayerElementListEditor extends PureComponent { this.goUpLayer(); }; + private onGroupSelection = () => { + const scene = this.getScene(); + if (scene) { + scene.groupSelection(); + } else { + console.warn('no scene!'); + } + }; + private onDeleteGroup = () => { appEvents.publish( new ShowConfirmModalEvent({ @@ -268,7 +285,7 @@ export class LayerElementListEditor extends PureComponent {
- + { Clear Selection )} - + {selection.length > 1 && ( + + )} + ); } diff --git a/public/app/plugins/panel/canvas/editor/MultiSelectionEditor.tsx b/public/app/plugins/panel/canvas/editor/MultiSelectionEditor.tsx deleted file mode 100644 index e105f592dce..00000000000 --- a/public/app/plugins/panel/canvas/editor/MultiSelectionEditor.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { FC } from 'react'; -import { Button } from '@grafana/ui'; -import { StandardEditorProps } from '@grafana/data'; - -import { InstanceState } from '../CanvasPanel'; -import { PanelOptions } from '../models.gen'; -import { GroupState } from 'app/features/canvas/runtime/group'; -import { ElementState } from 'app/features/canvas/runtime/element'; -import { LayerActionID } from '../types'; - -export const MultiSelectionEditor: FC> = ({ context }) => { - const createNewLayer = () => { - const currentSelectedElements = context?.instanceState.selected; - const currentLayer = currentSelectedElements[0].parent; - - const newLayer = new GroupState( - { - type: 'group', - elements: [], - }, - context.instanceState.scene, - currentSelectedElements[0].parent - ); - - currentSelectedElements.forEach((element: ElementState) => { - newLayer.doAction(LayerActionID.Duplicate, element); - currentLayer.doAction(LayerActionID.Delete, element); - }); - - currentLayer.elements.push(newLayer); - - context.instanceState.scene.save(); - }; - - return ( -
- -
- ); -}; diff --git a/public/app/plugins/panel/canvas/editor/elementsEditor.tsx b/public/app/plugins/panel/canvas/editor/elementsEditor.tsx deleted file mode 100644 index f1fc2d87782..00000000000 --- a/public/app/plugins/panel/canvas/editor/elementsEditor.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { NestedPanelOptions } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { Scene } from 'app/features/canvas/runtime/scene'; -import { MultiSelectionEditor } from './MultiSelectionEditor'; - -export interface CanvasEditorGroupOptions { - scene: Scene; - category?: string[]; -} - -export const getElementsEditor = (opts: CanvasEditorGroupOptions): NestedPanelOptions => { - return { - category: opts.category, - path: '--', - build: (builder, context) => { - builder.addCustomEditor({ - id: 'content', - path: '__', // not used - name: 'Options', - editor: MultiSelectionEditor, - settings: opts, - }); - }, - }; -}; diff --git a/public/app/plugins/panel/canvas/editor/layerEditor.tsx b/public/app/plugins/panel/canvas/editor/layerEditor.tsx index ccc3bb9ce5a..ded0763413d 100644 --- a/public/app/plugins/panel/canvas/editor/layerEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/layerEditor.tsx @@ -7,6 +7,7 @@ import { LayerElementListEditor } from './LayerElementListEditor'; import { GroupState } from 'app/features/canvas/runtime/group'; import { Scene } from 'app/features/canvas/runtime/scene'; import { ElementState } from 'app/features/canvas/runtime/element'; +import { PlacementEditor } from './PlacementEditor'; export interface LayerEditorProps { scene: Scene; @@ -58,6 +59,11 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions { + const currentLayer = scene.currentLayer; + if (currentLayer && !currentLayer.isRoot()) { + // TODO: the non-root nav option + } + builder.addCustomEditor({ id: 'content', path: 'root', @@ -66,17 +72,23 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions(CanvasPanel) .setNoPadding() // extend to panel edges @@ -20,24 +20,20 @@ export const plugin = new PanelPlugin(CanvasPanel) }); if (state) { + builder.addNestedOptions(getLayerEditor(state)); + const selection = state.selected; if (selection?.length === 1) { - builder.addNestedOptions( - getElementEditor({ - category: [`Selected element (id: ${selection[0].UID})`], // changing the ID forces are reload - element: selection[0], - scene: state.scene, - }) - ); - } else if (selection?.length > 1) { - builder.addNestedOptions( - getElementsEditor({ - category: [`Current selection`], - scene: state.scene, - }) - ); + const element = selection[0]; + if (!(element instanceof GroupState)) { + builder.addNestedOptions( + getElementEditor({ + category: [`Selected element (id: ${element.UID})`], // changing the ID forces reload + element, + scene: state.scene, + }) + ); + } } - - builder.addNestedOptions(getLayerEditor(state)); } });