api.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import {
  2. ExcalidrawElement,
  3. ExcalidrawGenericElement,
  4. ExcalidrawTextElement,
  5. ExcalidrawLinearElement,
  6. } from "../../element/types";
  7. import { newElement, newTextElement, newLinearElement } from "../../element";
  8. import { DEFAULT_VERTICAL_ALIGN } from "../../constants";
  9. import { getDefaultAppState } from "../../appState";
  10. import { GlobalTestState, createEvent, fireEvent } from "../test-utils";
  11. import { ImportedDataState } from "../../data/types";
  12. const { h } = window;
  13. export class API {
  14. static getSelectedElements = (): ExcalidrawElement[] => {
  15. return h.elements.filter(
  16. (element) => h.state.selectedElementIds[element.id],
  17. );
  18. };
  19. static getSelectedElement = (): ExcalidrawElement => {
  20. const selectedElements = API.getSelectedElements();
  21. if (selectedElements.length !== 1) {
  22. throw new Error(
  23. `expected 1 selected element; got ${selectedElements.length}`,
  24. );
  25. }
  26. return selectedElements[0];
  27. };
  28. static getStateHistory = () => {
  29. // @ts-ignore
  30. return h.history.stateHistory;
  31. };
  32. static clearSelection = () => {
  33. // @ts-ignore
  34. h.app.clearSelection(null);
  35. expect(API.getSelectedElements().length).toBe(0);
  36. };
  37. static createElement = <
  38. T extends Exclude<ExcalidrawElement["type"], "selection">
  39. >({
  40. type,
  41. id,
  42. x = 0,
  43. y = x,
  44. width = 100,
  45. height = width,
  46. isDeleted = false,
  47. ...rest
  48. }: {
  49. type: T;
  50. x?: number;
  51. y?: number;
  52. height?: number;
  53. width?: number;
  54. id?: string;
  55. isDeleted?: boolean;
  56. // generic element props
  57. strokeColor?: ExcalidrawGenericElement["strokeColor"];
  58. backgroundColor?: ExcalidrawGenericElement["backgroundColor"];
  59. fillStyle?: ExcalidrawGenericElement["fillStyle"];
  60. strokeWidth?: ExcalidrawGenericElement["strokeWidth"];
  61. strokeStyle?: ExcalidrawGenericElement["strokeStyle"];
  62. strokeSharpness?: ExcalidrawGenericElement["strokeSharpness"];
  63. roughness?: ExcalidrawGenericElement["roughness"];
  64. opacity?: ExcalidrawGenericElement["opacity"];
  65. // text props
  66. text?: T extends "text" ? ExcalidrawTextElement["text"] : never;
  67. fontSize?: T extends "text" ? ExcalidrawTextElement["fontSize"] : never;
  68. fontFamily?: T extends "text" ? ExcalidrawTextElement["fontFamily"] : never;
  69. textAlign?: T extends "text" ? ExcalidrawTextElement["textAlign"] : never;
  70. verticalAlign?: T extends "text"
  71. ? ExcalidrawTextElement["verticalAlign"]
  72. : never;
  73. }): T extends "arrow" | "line" | "draw"
  74. ? ExcalidrawLinearElement
  75. : T extends "text"
  76. ? ExcalidrawTextElement
  77. : ExcalidrawGenericElement => {
  78. let element: Mutable<ExcalidrawElement> = null!;
  79. const appState = h?.state || getDefaultAppState();
  80. const base = {
  81. x,
  82. y,
  83. strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
  84. backgroundColor:
  85. rest.backgroundColor ?? appState.currentItemBackgroundColor,
  86. fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
  87. strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth,
  88. strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
  89. strokeSharpness:
  90. rest.strokeSharpness ?? appState.currentItemStrokeSharpness,
  91. roughness: rest.roughness ?? appState.currentItemRoughness,
  92. opacity: rest.opacity ?? appState.currentItemOpacity,
  93. };
  94. switch (type) {
  95. case "rectangle":
  96. case "diamond":
  97. case "ellipse":
  98. element = newElement({
  99. type: type as "rectangle" | "diamond" | "ellipse",
  100. width,
  101. height,
  102. ...base,
  103. });
  104. break;
  105. case "text":
  106. element = newTextElement({
  107. ...base,
  108. text: rest.text || "test",
  109. fontSize: rest.fontSize ?? appState.currentItemFontSize,
  110. fontFamily: rest.fontFamily ?? appState.currentItemFontFamily,
  111. textAlign: rest.textAlign ?? appState.currentItemTextAlign,
  112. verticalAlign: rest.verticalAlign ?? DEFAULT_VERTICAL_ALIGN,
  113. });
  114. break;
  115. case "arrow":
  116. case "line":
  117. case "draw":
  118. element = newLinearElement({
  119. type: type as "arrow" | "line" | "draw",
  120. ...base,
  121. });
  122. break;
  123. }
  124. if (id) {
  125. element.id = id;
  126. }
  127. if (isDeleted) {
  128. element.isDeleted = isDeleted;
  129. }
  130. return element as any;
  131. };
  132. static dropFile(data: ImportedDataState | Blob) {
  133. const fileDropEvent = createEvent.drop(GlobalTestState.canvas);
  134. const file =
  135. data instanceof Blob
  136. ? data
  137. : new Blob(
  138. [
  139. JSON.stringify({
  140. type: "excalidraw",
  141. ...data,
  142. }),
  143. ],
  144. {
  145. type: "application/json",
  146. },
  147. );
  148. Object.defineProperty(fileDropEvent, "dataTransfer", {
  149. value: {
  150. files: [file],
  151. getData: (_type: string) => {
  152. return "";
  153. },
  154. },
  155. });
  156. fireEvent(GlobalTestState.canvas, fileDropEvent);
  157. }
  158. }