actionDistribute.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import {
  2. DistributeHorizontallyIcon,
  3. DistributeVerticallyIcon,
  4. } from "../components/icons";
  5. import { ToolButton } from "../components/ToolButton";
  6. import { distributeElements, Distribution } from "../distribute";
  7. import { getNonDeletedElements } from "../element";
  8. import { ExcalidrawElement } from "../element/types";
  9. import { t } from "../i18n";
  10. import { CODES, KEYS } from "../keys";
  11. import { getSelectedElements, isSomeElementSelected } from "../scene";
  12. import { AppState } from "../types";
  13. import { arrayToMap, getShortcutKey } from "../utils";
  14. import { register } from "./register";
  15. const enableActionGroup = (
  16. elements: readonly ExcalidrawElement[],
  17. appState: AppState,
  18. ) => getSelectedElements(getNonDeletedElements(elements), appState).length > 1;
  19. const distributeSelectedElements = (
  20. elements: readonly ExcalidrawElement[],
  21. appState: Readonly<AppState>,
  22. distribution: Distribution,
  23. ) => {
  24. const selectedElements = getSelectedElements(
  25. getNonDeletedElements(elements),
  26. appState,
  27. );
  28. const updatedElements = distributeElements(selectedElements, distribution);
  29. const updatedElementsMap = arrayToMap(updatedElements);
  30. return elements.map(
  31. (element) => updatedElementsMap.get(element.id) || element,
  32. );
  33. };
  34. export const distributeHorizontally = register({
  35. name: "distributeHorizontally",
  36. trackEvent: { category: "element" },
  37. perform: (elements, appState) => {
  38. return {
  39. appState,
  40. elements: distributeSelectedElements(elements, appState, {
  41. space: "between",
  42. axis: "x",
  43. }),
  44. commitToHistory: true,
  45. };
  46. },
  47. keyTest: (event) =>
  48. !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.H,
  49. PanelComponent: ({ elements, appState, updateData }) => (
  50. <ToolButton
  51. hidden={!enableActionGroup(elements, appState)}
  52. type="button"
  53. icon={<DistributeHorizontallyIcon theme={appState.theme} />}
  54. onClick={() => updateData(null)}
  55. title={`${t("labels.distributeHorizontally")} — ${getShortcutKey(
  56. "Alt+H",
  57. )}`}
  58. aria-label={t("labels.distributeHorizontally")}
  59. visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
  60. />
  61. ),
  62. });
  63. export const distributeVertically = register({
  64. name: "distributeVertically",
  65. trackEvent: { category: "element" },
  66. perform: (elements, appState) => {
  67. return {
  68. appState,
  69. elements: distributeSelectedElements(elements, appState, {
  70. space: "between",
  71. axis: "y",
  72. }),
  73. commitToHistory: true,
  74. };
  75. },
  76. keyTest: (event) =>
  77. !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
  78. PanelComponent: ({ elements, appState, updateData }) => (
  79. <ToolButton
  80. hidden={!enableActionGroup(elements, appState)}
  81. type="button"
  82. icon={<DistributeVerticallyIcon theme={appState.theme} />}
  83. onClick={() => updateData(null)}
  84. title={`${t("labels.distributeVertically")} — ${getShortcutKey("Alt+V")}`}
  85. aria-label={t("labels.distributeVertically")}
  86. visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
  87. />
  88. ),
  89. });