mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: show layer elements above element details (#41447)
This commit is contained in:
parent
5f5298ad25
commit
fbd8dc59d9
@ -1,6 +1,7 @@
|
|||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ReplaySubject, Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
import Moveable from 'moveable';
|
import Moveable from 'moveable';
|
||||||
import Selecto from 'selecto';
|
import Selecto from 'selecto';
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
import { ElementState } from './element';
|
import { ElementState } from './element';
|
||||||
import { RootElement } from './root';
|
import { RootElement } from './root';
|
||||||
import { GroupState } from './group';
|
import { GroupState } from './group';
|
||||||
|
import { LayerActionID } from 'app/plugins/panel/canvas/types';
|
||||||
|
|
||||||
export interface SelectionParams {
|
export interface SelectionParams {
|
||||||
targets: Array<HTMLElement | SVGElement>;
|
targets: Array<HTMLElement | SVGElement>;
|
||||||
@ -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() {
|
clearCurrentSelection() {
|
||||||
let event: MouseEvent = new MouseEvent('click');
|
let event: MouseEvent = new MouseEvent('click');
|
||||||
this.selecto?.clickTarget(event, this.div);
|
this.selecto?.clickTarget(event, this.div);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { css, cx } from '@emotion/css';
|
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 { AppEvents, GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||||
@ -21,6 +21,14 @@ type Props = StandardEditorProps<any, LayerEditorProps, PanelOptions>;
|
|||||||
export class LayerElementListEditor extends PureComponent<Props> {
|
export class LayerElementListEditor extends PureComponent<Props> {
|
||||||
style = getLayerDragStyles(config.theme);
|
style = getLayerDragStyles(config.theme);
|
||||||
|
|
||||||
|
getScene = () => {
|
||||||
|
const { settings } = this.props.item;
|
||||||
|
if (!settings?.layer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return settings.layer.scene;
|
||||||
|
};
|
||||||
|
|
||||||
onAddItem = (sel: SelectableValue<string>) => {
|
onAddItem = (sel: SelectableValue<string>) => {
|
||||||
const { settings } = this.props.item;
|
const { settings } = this.props.item;
|
||||||
if (!settings?.layer) {
|
if (!settings?.layer) {
|
||||||
@ -158,6 +166,15 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
this.goUpLayer();
|
this.goUpLayer();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onGroupSelection = () => {
|
||||||
|
const scene = this.getScene();
|
||||||
|
if (scene) {
|
||||||
|
scene.groupSelection();
|
||||||
|
} else {
|
||||||
|
console.warn('no scene!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onDeleteGroup = () => {
|
private onDeleteGroup = () => {
|
||||||
appEvents.publish(
|
appEvents.publish(
|
||||||
new ShowConfirmModalEvent({
|
new ShowConfirmModalEvent({
|
||||||
@ -268,7 +285,7 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Container>
|
<HorizontalGroup>
|
||||||
<ValuePicker
|
<ValuePicker
|
||||||
icon="plus"
|
icon="plus"
|
||||||
label="Add item"
|
label="Add item"
|
||||||
@ -282,7 +299,12 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
Clear Selection
|
Clear Selection
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Container>
|
{selection.length > 1 && (
|
||||||
|
<Button size="sm" variant="secondary" onClick={this.onGroupSelection}>
|
||||||
|
Group items
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</HorizontalGroup>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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<StandardEditorProps<any, InstanceState, PanelOptions>> = ({ 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 (
|
|
||||||
<div>
|
|
||||||
<Button icon="plus" size="sm" variant="secondary" onClick={createNewLayer}>
|
|
||||||
Group items
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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<any> => {
|
|
||||||
return {
|
|
||||||
category: opts.category,
|
|
||||||
path: '--',
|
|
||||||
build: (builder, context) => {
|
|
||||||
builder.addCustomEditor({
|
|
||||||
id: 'content',
|
|
||||||
path: '__', // not used
|
|
||||||
name: 'Options',
|
|
||||||
editor: MultiSelectionEditor,
|
|
||||||
settings: opts,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -7,6 +7,7 @@ import { LayerElementListEditor } from './LayerElementListEditor';
|
|||||||
import { GroupState } from 'app/features/canvas/runtime/group';
|
import { GroupState } from 'app/features/canvas/runtime/group';
|
||||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||||
|
import { PlacementEditor } from './PlacementEditor';
|
||||||
|
|
||||||
export interface LayerEditorProps {
|
export interface LayerEditorProps {
|
||||||
scene: Scene;
|
scene: Scene;
|
||||||
@ -58,6 +59,11 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions<LayerEdi
|
|||||||
|
|
||||||
// Dynamically fill the selected element
|
// Dynamically fill the selected element
|
||||||
build: (builder, context) => {
|
build: (builder, context) => {
|
||||||
|
const currentLayer = scene.currentLayer;
|
||||||
|
if (currentLayer && !currentLayer.isRoot()) {
|
||||||
|
// TODO: the non-root nav option
|
||||||
|
}
|
||||||
|
|
||||||
builder.addCustomEditor({
|
builder.addCustomEditor({
|
||||||
id: 'content',
|
id: 'content',
|
||||||
path: 'root',
|
path: 'root',
|
||||||
@ -66,17 +72,23 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions<LayerEdi
|
|||||||
settings: { scene, layer: scene.currentLayer, selected },
|
settings: { scene, layer: scene.currentLayer, selected },
|
||||||
});
|
});
|
||||||
|
|
||||||
// // force clean layer configuration
|
|
||||||
// const layer = canvasElementRegistry.getIfExists(options?.type ?? DEFAULT_CANVAS_ELEMENT_CONFIG.type)!;
|
|
||||||
//const currentOptions = { ...options, type: layer.id, config: { ...layer.defaultConfig, ...options?.config } };
|
|
||||||
const ctx = { ...context, options };
|
const ctx = { ...context, options };
|
||||||
|
|
||||||
// if (layer.registerOptionsUI) {
|
|
||||||
// layer.registerOptionsUI(builder, ctx);
|
|
||||||
// }
|
|
||||||
|
|
||||||
optionBuilder.addBackground(builder as any, ctx);
|
optionBuilder.addBackground(builder as any, ctx);
|
||||||
optionBuilder.addBorder(builder as any, ctx);
|
optionBuilder.addBorder(builder as any, ctx);
|
||||||
|
|
||||||
|
if (currentLayer && !currentLayer.isRoot()) {
|
||||||
|
builder.addCustomEditor({
|
||||||
|
category: ['Layout'],
|
||||||
|
id: 'content',
|
||||||
|
path: '__', // not used
|
||||||
|
name: 'Anchor',
|
||||||
|
editor: PlacementEditor,
|
||||||
|
settings: {
|
||||||
|
scene: opts.scene,
|
||||||
|
element: currentLayer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { CanvasPanel, InstanceState } from './CanvasPanel';
|
|||||||
import { PanelOptions } from './models.gen';
|
import { PanelOptions } from './models.gen';
|
||||||
import { getElementEditor } from './editor/elementEditor';
|
import { getElementEditor } from './editor/elementEditor';
|
||||||
import { getLayerEditor } from './editor/layerEditor';
|
import { getLayerEditor } from './editor/layerEditor';
|
||||||
import { getElementsEditor } from './editor/elementsEditor';
|
import { GroupState } from 'app/features/canvas/runtime/group';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
|
export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
|
||||||
.setNoPadding() // extend to panel edges
|
.setNoPadding() // extend to panel edges
|
||||||
@ -20,24 +20,20 @@ export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
|
builder.addNestedOptions(getLayerEditor(state));
|
||||||
|
|
||||||
const selection = state.selected;
|
const selection = state.selected;
|
||||||
if (selection?.length === 1) {
|
if (selection?.length === 1) {
|
||||||
builder.addNestedOptions(
|
const element = selection[0];
|
||||||
getElementEditor({
|
if (!(element instanceof GroupState)) {
|
||||||
category: [`Selected element (id: ${selection[0].UID})`], // changing the ID forces are reload
|
builder.addNestedOptions(
|
||||||
element: selection[0],
|
getElementEditor({
|
||||||
scene: state.scene,
|
category: [`Selected element (id: ${element.UID})`], // changing the ID forces reload
|
||||||
})
|
element,
|
||||||
);
|
scene: state.scene,
|
||||||
} else if (selection?.length > 1) {
|
})
|
||||||
builder.addNestedOptions(
|
);
|
||||||
getElementsEditor({
|
}
|
||||||
category: [`Current selection`],
|
|
||||||
scene: state.scene,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.addNestedOptions(getLayerEditor(state));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user