From 2af5ad4764c8f2a0c8ab223c5528ba44efde01c3 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Mon, 6 Dec 2021 21:04:58 -0800 Subject: [PATCH] Canvas: use name as UID (#42696) --- .../components/Layers/LayerDragDropList.tsx | 3 +- .../app/core/components/Layers/LayerName.tsx | 2 +- public/app/features/canvas/registry.ts | 2 +- .../app/features/canvas/runtime/element.tsx | 36 ++++++++++++++++--- public/app/features/canvas/runtime/group.tsx | 4 ++- public/app/features/canvas/runtime/scene.tsx | 27 ++++++++++++-- .../canvas/editor/LayerElementListEditor.tsx | 13 ++++++- public/app/plugins/panel/canvas/module.tsx | 2 +- 8 files changed, 75 insertions(+), 14 deletions(-) diff --git a/public/app/core/components/Layers/LayerDragDropList.tsx b/public/app/core/components/Layers/LayerDragDropList.tsx index 06b36e57848..282ed0be587 100644 --- a/public/app/core/components/Layers/LayerDragDropList.tsx +++ b/public/app/core/components/Layers/LayerDragDropList.tsx @@ -50,6 +50,7 @@ export const LayerDragDropList = ({ // reverse order const rows: any = []; const lastLayerIndex = excludeBaseLayer ? 1 : 0; + const shouldRenderDragIconLengthThreshold = excludeBaseLayer ? 2 : 1; for (let i = layers.length - 1; i >= lastLayerIndex; i--) { const element = layers[i]; const uid = element.getName(); @@ -91,7 +92,7 @@ export const LayerDragDropList = ({ onClick={() => onDelete(element)} surface="header" /> - {layers.length > 2 && ( + {layers.length > shouldRenderDragIconLengthThreshold && ( (() => [ diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 49aad239ed8..51e06c08b0d 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -13,12 +13,13 @@ import { DimensionContext } from 'app/features/dimensions'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { GroupState } from './group'; import { LayerElement } from 'app/core/components/Layers/types'; +import { Scene } from './scene'; let counter = 0; export class ElementState implements LayerElement { + // UID necessary for moveable to work (for now) readonly UID = counter++; - revId = 0; sizeStyle: CSSProperties = {}; dataStyle: CSSProperties = {}; @@ -36,17 +37,34 @@ export class ElementState implements LayerElement { placement: Placement; constructor(public item: CanvasElementItem, public options: CanvasElementOptions, public parent?: GroupState) { + const fallbackName = `Element ${Date.now()}`; if (!options) { - this.options = { type: item.id, name: `Element ${this.UID}` }; + this.options = { type: item.id, name: fallbackName }; } this.anchor = options.anchor ?? {}; this.placement = options.placement ?? {}; options.anchor = this.anchor; options.placement = this.placement; + const scene = this.getScene(); if (!options.name) { - options.name = `Element ${this.UID}`; + const newName = scene?.getNextElementName(); + options.name = newName ?? fallbackName; } + scene?.byName.set(options.name, this); + } + + private getScene(): Scene | undefined { + let trav = this.parent; + while (trav) { + if (trav.isRoot()) { + return trav.scene; + break; + } + trav = trav.parent; + } + + return undefined; } getName() { @@ -106,8 +124,6 @@ export class ElementState implements LayerElement { this.options.anchor = this.anchor; this.options.placement = this.placement; - - // console.log('validate', this.UID, this.item.id, this.placement, this.anchor); } // The parent size, need to set our own size based on offsets @@ -191,6 +207,10 @@ export class ElementState implements LayerElement { this.item = canvasElementRegistry.getIfExists(options.type) ?? notFoundItem; } + // rename handling + const oldName = this.options.name; + const newName = options.name; + this.revId++; this.options = { ...options }; let trav = this.parent; @@ -202,6 +222,12 @@ export class ElementState implements LayerElement { trav.revId++; trav = trav.parent; } + + const scene = this.getScene(); + if (oldName !== newName && scene) { + scene.byName.delete(oldName); + scene.byName.set(newName, this); + } } getSaveModel() { diff --git a/public/app/features/canvas/runtime/group.tsx b/public/app/features/canvas/runtime/group.tsx index d8d1ccfd398..d331d745f9c 100644 --- a/public/app/features/canvas/runtime/group.tsx +++ b/public/app/features/canvas/runtime/group.tsx @@ -103,6 +103,7 @@ export class GroupState extends ElementState { switch (action) { case LayerActionID.Delete: this.elements = this.elements.filter((e) => e !== element); + this.scene.byName.delete(element.options.name); this.scene.save(); this.reinitializeMoveable(); break; @@ -129,9 +130,10 @@ export class GroupState extends ElementState { copy.updateSize(element.width, element.height); copy.updateData(this.scene.context); if (updateName) { - copy.options.name = `Element ${copy.UID} (duplicate)`; + copy.options.name = this.scene.getNextElementName(); } this.elements.push(copy); + this.scene.byName.set(copy.options.name, copy); this.scene.save(); this.reinitializeMoveable(); break; diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index ad1c9d3e0af..852e218bfc3 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -36,6 +36,7 @@ export class Scene { styles = getStyles(config.theme2); readonly selection = new ReplaySubject(1); readonly moved = new Subject(); // called after resize/drag for editor updates + readonly byName = new Map(); root: RootElement; revId = 0; @@ -53,6 +54,25 @@ export class Scene { this.root = this.load(cfg, enableEditing); } + getNextElementName = (isGroup = false) => { + const label = isGroup ? 'Group' : 'Element'; + let idx = this.byName.size + 1; + + const max = idx + 100; + while (true && idx < max) { + const name = `${label} ${idx++}`; + if (!this.byName.has(name)) { + return name; + } + } + + return `${label} ${Date.now()}`; + }; + + canRename = (v: string) => { + return !this.byName.has(v); + }; + load(cfg: CanvasGroupOptions, enableEditing: boolean) { this.root = new RootElement( cfg ?? { @@ -103,7 +123,7 @@ export class Scene { const newLayer = new GroupState( { type: 'group', - name: `Group ${Date.now()}.${Math.floor(Math.random() * 100)}`, + name: this.getNextElementName(true), elements: [], }, this, @@ -111,12 +131,14 @@ export class Scene { ); currentSelectedElements.forEach((element: ElementState) => { - newLayer.doAction(LayerActionID.Duplicate, element, false); currentLayer.doAction(LayerActionID.Delete, element); + newLayer.doAction(LayerActionID.Duplicate, element, false); }); currentLayer.elements.push(newLayer); + this.byName.set(newLayer.getName(), newLayer); + this.save(); }); } @@ -133,7 +155,6 @@ export class Scene { } toggleAnchor(element: ElementState, k: keyof Anchor) { - console.log('TODO, smarter toggle', element.UID, element.anchor, k); const { div } = element; if (!div) { console.log('Not ready'); diff --git a/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx b/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx index 927aad0e3b8..492504ddf17 100644 --- a/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx @@ -126,10 +126,10 @@ export class LayerElementListEditor extends PureComponent { const { layer } = settings; + this.deleteGroup(); layer.elements.forEach((element: ElementState) => { layer.parent?.doAction(LayerActionID.Duplicate, element, false); }); - this.deleteGroup(); }; private onDecoupleGroup = () => { @@ -156,7 +156,11 @@ export class LayerElementListEditor extends PureComponent { 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(); }; @@ -215,6 +219,12 @@ export class LayerElementListEditor extends PureComponent { return element instanceof GroupState; }; + 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 ( <> @@ -241,6 +251,7 @@ export class LayerElementListEditor extends PureComponent { onDuplicate={onDuplicate} getLayerInfo={getLayerInfo} onNameChange={onNameChange} + verifyLayerNameUniqueness={verifyLayerNameUniqueness} isGroup={isGroup} layers={layer.elements} selection={selection} diff --git a/public/app/plugins/panel/canvas/module.tsx b/public/app/plugins/panel/canvas/module.tsx index 3892f61df7f..9b00e40f7a0 100644 --- a/public/app/plugins/panel/canvas/module.tsx +++ b/public/app/plugins/panel/canvas/module.tsx @@ -28,7 +28,7 @@ export const plugin = new PanelPlugin(CanvasPanel) if (!(element instanceof GroupState)) { builder.addNestedOptions( getElementEditor({ - category: [`Selected element (id: ${element.UID})`], // changing the ID forces reload + category: [`Selected element (${element.options.name})`], element, scene: state.scene, })