import { cloneDeep } from 'lodash'; import React from 'react'; import { canvasElementRegistry, CanvasFrameOptions } from 'app/features/canvas'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { DimensionContext } from 'app/features/dimensions'; import { LayerActionID } from 'app/plugins/panel/canvas/types'; import { CanvasElementItem } from '../element'; import { HorizontalConstraint, Placement, VerticalConstraint } from '../types'; import { ElementState } from './element'; import { RootElement } from './root'; import { Scene } from './scene'; export const frameItemDummy: CanvasElementItem = { id: 'frame', name: 'Frame', description: 'Frame', getNewOptions: () => ({ config: {}, }), // eslint-disable-next-line react/display-name display: () => { return
FRAME!
; }, }; export class FrameState extends ElementState { elements: ElementState[] = []; scene: Scene; constructor(public options: CanvasFrameOptions, scene: Scene, public parent?: FrameState) { super(frameItemDummy, options, parent); this.scene = scene; // mutate options object let { elements } = this.options; if (!elements) { this.options.elements = elements = []; } for (const c of elements) { if (c.type === 'frame') { this.elements.push(new FrameState(c as CanvasFrameOptions, scene, this)); } else { const item = canvasElementRegistry.getIfExists(c.type) ?? notFoundItem; this.elements.push(new ElementState(item, c, this)); } } } isRoot(): this is RootElement { return false; } updateData(ctx: DimensionContext) { super.updateData(ctx); for (const elem of this.elements) { elem.updateData(ctx); } } // used in the layer editor reorder(startIndex: number, endIndex: number) { const result = Array.from(this.elements); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); this.elements = result; this.reinitializeMoveable(); } doMove(child: ElementState, action: LayerActionID) { const vals = this.elements.filter((v) => v !== child); if (action === LayerActionID.MoveBottom) { vals.unshift(child); } else { vals.push(child); } this.elements = vals; this.scene.save(); 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, this.scene.isEditingEnabled)); } // ??? or should this be on the element directly? // are actions scoped to layers? doAction = (action: LayerActionID, element: ElementState, updateName = true, shiftItemsOnDuplicate = true) => { 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; case LayerActionID.Duplicate: if (element.item.id === 'frame') { console.log('Can not duplicate frames (yet)', action, element); return; } const opts = cloneDeep(element.options); if (shiftItemsOnDuplicate) { const { constraint, placement: oldPlacement } = element.options; const { vertical, horizontal } = constraint ?? {}; const placement = oldPlacement ?? ({} as Placement); switch (vertical) { case VerticalConstraint.Top: case VerticalConstraint.TopBottom: if (placement.top == null) { placement.top = 25; } else { placement.top += 10; } break; case VerticalConstraint.Bottom: if (placement.bottom == null) { placement.bottom = 100; } else { placement.bottom -= 10; } break; } switch (horizontal) { case HorizontalConstraint.Left: case HorizontalConstraint.LeftRight: if (placement.left == null) { placement.left = 50; } else { placement.left += 10; } break; case HorizontalConstraint.Right: if (placement.right == null) { placement.right = 50; } else { placement.right -= 10; } break; } opts.placement = placement; } const copy = new ElementState(element.item, opts, this); copy.updateData(this.scene.context); if (updateName) { copy.options.name = this.scene.getNextElementName(); } this.elements.push(copy); this.scene.byName.set(copy.options.name, copy); this.scene.save(); this.reinitializeMoveable(); break; case LayerActionID.MoveTop: case LayerActionID.MoveBottom: element.parent?.doMove(element, action); break; default: console.log('DO action', action, element); return; } }; render() { return (
{this.elements.map((v) => v.render())}
); } /** Recursively visit all nodes */ visit(visitor: (v: ElementState) => void) { super.visit(visitor); for (const e of this.elements) { visitor(e); } } getSaveModel() { return { ...this.options, elements: this.elements.map((v) => v.getSaveModel()), }; } }