mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Implement line editing (#1616)
* implement line editing * line editing with rotation * ensure adding new points is disabled on point dragging * fix hotkey replacement * don't paint bounding box when creating new multipoint * tweak points style, account for zoom and z-index * don't persist editingLinearElement to localStorage * don't mutate on noop points updates * account for rotation when adding new point * ensure clicking on points doesn't deselect element * tweak history handling around editingline element * update snapshots * refactor pointerMove handling * factor out point dragging * factor out pointerDown * improve positioning with rotation * revert to use roughjs for calculating points bounds * migrate from storing editingLinearElement.element to id * make GlobalScene.getElement into O(1) * use Alt for adding new points * fix adding and deleting a point with rotation * disable resize handlers & bounding box on line edit Co-authored-by: daishi <daishi@axlight.com>
This commit is contained in:
parent
db316f32e0
commit
14a66956d7
19 changed files with 1129 additions and 76 deletions
|
@ -10,6 +10,7 @@ import { ExcalidrawElement } from "../element/types";
|
|||
import { AppState } from "../types";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getElementsInGroup } from "../groups";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
const deleteSelectedElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
|
@ -29,26 +30,80 @@ const deleteSelectedElements = (
|
|||
};
|
||||
};
|
||||
|
||||
function handleGroupEditingState(
|
||||
appState: AppState,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): AppState {
|
||||
if (appState.editingGroupId) {
|
||||
const siblingElements = getElementsInGroup(
|
||||
getNonDeletedElements(elements),
|
||||
appState.editingGroupId!,
|
||||
);
|
||||
if (siblingElements.length) {
|
||||
return {
|
||||
...appState,
|
||||
selectedElementIds: { [siblingElements[0].id]: true },
|
||||
};
|
||||
}
|
||||
}
|
||||
return appState;
|
||||
}
|
||||
|
||||
export const actionDeleteSelected = register({
|
||||
name: "deleteSelectedElements",
|
||||
perform: (elements, appState) => {
|
||||
if (
|
||||
appState.editingLinearElement?.activePointIndex != null &&
|
||||
appState.editingLinearElement?.activePointIndex > -1
|
||||
) {
|
||||
const { elementId } = appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
if (element) {
|
||||
// case: deleting last point
|
||||
if (element.points.length < 2) {
|
||||
const nextElements = elements.filter((el) => el.id !== element.id);
|
||||
const nextAppState = handleGroupEditingState(appState, nextElements);
|
||||
|
||||
return {
|
||||
elements: nextElements,
|
||||
appState: {
|
||||
...nextAppState,
|
||||
editingLinearElement: null,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
LinearElementEditor.movePoint(
|
||||
element,
|
||||
appState.editingLinearElement.activePointIndex,
|
||||
"delete",
|
||||
);
|
||||
|
||||
return {
|
||||
elements: elements,
|
||||
appState: {
|
||||
...appState,
|
||||
editingLinearElement: {
|
||||
...appState.editingLinearElement,
|
||||
activePointIndex:
|
||||
appState.editingLinearElement.activePointIndex > 0
|
||||
? appState.editingLinearElement.activePointIndex - 1
|
||||
: 0,
|
||||
},
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let {
|
||||
elements: nextElements,
|
||||
appState: nextAppState,
|
||||
} = deleteSelectedElements(elements, appState);
|
||||
|
||||
if (appState.editingGroupId) {
|
||||
const siblingElements = getElementsInGroup(
|
||||
getNonDeletedElements(nextElements),
|
||||
appState.editingGroupId!,
|
||||
);
|
||||
if (siblingElements.length) {
|
||||
nextAppState = {
|
||||
...nextAppState,
|
||||
selectedElementIds: { [siblingElements[0].id]: true },
|
||||
};
|
||||
}
|
||||
}
|
||||
nextAppState = handleGroupEditingState(nextAppState, nextElements);
|
||||
|
||||
return {
|
||||
elements: nextElements,
|
||||
appState: {
|
||||
|
|
|
@ -8,10 +8,30 @@ import { t } from "../i18n";
|
|||
import { register } from "./register";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { isPathALoop } from "../math";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
export const actionFinalize = register({
|
||||
name: "finalize",
|
||||
perform: (elements, appState) => {
|
||||
if (appState.editingLinearElement) {
|
||||
const { elementId } = appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
|
||||
if (element) {
|
||||
return {
|
||||
elements:
|
||||
element.points.length < 2 || isInvisiblySmallElement(element)
|
||||
? elements.filter((el) => el.id !== element.id)
|
||||
: undefined,
|
||||
appState: {
|
||||
...appState,
|
||||
editingLinearElement: null,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let newElements = elements;
|
||||
if (window.document.activeElement instanceof HTMLElement) {
|
||||
window.document.activeElement.blur();
|
||||
|
@ -94,8 +114,8 @@ export const actionFinalize = register({
|
|||
},
|
||||
keyTest: (event, appState) =>
|
||||
(event.key === KEYS.ESCAPE &&
|
||||
!appState.draggingElement &&
|
||||
appState.multiElement === null) ||
|
||||
(appState.editingLinearElement !== null ||
|
||||
(!appState.draggingElement && appState.multiElement === null))) ||
|
||||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||
appState.multiElement !== null),
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
|
|
|
@ -30,23 +30,26 @@ const writeData = (
|
|||
const prevElementMap = getElementMap(prevElements);
|
||||
const nextElements = data.elements;
|
||||
const nextElementMap = getElementMap(nextElements);
|
||||
return {
|
||||
elements: nextElements
|
||||
.map((nextElement) =>
|
||||
newElementWith(
|
||||
prevElementMap[nextElement.id] || nextElement,
|
||||
nextElement,
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
prevElements
|
||||
.filter(
|
||||
(prevElement) => !nextElementMap.hasOwnProperty(prevElement.id),
|
||||
)
|
||||
.map((prevElement) =>
|
||||
newElementWith(prevElement, { isDeleted: true }),
|
||||
),
|
||||
|
||||
const elements = nextElements
|
||||
.map((nextElement) =>
|
||||
newElementWith(
|
||||
prevElementMap[nextElement.id] || nextElement,
|
||||
nextElement,
|
||||
),
|
||||
)
|
||||
.concat(
|
||||
prevElements
|
||||
.filter(
|
||||
(prevElement) => !nextElementMap.hasOwnProperty(prevElement.id),
|
||||
)
|
||||
.map((prevElement) =>
|
||||
newElementWith(prevElement, { isDeleted: true }),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
elements,
|
||||
appState: { ...appState, ...data.appState },
|
||||
commitToHistory,
|
||||
syncHistory: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue