manager.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React from "react";
  2. import {
  3. Action,
  4. ActionsManagerInterface,
  5. UpdaterFn,
  6. ActionName,
  7. ActionResult,
  8. } from "./types";
  9. import { ExcalidrawElement } from "../element/types";
  10. import { AppProps, AppState } from "../types";
  11. import { MODES } from "../constants";
  12. import Library from "../data/library";
  13. // This is the <App> component, but for now we don't care about anything but its
  14. // `canvas` state.
  15. type App = {
  16. canvas: HTMLCanvasElement | null;
  17. focusContainer: () => void;
  18. props: AppProps;
  19. library: Library;
  20. };
  21. export class ActionManager implements ActionsManagerInterface {
  22. actions = {} as ActionsManagerInterface["actions"];
  23. updater: (actionResult: ActionResult | Promise<ActionResult>) => void;
  24. getAppState: () => Readonly<AppState>;
  25. getElementsIncludingDeleted: () => readonly ExcalidrawElement[];
  26. app: App;
  27. constructor(
  28. updater: UpdaterFn,
  29. getAppState: () => AppState,
  30. getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
  31. app: App,
  32. ) {
  33. this.updater = (actionResult) => {
  34. if (actionResult && "then" in actionResult) {
  35. actionResult.then((actionResult) => {
  36. return updater(actionResult);
  37. });
  38. } else {
  39. return updater(actionResult);
  40. }
  41. };
  42. this.getAppState = getAppState;
  43. this.getElementsIncludingDeleted = getElementsIncludingDeleted;
  44. this.app = app;
  45. }
  46. registerAction(action: Action) {
  47. this.actions[action.name] = action;
  48. }
  49. registerAll(actions: readonly Action[]) {
  50. actions.forEach((action) => this.registerAction(action));
  51. }
  52. handleKeyDown(event: React.KeyboardEvent | KeyboardEvent) {
  53. const canvasActions = this.app.props.UIOptions.canvasActions;
  54. const data = Object.values(this.actions)
  55. .sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
  56. .filter(
  57. (action) =>
  58. (action.name in canvasActions
  59. ? canvasActions[action.name as keyof typeof canvasActions]
  60. : true) &&
  61. action.keyTest &&
  62. action.keyTest(
  63. event,
  64. this.getAppState(),
  65. this.getElementsIncludingDeleted(),
  66. ),
  67. );
  68. if (data.length === 0) {
  69. return false;
  70. }
  71. const { viewModeEnabled } = this.getAppState();
  72. if (viewModeEnabled) {
  73. if (!Object.values(MODES).includes(data[0].name)) {
  74. return false;
  75. }
  76. }
  77. event.preventDefault();
  78. this.updater(
  79. data[0].perform(
  80. this.getElementsIncludingDeleted(),
  81. this.getAppState(),
  82. null,
  83. this.app,
  84. ),
  85. );
  86. return true;
  87. }
  88. executeAction(action: Action) {
  89. this.updater(
  90. action.perform(
  91. this.getElementsIncludingDeleted(),
  92. this.getAppState(),
  93. null,
  94. this.app,
  95. ),
  96. );
  97. }
  98. // Id is an attribute that we can use to pass in data like keys.
  99. // This is needed for dynamically generated action components
  100. // like the user list. We can use this key to extract more
  101. // data from app state. This is an alternative to generic prop hell!
  102. renderAction = (name: ActionName, id?: string) => {
  103. const canvasActions = this.app.props.UIOptions.canvasActions;
  104. if (
  105. this.actions[name] &&
  106. "PanelComponent" in this.actions[name] &&
  107. (name in canvasActions
  108. ? canvasActions[name as keyof typeof canvasActions]
  109. : true)
  110. ) {
  111. const action = this.actions[name];
  112. const PanelComponent = action.PanelComponent!;
  113. const updateData = (formState?: any) => {
  114. this.updater(
  115. action.perform(
  116. this.getElementsIncludingDeleted(),
  117. this.getAppState(),
  118. formState,
  119. this.app,
  120. ),
  121. );
  122. };
  123. return (
  124. <PanelComponent
  125. elements={this.getElementsIncludingDeleted()}
  126. appState={this.getAppState()}
  127. updateData={updateData}
  128. id={id}
  129. appProps={this.app.props}
  130. />
  131. );
  132. }
  133. return null;
  134. };
  135. }