mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Toggle shapeSnap
This commit is contained in:
parent
452373d769
commit
5ac50bdc88
8 changed files with 188 additions and 113 deletions
|
@ -121,6 +121,8 @@ import {
|
||||||
ArrowheadCrowfootIcon,
|
ArrowheadCrowfootIcon,
|
||||||
ArrowheadCrowfootOneIcon,
|
ArrowheadCrowfootOneIcon,
|
||||||
ArrowheadCrowfootOneOrManyIcon,
|
ArrowheadCrowfootOneOrManyIcon,
|
||||||
|
snapShapeEnabledIcon,
|
||||||
|
snapShapeDisabledIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
|
|
||||||
import { Fonts } from "../fonts";
|
import { Fonts } from "../fonts";
|
||||||
|
@ -1818,3 +1820,41 @@ export const actionChangeArrowType = register({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionToggleShapeSnap = register({
|
||||||
|
name: "toggleShapeSnap",
|
||||||
|
label: "Toggle Snap to Shape",
|
||||||
|
trackEvent: false,
|
||||||
|
perform: (elements, appState) => {
|
||||||
|
return {
|
||||||
|
elements,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
isShapeSnapEnabled: !appState.isShapeSnapEnabled,
|
||||||
|
},
|
||||||
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
|
<fieldset>
|
||||||
|
<legend>{t("labels.shapeSnap")}</legend>
|
||||||
|
<ButtonIconSelect
|
||||||
|
group="button"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
text: t("labels.shapeSnapDisable"),
|
||||||
|
icon: snapShapeDisabledIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
text: t("labels.shapeSnapEnable"),
|
||||||
|
icon: snapShapeEnabledIcon,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={appState.isShapeSnapEnabled}
|
||||||
|
onChange={(value) => updateData(value)}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
|
@ -125,6 +125,7 @@ export type ActionName =
|
||||||
| "toggleLinearEditor"
|
| "toggleLinearEditor"
|
||||||
| "toggleEraserTool"
|
| "toggleEraserTool"
|
||||||
| "toggleHandTool"
|
| "toggleHandTool"
|
||||||
|
| "toggleShapeSnap"
|
||||||
| "selectAllElementsInFrame"
|
| "selectAllElementsInFrame"
|
||||||
| "removeAllElementsFromFrame"
|
| "removeAllElementsFromFrame"
|
||||||
| "updateFrameRendering"
|
| "updateFrameRendering"
|
||||||
|
|
|
@ -141,113 +141,113 @@ const APP_STATE_STORAGE_CONF = (<
|
||||||
T extends Record<keyof AppState, Values>,
|
T extends Record<keyof AppState, Values>,
|
||||||
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
|
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
|
||||||
config)({
|
config)({
|
||||||
showWelcomeScreen: { browser: true, export: false, server: false },
|
showWelcomeScreen: { browser: true, export: false, server: false },
|
||||||
theme: { browser: true, export: false, server: false },
|
theme: { browser: true, export: false, server: false },
|
||||||
collaborators: { browser: false, export: false, server: false },
|
collaborators: { browser: false, export: false, server: false },
|
||||||
currentChartType: { browser: true, export: false, server: false },
|
currentChartType: { browser: true, export: false, server: false },
|
||||||
currentItemBackgroundColor: { browser: true, export: false, server: false },
|
currentItemBackgroundColor: { browser: true, export: false, server: false },
|
||||||
currentItemEndArrowhead: { browser: true, export: false, server: false },
|
currentItemEndArrowhead: { browser: true, export: false, server: false },
|
||||||
currentItemFillStyle: { browser: true, export: false, server: false },
|
currentItemFillStyle: { browser: true, export: false, server: false },
|
||||||
currentItemFontFamily: { browser: true, export: false, server: false },
|
currentItemFontFamily: { browser: true, export: false, server: false },
|
||||||
currentItemFontSize: { browser: true, export: false, server: false },
|
currentItemFontSize: { browser: true, export: false, server: false },
|
||||||
currentItemRoundness: {
|
currentItemRoundness: {
|
||||||
browser: true,
|
browser: true,
|
||||||
export: false,
|
export: false,
|
||||||
server: false,
|
server: false,
|
||||||
},
|
},
|
||||||
currentItemArrowType: {
|
currentItemArrowType: {
|
||||||
browser: true,
|
browser: true,
|
||||||
export: false,
|
export: false,
|
||||||
server: false,
|
server: false,
|
||||||
},
|
},
|
||||||
currentItemOpacity: { browser: true, export: false, server: false },
|
currentItemOpacity: { browser: true, export: false, server: false },
|
||||||
currentItemRoughness: { browser: true, export: false, server: false },
|
currentItemRoughness: { browser: true, export: false, server: false },
|
||||||
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeColor: { browser: true, export: false, server: false },
|
currentItemStrokeColor: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeWidth: { browser: true, export: false, server: false },
|
currentItemStrokeWidth: { browser: true, export: false, server: false },
|
||||||
currentItemTextAlign: { browser: true, export: false, server: false },
|
currentItemTextAlign: { browser: true, export: false, server: false },
|
||||||
currentHoveredFontFamily: { browser: false, export: false, server: false },
|
currentHoveredFontFamily: { browser: false, export: false, server: false },
|
||||||
cursorButton: { browser: true, export: false, server: false },
|
cursorButton: { browser: true, export: false, server: false },
|
||||||
activeEmbeddable: { browser: false, export: false, server: false },
|
activeEmbeddable: { browser: false, export: false, server: false },
|
||||||
newElement: { browser: false, export: false, server: false },
|
newElement: { browser: false, export: false, server: false },
|
||||||
editingTextElement: { browser: false, export: false, server: false },
|
editingTextElement: { browser: false, export: false, server: false },
|
||||||
editingGroupId: { browser: true, export: false, server: false },
|
editingGroupId: { browser: true, export: false, server: false },
|
||||||
editingLinearElement: { browser: false, export: false, server: false },
|
editingLinearElement: { browser: false, export: false, server: false },
|
||||||
activeTool: { browser: true, export: false, server: false },
|
activeTool: { browser: true, export: false, server: false },
|
||||||
penMode: { browser: true, export: false, server: false },
|
penMode: { browser: true, export: false, server: false },
|
||||||
penDetected: { browser: true, export: false, server: false },
|
penDetected: { browser: true, export: false, server: false },
|
||||||
errorMessage: { browser: false, export: false, server: false },
|
errorMessage: { browser: false, export: false, server: false },
|
||||||
exportBackground: { browser: true, export: false, server: false },
|
exportBackground: { browser: true, export: false, server: false },
|
||||||
exportEmbedScene: { browser: true, export: false, server: false },
|
exportEmbedScene: { browser: true, export: false, server: false },
|
||||||
exportScale: { browser: true, export: false, server: false },
|
exportScale: { browser: true, export: false, server: false },
|
||||||
exportWithDarkMode: { browser: true, export: false, server: false },
|
exportWithDarkMode: { browser: true, export: false, server: false },
|
||||||
fileHandle: { browser: false, export: false, server: false },
|
fileHandle: { browser: false, export: false, server: false },
|
||||||
gridSize: { browser: true, export: true, server: true },
|
gridSize: { browser: true, export: true, server: true },
|
||||||
gridStep: { browser: true, export: true, server: true },
|
gridStep: { browser: true, export: true, server: true },
|
||||||
gridModeEnabled: { browser: true, export: true, server: true },
|
gridModeEnabled: { browser: true, export: true, server: true },
|
||||||
height: { browser: false, export: false, server: false },
|
height: { browser: false, export: false, server: false },
|
||||||
isBindingEnabled: { browser: false, export: false, server: false },
|
isBindingEnabled: { browser: false, export: false, server: false },
|
||||||
isShapeSnapEnabled: { browser: false, export: false, server: false },
|
isShapeSnapEnabled: { browser: true, export: false, server: false },
|
||||||
defaultSidebarDockedPreference: {
|
defaultSidebarDockedPreference: {
|
||||||
browser: true,
|
browser: true,
|
||||||
export: false,
|
export: false,
|
||||||
server: false,
|
server: false,
|
||||||
},
|
},
|
||||||
isLoading: { browser: false, export: false, server: false },
|
isLoading: { browser: false, export: false, server: false },
|
||||||
isResizing: { browser: false, export: false, server: false },
|
isResizing: { browser: false, export: false, server: false },
|
||||||
isRotating: { browser: false, export: false, server: false },
|
isRotating: { browser: false, export: false, server: false },
|
||||||
lastPointerDownWith: { browser: true, export: false, server: false },
|
lastPointerDownWith: { browser: true, export: false, server: false },
|
||||||
multiElement: { browser: false, export: false, server: false },
|
multiElement: { browser: false, export: false, server: false },
|
||||||
name: { browser: true, export: false, server: false },
|
name: { browser: true, export: false, server: false },
|
||||||
offsetLeft: { browser: false, export: false, server: false },
|
offsetLeft: { browser: false, export: false, server: false },
|
||||||
offsetTop: { browser: false, export: false, server: false },
|
offsetTop: { browser: false, export: false, server: false },
|
||||||
contextMenu: { browser: false, export: false, server: false },
|
contextMenu: { browser: false, export: false, server: false },
|
||||||
openMenu: { browser: true, export: false, server: false },
|
openMenu: { browser: true, export: false, server: false },
|
||||||
openPopup: { browser: false, export: false, server: false },
|
openPopup: { browser: false, export: false, server: false },
|
||||||
openSidebar: { browser: true, export: false, server: false },
|
openSidebar: { browser: true, export: false, server: false },
|
||||||
openDialog: { browser: false, export: false, server: false },
|
openDialog: { browser: false, export: false, server: false },
|
||||||
pasteDialog: { browser: false, export: false, server: false },
|
pasteDialog: { browser: false, export: false, server: false },
|
||||||
previousSelectedElementIds: { browser: true, export: false, server: false },
|
previousSelectedElementIds: { browser: true, export: false, server: false },
|
||||||
resizingElement: { browser: false, export: false, server: false },
|
resizingElement: { browser: false, export: false, server: false },
|
||||||
scrolledOutside: { browser: true, export: false, server: false },
|
scrolledOutside: { browser: true, export: false, server: false },
|
||||||
scrollX: { browser: true, export: false, server: false },
|
scrollX: { browser: true, export: false, server: false },
|
||||||
scrollY: { browser: true, export: false, server: false },
|
scrollY: { browser: true, export: false, server: false },
|
||||||
selectedElementIds: { browser: true, export: false, server: false },
|
selectedElementIds: { browser: true, export: false, server: false },
|
||||||
hoveredElementIds: { browser: false, export: false, server: false },
|
hoveredElementIds: { browser: false, export: false, server: false },
|
||||||
selectedGroupIds: { browser: true, export: false, server: false },
|
selectedGroupIds: { browser: true, export: false, server: false },
|
||||||
selectedElementsAreBeingDragged: {
|
selectedElementsAreBeingDragged: {
|
||||||
browser: false,
|
browser: false,
|
||||||
export: false,
|
export: false,
|
||||||
server: false,
|
server: false,
|
||||||
},
|
},
|
||||||
selectionElement: { browser: false, export: false, server: false },
|
selectionElement: { browser: false, export: false, server: false },
|
||||||
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
|
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
|
||||||
stats: { browser: true, export: false, server: false },
|
stats: { browser: true, export: false, server: false },
|
||||||
startBoundElement: { browser: false, export: false, server: false },
|
startBoundElement: { browser: false, export: false, server: false },
|
||||||
suggestedBindings: { browser: false, export: false, server: false },
|
suggestedBindings: { browser: false, export: false, server: false },
|
||||||
frameRendering: { browser: false, export: false, server: false },
|
frameRendering: { browser: false, export: false, server: false },
|
||||||
frameToHighlight: { browser: false, export: false, server: false },
|
frameToHighlight: { browser: false, export: false, server: false },
|
||||||
editingFrame: { browser: false, export: false, server: false },
|
editingFrame: { browser: false, export: false, server: false },
|
||||||
elementsToHighlight: { browser: false, export: false, server: false },
|
elementsToHighlight: { browser: false, export: false, server: false },
|
||||||
toast: { browser: false, export: false, server: false },
|
toast: { browser: false, export: false, server: false },
|
||||||
viewBackgroundColor: { browser: true, export: true, server: true },
|
viewBackgroundColor: { browser: true, export: true, server: true },
|
||||||
width: { browser: false, export: false, server: false },
|
width: { browser: false, export: false, server: false },
|
||||||
zenModeEnabled: { browser: true, export: false, server: false },
|
zenModeEnabled: { browser: true, export: false, server: false },
|
||||||
zoom: { browser: true, export: false, server: false },
|
zoom: { browser: true, export: false, server: false },
|
||||||
viewModeEnabled: { browser: false, export: false, server: false },
|
viewModeEnabled: { browser: false, export: false, server: false },
|
||||||
pendingImageElementId: { browser: false, export: false, server: false },
|
pendingImageElementId: { browser: false, export: false, server: false },
|
||||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||||
selectedLinearElement: { browser: true, export: false, server: false },
|
selectedLinearElement: { browser: true, export: false, server: false },
|
||||||
snapLines: { browser: false, export: false, server: false },
|
snapLines: { browser: false, export: false, server: false },
|
||||||
originSnapOffset: { browser: false, export: false, server: false },
|
originSnapOffset: { browser: false, export: false, server: false },
|
||||||
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
||||||
userToFollow: { browser: false, export: false, server: false },
|
userToFollow: { browser: false, export: false, server: false },
|
||||||
followedBy: { browser: false, export: false, server: false },
|
followedBy: { browser: false, export: false, server: false },
|
||||||
isCropping: { browser: false, export: false, server: false },
|
isCropping: { browser: false, export: false, server: false },
|
||||||
croppingElementId: { browser: false, export: false, server: false },
|
croppingElementId: { browser: false, export: false, server: false },
|
||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
ExportType extends "export" | "browser" | "server",
|
ExportType extends "export" | "browser" | "server",
|
||||||
|
@ -257,8 +257,8 @@ const _clearAppStateForStorage = <
|
||||||
) => {
|
) => {
|
||||||
type ExportableKeys = {
|
type ExportableKeys = {
|
||||||
[K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
|
[K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
|
||||||
? K
|
? K
|
||||||
: never;
|
: never;
|
||||||
}[keyof typeof APP_STATE_STORAGE_CONF];
|
}[keyof typeof APP_STATE_STORAGE_CONF];
|
||||||
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
|
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
|
||||||
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
|
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
|
||||||
|
|
|
@ -169,9 +169,12 @@ export const SelectedShapeActions = ({
|
||||||
renderAction("changeStrokeWidth")}
|
renderAction("changeStrokeWidth")}
|
||||||
|
|
||||||
{(appState.activeTool.type === "freedraw" ||
|
{(appState.activeTool.type === "freedraw" ||
|
||||||
targetElements.some((element) => element.type === "freedraw")) &&
|
targetElements.some((element) => element.type === "freedraw")) && (
|
||||||
renderAction("changeStrokeShape")}
|
<>
|
||||||
|
{renderAction("changeStrokeShape")}
|
||||||
|
{renderAction("toggleShapeSnap")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
{(hasStrokeStyle(appState.activeTool.type) ||
|
||||||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8959,7 +8959,19 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
if (this.state.isShapeSnapEnabled) {
|
if (this.state.isShapeSnapEnabled) {
|
||||||
const detectedElement = convertToShape(newElement);
|
const detectedElement = convertToShape(newElement);
|
||||||
|
|
||||||
if (detectedElement !== newElement) {
|
if (detectedElement !== newElement) {
|
||||||
|
if (detectedElement.type === "arrow") {
|
||||||
|
mutateElement(
|
||||||
|
detectedElement,
|
||||||
|
{
|
||||||
|
startArrowhead: this.state.currentItemStartArrowhead,
|
||||||
|
endArrowhead: this.state.currentItemEndArrowhead,
|
||||||
|
},
|
||||||
|
// TODO: Make arrows bind to nearby elements if possible
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.scene.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...this.scene
|
...this.scene
|
||||||
.getElementsIncludingDeleted()
|
.getElementsIncludingDeleted()
|
||||||
|
|
|
@ -1887,6 +1887,23 @@ export const eyeClosedIcon = createIcon(
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const snapShapeEnabledIcon = createIcon(
|
||||||
|
<g stroke="currentColor" fill="none" strokeWidth="1.5">
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||||
|
<circle cx="16" cy="8" r="1.5" fill="currentColor" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const snapShapeDisabledIcon = createIcon(
|
||||||
|
<g stroke="currentColor" fill="none" strokeWidth="1.5">
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||||
|
<line x1="4" y1="4" x2="20" y2="20" />
|
||||||
|
<line x1="4" y1="20" x2="20" y2="4" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
export const brainIcon = createIcon(
|
export const brainIcon = createIcon(
|
||||||
<g stroke="currentColor" fill="none">
|
<g stroke="currentColor" fill="none">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
|
|
@ -103,6 +103,9 @@
|
||||||
"loadingScene": "Loading scene…",
|
"loadingScene": "Loading scene…",
|
||||||
"loadScene": "Load scene from file",
|
"loadScene": "Load scene from file",
|
||||||
"align": "Align",
|
"align": "Align",
|
||||||
|
"shapeSnap": "Snap to shapes",
|
||||||
|
"shapeSnapDisable": "Disable snap to shapes",
|
||||||
|
"shapeSnapEnable": "Enable snap to shapes",
|
||||||
"alignTop": "Align top",
|
"alignTop": "Align top",
|
||||||
"alignBottom": "Align bottom",
|
"alignBottom": "Align bottom",
|
||||||
"alignLeft": "Align left",
|
"alignLeft": "Align left",
|
||||||
|
|
|
@ -390,7 +390,6 @@ export const convertToShape = (
|
||||||
return newArrowElement({
|
return newArrowElement({
|
||||||
...freeDrawElement,
|
...freeDrawElement,
|
||||||
type: recognizedShape.type,
|
type: recognizedShape.type,
|
||||||
endArrowhead: "arrow", // TODO: Get correct state
|
|
||||||
points: [
|
points: [
|
||||||
recognizedShape.simplified[0],
|
recognizedShape.simplified[0],
|
||||||
recognizedShape.simplified[recognizedShape.simplified.length - 2]
|
recognizedShape.simplified[recognizedShape.simplified.length - 2]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue