improve discoverability
This commit is contained in:
parent
3f00762a77
commit
80ff1562b8
56
packages/excalidraw/actions/actionCropEditor.tsx
Normal file
56
packages/excalidraw/actions/actionCropEditor.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { register } from "./register";
|
||||||
|
import { cropIcon } from "../components/icons";
|
||||||
|
import { StoreAction } from "../store";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { isImageElement } from "../element/typeChecks";
|
||||||
|
import { ExcalidrawImageElement } from "../element/types";
|
||||||
|
|
||||||
|
export const actionToggleCropEditor = register({
|
||||||
|
name: "cropEditor",
|
||||||
|
label: "helpDialog.cropStart",
|
||||||
|
icon: cropIcon,
|
||||||
|
paletteName: "Finish image cropping",
|
||||||
|
viewMode: true,
|
||||||
|
trackEvent: { category: "menu" },
|
||||||
|
keywords: ["image", "crop"],
|
||||||
|
perform(elements, appState, _, app) {
|
||||||
|
const selectedElement = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
includeBoundTextElement: true,
|
||||||
|
})[0] as ExcalidrawImageElement;
|
||||||
|
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
isCropping: false,
|
||||||
|
croppingElement: selectedElement,
|
||||||
|
},
|
||||||
|
storeAction: StoreAction.CAPTURE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
predicate: (elements, appState, _, app) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
|
if (
|
||||||
|
!appState.croppingElement &&
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isImageElement(selectedElements[0])
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData, app }) => {
|
||||||
|
const label = t("helpDialog.cropStart");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={cropIcon}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@ -88,3 +88,5 @@ export { actionToggleElementLock } from "./actionElementLock";
|
|||||||
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
||||||
|
|
||||||
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
||||||
|
|
||||||
|
export { actionToggleCropEditor } from "./actionCropEditor";
|
||||||
|
@ -138,7 +138,8 @@ export type ActionName =
|
|||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "autoResize"
|
| "autoResize"
|
||||||
| "elementStats"
|
| "elementStats"
|
||||||
| "searchMenu";
|
| "searchMenu"
|
||||||
|
| "cropEditor";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -26,6 +26,7 @@ import { trackEvent } from "../analytics";
|
|||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
@ -127,6 +128,11 @@ export const SelectedShapeActions = ({
|
|||||||
isLinearElement(targetElements[0]) &&
|
isLinearElement(targetElements[0]) &&
|
||||||
!isElbowArrow(targetElements[0]);
|
!isElbowArrow(targetElements[0]);
|
||||||
|
|
||||||
|
const showCropEditorAction =
|
||||||
|
!appState.croppingElement &&
|
||||||
|
targetElements.length === 1 &&
|
||||||
|
isImageElement(targetElements[0]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
<div>
|
<div>
|
||||||
@ -245,6 +251,7 @@ export const SelectedShapeActions = ({
|
|||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
|
{showCropEditorAction && renderAction("cropEditor")}
|
||||||
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -279,6 +279,7 @@ function CommandPaletteInner({
|
|||||||
actionManager.actions.increaseFontSize,
|
actionManager.actions.increaseFontSize,
|
||||||
actionManager.actions.decreaseFontSize,
|
actionManager.actions.decreaseFontSize,
|
||||||
actionManager.actions.toggleLinearEditor,
|
actionManager.actions.toggleLinearEditor,
|
||||||
|
actionManager.actions.cropEditor,
|
||||||
actionLink,
|
actionLink,
|
||||||
].map((action: Action) =>
|
].map((action: Action) =>
|
||||||
actionToCommand(
|
actionToCommand(
|
||||||
|
@ -222,6 +222,16 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
]}
|
]}
|
||||||
isOr={false}
|
isOr={false}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropStart")}
|
||||||
|
shortcuts={[t("helpDialog.doubleClick"), getShortcutKey("Enter")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropFinish")}
|
||||||
|
shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("helpDialog.preventBinding")}
|
label={t("helpDialog.preventBinding")}
|
||||||
|
@ -100,6 +100,14 @@ const getHints = ({
|
|||||||
return t("hints.text_editing");
|
return t("hints.text_editing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.croppingElement) {
|
||||||
|
return t("hints.leaveCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
|
return t("hints.enterCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTool.type === "selection") {
|
if (activeTool.type === "selection") {
|
||||||
if (
|
if (
|
||||||
appState.selectionElement &&
|
appState.selectionElement &&
|
||||||
|
@ -2147,3 +2147,12 @@ export const upIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const cropIcon = createIcon(
|
||||||
|
<g strokeWidth="1.25">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8 5v10a1 1 0 0 0 1 1h10" />
|
||||||
|
<path d="M5 8h10a1 1 0 0 1 1 1v10" />
|
||||||
|
</g>,
|
||||||
|
modifiedTablerIconProps,
|
||||||
|
);
|
||||||
|
@ -328,7 +328,9 @@
|
|||||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
||||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping"
|
"disableSnapping": "Hold CtrlOrCmd to disable snapping",
|
||||||
|
"enterCropEditor": "Double click the image or press ENTER to crop the image",
|
||||||
|
"leaveCropEditor": "Click outside the image or press ENTER or ESCAPE to finish cropping"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "Cannot show preview",
|
"cannotShowPreview": "Cannot show preview",
|
||||||
@ -399,7 +401,9 @@
|
|||||||
"zoomToSelection": "Zoom to selection",
|
"zoomToSelection": "Zoom to selection",
|
||||||
"toggleElementLock": "Lock/unlock selection",
|
"toggleElementLock": "Lock/unlock selection",
|
||||||
"movePageUpDown": "Move page up/down",
|
"movePageUpDown": "Move page up/down",
|
||||||
"movePageLeftRight": "Move page left/right"
|
"movePageLeftRight": "Move page left/right",
|
||||||
|
"cropStart": "Crop image",
|
||||||
|
"cropFinish": "Finish image cropping"
|
||||||
},
|
},
|
||||||
"clearCanvasDialog": {
|
"clearCanvasDialog": {
|
||||||
"title": "Clear canvas"
|
"title": "Clear canvas"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user