ui.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import { ToolName } from "../queries/toolQueries";
  2. import { fireEvent, GlobalTestState } from "../test-utils";
  3. import { KEYS, Key } from "../../keys";
  4. import {
  5. ExcalidrawElement,
  6. ExcalidrawLinearElement,
  7. ExcalidrawTextElement,
  8. } from "../../element/types";
  9. import { API } from "./api";
  10. const { h } = window;
  11. let altKey = false;
  12. let shiftKey = false;
  13. let ctrlKey = false;
  14. export class Keyboard {
  15. static withModifierKeys = (
  16. modifiers: { alt?: boolean; shift?: boolean; ctrl?: boolean },
  17. cb: () => void,
  18. ) => {
  19. const prevAltKey = altKey;
  20. const prevShiftKey = shiftKey;
  21. const prevCtrlKey = ctrlKey;
  22. altKey = !!modifiers.alt;
  23. shiftKey = !!modifiers.shift;
  24. ctrlKey = !!modifiers.ctrl;
  25. try {
  26. cb();
  27. } finally {
  28. altKey = prevAltKey;
  29. shiftKey = prevShiftKey;
  30. ctrlKey = prevCtrlKey;
  31. }
  32. };
  33. static hotkeyDown = (hotkey: Key) => {
  34. const key = KEYS[hotkey];
  35. if (typeof key !== "string") {
  36. throw new Error("must provide a hotkey, not a key code");
  37. }
  38. Keyboard.keyDown(key);
  39. };
  40. static hotkeyUp = (hotkey: Key) => {
  41. const key = KEYS[hotkey];
  42. if (typeof key !== "string") {
  43. throw new Error("must provide a hotkey, not a key code");
  44. }
  45. Keyboard.keyUp(key);
  46. };
  47. static keyDown = (key: string) => {
  48. fireEvent.keyDown(document, {
  49. key,
  50. ctrlKey,
  51. shiftKey,
  52. altKey,
  53. keyCode: key.toUpperCase().charCodeAt(0),
  54. which: key.toUpperCase().charCodeAt(0),
  55. });
  56. };
  57. static keyUp = (key: string) => {
  58. fireEvent.keyUp(document, {
  59. key,
  60. ctrlKey,
  61. shiftKey,
  62. altKey,
  63. keyCode: key.toUpperCase().charCodeAt(0),
  64. which: key.toUpperCase().charCodeAt(0),
  65. });
  66. };
  67. static hotkeyPress = (key: Key) => {
  68. Keyboard.hotkeyDown(key);
  69. Keyboard.hotkeyUp(key);
  70. };
  71. static keyPress = (key: string) => {
  72. Keyboard.keyDown(key);
  73. Keyboard.keyUp(key);
  74. };
  75. }
  76. export class Pointer {
  77. private clientX = 0;
  78. private clientY = 0;
  79. constructor(
  80. private readonly pointerType: "mouse" | "touch" | "pen",
  81. private readonly pointerId = 1,
  82. ) {}
  83. reset() {
  84. this.clientX = 0;
  85. this.clientY = 0;
  86. }
  87. getPosition() {
  88. return [this.clientX, this.clientY];
  89. }
  90. restorePosition(x = 0, y = 0) {
  91. this.clientX = x;
  92. this.clientY = y;
  93. fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent());
  94. }
  95. private getEvent() {
  96. return {
  97. clientX: this.clientX,
  98. clientY: this.clientY,
  99. pointerType: this.pointerType,
  100. pointerId: this.pointerId,
  101. altKey,
  102. shiftKey,
  103. ctrlKey,
  104. };
  105. }
  106. move(dx: number, dy: number) {
  107. if (dx !== 0 || dy !== 0) {
  108. this.clientX += dx;
  109. this.clientY += dy;
  110. fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent());
  111. }
  112. }
  113. down(dx = 0, dy = 0) {
  114. this.move(dx, dy);
  115. fireEvent.pointerDown(GlobalTestState.canvas, this.getEvent());
  116. }
  117. up(dx = 0, dy = 0) {
  118. this.move(dx, dy);
  119. fireEvent.pointerUp(GlobalTestState.canvas, this.getEvent());
  120. }
  121. click(dx = 0, dy = 0) {
  122. this.down(dx, dy);
  123. this.up();
  124. }
  125. doubleClick(dx = 0, dy = 0) {
  126. this.move(dx, dy);
  127. fireEvent.doubleClick(GlobalTestState.canvas, this.getEvent());
  128. }
  129. select(
  130. /** if multiple elements supplied, they're shift-selected */
  131. elements: ExcalidrawElement | ExcalidrawElement[],
  132. ) {
  133. API.clearSelection();
  134. Keyboard.withModifierKeys({ shift: true }, () => {
  135. elements = Array.isArray(elements) ? elements : [elements];
  136. elements.forEach((element) => {
  137. this.reset();
  138. this.click(element.x, element.y);
  139. });
  140. });
  141. this.reset();
  142. }
  143. clickOn(element: ExcalidrawElement) {
  144. this.reset();
  145. this.click(element.x, element.y);
  146. this.reset();
  147. }
  148. }
  149. const mouse = new Pointer("mouse");
  150. export class UI {
  151. static clickTool = (toolName: ToolName) => {
  152. fireEvent.click(GlobalTestState.renderResult.getByToolName(toolName));
  153. };
  154. static createElement<T extends ToolName>(
  155. type: T,
  156. {
  157. x = 0,
  158. y = 0,
  159. size = 10,
  160. width = size,
  161. height = width,
  162. }: {
  163. x?: number;
  164. y?: number;
  165. size?: number;
  166. width?: number;
  167. height?: number;
  168. } = {},
  169. ): T extends "arrow" | "line" | "draw"
  170. ? ExcalidrawLinearElement
  171. : T extends "text"
  172. ? ExcalidrawTextElement
  173. : ExcalidrawElement {
  174. UI.clickTool(type);
  175. mouse.reset();
  176. mouse.down(x, y);
  177. mouse.reset();
  178. mouse.up(x + (width ?? height ?? size), y + (height ?? size));
  179. return h.elements[h.elements.length - 1] as any;
  180. }
  181. static group(elements: ExcalidrawElement[]) {
  182. mouse.select(elements);
  183. Keyboard.withModifierKeys({ ctrl: true }, () => {
  184. Keyboard.keyPress("g");
  185. });
  186. }
  187. }