2021-01-05 20:06:14 +02:00
|
|
|
import { alignElements, Alignment } from "../align";
|
2020-10-31 11:40:06 +01:00
|
|
|
import {
|
|
|
|
AlignBottomIcon,
|
|
|
|
AlignLeftIcon,
|
|
|
|
AlignRightIcon,
|
|
|
|
AlignTopIcon,
|
|
|
|
CenterHorizontallyIcon,
|
|
|
|
CenterVerticallyIcon,
|
|
|
|
} from "../components/icons";
|
|
|
|
import { ToolButton } from "../components/ToolButton";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { getElementMap, getNonDeletedElements } from "../element";
|
2020-10-31 11:40:06 +01:00
|
|
|
import { ExcalidrawElement } from "../element/types";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { t } from "../i18n";
|
|
|
|
import { KEYS } from "../keys";
|
|
|
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
2020-10-31 11:40:06 +01:00
|
|
|
import { AppState } from "../types";
|
|
|
|
import { getShortcutKey } from "../utils";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { register } from "./register";
|
2020-10-31 11:40:06 +01:00
|
|
|
|
|
|
|
const enableActionGroup = (
|
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
appState: AppState,
|
|
|
|
) => getSelectedElements(getNonDeletedElements(elements), appState).length > 1;
|
|
|
|
|
2020-11-06 22:06:39 +02:00
|
|
|
const alignSelectedElements = (
|
2020-10-31 11:40:06 +01:00
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
appState: Readonly<AppState>,
|
|
|
|
alignment: Alignment,
|
2020-11-06 22:06:39 +02:00
|
|
|
) => {
|
2020-10-31 11:40:06 +01:00
|
|
|
const selectedElements = getSelectedElements(
|
|
|
|
getNonDeletedElements(elements),
|
|
|
|
appState,
|
|
|
|
);
|
|
|
|
|
|
|
|
const updatedElements = alignElements(selectedElements, alignment);
|
|
|
|
|
|
|
|
const updatedElementsMap = getElementMap(updatedElements);
|
|
|
|
|
|
|
|
return elements.map((element) => updatedElementsMap[element.id] || element);
|
2020-11-06 22:06:39 +02:00
|
|
|
};
|
2020-10-31 11:40:06 +01:00
|
|
|
|
|
|
|
export const actionAlignTop = register({
|
|
|
|
name: "alignTop",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "start",
|
|
|
|
axis: "y",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
2020-12-01 23:36:06 +02:00
|
|
|
keyTest: (event) =>
|
|
|
|
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP,
|
2020-10-31 11:40:06 +01:00
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<AlignTopIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
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",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "end",
|
|
|
|
axis: "y",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
2020-12-01 23:36:06 +02:00
|
|
|
keyTest: (event) =>
|
|
|
|
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_DOWN,
|
2020-10-31 11:40:06 +01:00
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<AlignBottomIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
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",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "start",
|
|
|
|
axis: "x",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
2020-12-01 23:36:06 +02:00
|
|
|
keyTest: (event) =>
|
|
|
|
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_LEFT,
|
2020-10-31 11:40:06 +01:00
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<AlignLeftIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
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",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "end",
|
|
|
|
axis: "x",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
2020-12-01 23:36:06 +02:00
|
|
|
keyTest: (event) =>
|
|
|
|
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_RIGHT,
|
2020-10-31 11:40:06 +01:00
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<AlignRightIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
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",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "center",
|
|
|
|
axis: "y",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<CenterVerticallyIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
onClick={() => updateData(null)}
|
|
|
|
title={t("labels.centerVertically")}
|
|
|
|
aria-label={t("labels.centerVertically")}
|
|
|
|
visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
});
|
|
|
|
|
|
|
|
export const actionAlignHorizontallyCentered = register({
|
|
|
|
name: "alignHorizontallyCentered",
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
return {
|
|
|
|
appState,
|
|
|
|
elements: alignSelectedElements(elements, appState, {
|
|
|
|
position: "center",
|
|
|
|
axis: "x",
|
|
|
|
}),
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
hidden={!enableActionGroup(elements, appState)}
|
|
|
|
type="button"
|
2021-03-13 18:58:06 +05:30
|
|
|
icon={<CenterHorizontallyIcon theme={appState.theme} />}
|
2020-10-31 11:40:06 +01:00
|
|
|
onClick={() => updateData(null)}
|
|
|
|
title={t("labels.centerHorizontally")}
|
|
|
|
aria-label={t("labels.centerHorizontally")}
|
|
|
|
visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
});
|