Canvas: add layer elements editor functionality (#40968)
This commit is contained in:
@@ -45,13 +45,13 @@ export interface CanvasElementItem<TConfig = any, TData = any> extends RegistryI
|
|||||||
/** The default width/height to use when adding */
|
/** The default width/height to use when adding */
|
||||||
defaultSize?: Placement;
|
defaultSize?: Placement;
|
||||||
|
|
||||||
defaultConfig: TConfig;
|
|
||||||
|
|
||||||
prepareData?: (ctx: DimensionContext, cfg: TConfig) => TData;
|
prepareData?: (ctx: DimensionContext, cfg: TConfig) => TData;
|
||||||
|
|
||||||
/** Component used to draw */
|
/** Component used to draw */
|
||||||
display: ComponentType<CanvasElementProps<TConfig, TData>>;
|
display: ComponentType<CanvasElementProps<TConfig, TData>>;
|
||||||
|
|
||||||
|
getNewOptions: (options?: CanvasElementOptions) => Omit<CanvasElementOptions<TConfig>, 'type'>;
|
||||||
|
|
||||||
/** Build the configuraiton UI */
|
/** Build the configuraiton UI */
|
||||||
registerOptionsUI?: PanelOptionsSupplier<CanvasElementOptions<TConfig>>;
|
registerOptionsUI?: PanelOptionsSupplier<CanvasElementOptions<TConfig>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,18 +64,20 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
|
|||||||
|
|
||||||
display: IconDisplay,
|
display: IconDisplay,
|
||||||
|
|
||||||
defaultConfig: {
|
getNewOptions: (options) => ({
|
||||||
path: {
|
placement: {
|
||||||
mode: ResourceDimensionMode.Fixed,
|
width: 50,
|
||||||
fixed: 'img/icons/unicons/question-circle.svg',
|
height: 50,
|
||||||
},
|
},
|
||||||
fill: { fixed: '#FFF899' },
|
...options,
|
||||||
},
|
config: {
|
||||||
|
path: {
|
||||||
defaultSize: {
|
mode: ResourceDimensionMode.Fixed,
|
||||||
width: 50,
|
fixed: 'img/icons/unicons/question-circle.svg',
|
||||||
height: 50,
|
},
|
||||||
},
|
fill: { fixed: '#FFF899' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: IconConfig) => {
|
prepareData: (ctx: DimensionContext, cfg: IconConfig) => {
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ export const notFoundItem: CanvasElementItem<NotFoundConfig> = {
|
|||||||
name: 'Not found',
|
name: 'Not found',
|
||||||
description: 'Display when element type is not found in the registry',
|
description: 'Display when element type is not found in the registry',
|
||||||
|
|
||||||
defaultConfig: {},
|
|
||||||
|
|
||||||
display: NotFoundDisplay,
|
display: NotFoundDisplay,
|
||||||
|
|
||||||
defaultSize: {
|
defaultSize: {
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getNewOptions: () => ({
|
||||||
|
config: {},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,16 +71,24 @@ export const textBoxItem: CanvasElementItem<TextBoxConfig, TextBoxData> = {
|
|||||||
|
|
||||||
display: TextBoxDisplay,
|
display: TextBoxDisplay,
|
||||||
|
|
||||||
defaultConfig: {
|
|
||||||
align: Align.Left,
|
|
||||||
valign: VAlign.Middle,
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultSize: {
|
defaultSize: {
|
||||||
width: 240,
|
width: 240,
|
||||||
height: 160,
|
height: 160,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getNewOptions: (options) => ({
|
||||||
|
background: {
|
||||||
|
color: {
|
||||||
|
fixed: 'grey',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
align: Align.Left,
|
||||||
|
valign: VAlign.Middle,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
// Called when data changes
|
// Called when data changes
|
||||||
prepareData: (ctx: DimensionContext, cfg: TextBoxConfig) => {
|
prepareData: (ctx: DimensionContext, cfg: TextBoxConfig) => {
|
||||||
const data: TextBoxData = {
|
const data: TextBoxData = {
|
||||||
@@ -89,9 +97,11 @@ export const textBoxItem: CanvasElementItem<TextBoxConfig, TextBoxData> = {
|
|||||||
valign: cfg.valign ?? VAlign.Middle,
|
valign: cfg.valign ?? VAlign.Middle,
|
||||||
size: cfg.size,
|
size: cfg.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cfg.color) {
|
if (cfg.color) {
|
||||||
data.color = ctx.getColor(cfg.color).value();
|
data.color = ctx.getColor(cfg.color).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { textBoxItem } from './elements/textBox';
|
|||||||
|
|
||||||
export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
|
export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
|
||||||
type: iconItem.id,
|
type: iconItem.id,
|
||||||
config: { ...iconItem.defaultConfig },
|
...iconItem.getNewOptions(),
|
||||||
placement: { ...iconItem.defaultSize },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canvasElementRegistry = new Registry<CanvasElementItem>(() => [
|
export const canvasElementRegistry = new Registry<CanvasElementItem>(() => [
|
||||||
|
|||||||
@@ -186,6 +186,10 @@ export class ElementState {
|
|||||||
this.options = { ...options };
|
this.options = { ...options };
|
||||||
let trav = this.parent;
|
let trav = this.parent;
|
||||||
while (trav) {
|
while (trav) {
|
||||||
|
if (trav.isRoot()) {
|
||||||
|
trav.scene.save();
|
||||||
|
break;
|
||||||
|
}
|
||||||
trav.revId++;
|
trav.revId++;
|
||||||
trav = trav.parent;
|
trav = trav.parent;
|
||||||
}
|
}
|
||||||
@@ -291,8 +295,14 @@ export class ElementState {
|
|||||||
render() {
|
render() {
|
||||||
const { item } = this;
|
const { item } = this;
|
||||||
return (
|
return (
|
||||||
<div key={`${this.UID}/${this.revId}`} style={{ ...this.sizeStyle, ...this.dataStyle }} ref={this.initElement}>
|
<div key={`${this.UID}`} style={{ ...this.sizeStyle, ...this.dataStyle }} ref={this.initElement}>
|
||||||
<item.display config={this.options.config} width={this.width} height={this.height} data={this.data} />
|
<item.display
|
||||||
|
key={`${this.UID}/${this.revId}`}
|
||||||
|
config={this.options.config}
|
||||||
|
width={this.width}
|
||||||
|
height={this.height}
|
||||||
|
data={this.data}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,17 @@ import { ElementState } from './element';
|
|||||||
import { CanvasElementItem } from '../element';
|
import { CanvasElementItem } from '../element';
|
||||||
import { LayerActionID } from 'app/plugins/panel/canvas/types';
|
import { LayerActionID } from 'app/plugins/panel/canvas/types';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { Scene } from './scene';
|
||||||
|
import { RootElement } from './root';
|
||||||
|
|
||||||
export const groupItemDummy: CanvasElementItem = {
|
export const groupItemDummy: CanvasElementItem = {
|
||||||
id: 'group',
|
id: 'group',
|
||||||
name: 'Group',
|
name: 'Group',
|
||||||
description: 'Group',
|
description: 'Group',
|
||||||
|
|
||||||
defaultConfig: {},
|
getNewOptions: () => ({
|
||||||
|
config: {},
|
||||||
|
}),
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
display: () => {
|
display: () => {
|
||||||
@@ -22,10 +26,13 @@ export const groupItemDummy: CanvasElementItem = {
|
|||||||
|
|
||||||
export class GroupState extends ElementState {
|
export class GroupState extends ElementState {
|
||||||
elements: ElementState[] = [];
|
elements: ElementState[] = [];
|
||||||
|
scene: Scene;
|
||||||
|
|
||||||
constructor(public options: CanvasGroupOptions, public parent?: GroupState) {
|
constructor(public options: CanvasGroupOptions, scene: Scene, public parent?: GroupState) {
|
||||||
super(groupItemDummy, options, parent);
|
super(groupItemDummy, options, parent);
|
||||||
|
|
||||||
|
this.scene = scene;
|
||||||
|
|
||||||
// mutate options object
|
// mutate options object
|
||||||
let { elements } = this.options;
|
let { elements } = this.options;
|
||||||
if (!elements) {
|
if (!elements) {
|
||||||
@@ -34,7 +41,7 @@ export class GroupState extends ElementState {
|
|||||||
|
|
||||||
for (const c of elements) {
|
for (const c of elements) {
|
||||||
if (c.type === 'group') {
|
if (c.type === 'group') {
|
||||||
this.elements.push(new GroupState(c as CanvasGroupOptions, this));
|
this.elements.push(new GroupState(c as CanvasGroupOptions, scene, this));
|
||||||
} else {
|
} else {
|
||||||
const item = canvasElementRegistry.getIfExists(c.type) ?? notFoundItem;
|
const item = canvasElementRegistry.getIfExists(c.type) ?? notFoundItem;
|
||||||
this.elements.push(new ElementState(item, c, this));
|
this.elements.push(new ElementState(item, c, this));
|
||||||
@@ -42,7 +49,7 @@ export class GroupState extends ElementState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRoot() {
|
isRoot(): this is RootElement {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +87,14 @@ export class GroupState extends ElementState {
|
|||||||
const [removed] = result.splice(startIndex, 1);
|
const [removed] = result.splice(startIndex, 1);
|
||||||
result.splice(endIndex, 0, removed);
|
result.splice(endIndex, 0, removed);
|
||||||
this.elements = result;
|
this.elements = result;
|
||||||
this.onChange(this.getSaveModel());
|
|
||||||
|
this.reinitializeMoveable();
|
||||||
|
}
|
||||||
|
|
||||||
|
reinitializeMoveable() {
|
||||||
|
// Need to first clear current selection and then re-init moveable with slight delay
|
||||||
|
this.scene.clearCurrentSelection();
|
||||||
|
setTimeout(() => this.scene.initMoveable(true), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ??? or should this be on the element directly?
|
// ??? or should this be on the element directly?
|
||||||
@@ -89,6 +103,8 @@ export class GroupState extends ElementState {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case LayerActionID.Delete:
|
case LayerActionID.Delete:
|
||||||
this.elements = this.elements.filter((e) => e !== element);
|
this.elements = this.elements.filter((e) => e !== element);
|
||||||
|
this.scene.save();
|
||||||
|
this.reinitializeMoveable();
|
||||||
break;
|
break;
|
||||||
case LayerActionID.Duplicate:
|
case LayerActionID.Duplicate:
|
||||||
if (element.item.id === 'group') {
|
if (element.item.id === 'group') {
|
||||||
@@ -108,18 +124,18 @@ export class GroupState extends ElementState {
|
|||||||
if (element.anchor.right) {
|
if (element.anchor.right) {
|
||||||
opts.placement!.right! += 10;
|
opts.placement!.right! += 10;
|
||||||
}
|
}
|
||||||
console.log('DUPLICATE', opts);
|
|
||||||
const copy = new ElementState(element.item, opts, this);
|
const copy = new ElementState(element.item, opts, this);
|
||||||
copy.updateSize(element.width, element.height);
|
copy.updateSize(element.width, element.height);
|
||||||
copy.updateData(element.data); // :bomb: <-- need some way to tell the scene to re-init size and data
|
copy.updateData(this.scene.context);
|
||||||
this.elements.push(copy);
|
this.elements.push(copy);
|
||||||
|
this.scene.save();
|
||||||
|
this.reinitializeMoveable();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('DO action', action, element);
|
console.log('DO action', action, element);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onChange(this.getSaveModel());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { CanvasGroupOptions, CanvasElementOptions } from 'app/features/canvas';
|
import { CanvasGroupOptions, CanvasElementOptions } from 'app/features/canvas';
|
||||||
import { GroupState } from './group';
|
import { GroupState } from './group';
|
||||||
|
import { Scene } from './scene';
|
||||||
|
|
||||||
export class RootElement extends GroupState {
|
export class RootElement extends GroupState {
|
||||||
constructor(public options: CanvasGroupOptions, private changeCallback: () => void) {
|
constructor(public options: CanvasGroupOptions, public scene: Scene, private changeCallback: () => void) {
|
||||||
super(options);
|
super(options, scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
isRoot() {
|
isRoot(): this is RootElement {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import Selecto from 'selecto';
|
|||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
import { GrafanaTheme2, PanelData } from '@grafana/data';
|
import { GrafanaTheme2, PanelData } from '@grafana/data';
|
||||||
import { stylesFactory } from '@grafana/ui';
|
import { stylesFactory } from '@grafana/ui';
|
||||||
import {
|
import { Anchor, CanvasGroupOptions, DEFAULT_CANVAS_ELEMENT_CONFIG, Placement } from 'app/features/canvas';
|
||||||
Anchor,
|
|
||||||
CanvasElementOptions,
|
|
||||||
CanvasGroupOptions,
|
|
||||||
DEFAULT_CANVAS_ELEMENT_CONFIG,
|
|
||||||
Placement,
|
|
||||||
} from 'app/features/canvas';
|
|
||||||
import {
|
import {
|
||||||
ColorDimensionConfig,
|
ColorDimensionConfig,
|
||||||
ResourceDimensionConfig,
|
ResourceDimensionConfig,
|
||||||
@@ -31,7 +25,6 @@ import { ElementState } from './element';
|
|||||||
import { RootElement } from './root';
|
import { RootElement } from './root';
|
||||||
|
|
||||||
export class Scene {
|
export class Scene {
|
||||||
private lookup = new Map<number, ElementState>();
|
|
||||||
styles = getStyles(config.theme2);
|
styles = getStyles(config.theme2);
|
||||||
readonly selection = new ReplaySubject<ElementState[]>(1);
|
readonly selection = new ReplaySubject<ElementState[]>(1);
|
||||||
readonly moved = new Subject<number>(); // called after resize/drag for editor updates
|
readonly moved = new Subject<number>(); // called after resize/drag for editor updates
|
||||||
@@ -56,15 +49,10 @@ export class Scene {
|
|||||||
type: 'group',
|
type: 'group',
|
||||||
elements: [DEFAULT_CANVAS_ELEMENT_CONFIG],
|
elements: [DEFAULT_CANVAS_ELEMENT_CONFIG],
|
||||||
},
|
},
|
||||||
|
this,
|
||||||
this.save // callback when changes are made
|
this.save // callback when changes are made
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build the scene registry
|
|
||||||
this.lookup.clear();
|
|
||||||
this.root.visit((v) => {
|
|
||||||
this.lookup.set(v.UID, v);
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.div && enableEditing) {
|
if (this.div && enableEditing) {
|
||||||
this.initMoveable();
|
this.initMoveable();
|
||||||
@@ -92,20 +80,13 @@ export class Scene {
|
|||||||
this.root.updateSize(width, height);
|
this.root.updateSize(width, height);
|
||||||
|
|
||||||
if (this.selecto?.getSelectedTargets().length) {
|
if (this.selecto?.getSelectedTargets().length) {
|
||||||
let event: MouseEvent = new MouseEvent('click');
|
this.clearCurrentSelection();
|
||||||
this.selecto.clickTarget(event, this.div);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(uid: number, cfg: CanvasElementOptions) {
|
clearCurrentSelection() {
|
||||||
const elem = this.lookup.get(uid);
|
let event: MouseEvent = new MouseEvent('click');
|
||||||
if (!elem) {
|
this.selecto?.clickTarget(event, this.div);
|
||||||
throw new Error('element not found: ' + uid + ' // ' + [...this.lookup.keys()]);
|
|
||||||
}
|
|
||||||
this.revId++;
|
|
||||||
elem.onChange(cfg);
|
|
||||||
elem.updateData(this.context); // Refresh any data that may have changed
|
|
||||||
this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAnchor(element: ElementState, k: keyof Anchor) {
|
toggleAnchor(element: ElementState, k: keyof Anchor) {
|
||||||
@@ -157,12 +138,16 @@ export class Scene {
|
|||||||
this.div = sceneContainer;
|
this.div = sceneContainer;
|
||||||
};
|
};
|
||||||
|
|
||||||
initMoveable = () => {
|
initMoveable = (destroySelecto = false) => {
|
||||||
const targetElements: HTMLDivElement[] = [];
|
const targetElements: HTMLDivElement[] = [];
|
||||||
this.root.elements.forEach((element: ElementState) => {
|
this.root.elements.forEach((element: ElementState) => {
|
||||||
targetElements.push(element.div!);
|
targetElements.push(element.div!);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (destroySelecto) {
|
||||||
|
this.selecto?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
this.selecto = new Selecto({
|
this.selecto = new Selecto({
|
||||||
container: this.div,
|
container: this.div,
|
||||||
selectableTargets: targetElements,
|
selectableTargets: targetElements,
|
||||||
@@ -188,6 +173,14 @@ export class Scene {
|
|||||||
});
|
});
|
||||||
this.moved.next(Date.now()); // TODO only on end
|
this.moved.next(Date.now()); // TODO only on end
|
||||||
})
|
})
|
||||||
|
.on('dragEnd', (event) => {
|
||||||
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
|
|
||||||
|
if (targetedElement && targetedElement.parent) {
|
||||||
|
const parent = targetedElement.parent;
|
||||||
|
targetedElement.updateSize(parent.width, parent.height);
|
||||||
|
}
|
||||||
|
})
|
||||||
.on('resize', (event) => {
|
.on('resize', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
const targetedElement = this.findElementByTarget(event.target);
|
||||||
targetedElement!.applyResize(event);
|
targetedElement!.applyResize(event);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export class CanvasPanel extends Component<Props, State> {
|
|||||||
// console.log('send changes', root);
|
// console.log('send changes', root);
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props) {
|
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||||
const { width, height, data } = this.props;
|
const { width, height, data } = this.props;
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
|
||||||
@@ -109,6 +109,10 @@ export class CanvasPanel extends Component<Props, State> {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.refresh !== nextState.refresh) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
// After editing, the options are valid, but the scene was in a different panel or inline editing mode has changed
|
// After editing, the options are valid, but the scene was in a different panel or inline editing mode has changed
|
||||||
const shouldUpdateSceneAndPanel =
|
const shouldUpdateSceneAndPanel =
|
||||||
(this.needsReload && this.props.options !== nextProps.options) ||
|
(this.needsReload && this.props.options !== nextProps.options) ||
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautif
|
|||||||
import { PanelOptions } from '../models.gen';
|
import { PanelOptions } from '../models.gen';
|
||||||
import { InstanceState } from '../CanvasPanel';
|
import { InstanceState } from '../CanvasPanel';
|
||||||
import { LayerActionID } from '../types';
|
import { LayerActionID } from '../types';
|
||||||
import { canvasElementRegistry } from 'app/features/canvas';
|
import { CanvasElementOptions, canvasElementRegistry } from 'app/features/canvas';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
|
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||||
|
import { notFoundItem } from 'app/features/canvas/elements/notFound';
|
||||||
|
|
||||||
type Props = StandardEditorProps<any, InstanceState, PanelOptions>;
|
type Props = StandardEditorProps<any, InstanceState, PanelOptions>;
|
||||||
|
|
||||||
@@ -17,20 +19,22 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
style = getLayerDragStyles(config.theme);
|
style = getLayerDragStyles(config.theme);
|
||||||
|
|
||||||
onAddItem = (sel: SelectableValue<string>) => {
|
onAddItem = (sel: SelectableValue<string>) => {
|
||||||
// const reg = drawItemsRegistry.getIfExists(sel.value);
|
const { settings } = this.props.item;
|
||||||
// if (!reg) {
|
if (!settings?.layer) {
|
||||||
// console.error('NOT FOUND', sel);
|
return;
|
||||||
// return;
|
}
|
||||||
// }
|
const { layer } = settings;
|
||||||
// const layer = this.props.value;
|
|
||||||
// const item = newItem(reg, layer.items.length);
|
const item = canvasElementRegistry.getIfExists(sel.value) ?? notFoundItem;
|
||||||
// const isList = this.props.context.options?.mode === LayoutMode.List;
|
const newElementOptions = item.getNewOptions() as CanvasElementOptions;
|
||||||
// const items = isList ? [item, ...layer.items] : [...layer.items, item];
|
newElementOptions.type = item.id;
|
||||||
// this.props.onChange({
|
const newElement = new ElementState(item, newElementOptions, layer);
|
||||||
// ...layer,
|
newElement.updateSize(newElement.width, newElement.height);
|
||||||
// items,
|
newElement.updateData(layer.scene.context);
|
||||||
// });
|
layer.elements.push(newElement);
|
||||||
// this.onSelect(item);
|
layer.scene.save();
|
||||||
|
|
||||||
|
layer.reinitializeMoveable();
|
||||||
};
|
};
|
||||||
|
|
||||||
onSelect = (item: any) => {
|
onSelect = (item: any) => {
|
||||||
@@ -45,6 +49,18 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onClearSelection = () => {
|
||||||
|
const { settings } = this.props.item;
|
||||||
|
|
||||||
|
if (!settings?.layer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { layer } = settings;
|
||||||
|
|
||||||
|
layer.scene.clearCurrentSelection();
|
||||||
|
};
|
||||||
|
|
||||||
getRowStyle = (sel: boolean) => {
|
getRowStyle = (sel: boolean) => {
|
||||||
return sel ? `${this.style.row} ${this.style.sel}` : this.style.row;
|
return sel ? `${this.style.row} ${this.style.sel}` : this.style.row;
|
||||||
};
|
};
|
||||||
@@ -108,7 +124,7 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
name="copy"
|
name="copy"
|
||||||
title={'duplicate'}
|
title={'Duplicate'}
|
||||||
className={styles.actionIcon}
|
className={styles.actionIcon}
|
||||||
onClick={() => layer.doAction(LayerActionID.Duplicate, element)}
|
onClick={() => layer.doAction(LayerActionID.Duplicate, element)}
|
||||||
surface="header"
|
surface="header"
|
||||||
@@ -116,7 +132,7 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
name="trash-alt"
|
name="trash-alt"
|
||||||
title={'remove'}
|
title={'Remove'}
|
||||||
className={cx(styles.actionIcon, styles.dragIcon)}
|
className={cx(styles.actionIcon, styles.dragIcon)}
|
||||||
onClick={() => layer.doAction(LayerActionID.Delete, element)}
|
onClick={() => layer.doAction(LayerActionID.Delete, element)}
|
||||||
surface="header"
|
surface="header"
|
||||||
@@ -152,7 +168,7 @@ export class LayerElementListEditor extends PureComponent<Props> {
|
|||||||
isFullWidth={false}
|
isFullWidth={false}
|
||||||
/>
|
/>
|
||||||
{selection.length > 0 && (
|
{selection.length > 0 && (
|
||||||
<Button size="sm" variant="secondary" onClick={() => console.log('TODO!')}>
|
<Button size="sm" variant="secondary" onClick={this.onClearSelection}>
|
||||||
Clear Selection
|
Clear Selection
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cloneDeep, get as lodashGet } from 'lodash';
|
import { get as lodashGet } from 'lodash';
|
||||||
import { optionBuilder } from './options';
|
import { optionBuilder } from './options';
|
||||||
import { CanvasElementOptions, canvasElementRegistry, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas';
|
import { CanvasElementOptions, canvasElementRegistry, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas';
|
||||||
import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders';
|
import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders';
|
||||||
@@ -32,14 +32,15 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
options = {
|
options = {
|
||||||
...options, // keep current options
|
...options,
|
||||||
|
...layer.getNewOptions(options),
|
||||||
type: layer.id,
|
type: layer.id,
|
||||||
config: cloneDeep(layer.defaultConfig ?? {}),
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
options = setOptionImmutably(options, path, value);
|
options = setOptionImmutably(options, path, value);
|
||||||
}
|
}
|
||||||
opts.scene.onChange(opts.element.UID, options);
|
opts.element.onChange(options);
|
||||||
|
opts.element.updateData(opts.scene.context);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -64,7 +65,13 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
|
|||||||
|
|
||||||
// force clean layer configuration
|
// force clean layer configuration
|
||||||
const layer = canvasElementRegistry.getIfExists(options?.type ?? DEFAULT_CANVAS_ELEMENT_CONFIG.type)!;
|
const layer = canvasElementRegistry.getIfExists(options?.type ?? DEFAULT_CANVAS_ELEMENT_CONFIG.type)!;
|
||||||
const currentOptions = { ...options, type: layer.id, config: { ...layer.defaultConfig, ...options?.config } };
|
let currentOptions = options;
|
||||||
|
if (!currentOptions) {
|
||||||
|
currentOptions = {
|
||||||
|
...layer.getNewOptions(options),
|
||||||
|
type: layer.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
const ctx = { ...context, options: currentOptions };
|
const ctx = { ...context, options: currentOptions };
|
||||||
|
|
||||||
if (layer.registerOptionsUI) {
|
if (layer.registerOptionsUI) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { InstanceState } from '../CanvasPanel';
|
|||||||
import { LayerElementListEditor } from './LayerElementListEditor';
|
import { LayerElementListEditor } from './LayerElementListEditor';
|
||||||
|
|
||||||
export function getLayerEditor(opts: InstanceState): NestedPanelOptions<InstanceState> {
|
export function getLayerEditor(opts: InstanceState): NestedPanelOptions<InstanceState> {
|
||||||
const { layer, scene } = opts;
|
const { layer } = opts;
|
||||||
const options = layer.options || { elements: [] };
|
const options = layer.options || { elements: [] };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -24,7 +24,7 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions<Instance
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const c = setOptionImmutably(options, path, value);
|
const c = setOptionImmutably(options, path, value);
|
||||||
scene.onChange(layer.UID, c);
|
layer.onChange(c);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user