mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Alt-drag duplication
This commit is contained in:
parent
49dcf23101
commit
637cb82c9c
2 changed files with 18 additions and 138 deletions
|
@ -126,7 +126,6 @@ import { restore, restoreElements } from "../data/restore";
|
|||
import {
|
||||
dragNewElement,
|
||||
dragSelectedElements,
|
||||
duplicateElement,
|
||||
getCommonBounds,
|
||||
getCursorForResizingElement,
|
||||
getDragOffsetXY,
|
||||
|
@ -152,7 +151,6 @@ import {
|
|||
bindOrUnbindLinearElement,
|
||||
bindOrUnbindLinearElements,
|
||||
fixBindingsAfterDeletion,
|
||||
fixBindingsAfterDuplication,
|
||||
getHoveredElementForBinding,
|
||||
isBindingEnabled,
|
||||
isLinearElementSimpleAndAlreadyBound,
|
||||
|
@ -291,7 +289,6 @@ import {
|
|||
} from "../element/image";
|
||||
import { fileOpen } from "../data/filesystem";
|
||||
import {
|
||||
bindTextToShapeAfterDuplication,
|
||||
getBoundTextElement,
|
||||
getContainerCenter,
|
||||
getContainerElement,
|
||||
|
@ -308,7 +305,6 @@ import { Fonts, getLineHeight } from "../fonts";
|
|||
import {
|
||||
getFrameChildren,
|
||||
isCursorInFrame,
|
||||
bindElementsToFramesAfterDuplication,
|
||||
addElementsToFrame,
|
||||
replaceAllElementsInFrame,
|
||||
removeElementsFromFrame,
|
||||
|
@ -8423,129 +8419,26 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
pointerDownState.hit.hasBeenDuplicated = true;
|
||||
|
||||
const nextElements = [];
|
||||
const elementsToAppend = [];
|
||||
const groupIdMap = new Map();
|
||||
const oldIdToDuplicatedId = new Map();
|
||||
const hitElement = pointerDownState.hit.element;
|
||||
const selectedElementIds = new Set(
|
||||
this.scene
|
||||
.getSelectedElements({
|
||||
selectedElementIds: this.state.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
})
|
||||
.map((element) => element.id),
|
||||
);
|
||||
|
||||
const elements = this.scene.getElementsIncludingDeleted();
|
||||
|
||||
for (const element of elements) {
|
||||
const isInSelection =
|
||||
selectedElementIds.has(element.id) ||
|
||||
// case: the state.selectedElementIds might not have been
|
||||
// updated yet by the time this mousemove event is fired
|
||||
(element.id === hitElement?.id &&
|
||||
pointerDownState.hit.wasAddedToSelection);
|
||||
|
||||
if (isInSelection) {
|
||||
const duplicatedElement = duplicateElement(
|
||||
this.state.editingGroupId,
|
||||
groupIdMap,
|
||||
element,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
// NOTE (mtolmacs): This is a temporary fix for very large scenes
|
||||
if (
|
||||
Math.abs(duplicatedElement.x) > 1e7 ||
|
||||
Math.abs(duplicatedElement.x) > 1e7 ||
|
||||
Math.abs(duplicatedElement.width) > 1e7 ||
|
||||
Math.abs(duplicatedElement.height) > 1e7
|
||||
) {
|
||||
console.error(
|
||||
`Alt+dragging duplicated element with invalid dimensions`,
|
||||
duplicatedElement.x,
|
||||
duplicatedElement.y,
|
||||
duplicatedElement.width,
|
||||
duplicatedElement.height,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const origElement = pointerDownState.originalElements.get(
|
||||
element.id,
|
||||
)!;
|
||||
|
||||
// NOTE (mtolmacs): This is a temporary fix for very large scenes
|
||||
if (
|
||||
Math.abs(origElement.x) > 1e7 ||
|
||||
Math.abs(origElement.x) > 1e7 ||
|
||||
Math.abs(origElement.width) > 1e7 ||
|
||||
Math.abs(origElement.height) > 1e7
|
||||
) {
|
||||
console.error(
|
||||
`Alt+dragging duplicated element with invalid dimensions`,
|
||||
origElement.x,
|
||||
origElement.y,
|
||||
origElement.width,
|
||||
origElement.height,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mutateElement(duplicatedElement, {
|
||||
x: origElement.x,
|
||||
y: origElement.y,
|
||||
});
|
||||
|
||||
// put duplicated element to pointerDownState.originalElements
|
||||
// so that we can snap to the duplicated element without releasing
|
||||
pointerDownState.originalElements.set(
|
||||
duplicatedElement.id,
|
||||
duplicatedElement,
|
||||
);
|
||||
|
||||
nextElements.push(duplicatedElement);
|
||||
elementsToAppend.push(element);
|
||||
oldIdToDuplicatedId.set(element.id, duplicatedElement.id);
|
||||
} else {
|
||||
nextElements.push(element);
|
||||
}
|
||||
const selectedElements = this.scene.getSelectedElements({
|
||||
selectedElementIds: this.state.selectedElementIds,
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
});
|
||||
if (
|
||||
hitElement &&
|
||||
!selectedElements.find((el) => el.id === hitElement.id)
|
||||
) {
|
||||
selectedElements.push(hitElement);
|
||||
}
|
||||
const clonedElements = duplicateElements(selectedElements, {
|
||||
appState: this.state,
|
||||
randomizeSeed: true,
|
||||
});
|
||||
|
||||
let nextSceneElements: ExcalidrawElement[] = [
|
||||
...nextElements,
|
||||
...elementsToAppend,
|
||||
];
|
||||
|
||||
const mappedNewSceneElements = this.props.onDuplicate?.(
|
||||
nextSceneElements,
|
||||
elements,
|
||||
);
|
||||
|
||||
nextSceneElements = mappedNewSceneElements || nextSceneElements;
|
||||
|
||||
syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
|
||||
|
||||
bindTextToShapeAfterDuplication(
|
||||
nextElements,
|
||||
elementsToAppend,
|
||||
oldIdToDuplicatedId,
|
||||
);
|
||||
fixBindingsAfterDuplication(
|
||||
nextSceneElements,
|
||||
elementsToAppend,
|
||||
oldIdToDuplicatedId,
|
||||
"duplicatesServeAsOld",
|
||||
);
|
||||
bindElementsToFramesAfterDuplication(
|
||||
nextSceneElements,
|
||||
elementsToAppend,
|
||||
oldIdToDuplicatedId,
|
||||
const nextSceneElements = syncMovedIndices(
|
||||
[...clonedElements, ...this.scene.getElementsIncludingDeleted()],
|
||||
arrayToMap(clonedElements),
|
||||
);
|
||||
|
||||
this.scene.replaceAllElements(nextSceneElements);
|
||||
|
|
|
@ -51,13 +51,7 @@ import {
|
|||
} from "../scene/scrollbars";
|
||||
import { getCornerRadius } from "../shapes";
|
||||
import { type InteractiveCanvasAppState } from "../types";
|
||||
import {
|
||||
arrayToMap,
|
||||
invariant,
|
||||
isDevEnv,
|
||||
isTestEnv,
|
||||
throttleRAF,
|
||||
} from "../utils";
|
||||
import { arrayToMap, invariant, throttleRAF } from "../utils";
|
||||
|
||||
import {
|
||||
bootstrapCanvas,
|
||||
|
@ -895,13 +889,6 @@ const _renderInteractiveScene = ({
|
|||
// Arrows have a different highlight behavior when
|
||||
// they are the only selected element
|
||||
if (appState.selectedLinearElement) {
|
||||
if (isTestEnv() || isDevEnv()) {
|
||||
invariant(
|
||||
selectedElements.length <= 1,
|
||||
`There is an active selectedLinearElement on app state but the selectedElements length is ${selectedElements?.length} not 1`,
|
||||
);
|
||||
}
|
||||
|
||||
const editor = appState.selectedLinearElement;
|
||||
const firstSelectedLinear = selectedElements.find(
|
||||
(el) => el.id === editor.elementId, // Don't forget bound text elements!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue