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 {
|
import {
|
||||||
dragNewElement,
|
dragNewElement,
|
||||||
dragSelectedElements,
|
dragSelectedElements,
|
||||||
duplicateElement,
|
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
getDragOffsetXY,
|
getDragOffsetXY,
|
||||||
|
@ -152,7 +151,6 @@ import {
|
||||||
bindOrUnbindLinearElement,
|
bindOrUnbindLinearElement,
|
||||||
bindOrUnbindLinearElements,
|
bindOrUnbindLinearElements,
|
||||||
fixBindingsAfterDeletion,
|
fixBindingsAfterDeletion,
|
||||||
fixBindingsAfterDuplication,
|
|
||||||
getHoveredElementForBinding,
|
getHoveredElementForBinding,
|
||||||
isBindingEnabled,
|
isBindingEnabled,
|
||||||
isLinearElementSimpleAndAlreadyBound,
|
isLinearElementSimpleAndAlreadyBound,
|
||||||
|
@ -291,7 +289,6 @@ import {
|
||||||
} from "../element/image";
|
} from "../element/image";
|
||||||
import { fileOpen } from "../data/filesystem";
|
import { fileOpen } from "../data/filesystem";
|
||||||
import {
|
import {
|
||||||
bindTextToShapeAfterDuplication,
|
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
getContainerCenter,
|
getContainerCenter,
|
||||||
getContainerElement,
|
getContainerElement,
|
||||||
|
@ -308,7 +305,6 @@ import { Fonts, getLineHeight } from "../fonts";
|
||||||
import {
|
import {
|
||||||
getFrameChildren,
|
getFrameChildren,
|
||||||
isCursorInFrame,
|
isCursorInFrame,
|
||||||
bindElementsToFramesAfterDuplication,
|
|
||||||
addElementsToFrame,
|
addElementsToFrame,
|
||||||
replaceAllElementsInFrame,
|
replaceAllElementsInFrame,
|
||||||
removeElementsFromFrame,
|
removeElementsFromFrame,
|
||||||
|
@ -8423,129 +8419,26 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
pointerDownState.hit.hasBeenDuplicated = true;
|
pointerDownState.hit.hasBeenDuplicated = true;
|
||||||
|
|
||||||
const nextElements = [];
|
|
||||||
const elementsToAppend = [];
|
|
||||||
const groupIdMap = new Map();
|
|
||||||
const oldIdToDuplicatedId = new Map();
|
|
||||||
const hitElement = pointerDownState.hit.element;
|
const hitElement = pointerDownState.hit.element;
|
||||||
const selectedElementIds = new Set(
|
const selectedElements = this.scene.getSelectedElements({
|
||||||
this.scene
|
selectedElementIds: this.state.selectedElementIds,
|
||||||
.getSelectedElements({
|
includeBoundTextElement: true,
|
||||||
selectedElementIds: this.state.selectedElementIds,
|
includeElementsInFrames: true,
|
||||||
includeBoundTextElement: true,
|
});
|
||||||
includeElementsInFrames: true,
|
if (
|
||||||
})
|
hitElement &&
|
||||||
.map((element) => element.id),
|
!selectedElements.find((el) => el.id === hitElement.id)
|
||||||
);
|
) {
|
||||||
|
selectedElements.push(hitElement);
|
||||||
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 clonedElements = duplicateElements(selectedElements, {
|
||||||
|
appState: this.state,
|
||||||
|
randomizeSeed: true,
|
||||||
|
});
|
||||||
|
|
||||||
let nextSceneElements: ExcalidrawElement[] = [
|
const nextSceneElements = syncMovedIndices(
|
||||||
...nextElements,
|
[...clonedElements, ...this.scene.getElementsIncludingDeleted()],
|
||||||
...elementsToAppend,
|
arrayToMap(clonedElements),
|
||||||
];
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scene.replaceAllElements(nextSceneElements);
|
this.scene.replaceAllElements(nextSceneElements);
|
||||||
|
|
|
@ -51,13 +51,7 @@ import {
|
||||||
} from "../scene/scrollbars";
|
} from "../scene/scrollbars";
|
||||||
import { getCornerRadius } from "../shapes";
|
import { getCornerRadius } from "../shapes";
|
||||||
import { type InteractiveCanvasAppState } from "../types";
|
import { type InteractiveCanvasAppState } from "../types";
|
||||||
import {
|
import { arrayToMap, invariant, throttleRAF } from "../utils";
|
||||||
arrayToMap,
|
|
||||||
invariant,
|
|
||||||
isDevEnv,
|
|
||||||
isTestEnv,
|
|
||||||
throttleRAF,
|
|
||||||
} from "../utils";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bootstrapCanvas,
|
bootstrapCanvas,
|
||||||
|
@ -895,13 +889,6 @@ const _renderInteractiveScene = ({
|
||||||
// Arrows have a different highlight behavior when
|
// Arrows have a different highlight behavior when
|
||||||
// they are the only selected element
|
// they are the only selected element
|
||||||
if (appState.selectedLinearElement) {
|
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 editor = appState.selectedLinearElement;
|
||||||
const firstSelectedLinear = selectedElements.find(
|
const firstSelectedLinear = selectedElements.find(
|
||||||
(el) => el.id === editor.elementId, // Don't forget bound text elements!
|
(el) => el.id === editor.elementId, // Don't forget bound text elements!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue