manager.tsx 3.9 KB

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