|
@@ -341,6 +341,7 @@ import {
|
|
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
|
|
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
|
|
import { jotaiStore } from "../jotai";
|
|
import { jotaiStore } from "../jotai";
|
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
|
|
|
+import { ImageSceneDataError } from "../errors";
|
|
import {
|
|
import {
|
|
getSnapLinesAtPointer,
|
|
getSnapLinesAtPointer,
|
|
snapDraggedElements,
|
|
snapDraggedElements,
|
|
@@ -2272,6 +2273,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
// prefer spreadsheet data over image file (MS Office/Libre Office)
|
|
// prefer spreadsheet data over image file (MS Office/Libre Office)
|
|
if (isSupportedImageFile(file) && !data.spreadsheet) {
|
|
if (isSupportedImageFile(file) && !data.spreadsheet) {
|
|
|
|
+ if (!this.isToolSupported("image")) {
|
|
|
|
+ this.setState({ errorMessage: t("errors.imageToolNotSupported") });
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
const imageElement = this.createImageElement({ sceneX, sceneY });
|
|
const imageElement = this.createImageElement({ sceneX, sceneY });
|
|
this.insertImageElement(imageElement, file);
|
|
this.insertImageElement(imageElement, file);
|
|
this.initializeImageDimensions(imageElement);
|
|
this.initializeImageDimensions(imageElement);
|
|
@@ -2477,7 +2483,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
) {
|
|
) {
|
|
if (
|
|
if (
|
|
!isPlainPaste &&
|
|
!isPlainPaste &&
|
|
- mixedContent.some((node) => node.type === "imageUrl")
|
|
|
|
|
|
+ mixedContent.some((node) => node.type === "imageUrl") &&
|
|
|
|
+ this.isToolSupported("image")
|
|
) {
|
|
) {
|
|
const imageURLs = mixedContent
|
|
const imageURLs = mixedContent
|
|
.filter((node) => node.type === "imageUrl")
|
|
.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 = (
|
|
setActiveTool = (
|
|
tool: (
|
|
tool: (
|
|
| (
|
|
| (
|
|
@@ -3296,6 +3313,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
| { type: "custom"; customType: string }
|
|
| { type: "custom"; customType: string }
|
|
) & { locked?: boolean },
|
|
) & { 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);
|
|
const nextActiveTool = updateActiveTool(this.state, tool);
|
|
if (nextActiveTool.type === "hand") {
|
|
if (nextActiveTool.type === "hand") {
|
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
|
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
|
|
@@ -7479,6 +7503,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
imageFile: File,
|
|
imageFile: File,
|
|
showCursorImagePreview?: boolean,
|
|
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);
|
|
this.scene.addNewElement(imageElement);
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -7863,7 +7894,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
);
|
|
);
|
|
|
|
|
|
try {
|
|
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
|
|
// 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) {
|
|
} 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 });
|
|
this.setState({ isLoading: false, errorMessage: error.message });
|
|
}
|
|
}
|
|
};
|
|
};
|