mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-25 08:54:20 +02:00
* change lock label * feat: add unlock logic for single units on pointer up * feat: add unlock popup * fix: linting errors * style: padding tweaks * style: remove redundant line * feat: lock multiple units together * style: tweak color & position * feat: add highlight for locked elements * feat: select groups correctly after unlocking * test: update snapshots * fix: lasso from selecting locked elements * fix: should prevent selecting unlocked elements and setting locked id at the same time * fix: reset hit locked id immediately when appropriate * feat: capture locked units in delta * test: update locking test * feat: show lock highlight when locking (including undo/redo) * feat: make locked highlighting consistent * feat: show correct cursor type when moving over locked elements * fix history * remove `lockedUnits.singleUnits` * tweak button * do not render UnlockPopup if not locked element selected * tweak actions * refactor: simplify type * refactor: rename type * refactor: simplify hit element setting & checking * fix: prefer locked over link * rename to `activeLockedId` * refactor: getElementAtPosition takes an optional hitelments array * fix: avoid setting active locked id after resizing --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
76 lines
1.8 KiB
TypeScript
76 lines
1.8 KiB
TypeScript
import {
|
|
getCommonBounds,
|
|
getElementsInGroup,
|
|
selectGroupsFromGivenElements,
|
|
} from "@excalidraw/element";
|
|
import { sceneCoordsToViewportCoords } from "@excalidraw/common";
|
|
|
|
import { flushSync } from "react-dom";
|
|
|
|
import { actionToggleElementLock } from "../actions";
|
|
import { t } from "../i18n";
|
|
|
|
import "./UnlockPopup.scss";
|
|
|
|
import { LockedIconFilled } from "./icons";
|
|
|
|
import type App from "./App";
|
|
|
|
import type { AppState } from "../types";
|
|
|
|
const UnlockPopup = ({
|
|
app,
|
|
activeLockedId,
|
|
}: {
|
|
app: App;
|
|
activeLockedId: NonNullable<AppState["activeLockedId"]>;
|
|
}) => {
|
|
const element = app.scene.getElement(activeLockedId);
|
|
|
|
const elements = element
|
|
? [element]
|
|
: getElementsInGroup(app.scene.getNonDeletedElementsMap(), activeLockedId);
|
|
|
|
if (elements.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const [x, y] = getCommonBounds(elements);
|
|
const { x: viewX, y: viewY } = sceneCoordsToViewportCoords(
|
|
{ sceneX: x, sceneY: y },
|
|
app.state,
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className="UnlockPopup"
|
|
style={{
|
|
bottom: `${app.state.height + 12 - viewY + app.state.offsetTop}px`,
|
|
left: `${viewX - app.state.offsetLeft}px`,
|
|
}}
|
|
onClick={() => {
|
|
flushSync(() => {
|
|
const groupIds = selectGroupsFromGivenElements(elements, app.state);
|
|
app.setState({
|
|
selectedElementIds: elements.reduce(
|
|
(acc, element) => ({
|
|
...acc,
|
|
[element.id]: true,
|
|
}),
|
|
{},
|
|
),
|
|
selectedGroupIds: groupIds,
|
|
activeLockedId: null,
|
|
});
|
|
});
|
|
app.actionManager.executeAction(actionToggleElementLock);
|
|
}}
|
|
title={t("labels.elementLock.unlock")}
|
|
>
|
|
{LockedIconFilled}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UnlockPopup;
|