frame.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { cloneDeep } from 'lodash';
  2. import React from 'react';
  3. import { canvasElementRegistry, CanvasFrameOptions } from 'app/features/canvas';
  4. import { notFoundItem } from 'app/features/canvas/elements/notFound';
  5. import { DimensionContext } from 'app/features/dimensions';
  6. import { LayerActionID } from 'app/plugins/panel/canvas/types';
  7. import { CanvasElementItem } from '../element';
  8. import { HorizontalConstraint, Placement, VerticalConstraint } from '../types';
  9. import { ElementState } from './element';
  10. import { RootElement } from './root';
  11. import { Scene } from './scene';
  12. export const frameItemDummy: CanvasElementItem = {
  13. id: 'frame',
  14. name: 'Frame',
  15. description: 'Frame',
  16. getNewOptions: () => ({
  17. config: {},
  18. }),
  19. // eslint-disable-next-line react/display-name
  20. display: () => {
  21. return <div>FRAME!</div>;
  22. },
  23. };
  24. export class FrameState extends ElementState {
  25. elements: ElementState[] = [];
  26. scene: Scene;
  27. constructor(public options: CanvasFrameOptions, scene: Scene, public parent?: FrameState) {
  28. super(frameItemDummy, options, parent);
  29. this.scene = scene;
  30. // mutate options object
  31. let { elements } = this.options;
  32. if (!elements) {
  33. this.options.elements = elements = [];
  34. }
  35. for (const c of elements) {
  36. if (c.type === 'frame') {
  37. this.elements.push(new FrameState(c as CanvasFrameOptions, scene, this));
  38. } else {
  39. const item = canvasElementRegistry.getIfExists(c.type) ?? notFoundItem;
  40. this.elements.push(new ElementState(item, c, this));
  41. }
  42. }
  43. }
  44. isRoot(): this is RootElement {
  45. return false;
  46. }
  47. updateData(ctx: DimensionContext) {
  48. super.updateData(ctx);
  49. for (const elem of this.elements) {
  50. elem.updateData(ctx);
  51. }
  52. }
  53. // used in the layer editor
  54. reorder(startIndex: number, endIndex: number) {
  55. const result = Array.from(this.elements);
  56. const [removed] = result.splice(startIndex, 1);
  57. result.splice(endIndex, 0, removed);
  58. this.elements = result;
  59. this.reinitializeMoveable();
  60. }
  61. doMove(child: ElementState, action: LayerActionID) {
  62. const vals = this.elements.filter((v) => v !== child);
  63. if (action === LayerActionID.MoveBottom) {
  64. vals.unshift(child);
  65. } else {
  66. vals.push(child);
  67. }
  68. this.elements = vals;
  69. this.scene.save();
  70. this.reinitializeMoveable();
  71. }
  72. reinitializeMoveable() {
  73. // Need to first clear current selection and then re-init moveable with slight delay
  74. this.scene.clearCurrentSelection();
  75. setTimeout(() => this.scene.initMoveable(true, this.scene.isEditingEnabled));
  76. }
  77. // ??? or should this be on the element directly?
  78. // are actions scoped to layers?
  79. doAction = (action: LayerActionID, element: ElementState, updateName = true, shiftItemsOnDuplicate = true) => {
  80. switch (action) {
  81. case LayerActionID.Delete:
  82. this.elements = this.elements.filter((e) => e !== element);
  83. this.scene.byName.delete(element.options.name);
  84. this.scene.save();
  85. this.reinitializeMoveable();
  86. break;
  87. case LayerActionID.Duplicate:
  88. if (element.item.id === 'frame') {
  89. console.log('Can not duplicate frames (yet)', action, element);
  90. return;
  91. }
  92. const opts = cloneDeep(element.options);
  93. if (shiftItemsOnDuplicate) {
  94. const { constraint, placement: oldPlacement } = element.options;
  95. const { vertical, horizontal } = constraint ?? {};
  96. const placement = oldPlacement ?? ({} as Placement);
  97. switch (vertical) {
  98. case VerticalConstraint.Top:
  99. case VerticalConstraint.TopBottom:
  100. if (placement.top == null) {
  101. placement.top = 25;
  102. } else {
  103. placement.top += 10;
  104. }
  105. break;
  106. case VerticalConstraint.Bottom:
  107. if (placement.bottom == null) {
  108. placement.bottom = 100;
  109. } else {
  110. placement.bottom -= 10;
  111. }
  112. break;
  113. }
  114. switch (horizontal) {
  115. case HorizontalConstraint.Left:
  116. case HorizontalConstraint.LeftRight:
  117. if (placement.left == null) {
  118. placement.left = 50;
  119. } else {
  120. placement.left += 10;
  121. }
  122. break;
  123. case HorizontalConstraint.Right:
  124. if (placement.right == null) {
  125. placement.right = 50;
  126. } else {
  127. placement.right -= 10;
  128. }
  129. break;
  130. }
  131. opts.placement = placement;
  132. }
  133. const copy = new ElementState(element.item, opts, this);
  134. copy.updateData(this.scene.context);
  135. if (updateName) {
  136. copy.options.name = this.scene.getNextElementName();
  137. }
  138. this.elements.push(copy);
  139. this.scene.byName.set(copy.options.name, copy);
  140. this.scene.save();
  141. this.reinitializeMoveable();
  142. break;
  143. case LayerActionID.MoveTop:
  144. case LayerActionID.MoveBottom:
  145. element.parent?.doMove(element, action);
  146. break;
  147. default:
  148. console.log('DO action', action, element);
  149. return;
  150. }
  151. };
  152. render() {
  153. return (
  154. <div key={this.UID} ref={this.initElement} style={{ overflow: 'hidden' }}>
  155. {this.elements.map((v) => v.render())}
  156. </div>
  157. );
  158. }
  159. /** Recursively visit all nodes */
  160. visit(visitor: (v: ElementState) => void) {
  161. super.visit(visitor);
  162. for (const e of this.elements) {
  163. visitor(e);
  164. }
  165. }
  166. getSaveModel() {
  167. return {
  168. ...this.options,
  169. elements: this.elements.map((v) => v.getSaveModel()),
  170. };
  171. }
  172. }