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 // reverse order
const rows: any = []; const rows: any = [];
const lastLayerIndex = excludeBaseLayer ? 1 : 0; const lastLayerIndex = excludeBaseLayer ? 1 : 0;
const shouldRenderDragIconLengthThreshold = excludeBaseLayer ? 2 : 1;
for (let i = layers.length - 1; i >= lastLayerIndex; i--) { for (let i = layers.length - 1; i >= lastLayerIndex; i--) {
const element = layers[i]; const element = layers[i];
const uid = element.getName(); const uid = element.getName();
@ -91,7 +92,7 @@ export const LayerDragDropList = <T extends LayerElement>({
onClick={() => onDelete(element)} onClick={() => onDelete(element)}
surface="header" surface="header"
/> />
{layers.length > 2 && ( {layers.length > shouldRenderDragIconLengthThreshold && (
<Icon <Icon
title="Drag and drop to reorder" title="Drag and drop to reorder"
name="draggabledots" name="draggabledots"

View File

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

View File

@ -6,7 +6,7 @@ import { textBoxItem } from './elements/textBox';
export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = { export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = {
...iconItem.getNewOptions(), ...iconItem.getNewOptions(),
type: iconItem.id, type: iconItem.id,
name: `Group ${Date.now()}.${Math.floor(Math.random() * 100)}`, name: `Element 1`,
}; };
export const canvasElementRegistry = new Registry<CanvasElementItem>(() => [ 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 { notFoundItem } from 'app/features/canvas/elements/notFound';
import { GroupState } from './group'; import { GroupState } from './group';
import { LayerElement } from 'app/core/components/Layers/types'; import { LayerElement } from 'app/core/components/Layers/types';
import { Scene } from './scene';
let counter = 0; let counter = 0;
export class ElementState implements LayerElement { export class ElementState implements LayerElement {
// UID necessary for moveable to work (for now)
readonly UID = counter++; readonly UID = counter++;
revId = 0; revId = 0;
sizeStyle: CSSProperties = {}; sizeStyle: CSSProperties = {};
dataStyle: CSSProperties = {}; dataStyle: CSSProperties = {};
@ -36,17 +37,34 @@ export class ElementState implements LayerElement {
placement: Placement; placement: Placement;
constructor(public item: CanvasElementItem, public options: CanvasElementOptions, public parent?: GroupState) { constructor(public item: CanvasElementItem, public options: CanvasElementOptions, public parent?: GroupState) {
const fallbackName = `Element ${Date.now()}`;
if (!options) { if (!options) {
this.options = { type: item.id, name: `Element ${this.UID}` }; this.options = { type: item.id, name: fallbackName };
} }
this.anchor = options.anchor ?? {}; this.anchor = options.anchor ?? {};
this.placement = options.placement ?? {}; this.placement = options.placement ?? {};
options.anchor = this.anchor; options.anchor = this.anchor;
options.placement = this.placement; options.placement = this.placement;
const scene = this.getScene();
if (!options.name) { 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() { getName() {
@ -106,8 +124,6 @@ export class ElementState implements LayerElement {
this.options.anchor = this.anchor; this.options.anchor = this.anchor;
this.options.placement = this.placement; 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 // 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; this.item = canvasElementRegistry.getIfExists(options.type) ?? notFoundItem;
} }
// rename handling
const oldName = this.options.name;
const newName = options.name;
this.revId++; this.revId++;
this.options = { ...options }; this.options = { ...options };
let trav = this.parent; let trav = this.parent;
@ -202,6 +222,12 @@ export class ElementState implements LayerElement {
trav.revId++; trav.revId++;
trav = trav.parent; trav = trav.parent;
} }
const scene = this.getScene();
if (oldName !== newName && scene) {
scene.byName.delete(oldName);
scene.byName.set(newName, this);
}
} }
getSaveModel() { getSaveModel() {

View File

@ -103,6 +103,7 @@ 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.byName.delete(element.options.name);
this.scene.save(); this.scene.save();
this.reinitializeMoveable(); this.reinitializeMoveable();
break; break;
@ -129,9 +130,10 @@ export class GroupState extends ElementState {
copy.updateSize(element.width, element.height); copy.updateSize(element.width, element.height);
copy.updateData(this.scene.context); copy.updateData(this.scene.context);
if (updateName) { if (updateName) {
copy.options.name = `Element ${copy.UID} (duplicate)`; copy.options.name = this.scene.getNextElementName();
} }
this.elements.push(copy); this.elements.push(copy);
this.scene.byName.set(copy.options.name, copy);
this.scene.save(); this.scene.save();
this.reinitializeMoveable(); this.reinitializeMoveable();
break; break;

View File

@ -36,6 +36,7 @@ export class Scene {
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
readonly byName = new Map<string, ElementState>();
root: RootElement; root: RootElement;
revId = 0; revId = 0;
@ -53,6 +54,25 @@ export class Scene {
this.root = this.load(cfg, enableEditing); 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) { load(cfg: CanvasGroupOptions, enableEditing: boolean) {
this.root = new RootElement( this.root = new RootElement(
cfg ?? { cfg ?? {
@ -103,7 +123,7 @@ export class Scene {
const newLayer = new GroupState( const newLayer = new GroupState(
{ {
type: 'group', type: 'group',
name: `Group ${Date.now()}.${Math.floor(Math.random() * 100)}`, name: this.getNextElementName(true),
elements: [], elements: [],
}, },
this, this,
@ -111,12 +131,14 @@ export class Scene {
); );
currentSelectedElements.forEach((element: ElementState) => { currentSelectedElements.forEach((element: ElementState) => {
newLayer.doAction(LayerActionID.Duplicate, element, false);
currentLayer.doAction(LayerActionID.Delete, element); currentLayer.doAction(LayerActionID.Delete, element);
newLayer.doAction(LayerActionID.Duplicate, element, false);
}); });
currentLayer.elements.push(newLayer); currentLayer.elements.push(newLayer);
this.byName.set(newLayer.getName(), newLayer);
this.save(); this.save();
}); });
} }
@ -133,7 +155,6 @@ export class Scene {
} }
toggleAnchor(element: ElementState, k: keyof Anchor) { toggleAnchor(element: ElementState, k: keyof Anchor) {
console.log('TODO, smarter toggle', element.UID, element.anchor, k);
const { div } = element; const { div } = element;
if (!div) { if (!div) {
console.log('Not ready'); console.log('Not ready');

View File

@ -126,10 +126,10 @@ export class LayerElementListEditor extends PureComponent<Props> {
const { layer } = settings; const { layer } = settings;
this.deleteGroup();
layer.elements.forEach((element: ElementState) => { layer.elements.forEach((element: ElementState) => {
layer.parent?.doAction(LayerActionID.Duplicate, element, false); layer.parent?.doAction(LayerActionID.Duplicate, element, false);
}); });
this.deleteGroup();
}; };
private onDecoupleGroup = () => { private onDecoupleGroup = () => {
@ -156,7 +156,11 @@ export class LayerElementListEditor extends PureComponent<Props> {
const { layer } = settings; 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); layer.parent?.doAction(LayerActionID.Delete, layer);
this.goUpLayer(); this.goUpLayer();
}; };
@ -215,6 +219,12 @@ export class LayerElementListEditor extends PureComponent<Props> {
return element instanceof GroupState; 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()) : []; const selection: string[] = settings.selected ? settings.selected.map((v) => v.getName()) : [];
return ( return (
<> <>
@ -241,6 +251,7 @@ export class LayerElementListEditor extends PureComponent<Props> {
onDuplicate={onDuplicate} onDuplicate={onDuplicate}
getLayerInfo={getLayerInfo} getLayerInfo={getLayerInfo}
onNameChange={onNameChange} onNameChange={onNameChange}
verifyLayerNameUniqueness={verifyLayerNameUniqueness}
isGroup={isGroup} isGroup={isGroup}
layers={layer.elements} layers={layer.elements}
selection={selection} selection={selection}

View File

@ -28,7 +28,7 @@ export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
if (!(element instanceof GroupState)) { if (!(element instanceof GroupState)) {
builder.addNestedOptions( builder.addNestedOptions(
getElementEditor({ getElementEditor({
category: [`Selected element (id: ${element.UID})`], // changing the ID forces reload category: [`Selected element (${element.options.name})`],
element, element,
scene: state.scene, scene: state.scene,
}) })