feat: support disabling image tool (#6320)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
9d1d45a8ea
commit
9c425224c7
@ -13,7 +13,7 @@ import {
|
|||||||
hasStrokeWidth,
|
hasStrokeWidth,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import { SHAPES } from "../shapes";
|
import { SHAPES } from "../shapes";
|
||||||
import { AppClassProperties, UIAppState, Zoom } from "../types";
|
import { AppClassProperties, AppProps, UIAppState, Zoom } from "../types";
|
||||||
import { capitalizeString, isTransparent } from "../utils";
|
import { capitalizeString, isTransparent } from "../utils";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
@ -218,10 +218,12 @@ export const ShapesSwitcher = ({
|
|||||||
activeTool,
|
activeTool,
|
||||||
appState,
|
appState,
|
||||||
app,
|
app,
|
||||||
|
UIOptions,
|
||||||
}: {
|
}: {
|
||||||
activeTool: UIAppState["activeTool"];
|
activeTool: UIAppState["activeTool"];
|
||||||
appState: UIAppState;
|
appState: UIAppState;
|
||||||
app: AppClassProperties;
|
app: AppClassProperties;
|
||||||
|
UIOptions: AppProps["UIOptions"];
|
||||||
}) => {
|
}) => {
|
||||||
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
||||||
|
|
||||||
@ -232,6 +234,14 @@ export const ShapesSwitcher = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
{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 label = t(`toolBar.${value}`);
|
||||||
const letter =
|
const letter =
|
||||||
key && capitalizeString(typeof key === "string" ? key : key[0]);
|
key && capitalizeString(typeof key === "string" ? key : key[0]);
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -280,6 +280,7 @@ const LayerUI = ({
|
|||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
appState={appState}
|
appState={appState}
|
||||||
activeTool={appState.activeTool}
|
activeTool={appState.activeTool}
|
||||||
|
UIOptions={UIOptions}
|
||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
@ -470,6 +471,7 @@ const LayerUI = ({
|
|||||||
renderSidebars={renderSidebars}
|
renderSidebars={renderSidebars}
|
||||||
device={device}
|
device={device}
|
||||||
renderWelcomeScreen={renderWelcomeScreen}
|
renderWelcomeScreen={renderWelcomeScreen}
|
||||||
|
UIOptions={UIOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!device.editor.isMobile && (
|
{!device.editor.isMobile && (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
AppClassProperties,
|
AppClassProperties,
|
||||||
|
AppProps,
|
||||||
AppState,
|
AppState,
|
||||||
Device,
|
Device,
|
||||||
ExcalidrawProps,
|
ExcalidrawProps,
|
||||||
@ -45,6 +46,7 @@ type MobileMenuProps = {
|
|||||||
renderSidebars: () => JSX.Element | null;
|
renderSidebars: () => JSX.Element | null;
|
||||||
device: Device;
|
device: Device;
|
||||||
renderWelcomeScreen: boolean;
|
renderWelcomeScreen: boolean;
|
||||||
|
UIOptions: AppProps["UIOptions"];
|
||||||
app: AppClassProperties;
|
app: AppClassProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ export const MobileMenu = ({
|
|||||||
renderSidebars,
|
renderSidebars,
|
||||||
device,
|
device,
|
||||||
renderWelcomeScreen,
|
renderWelcomeScreen,
|
||||||
|
UIOptions,
|
||||||
app,
|
app,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const {
|
const {
|
||||||
@ -83,6 +86,7 @@ export const MobileMenu = ({
|
|||||||
<ShapesSwitcher
|
<ShapesSwitcher
|
||||||
appState={appState}
|
appState={appState}
|
||||||
activeTool={appState.activeTool}
|
activeTool={appState.activeTool}
|
||||||
|
UIOptions={UIOptions}
|
||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
|
@ -222,6 +222,9 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
|||||||
toggleTheme: null,
|
toggleTheme: null,
|
||||||
saveAsImage: true,
|
saveAsImage: true,
|
||||||
},
|
},
|
||||||
|
tools: {
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// breakpoints
|
// breakpoints
|
||||||
|
@ -3,7 +3,7 @@ import { cleanAppStateForExport } from "../appState";
|
|||||||
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { ExcalidrawElement, FileId } from "../element/types";
|
import { ExcalidrawElement, FileId } from "../element/types";
|
||||||
import { CanvasError } from "../errors";
|
import { CanvasError, ImageSceneDataError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { AppState, DataURL, LibraryItem } from "../types";
|
import { AppState, DataURL, LibraryItem } from "../types";
|
||||||
@ -24,15 +24,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
|||||||
).decodePngMetadata(blob);
|
).decodePngMetadata(blob);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === "INVALID") {
|
if (error.message === "INVALID") {
|
||||||
throw new DOMException(
|
throw new ImageSceneDataError(
|
||||||
t("alerts.imageDoesNotContainScene"),
|
t("alerts.imageDoesNotContainScene"),
|
||||||
"EncodingError",
|
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new DOMException(
|
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||||
t("alerts.cannotRestoreFromImage"),
|
|
||||||
"EncodingError",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -58,15 +55,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
|||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === "INVALID") {
|
if (error.message === "INVALID") {
|
||||||
throw new DOMException(
|
throw new ImageSceneDataError(
|
||||||
t("alerts.imageDoesNotContainScene"),
|
t("alerts.imageDoesNotContainScene"),
|
||||||
"EncodingError",
|
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new DOMException(
|
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||||
t("alerts.cannotRestoreFromImage"),
|
|
||||||
"EncodingError",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,8 +125,19 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
fileHandle?: FileSystemHandle | null,
|
fileHandle?: FileSystemHandle | null,
|
||||||
) => {
|
) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
|
let data;
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(contents);
|
try {
|
||||||
|
data = JSON.parse(contents);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (isSupportedImageFile(blob)) {
|
||||||
|
throw new ImageSceneDataError(
|
||||||
|
t("alerts.imageDoesNotContainScene"),
|
||||||
|
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
if (isValidExcalidrawData(data)) {
|
if (isValidExcalidrawData(data)) {
|
||||||
return {
|
return {
|
||||||
type: MIME_TYPES.excalidraw,
|
type: MIME_TYPES.excalidraw,
|
||||||
@ -162,7 +167,9 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
}
|
}
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error.message);
|
if (error instanceof ImageSceneDataError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -16,3 +16,19 @@ export class AbortError extends DOMException {
|
|||||||
super(message, "AbortError");
|
super(message, "AbortError");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageSceneDataErrorCode =
|
||||||
|
| "IMAGE_NOT_CONTAINS_SCENE_DATA"
|
||||||
|
| "IMAGE_SCENE_DATA_ERROR";
|
||||||
|
|
||||||
|
export class ImageSceneDataError extends Error {
|
||||||
|
public code;
|
||||||
|
constructor(
|
||||||
|
message = "Image Scene Data Error",
|
||||||
|
code: ImageSceneDataErrorCode = "IMAGE_SCENE_DATA_ERROR",
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "EncodingError";
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -209,6 +209,7 @@
|
|||||||
"importLibraryError": "Couldn't load library",
|
"importLibraryError": "Couldn't load library",
|
||||||
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
|
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
|
||||||
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
|
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
|
||||||
|
"imageToolNotSupported": "Images are disabled.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
|
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
|
||||||
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
|
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
|
||||||
|
@ -15,6 +15,16 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Added support for disabling `image` tool (also disabling image insertion in general, though keeps support for importing from `.excalidraw` files) [#6320](https://github.com/excalidraw/excalidraw/pull/6320).
|
||||||
|
|
||||||
|
For disabling `image` you need to set 👇
|
||||||
|
|
||||||
|
```
|
||||||
|
UIOptions.tools = {
|
||||||
|
image: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Support `excalidrawAPI` prop for accessing the Excalidraw API [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
- Support `excalidrawAPI` prop for accessing the Excalidraw API [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||||
|
|
||||||
- Export [`getCommonBounds`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#getcommonbounds) helper from the package [#7247](https://github.com/excalidraw/excalidraw/pull/7247).
|
- Export [`getCommonBounds`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#getcommonbounds) helper from the package [#7247](https://github.com/excalidraw/excalidraw/pull/7247).
|
||||||
|
@ -98,6 +98,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
|||||||
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
||||||
const [exportEmbedScene, setExportEmbedScene] = useState(false);
|
const [exportEmbedScene, setExportEmbedScene] = useState(false);
|
||||||
const [theme, setTheme] = useState<Theme>("light");
|
const [theme, setTheme] = useState<Theme>("light");
|
||||||
|
const [disableImageTool, setDisableImageTool] = useState(false);
|
||||||
const [isCollaborating, setIsCollaborating] = useState(false);
|
const [isCollaborating, setIsCollaborating] = useState(false);
|
||||||
const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
|
const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
|
||||||
{},
|
{},
|
||||||
@ -606,6 +607,16 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
|||||||
/>
|
/>
|
||||||
Switch to Dark Theme
|
Switch to Dark Theme
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={disableImageTool === true}
|
||||||
|
onChange={() => {
|
||||||
|
setDisableImageTool(!disableImageTool);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Disable Image Tool
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -686,6 +697,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
|||||||
canvasActions: {
|
canvasActions: {
|
||||||
loadScene: false,
|
loadScene: false,
|
||||||
},
|
},
|
||||||
|
tools: { image: !disableImageTool },
|
||||||
}}
|
}}
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
onLinkOpen={onLinkOpen}
|
onLinkOpen={onLinkOpen}
|
||||||
|
@ -56,6 +56,9 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
...DEFAULT_UI_OPTIONS.canvasActions,
|
...DEFAULT_UI_OPTIONS.canvasActions,
|
||||||
...canvasActions,
|
...canvasActions,
|
||||||
},
|
},
|
||||||
|
tools: {
|
||||||
|
image: props.UIOptions?.tools?.image ?? true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (canvasActions?.export) {
|
if (canvasActions?.export) {
|
||||||
|
@ -471,7 +471,7 @@ export type ExportOpts = {
|
|||||||
// truthiness value will determine whether the action is rendered or not
|
// truthiness value will determine whether the action is rendered or not
|
||||||
// (see manager renderAction). We also override canvasAction values in
|
// (see manager renderAction). We also override canvasAction values in
|
||||||
// excalidraw package index.tsx.
|
// excalidraw package index.tsx.
|
||||||
type CanvasActions = Partial<{
|
export type CanvasActions = Partial<{
|
||||||
changeViewBackgroundColor: boolean;
|
changeViewBackgroundColor: boolean;
|
||||||
clearCanvas: boolean;
|
clearCanvas: boolean;
|
||||||
export: false | ExportOpts;
|
export: false | ExportOpts;
|
||||||
@ -481,9 +481,12 @@ type CanvasActions = Partial<{
|
|||||||
saveAsImage: boolean;
|
saveAsImage: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type UIOptions = Partial<{
|
export type UIOptions = Partial<{
|
||||||
dockedSidebarBreakpoint: number;
|
dockedSidebarBreakpoint: number;
|
||||||
canvasActions: CanvasActions;
|
canvasActions: CanvasActions;
|
||||||
|
tools: {
|
||||||
|
image: boolean;
|
||||||
|
};
|
||||||
/** @deprecated does nothing. Will be removed in 0.15 */
|
/** @deprecated does nothing. Will be removed in 0.15 */
|
||||||
welcomeScreen?: boolean;
|
welcomeScreen?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user