Compare commits
3 Commits
master
...
dwelle/sup
Author | SHA1 | Date | |
---|---|---|---|
|
ba951b9a03 | ||
|
6fb5c2acda | ||
|
df9af0052d |
@ -14,7 +14,7 @@ import {
|
||||
hasText,
|
||||
} from "../scene";
|
||||
import { SHAPES } from "../shapes";
|
||||
import { AppState, Zoom } from "../types";
|
||||
import { AppProps, AppState, CanvasActions, Zoom } from "../types";
|
||||
import {
|
||||
capitalizeString,
|
||||
isTransparent,
|
||||
@ -213,15 +213,25 @@ export const ShapesSwitcher = ({
|
||||
setAppState,
|
||||
onImageAction,
|
||||
appState,
|
||||
UIOptions,
|
||||
}: {
|
||||
canvas: HTMLCanvasElement | null;
|
||||
activeTool: AppState["activeTool"];
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
||||
appState: AppState;
|
||||
UIOptions: AppProps["UIOptions"];
|
||||
}) => (
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||
if (
|
||||
UIOptions.canvasActions.tools[
|
||||
value as Extract<typeof value, keyof CanvasActions["tools"]>
|
||||
] === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = t(`toolBar.${value}`);
|
||||
const letter =
|
||||
key && capitalizeString(typeof key === "string" ? key : key[0]);
|
||||
|
@ -205,6 +205,7 @@ import {
|
||||
PointerDownState,
|
||||
SceneData,
|
||||
Device,
|
||||
CanvasActions,
|
||||
} from "../types";
|
||||
import {
|
||||
debounce,
|
||||
@ -292,6 +293,7 @@ import {
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import { ImageSceneDataError } from "../errors";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
@ -1547,6 +1549,10 @@ 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 { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||
{ clientX: cursorX, clientY: cursorY },
|
||||
this.state,
|
||||
@ -2343,11 +2349,32 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
});
|
||||
|
||||
// we pruposefuly widen tyhe `tool` type so this helper can be called with
|
||||
// any tool without having to type check it
|
||||
private isToolSupported = <
|
||||
T extends typeof SHAPES[number]["value"] | "eraser" | "hand" | "custom",
|
||||
>(
|
||||
tool: T,
|
||||
) => {
|
||||
return (
|
||||
this.props.UIOptions.canvasActions.tools[
|
||||
tool as Extract<T, keyof CanvasActions["tools"]>
|
||||
] !== false
|
||||
);
|
||||
};
|
||||
|
||||
private setActiveTool = (
|
||||
tool:
|
||||
| { type: typeof SHAPES[number]["value"] | "eraser" | "hand" }
|
||||
| { type: "custom"; customType: string },
|
||||
) => {
|
||||
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.canvas, CURSOR_TYPE.GRAB);
|
||||
@ -5994,7 +6021,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const { file, fileHandle } = await getFileFromEvent(event);
|
||||
|
||||
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
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@ -6103,6 +6133,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 });
|
||||
}
|
||||
};
|
||||
|
@ -305,6 +305,7 @@ const LayerUI = ({
|
||||
insertOnCanvasDirectly: pointerType !== "mouse",
|
||||
});
|
||||
}}
|
||||
UIOptions={UIOptions}
|
||||
/>
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
@ -408,6 +409,7 @@ const LayerUI = ({
|
||||
renderSidebars={renderSidebars}
|
||||
device={device}
|
||||
renderWelcomeScreen={renderWelcomeScreen}
|
||||
UIOptions={UIOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { AppState, Device, ExcalidrawProps } from "../types";
|
||||
import { AppProps, AppState, Device, ExcalidrawProps } from "../types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { t } from "../i18n";
|
||||
import Stack from "./Stack";
|
||||
@ -42,6 +42,7 @@ type MobileMenuProps = {
|
||||
renderSidebars: () => JSX.Element | null;
|
||||
device: Device;
|
||||
renderWelcomeScreen: boolean;
|
||||
UIOptions: AppProps["UIOptions"];
|
||||
};
|
||||
|
||||
export const MobileMenu = ({
|
||||
@ -59,6 +60,7 @@ export const MobileMenu = ({
|
||||
renderSidebars,
|
||||
device,
|
||||
renderWelcomeScreen,
|
||||
UIOptions,
|
||||
}: MobileMenuProps) => {
|
||||
const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels();
|
||||
const renderToolbar = () => {
|
||||
@ -82,6 +84,7 @@ export const MobileMenu = ({
|
||||
insertOnCanvasDirectly: pointerType !== "mouse",
|
||||
});
|
||||
}}
|
||||
UIOptions={UIOptions}
|
||||
/>
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
|
@ -164,6 +164,9 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
||||
saveToActiveFile: true,
|
||||
toggleTheme: null,
|
||||
saveAsImage: true,
|
||||
tools: {
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { cleanAppStateForExport } from "../appState";
|
||||
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement, FileId } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
import { CanvasError, ImageSceneDataError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { AppState, DataURL, LibraryItem } from "../types";
|
||||
@ -24,15 +24,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
||||
).decodePngMetadata(blob);
|
||||
} catch (error: any) {
|
||||
if (error.message === "INVALID") {
|
||||
throw new DOMException(
|
||||
throw new ImageSceneDataError(
|
||||
t("alerts.imageDoesNotContainScene"),
|
||||
"EncodingError",
|
||||
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||
);
|
||||
} else {
|
||||
throw new DOMException(
|
||||
t("alerts.cannotRestoreFromImage"),
|
||||
"EncodingError",
|
||||
);
|
||||
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -58,15 +55,12 @@ const parseFileContents = async (blob: Blob | File) => {
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.message === "INVALID") {
|
||||
throw new DOMException(
|
||||
throw new ImageSceneDataError(
|
||||
t("alerts.imageDoesNotContainScene"),
|
||||
"EncodingError",
|
||||
"IMAGE_NOT_CONTAINS_SCENE_DATA",
|
||||
);
|
||||
} else {
|
||||
throw new DOMException(
|
||||
t("alerts.cannotRestoreFromImage"),
|
||||
"EncodingError",
|
||||
);
|
||||
throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,3 +16,19 @@ export class AbortError extends DOMException {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -618,6 +618,7 @@ const ExcalidrawWrapper = () => {
|
||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||
UIOptions={{
|
||||
canvasActions: {
|
||||
tools: { image: false },
|
||||
toggleTheme: true,
|
||||
export: {
|
||||
onExportToBackend,
|
||||
|
@ -206,6 +206,7 @@
|
||||
"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_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": {
|
||||
"start": "Looks like you are using Brave browser with the",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
|
@ -52,6 +52,10 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
canvasActions: {
|
||||
...DEFAULT_UI_OPTIONS.canvasActions,
|
||||
...canvasActions,
|
||||
tools: {
|
||||
...DEFAULT_UI_OPTIONS.canvasActions.tools,
|
||||
...props.UIOptions?.canvasActions?.tools,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -375,7 +375,7 @@ export type ExportOpts = {
|
||||
// truthiness value will determine whether the action is rendered or not
|
||||
// (see manager renderAction). We also override canvasAction values in
|
||||
// excalidraw package index.tsx.
|
||||
type CanvasActions = Partial<{
|
||||
export type CanvasActions = Partial<{
|
||||
changeViewBackgroundColor: boolean;
|
||||
clearCanvas: boolean;
|
||||
export: false | ExportOpts;
|
||||
@ -383,6 +383,9 @@ type CanvasActions = Partial<{
|
||||
saveToActiveFile: boolean;
|
||||
toggleTheme: boolean | null;
|
||||
saveAsImage: boolean;
|
||||
tools: {
|
||||
image: boolean;
|
||||
};
|
||||
}>;
|
||||
|
||||
type UIOptions = Partial<{
|
||||
|
Loading…
x
Reference in New Issue
Block a user