Loop Lock/Unlock
This commit is contained in:
parent
6e655cdb24
commit
54b4a304c9
@ -475,3 +475,5 @@ export enum UserIdleState {
|
|||||||
AWAY = "away",
|
AWAY = "away",
|
||||||
IDLE = "idle",
|
IDLE = "idle",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MIN_LOOP_LOCK_DISTANCE = 20;
|
||||||
|
@ -67,6 +67,8 @@ import {
|
|||||||
|
|
||||||
import { getLockedLinearCursorAlignSize } from "./sizeHelpers";
|
import { getLockedLinearCursorAlignSize } from "./sizeHelpers";
|
||||||
|
|
||||||
|
import { isLineElement } from "./typeChecks";
|
||||||
|
|
||||||
import type Scene from "./Scene";
|
import type Scene from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
@ -1249,6 +1251,16 @@ export class LinearElementEditor {
|
|||||||
scene: Scene,
|
scene: Scene,
|
||||||
pointIndices: readonly number[],
|
pointIndices: readonly number[],
|
||||||
) {
|
) {
|
||||||
|
// Handle loop lock behavior
|
||||||
|
if (isLineElement(element) && element.loopLock) {
|
||||||
|
if (
|
||||||
|
pointIndices.includes(0) ||
|
||||||
|
pointIndices.includes(element.points.length - 1)
|
||||||
|
) {
|
||||||
|
scene.mutateElement(element, { loopLock: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
|
|
||||||
@ -1315,6 +1327,31 @@ export class LinearElementEditor {
|
|||||||
) {
|
) {
|
||||||
const { points } = element;
|
const { points } = element;
|
||||||
|
|
||||||
|
// Handle loop lock behavior
|
||||||
|
if (isLineElement(element) && element.loopLock) {
|
||||||
|
const firstPointUpdate = targetPoints.find(({ index }) => index === 0);
|
||||||
|
const lastPointUpdate = targetPoints.find(
|
||||||
|
({ index }) => index === points.length - 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (firstPointUpdate) {
|
||||||
|
targetPoints.push({
|
||||||
|
index: points.length - 1,
|
||||||
|
point: pointFrom(
|
||||||
|
firstPointUpdate.point[0],
|
||||||
|
firstPointUpdate.point[1],
|
||||||
|
),
|
||||||
|
isDragging: firstPointUpdate.isDragging,
|
||||||
|
});
|
||||||
|
} else if (lastPointUpdate) {
|
||||||
|
targetPoints.push({
|
||||||
|
index: 0,
|
||||||
|
point: pointFrom(lastPointUpdate.point[0], lastPointUpdate.point[1]),
|
||||||
|
isDragging: lastPointUpdate.isDragging,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// in case we're moving start point, instead of modifying its position
|
// in case we're moving start point, instead of modifying its position
|
||||||
// which would break the invariant of it being at [0,0], we move
|
// which would break the invariant of it being at [0,0], we move
|
||||||
// all the other points in the opposite direction by delta to
|
// all the other points in the opposite direction by delta to
|
||||||
|
@ -25,6 +25,7 @@ import type {
|
|||||||
ExcalidrawMagicFrameElement,
|
ExcalidrawMagicFrameElement,
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
|
ExcalidrawLineElement,
|
||||||
PointBinding,
|
PointBinding,
|
||||||
FixedPointBinding,
|
FixedPointBinding,
|
||||||
ExcalidrawFlowchartNodeElement,
|
ExcalidrawFlowchartNodeElement,
|
||||||
@ -356,3 +357,9 @@ export const isBounds = (box: unknown): box is Bounds =>
|
|||||||
typeof box[1] === "number" &&
|
typeof box[1] === "number" &&
|
||||||
typeof box[2] === "number" &&
|
typeof box[2] === "number" &&
|
||||||
typeof box[3] === "number";
|
typeof box[3] === "number";
|
||||||
|
|
||||||
|
export const isLineElement = (
|
||||||
|
element?: ExcalidrawElement | null,
|
||||||
|
): element is ExcalidrawLineElement => {
|
||||||
|
return element != null && element.type === "line";
|
||||||
|
};
|
||||||
|
@ -321,6 +321,12 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
|
|||||||
endArrowhead: Arrowhead | null;
|
endArrowhead: Arrowhead | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ExcalidrawLineElement = ExcalidrawLinearElement &
|
||||||
|
Readonly<{
|
||||||
|
type: "line";
|
||||||
|
loopLock: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type FixedSegment = {
|
export type FixedSegment = {
|
||||||
start: LocalPoint;
|
start: LocalPoint;
|
||||||
end: LocalPoint;
|
end: LocalPoint;
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||||
|
import {
|
||||||
import { isElbowArrow, isLinearElement } from "@excalidraw/element/typeChecks";
|
isElbowArrow,
|
||||||
|
isLinearElement,
|
||||||
|
isLineElement,
|
||||||
|
} from "@excalidraw/element/typeChecks";
|
||||||
import { arrayToMap } from "@excalidraw/common";
|
import { arrayToMap } from "@excalidraw/common";
|
||||||
|
import { MIN_LOOP_LOCK_DISTANCE } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
import { pointFrom } from "@excalidraw/math";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawLineElement,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette";
|
import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { lineEditorIcon } from "../components/icons";
|
import {
|
||||||
|
lineEditorIcon,
|
||||||
|
LoopUnlockedIcon,
|
||||||
|
LoopLockedIcon,
|
||||||
|
} from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { CaptureUpdateAction } from "../store";
|
import { CaptureUpdateAction } from "../store";
|
||||||
|
|
||||||
@ -82,3 +93,120 @@ export const actionToggleLinearEditor = register({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionToggleLoopLock = register({
|
||||||
|
name: "toggleLoopLock",
|
||||||
|
category: DEFAULT_CATEGORIES.elements,
|
||||||
|
label: (elements, appState, app) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if all selected elements are locked
|
||||||
|
const allLocked =
|
||||||
|
selectedElements.length > 0 &&
|
||||||
|
selectedElements.every(
|
||||||
|
(element) => isLineElement(element) && element.loopLock,
|
||||||
|
);
|
||||||
|
|
||||||
|
return allLocked ? "labels.loopLock.unlock" : "labels.loopLock.lock";
|
||||||
|
},
|
||||||
|
trackEvent: {
|
||||||
|
category: "element",
|
||||||
|
},
|
||||||
|
predicate: (elements, appState, _, app) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectedElements.length > 0 &&
|
||||||
|
selectedElements.every(
|
||||||
|
(element) => isLineElement(element) && element.points.length >= 3,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
perform(elements, appState, _, app) {
|
||||||
|
const selectedElements = app.scene
|
||||||
|
.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
})
|
||||||
|
.filter((element) => isLineElement(element)) as ExcalidrawLineElement[];
|
||||||
|
|
||||||
|
if (!selectedElements.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should lock or unlock based on current state
|
||||||
|
// If all elements are locked, unlock all. Otherwise, lock all.
|
||||||
|
const allLocked = selectedElements.every((element) => element.loopLock);
|
||||||
|
const newLoopLockState = !allLocked;
|
||||||
|
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
const updatedPoints = [...element.points];
|
||||||
|
|
||||||
|
if (newLoopLockState) {
|
||||||
|
const firstPoint = updatedPoints[0];
|
||||||
|
const lastPoint = updatedPoints[updatedPoints.length - 1];
|
||||||
|
|
||||||
|
const distance = Math.hypot(
|
||||||
|
firstPoint[0] - lastPoint[0],
|
||||||
|
firstPoint[1] - lastPoint[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distance > MIN_LOOP_LOCK_DISTANCE) {
|
||||||
|
updatedPoints.push(pointFrom(firstPoint[0], firstPoint[1]));
|
||||||
|
} else {
|
||||||
|
updatedPoints[updatedPoints.length - 1] = pointFrom(
|
||||||
|
firstPoint[0],
|
||||||
|
firstPoint[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.scene.mutateElement(element, {
|
||||||
|
loopLock: newLoopLockState,
|
||||||
|
points: updatedPoints,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
appState,
|
||||||
|
elements,
|
||||||
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData, app }) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedElements.length === 0 ||
|
||||||
|
!selectedElements.every(
|
||||||
|
(element) => isLineElement(element) && element.points.length >= 3,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all are locked, show locked icon. Otherwise show unlocked
|
||||||
|
const allLocked = selectedElements.every(
|
||||||
|
(element) => isLineElement(element) && element.loopLock,
|
||||||
|
);
|
||||||
|
|
||||||
|
const label = t(
|
||||||
|
allLocked ? "labels.loopLock.unlock" : "labels.loopLock.lock",
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={allLocked ? LoopLockedIcon : LoopUnlockedIcon}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -141,7 +141,8 @@ export type ActionName =
|
|||||||
| "cropEditor"
|
| "cropEditor"
|
||||||
| "wrapSelectionInFrame"
|
| "wrapSelectionInFrame"
|
||||||
| "toggleLassoTool"
|
| "toggleLassoTool"
|
||||||
| "toggleShapeSwitch";
|
| "toggleShapeSwitch"
|
||||||
|
| "toggleLoopLock";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
isImageElement,
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
|
isLineElement,
|
||||||
} from "@excalidraw/element/typeChecks";
|
} from "@excalidraw/element/typeChecks";
|
||||||
|
|
||||||
import { hasStrokeColor, toolIsArrow } from "@excalidraw/element/comparisons";
|
import { hasStrokeColor, toolIsArrow } from "@excalidraw/element/comparisons";
|
||||||
@ -26,6 +27,7 @@ import { hasStrokeColor, toolIsArrow } from "@excalidraw/element/comparisons";
|
|||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawElementType,
|
ExcalidrawElementType,
|
||||||
|
ExcalidrawLineElement,
|
||||||
NonDeletedElementsMap,
|
NonDeletedElementsMap,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
@ -145,6 +147,17 @@ export const SelectedShapeActions = ({
|
|||||||
isLinearElement(targetElements[0]) &&
|
isLinearElement(targetElements[0]) &&
|
||||||
!isElbowArrow(targetElements[0]);
|
!isElbowArrow(targetElements[0]);
|
||||||
|
|
||||||
|
const showLoopLockAction =
|
||||||
|
targetElements.length > 0 &&
|
||||||
|
isLineElement(targetElements[0]) &&
|
||||||
|
targetElements.every(
|
||||||
|
(element) =>
|
||||||
|
isLineElement(element) &&
|
||||||
|
element.points.length >= 4 &&
|
||||||
|
element.loopLock ===
|
||||||
|
(targetElements[0] as ExcalidrawLineElement).loopLock,
|
||||||
|
);
|
||||||
|
|
||||||
const showCropEditorAction =
|
const showCropEditorAction =
|
||||||
!appState.croppingElementId &&
|
!appState.croppingElementId &&
|
||||||
targetElements.length === 1 &&
|
targetElements.length === 1 &&
|
||||||
@ -273,6 +286,7 @@ export const SelectedShapeActions = ({
|
|||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
{showCropEditorAction && renderAction("cropEditor")}
|
{showCropEditorAction && renderAction("cropEditor")}
|
||||||
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
||||||
|
{showLoopLockAction && renderAction("toggleLoopLock")}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
)}
|
)}
|
||||||
|
@ -129,6 +129,54 @@ export const PinIcon = createIcon(
|
|||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const LoopLockedIcon = createIcon(
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M7.11 11.11h5.17m-5.17 0h5.17m0 0c1.02 0 1.53.51 1.53 1.53m-1.53-1.53c1.02 0 1.53.51 1.53 1.53m0 0v3.07m0-3.07v3.07m0 0c0 1.03-.5 1.54-1.53 1.54m1.53-1.54c0 1.03-.5 1.54-1.53 1.54m0 0H7.11m5.17 0H7.11m0 0c-1.02 0-1.53-.5-1.53-1.54m1.53 1.54c-1.02 0-1.53-.5-1.53-1.54m0 0v-3.07m0 3.07v-3.07m0 0c0-1.02.51-1.53 1.53-1.53m-1.53 1.53c0-1.02.51-1.53 1.53-1.53M7.7 10.86c.07-.57-.1-2.84.43-3.44.55-.6 2.27-.72 2.82-.17.56.55.43 2.9.51 3.47m-3.76.14c.07-.57-.1-2.84.43-3.44.55-.6 2.27-.72 2.82-.17.56.55.43 2.9.51 3.47M10.63 14.55l-.01.06a.16.16 0 0 1-.04.07.43.43 0 0 1-.07.06l-.1.05-.11.04-.13.04-.15.03-.16.01-.16.01-.17-.01-.15-.01-.15-.03a.74.74 0 0 1-.14-.04l-.11-.04-.09-.05-.07-.06a.18.18 0 0 1-.05-.07l-.01-.06.01-.06a.18.18 0 0 1 .05-.07l.07-.05a.35.35 0 0 1 .09-.06l.11-.04a.74.74 0 0 1 .14-.04l.15-.03.15-.01.17-.01.16.01.16.01.15.03.13.04.11.04.1.06.07.05.04.07.01.06c.01.01.01-.01 0 0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="transparent"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M0 0h18.09M0 0h18.09m0 0v18.21m0-18.21v18.21m0 0H0m18.09 0H0m0 0V0m0 18.21V0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M3.13 6.27c.79-.86 2.9-4.72 4.7-5.18 1.8-.46 5.08 2.02 6.09 2.42M3.12 6.27c.8-.86 2.92-4.72 4.72-5.18 1.8-.46 5.07 2.02 6.08 2.42M4.17 8.01l-.02.27-.07.26c-.03.1-.07.17-.12.25-.04.08-.1.15-.15.22l-.2.2c-.07.05-.14.11-.22.15-.08.05-.16.1-.25.12l-.26.07a2.45 2.45 0 0 1-.55 0 1.42 1.42 0 0 1-.51-.19c-.08-.04-.15-.1-.22-.15a1.77 1.77 0 0 1-.35-.42c-.05-.08-.08-.16-.12-.25a1.97 1.97 0 0 1-.07-.26l-.02-.27.02-.28.07-.26c.04-.08.07-.17.12-.25a1.77 1.77 0 0 1 .57-.57c.08-.05.17-.08.25-.12l.26-.07a2.64 2.64 0 0 1 .55 0l.26.07c.09.04.17.07.25.12a1.77 1.77 0 0 1 .42.35c.05.07.11.14.15.22a1.42 1.42 0 0 1 .21.8c.01.03.01-.06 0 0M17.05 4.88l-.02.27-.07.26c-.03.09-.07.17-.12.25-.04.08-.1.15-.15.22l-.2.2-.22.15c-.08.05-.16.09-.25.12l-.26.07a2.45 2.45 0 0 1-.55 0 1.42 1.42 0 0 1-.51-.19c-.08-.04-.15-.1-.22-.15a1.77 1.77 0 0 1-.35-.42c-.05-.08-.08-.16-.12-.25a1.97 1.97 0 0 1-.07-.26l-.02-.27c0-.1 0-.2.02-.28l.07-.26c.04-.08.07-.17.12-.25a1.77 1.77 0 0 1 .57-.57c.08-.05.17-.08.25-.12l.26-.07a2.64 2.64 0 0 1 .55 0l.26.07c.09.04.17.07.25.12a1.77 1.77 0 0 1 .42.35l.15.22a1.42 1.42 0 0 1 .2.79c.02.04.02-.05 0 0M4.4 7.72c3.04-.7 6.06-1.42 9.5-2.22M4.4 7.72l9.5-2.22"
|
||||||
|
/>
|
||||||
|
</g>,
|
||||||
|
modifiedTablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LoopUnlockedIcon = createIcon(
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M6.74 11.12h5.17m-5.17 0h5.17m0 0c1.02 0 1.53.5 1.53 1.53m-1.53-1.53c1.02 0 1.53.5 1.53 1.53m0 0v3.07m0-3.07v3.07m0 0c0 1.03-.5 1.54-1.53 1.54m1.53-1.54c0 1.03-.5 1.54-1.53 1.54m0 0H6.74m5.17 0H6.74m0 0c-1.02 0-1.53-.51-1.53-1.54m1.53 1.54c-1.02 0-1.53-.51-1.53-1.54m0 0v-3.07m0 3.07v-3.07m0 0c0-1.02.51-1.53 1.53-1.53m-1.53 1.53c0-1.02.51-1.53 1.53-1.53M7.33 10.87c.08-.57-.08-2.8.45-3.44.54-.63 2.2-.53 2.76-.32.57.22.55 1.34.66 1.61m-3.87 2.15c.08-.57-.08-2.8.45-3.44.54-.63 2.2-.53 2.76-.32.57.22.55 1.34.66 1.61M10.26 14.56v.06a.16.16 0 0 1-.05.07.43.43 0 0 1-.07.06c-.03 0-.06.03-.1.05-.03 0-.07.03-.1.04l-.14.04-.15.03H9.5c-.05.02-.1.02-.16.02l-.17-.01-.15-.01-.15-.03a.74.74 0 0 1-.14-.04l-.1-.04-.1-.05-.07-.06a.18.18 0 0 1-.05-.07v-.12a.18.18 0 0 1 .05-.07l.07-.05a.35.35 0 0 1 .1-.06l.1-.04a.74.74 0 0 1 .14-.04l.15-.03.15-.01.17-.01h.16c.05 0 .11 0 .16.02l.15.03c.04 0 .1.02.13.04.04 0 .08.03.11.04l.1.06c.03 0 .05.03.07.05l.04.07.01.06c.01 0 .01-.01 0 0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="transparent"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M0 0h18.09M0 0h18.09m0 0v18.21m0-18.21v18.21m0 0H0m18.09 0H0m0 0V0m0 18.21V0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M3.13 6.28c.79-.86 2.9-4.72 4.7-5.18 1.8-.46 5.08 2.02 6.09 2.42M3.12 6.28c.8-.86 2.92-4.72 4.72-5.18 1.8-.46 5.07 2.02 6.08 2.42M4.17 8.02l-.02.27-.07.26c-.03.09-.07.17-.12.25-.04.08-.1.15-.15.22l-.2.2-.22.15c-.08.05-.16.09-.25.12l-.26.07a2.45 2.45 0 0 1-.55 0 1.42 1.42 0 0 1-.51-.19c-.08-.04-.15-.1-.22-.15a1.77 1.77 0 0 1-.35-.42c-.05-.08-.08-.16-.12-.25a1.97 1.97 0 0 1-.07-.26l-.02-.27.02-.28.07-.26c.04-.08.07-.17.12-.25a1.77 1.77 0 0 1 .57-.57c.08-.05.17-.08.25-.12l.26-.07a2.64 2.64 0 0 1 .55 0l.26.07c.09.04.17.07.25.12a1.77 1.77 0 0 1 .42.35c.05.07.11.14.15.22a1.42 1.42 0 0 1 .21.79c.01.04.01-.05 0 0M17.05 4.89l-.02.27-.07.26c-.03.09-.07.17-.12.25-.04.08-.1.15-.15.22l-.2.2-.22.15c-.08.05-.16.09-.25.12l-.26.07a2.45 2.45 0 0 1-.55 0 1.42 1.42 0 0 1-.51-.19c-.08-.04-.15-.1-.22-.15a1.77 1.77 0 0 1-.35-.42c-.05-.08-.08-.16-.12-.25a1.97 1.97 0 0 1-.07-.26l-.02-.27c0-.1 0-.2.02-.28l.07-.26c.04-.08.07-.17.12-.25a1.77 1.77 0 0 1 .57-.57c.08-.05.17-.08.25-.12l.26-.07a2.64 2.64 0 0 1 .55 0l.26.07c.09.04.17.07.25.12a1.77 1.77 0 0 1 .42.35l.15.22a1.42 1.42 0 0 1 .2.79c.02.04.02-.05 0 0"
|
||||||
|
/>
|
||||||
|
</g>,
|
||||||
|
modifiedTablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
// tabler-icons: lock-open (via Figma)
|
// tabler-icons: lock-open (via Figma)
|
||||||
export const UnlockedIcon = createIcon(
|
export const UnlockedIcon = createIcon(
|
||||||
<g>
|
<g>
|
||||||
|
@ -141,6 +141,10 @@
|
|||||||
"edit": "Edit line",
|
"edit": "Edit line",
|
||||||
"editArrow": "Edit arrow"
|
"editArrow": "Edit arrow"
|
||||||
},
|
},
|
||||||
|
"loopLock": {
|
||||||
|
"unlock": "Unlock loop",
|
||||||
|
"lock": "Lock loop"
|
||||||
|
},
|
||||||
"elementLock": {
|
"elementLock": {
|
||||||
"lock": "Lock",
|
"lock": "Lock",
|
||||||
"unlock": "Unlock",
|
"unlock": "Unlock",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user