123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import { getNonDeletedElements } from "@excalidraw/element";
- import { isFrameLikeElement } from "@excalidraw/element/typeChecks";
- import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element/frame";
- import { KEYS, arrayToMap, getShortcutKey } from "@excalidraw/common";
- import { alignElements } from "@excalidraw/element/align";
- import { CaptureUpdateAction } from "@excalidraw/element/store";
- import type { ExcalidrawElement } from "@excalidraw/element/types";
- import type { Alignment } from "@excalidraw/element/align";
- import { ToolButton } from "../components/ToolButton";
- import {
- AlignBottomIcon,
- AlignLeftIcon,
- AlignRightIcon,
- AlignTopIcon,
- CenterHorizontallyIcon,
- CenterVerticallyIcon,
- } from "../components/icons";
- import { t } from "../i18n";
- import { isSomeElementSelected } from "../scene";
- import { register } from "./register";
- import type { AppClassProperties, AppState, UIAppState } from "../types";
- export const alignActionsPredicate = (
- appState: UIAppState,
- app: AppClassProperties,
- ) => {
- const selectedElements = app.scene.getSelectedElements(appState);
- return (
- selectedElements.length > 1 &&
- // TODO enable aligning frames when implemented properly
- !selectedElements.some((el) => isFrameLikeElement(el))
- );
- };
- const alignSelectedElements = (
- elements: readonly ExcalidrawElement[],
- appState: Readonly<AppState>,
- app: AppClassProperties,
- alignment: Alignment,
- ) => {
- const selectedElements = app.scene.getSelectedElements(appState);
- const updatedElements = alignElements(selectedElements, alignment, app.scene);
- const updatedElementsMap = arrayToMap(updatedElements);
- return updateFrameMembershipOfSelectedElements(
- elements.map((element) => updatedElementsMap.get(element.id) || element),
- appState,
- app,
- );
- };
- export const actionAlignTop = register({
- name: "alignTop",
- label: "labels.alignTop",
- icon: AlignTopIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "start",
- axis: "y",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- keyTest: (event) =>
- event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP,
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={AlignTopIcon}
- onClick={() => updateData(null)}
- title={`${t("labels.alignTop")} — ${getShortcutKey(
- "CtrlOrCmd+Shift+Up",
- )}`}
- aria-label={t("labels.alignTop")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
- export const actionAlignBottom = register({
- name: "alignBottom",
- label: "labels.alignBottom",
- icon: AlignBottomIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "end",
- axis: "y",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- keyTest: (event) =>
- event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_DOWN,
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={AlignBottomIcon}
- onClick={() => updateData(null)}
- title={`${t("labels.alignBottom")} — ${getShortcutKey(
- "CtrlOrCmd+Shift+Down",
- )}`}
- aria-label={t("labels.alignBottom")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
- export const actionAlignLeft = register({
- name: "alignLeft",
- label: "labels.alignLeft",
- icon: AlignLeftIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "start",
- axis: "x",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- keyTest: (event) =>
- event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_LEFT,
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={AlignLeftIcon}
- onClick={() => updateData(null)}
- title={`${t("labels.alignLeft")} — ${getShortcutKey(
- "CtrlOrCmd+Shift+Left",
- )}`}
- aria-label={t("labels.alignLeft")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
- export const actionAlignRight = register({
- name: "alignRight",
- label: "labels.alignRight",
- icon: AlignRightIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "end",
- axis: "x",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- keyTest: (event) =>
- event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_RIGHT,
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={AlignRightIcon}
- onClick={() => updateData(null)}
- title={`${t("labels.alignRight")} — ${getShortcutKey(
- "CtrlOrCmd+Shift+Right",
- )}`}
- aria-label={t("labels.alignRight")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
- export const actionAlignVerticallyCentered = register({
- name: "alignVerticallyCentered",
- label: "labels.centerVertically",
- icon: CenterVerticallyIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "center",
- axis: "y",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={CenterVerticallyIcon}
- onClick={() => updateData(null)}
- title={t("labels.centerVertically")}
- aria-label={t("labels.centerVertically")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
- export const actionAlignHorizontallyCentered = register({
- name: "alignHorizontallyCentered",
- label: "labels.centerHorizontally",
- icon: CenterHorizontallyIcon,
- trackEvent: { category: "element" },
- predicate: (elements, appState, appProps, app) =>
- alignActionsPredicate(appState, app),
- perform: (elements, appState, _, app) => {
- return {
- appState,
- elements: alignSelectedElements(elements, appState, app, {
- position: "center",
- axis: "x",
- }),
- captureUpdate: CaptureUpdateAction.IMMEDIATELY,
- };
- },
- PanelComponent: ({ elements, appState, updateData, app }) => (
- <ToolButton
- hidden={!alignActionsPredicate(appState, app)}
- type="button"
- icon={CenterHorizontallyIcon}
- onClick={() => updateData(null)}
- title={t("labels.centerHorizontally")}
- aria-label={t("labels.centerHorizontally")}
- visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
- />
- ),
- });
|