Canvas: use name as UID (#42696)

This commit is contained in:
Nathan Marrs 2021-12-06 21:04:58 -08:00 committed by GitHub
parent a345b06095
commit 2af5ad4764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 14 deletions

View File

@ -50,6 +50,7 @@ export const LayerDragDropList = <T extends LayerElement>({
// 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 = <T extends LayerElement>({
onClick={() => onDelete(element)}
surface="header"
/>
{layers.length > 2 && (
{layers.length > shouldRenderDragIconLengthThreshold && (
<Icon
title="Drag and drop to reorder"
name="draggabledots"

View File

@ -40,7 +40,7 @@ export const LayerName = ({ name, onChange, verifyLayerNameUniqueness }: LayerNa
return;
}
if (verifyLayerNameUniqueness && !verifyLayerNameUniqueness(newName)) {
if (verifyLayerNameUniqueness && !verifyLayerNameUniqueness(newName) && newName !== name) {
setValidationError('Layer name already exists');
return;
}

View File

@ -6,7 +6,7 @@ import { textBoxItem } from './elements/textBox';
export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
...iconItem.getNewOptions(),
type: iconItem.id,
name: `Group ${Date.now()}.${Math.floor(Math.random() * 100)}`,
name: `Element 1`,
};
export const canvasElementRegistry = new Registry<CanvasElementItem>(() => [

View File

@ -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() {

View File

@ -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;

View File

@ -36,6 +36,7 @@ export class Scene {
styles = getStyles(config.theme2);
readonly selection = new ReplaySubject<ElementState[]>(1);
readonly moved = new Subject<number>(); // called after resize/drag for editor updates
readonly byName = new Map<string, ElementState>();
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');

View File

@ -126,10 +126,10 @@ export class LayerElementListEditor extends PureComponent<Props> {
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<Props> {
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<Props> {
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<Props> {
onDuplicate={onDuplicate}
getLayerInfo={getLayerInfo}
onNameChange={onNameChange}
verifyLayerNameUniqueness={verifyLayerNameUniqueness}
isGroup={isGroup}
layers={layer.elements}
selection={selection}

View File

@ -28,7 +28,7 @@ export const plugin = new PanelPlugin<PanelOptions>(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,
})