2020-03-16 22:53:02 +01:00
|
|
|
import { KEYS } from "../keys";
|
|
|
|
import { register } from "./register";
|
|
|
|
import { ExcalidrawElement } from "../element/types";
|
2020-04-08 09:49:52 -07:00
|
|
|
import { duplicateElement, getNonDeletedElements } from "../element";
|
2021-12-16 21:14:03 +05:30
|
|
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
2020-04-02 00:13:53 +09:00
|
|
|
import { ToolButton } from "../components/ToolButton";
|
|
|
|
import { clone } from "../components/icons";
|
|
|
|
import { t } from "../i18n";
|
2021-12-16 21:14:03 +05:30
|
|
|
import { arrayToMap, getShortcutKey } from "../utils";
|
2020-07-07 13:53:44 +02:00
|
|
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
2020-09-11 17:06:07 +02:00
|
|
|
import {
|
|
|
|
selectGroupsForSelectedElements,
|
|
|
|
getSelectedGroupForElement,
|
|
|
|
getElementsInGroup,
|
|
|
|
} from "../groups";
|
2020-08-08 21:04:15 -07:00
|
|
|
import { AppState } from "../types";
|
|
|
|
import { fixBindingsAfterDuplication } from "../element/binding";
|
|
|
|
import { ActionResult } from "./types";
|
2020-12-07 19:24:55 +02:00
|
|
|
import { GRID_SIZE } from "../constants";
|
2021-12-16 21:14:03 +05:30
|
|
|
import { bindTextToShapeAfterDuplication } from "../element/textElement";
|
|
|
|
import { isBoundToContainer } from "../element/typeChecks";
|
2020-03-16 22:53:02 +01:00
|
|
|
|
|
|
|
export const actionDuplicateSelection = register({
|
|
|
|
name: "duplicateSelection",
|
|
|
|
perform: (elements, appState) => {
|
2021-12-13 13:35:07 +01:00
|
|
|
// duplicate selected point(s) if editing a line
|
2020-07-07 13:53:44 +02:00
|
|
|
if (appState.editingLinearElement) {
|
2021-12-13 13:35:07 +01:00
|
|
|
const ret = LinearElementEditor.duplicateSelectedPoints(appState);
|
|
|
|
|
|
|
|
if (!ret) {
|
2020-07-07 13:53:44 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-12-13 13:35:07 +01:00
|
|
|
|
2020-07-07 13:53:44 +02:00
|
|
|
return {
|
|
|
|
elements,
|
2021-12-13 13:35:07 +01:00
|
|
|
appState: ret.appState,
|
2020-07-07 13:53:44 +02:00
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-03-16 22:53:02 +01:00
|
|
|
return {
|
2020-08-08 21:04:15 -07:00
|
|
|
...duplicateElements(elements, appState),
|
2020-03-19 14:51:05 +01:00
|
|
|
commitToHistory: true,
|
2020-03-16 22:53:02 +01:00
|
|
|
};
|
|
|
|
},
|
|
|
|
contextItemLabel: "labels.duplicateSelection",
|
2020-12-01 23:36:06 +02:00
|
|
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,
|
2020-04-02 00:13:53 +09:00
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
<ToolButton
|
|
|
|
type="button"
|
|
|
|
icon={clone}
|
2020-04-07 14:39:06 +03:00
|
|
|
title={`${t("labels.duplicateSelection")} — ${getShortcutKey(
|
2020-04-02 00:13:53 +09:00
|
|
|
"CtrlOrCmd+D",
|
|
|
|
)}`}
|
|
|
|
aria-label={t("labels.duplicateSelection")}
|
|
|
|
onClick={() => updateData(null)}
|
2020-04-08 09:49:52 -07:00
|
|
|
visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
|
2020-04-02 00:13:53 +09:00
|
|
|
/>
|
|
|
|
),
|
2020-03-16 22:53:02 +01:00
|
|
|
});
|
2020-08-08 21:04:15 -07:00
|
|
|
|
|
|
|
const duplicateElements = (
|
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
appState: AppState,
|
|
|
|
): Partial<ActionResult> => {
|
|
|
|
const groupIdMap = new Map();
|
|
|
|
const newElements: ExcalidrawElement[] = [];
|
|
|
|
const oldElements: ExcalidrawElement[] = [];
|
|
|
|
const oldIdToDuplicatedId = new Map();
|
2020-09-11 17:06:07 +02:00
|
|
|
|
|
|
|
const duplicateAndOffsetElement = (element: ExcalidrawElement) => {
|
|
|
|
const newElement = duplicateElement(
|
|
|
|
appState.editingGroupId,
|
|
|
|
groupIdMap,
|
|
|
|
element,
|
|
|
|
{
|
2020-12-07 19:24:55 +02:00
|
|
|
x: element.x + GRID_SIZE / 2,
|
|
|
|
y: element.y + GRID_SIZE / 2,
|
2020-09-11 17:06:07 +02:00
|
|
|
},
|
|
|
|
);
|
|
|
|
oldIdToDuplicatedId.set(element.id, newElement.id);
|
|
|
|
oldElements.push(element);
|
|
|
|
newElements.push(newElement);
|
|
|
|
return newElement;
|
|
|
|
};
|
|
|
|
|
|
|
|
const finalElements: ExcalidrawElement[] = [];
|
|
|
|
|
2020-11-06 22:06:30 +02:00
|
|
|
let index = 0;
|
2021-12-16 21:14:03 +05:30
|
|
|
const selectedElementIds = arrayToMap(
|
|
|
|
getSelectedElements(elements, appState, true),
|
|
|
|
);
|
2020-11-06 22:06:30 +02:00
|
|
|
while (index < elements.length) {
|
|
|
|
const element = elements[index];
|
2021-12-16 21:14:03 +05:30
|
|
|
if (selectedElementIds.get(element.id)) {
|
2020-09-11 17:06:07 +02:00
|
|
|
if (element.groupIds.length) {
|
|
|
|
const groupId = getSelectedGroupForElement(appState, element);
|
|
|
|
// if group selected, duplicate it atomically
|
|
|
|
if (groupId) {
|
|
|
|
const groupElements = getElementsInGroup(elements, groupId);
|
|
|
|
finalElements.push(
|
|
|
|
...groupElements,
|
|
|
|
...groupElements.map((element) =>
|
|
|
|
duplicateAndOffsetElement(element),
|
|
|
|
),
|
|
|
|
);
|
2020-11-06 22:06:30 +02:00
|
|
|
index = index + groupElements.length;
|
2020-09-11 17:06:07 +02:00
|
|
|
continue;
|
|
|
|
}
|
2020-08-08 21:04:15 -07:00
|
|
|
}
|
2020-09-11 17:06:07 +02:00
|
|
|
finalElements.push(element, duplicateAndOffsetElement(element));
|
|
|
|
} else {
|
|
|
|
finalElements.push(element);
|
|
|
|
}
|
2020-11-06 22:06:30 +02:00
|
|
|
index++;
|
2020-09-11 17:06:07 +02:00
|
|
|
}
|
2021-12-16 21:14:03 +05:30
|
|
|
bindTextToShapeAfterDuplication(
|
|
|
|
finalElements,
|
|
|
|
oldElements,
|
|
|
|
oldIdToDuplicatedId,
|
|
|
|
);
|
2020-08-08 21:04:15 -07:00
|
|
|
fixBindingsAfterDuplication(finalElements, oldElements, oldIdToDuplicatedId);
|
2020-09-11 17:06:07 +02:00
|
|
|
|
2020-08-08 21:04:15 -07:00
|
|
|
return {
|
|
|
|
elements: finalElements,
|
|
|
|
appState: selectGroupsForSelectedElements(
|
|
|
|
{
|
|
|
|
...appState,
|
|
|
|
selectedGroupIds: {},
|
|
|
|
selectedElementIds: newElements.reduce((acc, element) => {
|
2021-12-16 21:14:03 +05:30
|
|
|
if (!isBoundToContainer(element)) {
|
|
|
|
acc[element.id] = true;
|
|
|
|
}
|
2020-08-08 21:04:15 -07:00
|
|
|
return acc;
|
|
|
|
}, {} as any),
|
|
|
|
},
|
|
|
|
getNonDeletedElements(finalElements),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
};
|