duplicate point on cmd+d (#1831)
This commit is contained in:
parent
84abda82d5
commit
b1261eea70
@ -8,10 +8,47 @@ import { ToolButton } from "../components/ToolButton";
|
|||||||
import { clone } from "../components/icons";
|
import { clone } from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
import { mutateElement } from "../element/mutateElement";
|
||||||
|
|
||||||
export const actionDuplicateSelection = register({
|
export const actionDuplicateSelection = register({
|
||||||
name: "duplicateSelection",
|
name: "duplicateSelection",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
|
// duplicate point if selected while editing multi-point element
|
||||||
|
if (appState.editingLinearElement) {
|
||||||
|
const { activePointIndex, elementId } = appState.editingLinearElement;
|
||||||
|
const element = LinearElementEditor.getElement(elementId);
|
||||||
|
if (!element || activePointIndex === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { points } = element;
|
||||||
|
const selectedPoint = points[activePointIndex];
|
||||||
|
const nextPoint = points[activePointIndex + 1];
|
||||||
|
mutateElement(element, {
|
||||||
|
points: [
|
||||||
|
...points.slice(0, activePointIndex + 1),
|
||||||
|
nextPoint
|
||||||
|
? [
|
||||||
|
(selectedPoint[0] + nextPoint[0]) / 2,
|
||||||
|
(selectedPoint[1] + nextPoint[1]) / 2,
|
||||||
|
]
|
||||||
|
: [selectedPoint[0] + 30, selectedPoint[1] + 30],
|
||||||
|
...points.slice(activePointIndex + 1),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
editingLinearElement: {
|
||||||
|
...appState.editingLinearElement,
|
||||||
|
activePointIndex: activePointIndex + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
elements,
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const groupIdMap = new Map();
|
const groupIdMap = new Map();
|
||||||
return {
|
return {
|
||||||
appState,
|
appState,
|
||||||
|
@ -2,12 +2,15 @@ import React from "react";
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
|
||||||
export type ActionResult = {
|
/** if false, the action should be prevented */
|
||||||
elements?: readonly ExcalidrawElement[] | null;
|
export type ActionResult =
|
||||||
appState?: AppState | null;
|
| {
|
||||||
commitToHistory: boolean;
|
elements?: readonly ExcalidrawElement[] | null;
|
||||||
syncHistory?: boolean;
|
appState?: AppState | null;
|
||||||
};
|
commitToHistory: boolean;
|
||||||
|
syncHistory?: boolean;
|
||||||
|
}
|
||||||
|
| false;
|
||||||
|
|
||||||
type ActionFn = (
|
type ActionFn = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
|
@ -269,51 +269,53 @@ class App extends React.Component<any, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncActionResult = withBatchedUpdates((res: ActionResult) => {
|
private syncActionResult = withBatchedUpdates(
|
||||||
if (this.unmounted) {
|
(actionResult: ActionResult) => {
|
||||||
return;
|
if (this.unmounted || actionResult === false) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
let editingElement: AppState["editingElement"] | null = null;
|
|
||||||
if (res.elements) {
|
|
||||||
res.elements.forEach((element) => {
|
|
||||||
if (
|
|
||||||
this.state.editingElement?.id === element.id &&
|
|
||||||
this.state.editingElement !== element &&
|
|
||||||
isNonDeletedElement(element)
|
|
||||||
) {
|
|
||||||
editingElement = element;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
globalSceneState.replaceAllElements(res.elements);
|
|
||||||
if (res.commitToHistory) {
|
|
||||||
history.resumeRecording();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (res.appState || editingElement) {
|
let editingElement: AppState["editingElement"] | null = null;
|
||||||
if (res.commitToHistory) {
|
if (actionResult.elements) {
|
||||||
history.resumeRecording();
|
actionResult.elements.forEach((element) => {
|
||||||
}
|
if (
|
||||||
this.setState(
|
this.state.editingElement?.id === element.id &&
|
||||||
(state) => ({
|
this.state.editingElement !== element &&
|
||||||
...res.appState,
|
isNonDeletedElement(element)
|
||||||
editingElement:
|
) {
|
||||||
editingElement || res.appState?.editingElement || null,
|
editingElement = element;
|
||||||
isCollaborating: state.isCollaborating,
|
|
||||||
collaborators: state.collaborators,
|
|
||||||
}),
|
|
||||||
() => {
|
|
||||||
if (res.syncHistory) {
|
|
||||||
history.setCurrentState(
|
|
||||||
this.state,
|
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
globalSceneState.replaceAllElements(actionResult.elements);
|
||||||
}
|
if (actionResult.commitToHistory) {
|
||||||
});
|
history.resumeRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionResult.appState || editingElement) {
|
||||||
|
if (actionResult.commitToHistory) {
|
||||||
|
history.resumeRecording();
|
||||||
|
}
|
||||||
|
this.setState(
|
||||||
|
(state) => ({
|
||||||
|
...actionResult.appState,
|
||||||
|
editingElement:
|
||||||
|
editingElement || actionResult.appState?.editingElement || null,
|
||||||
|
isCollaborating: state.isCollaborating,
|
||||||
|
collaborators: state.collaborators,
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
if (actionResult.syncHistory) {
|
||||||
|
history.setCurrentState(
|
||||||
|
this.state,
|
||||||
|
globalSceneState.getElementsIncludingDeleted(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
||||||
"rotate": "You can constrain angles by holding SHIFT while rotating",
|
"rotate": "You can constrain angles by holding SHIFT while rotating",
|
||||||
"lineEditor_info": "Double-click or press Enter to edit points",
|
"lineEditor_info": "Double-click or press Enter to edit points",
|
||||||
"lineEditor_pointSelected": "Press Delete to remove point or drag to move",
|
"lineEditor_pointSelected": "Press Delete to remove point, CtrlOrCmd+D to duplicate, or drag to move",
|
||||||
"lineEditor_nothingSelected": "Select a point to move or remove, or hold Alt and click to add new points"
|
"lineEditor_nothingSelected": "Select a point to move or remove, or hold Alt and click to add new points"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user