feat: Element locking (#4964)

Co-authored-by: dwelle <luzar.david@gmail.com>
Co-authored-by: Zsolt Viczian <viczian.zsolt@gmail.com>
This commit is contained in:
Tom Sherman 2022-04-07 12:43:29 +01:00 committed by GitHub
parent c2fce6d8c4
commit 327ed0e2d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1066 additions and 53 deletions

View file

@ -31,6 +31,7 @@ import {
actionBindText,
actionUngroup,
actionLink,
actionToggleLock,
} from "../actions";
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
import { ActionManager } from "../actions/manager";
@ -1134,7 +1135,7 @@ class App extends React.Component<AppProps, AppState> {
prevState.activeTool !== this.state.activeTool &&
multiElement != null &&
isBindingEnabled(this.state) &&
isBindingElement(multiElement)
isBindingElement(multiElement, false)
) {
maybeBindLinearElement(
multiElement,
@ -1546,6 +1547,7 @@ class App extends React.Component<AppProps, AppState> {
fontFamily: this.state.currentItemFontFamily,
textAlign: this.state.currentItemTextAlign,
verticalAlign: DEFAULT_VERTICAL_ALIGN,
locked: false,
});
this.scene.replaceAllElements([
@ -2126,12 +2128,14 @@ class App extends React.Component<AppProps, AppState> {
of all hit elements */
preferSelected?: boolean;
includeBoundTextElement?: boolean;
includeLockedElements?: boolean;
},
): NonDeleted<ExcalidrawElement> | null {
const allHitElements = this.getElementsAtPosition(
x,
y,
opts?.includeBoundTextElement,
opts?.includeLockedElements,
);
if (allHitElements.length > 1) {
if (opts?.preferSelected) {
@ -2164,14 +2168,19 @@ class App extends React.Component<AppProps, AppState> {
x: number,
y: number,
includeBoundTextElement: boolean = false,
includeLockedElements: boolean = false,
): NonDeleted<ExcalidrawElement>[] {
const elements = includeBoundTextElement
? this.scene.getElements()
: this.scene
.getElements()
.filter(
(element) => !(isTextElement(element) && element.containerId),
);
const elements =
includeBoundTextElement && includeLockedElements
? this.scene.getElements()
: this.scene
.getElements()
.filter(
(element) =>
(includeLockedElements || !element.locked) &&
(includeBoundTextElement ||
!(isTextElement(element) && element.containerId)),
);
return getElementsAtPosition(elements, (element) =>
hitTest(element, this.state, x, y),
@ -2213,7 +2222,7 @@ class App extends React.Component<AppProps, AppState> {
if (selectedElements.length === 1) {
if (isTextElement(selectedElements[0])) {
existingTextElement = selectedElements[0];
} else if (isTextBindableContainer(selectedElements[0])) {
} else if (isTextBindableContainer(selectedElements[0], false)) {
container = selectedElements[0];
existingTextElement = getBoundTextElement(container);
}
@ -2233,7 +2242,8 @@ class App extends React.Component<AppProps, AppState> {
this.scene
.getElements()
.filter(
(ele) => isTextBindableContainer(ele) && !getBoundTextElement(ele),
(ele) =>
isTextBindableContainer(ele, false) && !getBoundTextElement(ele),
),
sceneX,
sceneY,
@ -2291,6 +2301,7 @@ class App extends React.Component<AppProps, AppState> {
: DEFAULT_VERTICAL_ALIGN,
containerId: container?.id ?? undefined,
groupIds: container?.groupIds ?? [],
locked: false,
});
this.setState({ editingElement: element });
@ -2597,7 +2608,7 @@ class App extends React.Component<AppProps, AppState> {
// Hovering with a selected tool or creating new linear element via click
// and point
const { draggingElement } = this.state;
if (isBindingElement(draggingElement)) {
if (isBindingElement(draggingElement, false)) {
this.maybeSuggestBindingsForLinearElementAtCoords(
draggingElement,
[scenePointer],
@ -2780,7 +2791,8 @@ class App extends React.Component<AppProps, AppState> {
this.isHittingCommonBoundingBoxOfSelectedElements(
scenePointer,
selectedElements,
))
)) &&
!hitElement?.locked
) {
setCursor(this.canvas, CURSOR_TYPE.MOVE);
} else {
@ -2796,6 +2808,10 @@ class App extends React.Component<AppProps, AppState> {
) => {
const updateElementIds = (elements: ExcalidrawElement[]) => {
elements.forEach((element) => {
if (element.locked) {
return;
}
idsToUpdate.push(element.id);
if (event.altKey) {
if (
@ -3617,6 +3633,7 @@ class App extends React.Component<AppProps, AppState> {
opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemLinearStrokeSharpness,
simulatePressure: event.pressure === 0.5,
locked: false,
});
this.setState((prevState) => ({
@ -3672,6 +3689,7 @@ class App extends React.Component<AppProps, AppState> {
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemLinearStrokeSharpness,
locked: false,
});
return element;
@ -3759,6 +3777,7 @@ class App extends React.Component<AppProps, AppState> {
strokeSharpness: this.state.currentItemLinearStrokeSharpness,
startArrowhead,
endArrowhead,
locked: false,
});
this.setState((prevState) => ({
selectedElementIds: {
@ -3807,6 +3826,7 @@ class App extends React.Component<AppProps, AppState> {
roughness: this.state.currentItemRoughness,
opacity: this.state.currentItemOpacity,
strokeSharpness: this.state.currentItemStrokeSharpness,
locked: false,
});
if (element.type === "selection") {
@ -4106,7 +4126,7 @@ class App extends React.Component<AppProps, AppState> {
});
}
if (isBindingElement(draggingElement)) {
if (isBindingElement(draggingElement, false)) {
// When creating a linear element by dragging
this.maybeSuggestBindingsForLinearElementAtCoords(
draggingElement,
@ -4385,7 +4405,7 @@ class App extends React.Component<AppProps, AppState> {
} else if (pointerDownState.drag.hasOccurred && !multiElement) {
if (
isBindingEnabled(this.state) &&
isBindingElement(draggingElement)
isBindingElement(draggingElement, false)
) {
maybeBindLinearElement(
draggingElement,
@ -5303,7 +5323,10 @@ class App extends React.Component<AppProps, AppState> {
}
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
const element = this.getElementAtPosition(x, y, { preferSelected: true });
const element = this.getElementAtPosition(x, y, {
preferSelected: true,
includeLockedElements: true,
});
const type = element ? "element" : "canvas";
@ -5615,6 +5638,8 @@ class App extends React.Component<AppProps, AppState> {
(maybeFlipHorizontal || maybeFlipVertical) && separator,
actionLink.contextItemPredicate(elements, this.state) && actionLink,
actionDuplicateSelection,
actionToggleLock,
separator,
actionDeleteSelected,
],
top,

View file

@ -363,6 +363,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
getShortcutKey(`Alt+${t("helpDialog.drag")}`),
]}
/>
<Shortcut
label={t("helpDialog.toggleElementLock")}
shortcuts={[getShortcutKey("CtrlOrCmd+Shift+L")]}
/>
<Shortcut
label={t("buttons.undo")}
shortcuts={[getShortcutKey("CtrlOrCmd+Z")]}