feat: support disabling image tool (#6320)

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
David Luzar
2023-11-14 10:25:41 +01:00
committed by GitHub
parent 9d1d45a8ea
commit 9c425224c7
12 changed files with 136 additions and 20 deletions

View File

@ -13,7 +13,7 @@ import {
hasStrokeWidth,
} from "../scene";
import { SHAPES } from "../shapes";
import { AppClassProperties, UIAppState, Zoom } from "../types";
import { AppClassProperties, AppProps, UIAppState, Zoom } from "../types";
import { capitalizeString, isTransparent } from "../utils";
import Stack from "./Stack";
import { ToolButton } from "./ToolButton";
@ -218,10 +218,12 @@ export const ShapesSwitcher = ({
activeTool,
appState,
app,
UIOptions,
}: {
activeTool: UIAppState["activeTool"];
appState: UIAppState;
app: AppClassProperties;
UIOptions: AppProps["UIOptions"];
}) => {
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
@ -232,6 +234,14 @@ export const ShapesSwitcher = ({
return (
<>
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
if (
UIOptions.tools?.[
value as Extract<typeof value, keyof AppProps["UIOptions"]["tools"]>
] === false
) {
return null;
}
const label = t(`toolBar.${value}`);
const letter =
key && capitalizeString(typeof key === "string" ? key : key[0]);

View File

@ -341,6 +341,7 @@ import {
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import { ImageSceneDataError } from "../errors";
import {
getSnapLinesAtPointer,
snapDraggedElements,
@ -2272,6 +2273,11 @@ class App extends React.Component<AppProps, AppState> {
// prefer spreadsheet data over image file (MS Office/Libre Office)
if (isSupportedImageFile(file) && !data.spreadsheet) {
if (!this.isToolSupported("image")) {
this.setState({ errorMessage: t("errors.imageToolNotSupported") });
return;
}
const imageElement = this.createImageElement({ sceneX, sceneY });
this.insertImageElement(imageElement, file);
this.initializeImageDimensions(imageElement);
@ -2477,7 +2483,8 @@ class App extends React.Component<AppProps, AppState> {
) {
if (
!isPlainPaste &&
mixedContent.some((node) => node.type === "imageUrl")
mixedContent.some((node) => node.type === "imageUrl") &&
this.isToolSupported("image")
) {
const imageURLs = mixedContent
.filter((node) => node.type === "imageUrl")
@ -3284,6 +3291,16 @@ class App extends React.Component<AppProps, AppState> {
}
});
// We purposely widen the `tool` type so this helper can be called with
// any tool without having to type check it
private isToolSupported = <T extends ToolType | "custom">(tool: T) => {
return (
this.props.UIOptions.tools?.[
tool as Extract<T, keyof AppProps["UIOptions"]["tools"]>
] !== false
);
};
setActiveTool = (
tool: (
| (
@ -3296,6 +3313,13 @@ class App extends React.Component<AppProps, AppState> {
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {
if (!this.isToolSupported(tool.type)) {
console.warn(
`"${tool.type}" tool is disabled via "UIOptions.canvasActions.tools.${tool.type}"`,
);
return;
}
const nextActiveTool = updateActiveTool(this.state, tool);
if (nextActiveTool.type === "hand") {
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
@ -7479,6 +7503,13 @@ class App extends React.Component<AppProps, AppState> {
imageFile: File,
showCursorImagePreview?: boolean,
) => {
// we should be handling all cases upstream, but in case we forget to handle
// a future case, let's throw here
if (!this.isToolSupported("image")) {
this.setState({ errorMessage: t("errors.imageToolNotSupported") });
return;
}
this.scene.addNewElement(imageElement);
try {
@ -7863,7 +7894,10 @@ class App extends React.Component<AppProps, AppState> {
);
try {
if (isSupportedImageFile(file)) {
// if image tool not supported, don't show an error here and let it fall
// through so we still support importing scene data from images. If no
// scene data encoded, we'll show an error then
if (isSupportedImageFile(file) && this.isToolSupported("image")) {
// first attempt to decode scene from the image if it's embedded
// ---------------------------------------------------------------------
@ -7991,6 +8025,17 @@ class App extends React.Component<AppProps, AppState> {
});
}
} catch (error: any) {
if (
error instanceof ImageSceneDataError &&
error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
!this.isToolSupported("image")
) {
this.setState({
isLoading: false,
errorMessage: t("errors.imageToolNotSupported"),
});
return;
}
this.setState({ isLoading: false, errorMessage: error.message });
}
};

View File

@ -280,6 +280,7 @@ const LayerUI = ({
<ShapesSwitcher
appState={appState}
activeTool={appState.activeTool}
UIOptions={UIOptions}
app={app}
/>
</Stack.Row>
@ -470,6 +471,7 @@ const LayerUI = ({
renderSidebars={renderSidebars}
device={device}
renderWelcomeScreen={renderWelcomeScreen}
UIOptions={UIOptions}
/>
)}
{!device.editor.isMobile && (

View File

@ -1,6 +1,7 @@
import React from "react";
import {
AppClassProperties,
AppProps,
AppState,
Device,
ExcalidrawProps,
@ -45,6 +46,7 @@ type MobileMenuProps = {
renderSidebars: () => JSX.Element | null;
device: Device;
renderWelcomeScreen: boolean;
UIOptions: AppProps["UIOptions"];
app: AppClassProperties;
};
@ -62,6 +64,7 @@ export const MobileMenu = ({
renderSidebars,
device,
renderWelcomeScreen,
UIOptions,
app,
}: MobileMenuProps) => {
const {
@ -83,6 +86,7 @@ export const MobileMenu = ({
<ShapesSwitcher
appState={appState}
activeTool={appState.activeTool}
UIOptions={UIOptions}
app={app}
/>
</Stack.Row>