manager.tsx 3.5 KB

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