diff --git a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx
index 6f0fd30a70..4684d6c791 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx
@@ -4,6 +4,34 @@
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
+### Turning off Aggressive Anti-Fingerprinting in Brave browser
+
+When *Aggressive Anti-Fingerprinting* is turned on, the `measureText` API breaks which in turn breaks the Text Elements in your drawings. Here is more [info](https://github.com/excalidraw/excalidraw/pull/6336) on the same.
+
+We strongly recommend turning it off. You can follow the steps below on how to do so.
+
+
+1. Open [excalidraw.com](https://excalidraw.com) in Brave and click on the **Shield** button
+
+
+
+
+2. Once opened, look for **Aggressively Block Fingerprinting**
+
+
+
+3. Switch to **Block Fingerprinting**
+
+
+
+4. Thats all. All text elements should be fixed now 🎉
+
+
+
+If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
+
+
+
## Need help?
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw).
diff --git a/dev-docs/docs/assets/aggressive-block-fingerprint.png b/dev-docs/docs/assets/aggressive-block-fingerprint.png
new file mode 100644
index 0000000000..236a12dbeb
Binary files /dev/null and b/dev-docs/docs/assets/aggressive-block-fingerprint.png differ
diff --git a/dev-docs/docs/assets/block-fingerprint.png b/dev-docs/docs/assets/block-fingerprint.png
new file mode 100644
index 0000000000..bbbf4d26d0
Binary files /dev/null and b/dev-docs/docs/assets/block-fingerprint.png differ
diff --git a/dev-docs/docs/assets/brave-shield.png b/dev-docs/docs/assets/brave-shield.png
new file mode 100644
index 0000000000..bbb1216533
Binary files /dev/null and b/dev-docs/docs/assets/brave-shield.png differ
diff --git a/package.json b/package.json
index a19b2fb89f..5816786e3c 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,6 @@
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.5",
"@tldraw/vec": "1.7.1",
- "@types/jest": "27.4.0",
- "@types/pica": "5.1.3",
- "@types/react": "18.0.15",
- "@types/react-dom": "18.0.6",
- "@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.29.1",
"clsx": "1.1.1",
"cross-env": "7.0.3",
@@ -57,7 +52,6 @@
"sass": "1.51.0",
"socket.io-client": "2.3.1",
"tunnel-rat": "0.1.0",
- "typescript": "4.9.4",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
"workbox-cacheable-response": "^6.5.4",
@@ -75,9 +69,14 @@
"@excalidraw/eslint-config": "1.0.0",
"@excalidraw/prettier-config": "1.0.2",
"@types/chai": "4.3.0",
+ "@types/jest": "27.4.0",
"@types/lodash.throttle": "4.1.7",
"@types/pako": "1.0.3",
+ "@types/pica": "5.1.3",
+ "@types/react": "18.0.15",
+ "@types/react-dom": "18.0.6",
"@types/resize-observer-browser": "0.1.7",
+ "@types/socket.io-client": "1.4.36",
"chai": "4.3.6",
"dotenv": "16.0.1",
"eslint-config-prettier": "8.5.0",
@@ -88,7 +87,8 @@
"lint-staged": "12.3.7",
"pepjs": "0.5.3",
"prettier": "2.6.2",
- "rewire": "6.0.0"
+ "rewire": "6.0.0",
+ "typescript": "4.9.4"
},
"engines": {
"node": ">=14.0.0"
diff --git a/scripts/locales-coverage-description.js b/scripts/locales-coverage-description.js
index 08db5b841f..0f9bacfaaf 100644
--- a/scripts/locales-coverage-description.js
+++ b/scripts/locales-coverage-description.js
@@ -2,6 +2,9 @@ const fs = require("fs");
const THRESSHOLD = 85;
+// we're using BCP 47 language tags as keys
+// e.g. https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1
+
const crowdinMap = {
"ar-SA": "en-ar",
"bg-BG": "en-bg",
@@ -52,6 +55,7 @@ const crowdinMap = {
"kk-KZ": "en-kk",
"vi-VN": "en-vi",
"mr-IN": "en-mr",
+ "th-TH": "en-th",
};
const flags = {
@@ -104,6 +108,7 @@ const flags = {
"eu-ES": "🇪🇦",
"vi-VN": "🇻🇳",
"mr-IN": "🇮🇳",
+ "th-TH": "🇹🇭",
};
const languages = {
@@ -156,6 +161,7 @@ const languages = {
"zh-TW": "繁體中文",
"vi-VN": "Tiếng Việt",
"mr-IN": "मराठी",
+ "th-TH": "ภาษาไทย",
};
const percentages = fs.readFileSync(
diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx
index 4e6f0d5876..309e46bdc1 100644
--- a/src/actions/actionProperties.tsx
+++ b/src/actions/actionProperties.tsx
@@ -745,16 +745,19 @@ export const actionChangeTextAlign = register({
value: "left",
text: t("labels.left"),
icon: TextAlignLeftIcon,
+ testId: "align-left",
},
{
value: "center",
text: t("labels.center"),
icon: TextAlignCenterIcon,
+ testId: "align-horizontal-center",
},
{
value: "right",
text: t("labels.right"),
icon: TextAlignRightIcon,
+ testId: "align-right",
},
]}
value={getFormValue(
diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx
index 2ee9babfc7..3bbc0ff1a6 100644
--- a/src/components/Actions.tsx
+++ b/src/components/Actions.tsx
@@ -30,7 +30,10 @@ import clsx from "clsx";
import { actionToggleZenMode } from "../actions";
import "./Actions.scss";
import { Tooltip } from "./Tooltip";
-import { shouldAllowVerticalAlign } from "../element/textElement";
+import {
+ shouldAllowVerticalAlign,
+ suppportsHorizontalAlign,
+} from "../element/textElement";
export const SelectedShapeActions = ({
appState,
@@ -122,7 +125,8 @@ export const SelectedShapeActions = ({
{renderAction("changeFontFamily")}
- {renderAction("changeTextAlign")}
+ {suppportsHorizontalAlign(targetElements) &&
+ renderAction("changeTextAlign")}
>
)}
diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx
new file mode 100644
index 0000000000..baf25ab9bc
--- /dev/null
+++ b/src/components/App.test.tsx
@@ -0,0 +1,45 @@
+import ReactDOM from "react-dom";
+import * as Renderer from "../renderer/renderScene";
+import { reseed } from "../random";
+import { render, queryByTestId } from "../tests/test-utils";
+
+import ExcalidrawApp from "../excalidraw-app";
+
+const renderScene = jest.spyOn(Renderer, "renderScene");
+
+describe("Test ", () => {
+ beforeEach(async () => {
+ // Unmount ReactDOM from root
+ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+ localStorage.clear();
+ renderScene.mockClear();
+ reseed(7);
+ });
+
+ it("should show error modal when using brave and measureText API is not working", async () => {
+ (global.navigator as any).brave = {
+ isBrave: {
+ name: "isBrave",
+ },
+ };
+
+ const originalContext = global.HTMLCanvasElement.prototype.getContext("2d");
+ //@ts-ignore
+ global.HTMLCanvasElement.prototype.getContext = (contextId) => {
+ return {
+ ...originalContext,
+ measureText: () => ({
+ width: 0,
+ }),
+ };
+ };
+
+ await render();
+ expect(
+ queryByTestId(
+ document.querySelector(".excalidraw-modal-container")!,
+ "brave-measure-text-error",
+ ),
+ ).toMatchSnapshot();
+ });
+});
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 87348a0ffc..67403e0d9f 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -62,6 +62,7 @@ import {
GRID_SIZE,
IMAGE_RENDER_TIMEOUT,
isAndroid,
+ isBrave,
LINE_CONFIRM_THRESHOLD,
MAX_ALLOWED_FILE_BYTES,
MIME_TYPES,
@@ -267,6 +268,7 @@ import {
getContainerDims,
getContainerElement,
getTextBindableContainerAtPosition,
+ isMeasureTextSupported,
isValidTextContainer,
} from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
@@ -285,6 +287,7 @@ import { actionToggleHandTool } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import { actionCreateContainerFromText } from "../actions/actionBoundText";
+import BraveMeasureTextError from "./BraveMeasureTextError";
const deviceContextInitialValue = {
isSmScreen: false,
@@ -429,7 +432,6 @@ class App extends React.Component {
};
this.id = nanoid();
-
this.library = new Library(this);
if (excalidrawRef) {
const readyPromise =
@@ -711,6 +713,8 @@ class App extends React.Component {
const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
let name = actionResult?.appState?.name ?? this.state.name;
+ const errorMessage =
+ actionResult?.appState?.errorMessage ?? this.state.errorMessage;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
}
@@ -726,7 +730,6 @@ class App extends React.Component {
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
-
this.setState(
(state) => {
// using Object.assign instead of spread to fool TS 4.2.2+ into
@@ -744,6 +747,7 @@ class App extends React.Component {
gridSize,
theme,
name,
+ errorMessage,
});
},
() => {
@@ -872,7 +876,6 @@ class App extends React.Component {
),
};
}
-
// FontFaceSet loadingdone event we listen on may not always fire
// (looking at you Safari), so on init we manually load fonts for current
// text elements on canvas, and rerender them once done. This also
@@ -1000,6 +1003,13 @@ class App extends React.Component {
} else {
this.updateDOMRect(this.initializeScene);
}
+
+ // note that this check seems to always pass in localhost
+ if (isBrave() && !isMeasureTextSupported()) {
+ this.setState({
+ errorMessage: ,
+ });
+ }
}
public componentWillUnmount() {
diff --git a/src/components/BraveMeasureTextError.tsx b/src/components/BraveMeasureTextError.tsx
new file mode 100644
index 0000000000..8a4a71e4fd
--- /dev/null
+++ b/src/components/BraveMeasureTextError.tsx
@@ -0,0 +1,42 @@
+import { t } from "../i18n";
+const BraveMeasureTextError = () => {
+ return (
+
+
+ {t("errors.brave_measure_text_error.start")}
+
+ {t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
+ {" "}
+ {t("errors.brave_measure_text_error.setting_enabled")}.
+
+
+ {t("errors.brave_measure_text_error.break")}{" "}
+
+ {t("errors.brave_measure_text_error.text_elements")}
+ {" "}
+ {t("errors.brave_measure_text_error.in_your_drawings")}.
+
+
+ {t("errors.brave_measure_text_error.strongly_recommend")}{" "}
+
+ {" "}
+ {t("errors.brave_measure_text_error.steps")}
+ {" "}
+ {t("errors.brave_measure_text_error.how")}.
+
+
+ {t("errors.brave_measure_text_error.disable_setting")}{" "}
+
+ {t("errors.brave_measure_text_error.issue")}
+ {" "}
+ {t("errors.brave_measure_text_error.write")}{" "}
+
+ {t("errors.brave_measure_text_error.discord")}
+
+ .
+
+
+ );
+};
+
+export default BraveMeasureTextError;
diff --git a/src/components/ErrorDialog.tsx b/src/components/ErrorDialog.tsx
index c1c7899812..56c303c157 100644
--- a/src/components/ErrorDialog.tsx
+++ b/src/components/ErrorDialog.tsx
@@ -5,13 +5,13 @@ import { Dialog } from "./Dialog";
import { useExcalidrawContainer } from "./App";
export const ErrorDialog = ({
- message,
+ children,
onClose,
}: {
- message: string;
+ children?: React.ReactNode;
onClose?: () => void;
}) => {
- const [modalIsShown, setModalIsShown] = useState(!!message);
+ const [modalIsShown, setModalIsShown] = useState(!!children);
const { container: excalidrawContainer } = useExcalidrawContainer();
const handleClose = React.useCallback(() => {
@@ -32,7 +32,7 @@ export const ErrorDialog = ({
onCloseRequest={handleClose}
title={t("errorDialog.title")}
>
- {message}
+ {children}
)}
>
diff --git a/src/components/ExportDialog.scss b/src/components/ExportDialog.scss
index 7bc0c808ed..3cb31c4843 100644
--- a/src/components/ExportDialog.scss
+++ b/src/components/ExportDialog.scss
@@ -9,6 +9,10 @@
text-align: center;
padding: var(--preview-padding);
margin-bottom: calc(var(--space-factor) * 3);
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
.ExportDialog__preview canvas {
diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx
index bea074d625..7103d3adec 100644
--- a/src/components/LayerUI.tsx
+++ b/src/components/LayerUI.tsx
@@ -364,10 +364,9 @@ const LayerUI = ({
{appState.isLoading && }
{appState.errorMessage && (
- setAppState({ errorMessage: null })}
- />
+ setAppState({ errorMessage: null })}>
+ {appState.errorMessage}
+
)}
{appState.openDialog === "help" && (
should show error modal when using brave and measureText API is not working 1`] = `
+
+
+ Looks like you are using Brave browser with the
+
+
+ Aggressively Block Fingerprinting
+
+
+ setting enabled
+ .
+
+
+ This could result in breaking the
+
+
+ Text Elements
+
+
+ in your drawings
+ .
+
+
+ We strongly recommend disabling this setting. You can follow
+
+
+
+ these steps
+
+
+ on how to do so
+ .
+
+
+ If disabling this setting doesn't fix the display of text elements, please open an
+
+
+ issue
+
+
+ on our GitHub, or write us on
+
+
+ Discord
+
+ .
+
+
+`;
diff --git a/src/constants.ts b/src/constants.ts
index aa25667a27..ef563e4a41 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -12,6 +12,9 @@ export const isFirefox =
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
export const isSafari =
!isChrome && navigator.userAgent.indexOf("Safari") !== -1;
+// keeping function so it can be mocked in test
+export const isBrave = () =>
+ (navigator as any).brave?.isBrave?.name === "isBrave";
export const APP_NAME = "Excalidraw";
diff --git a/src/data/blob.ts b/src/data/blob.ts
index 35c040ef3b..47cff293fa 100644
--- a/src/data/blob.ts
+++ b/src/data/blob.ts
@@ -157,7 +157,7 @@ export const loadSceneOrLibraryFromBlob = async (
},
localAppState,
localElements,
- { repairBindings: true },
+ { repairBindings: true, refreshDimensions: true },
),
};
} else if (isValidLibrary(data)) {
diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts
index d95bad8165..40898853d3 100644
--- a/src/element/textElement.test.ts
+++ b/src/element/textElement.test.ts
@@ -16,7 +16,7 @@ describe("Test wrapText", () => {
const text = "Hello whats up ";
const maxWidth = 200 - BOUND_TEXT_PADDING * 2;
const res = wrapText(text, font, maxWidth);
- expect(res).toBe("Hello whats up ");
+ expect(res).toBe(text);
});
it("should work with emojis", () => {
@@ -26,7 +26,7 @@ describe("Test wrapText", () => {
expect(res).toBe("😀");
});
- it("should show the text correctly when min width reached", () => {
+ it("should show the text correctly when max width reached", () => {
const text = "Hello😀";
const maxWidth = 10;
const res = wrapText(text, font, maxWidth);
@@ -136,6 +136,7 @@ whats up`,
});
});
});
+
describe("When text is long", () => {
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
[
@@ -175,6 +176,14 @@ break it now`,
});
});
});
+
+ it("should wrap the text correctly when word length is exactly equal to max width", () => {
+ const text = "Hello Excalidraw";
+ // Length of "Excalidraw" is 100 and exacty equal to max width
+ const res = wrapText(text, font, 100);
+ expect(res).toEqual(`Hello
+Excalidraw`);
+ });
});
describe("Test measureText", () => {
diff --git a/src/element/textElement.ts b/src/element/textElement.ts
index 0e74e7ec89..d1bf7f61f7 100644
--- a/src/element/textElement.ts
+++ b/src/element/textElement.ts
@@ -8,7 +8,13 @@ import {
NonDeletedExcalidrawElement,
} from "./types";
import { mutateElement } from "./mutateElement";
-import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
+import {
+ BOUND_TEXT_PADDING,
+ DEFAULT_FONT_FAMILY,
+ DEFAULT_FONT_SIZE,
+ TEXT_ALIGN,
+ VERTICAL_ALIGN,
+} from "../constants";
import { MaybeTransformHandleType } from "./transformHandles";
import Scene from "../scene/Scene";
import { isTextElement } from ".";
@@ -323,25 +329,38 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
const lines: Array = [];
const originalLines = text.split("\n");
const spaceWidth = getLineWidth(" ", font);
+
+ let currentLine = "";
+ let currentLineWidthTillNow = 0;
+
const push = (str: string) => {
if (str.trim()) {
lines.push(str);
}
};
+
+ const resetParams = () => {
+ currentLine = "";
+ currentLineWidthTillNow = 0;
+ };
+
originalLines.forEach((originalLine) => {
- const words = originalLine.split(" ");
- // This means its newline so push it
- if (words.length === 1 && words[0] === "") {
- lines.push(words[0]);
+ const currentLineWidth = getTextWidth(originalLine, font);
+
+ //Push the line if its <= maxWidth
+ if (currentLineWidth <= maxWidth) {
+ lines.push(originalLine);
return; // continue
}
- let currentLine = "";
- let currentLineWidthTillNow = 0;
+ const words = originalLine.split(" ");
+
+ resetParams();
let index = 0;
while (index < words.length) {
const currentWordWidth = getLineWidth(words[index], font);
+
// This will only happen when single word takes entire width
if (currentWordWidth === maxWidth) {
push(words[index]);
@@ -353,8 +372,8 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
// push current line since the current word exceeds the max width
// so will be appended in next line
push(currentLine);
- currentLine = "";
- currentLineWidthTillNow = 0;
+
+ resetParams();
while (words[index].length > 0) {
const currentChar = String.fromCodePoint(
@@ -365,10 +384,6 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
words[index] = words[index].slice(currentChar.length);
if (currentLineWidthTillNow >= maxWidth) {
- // only remove last trailing space which we have added when joining words
- if (currentLine.slice(-1) === " ") {
- currentLine = currentLine.slice(0, -1);
- }
push(currentLine);
currentLine = currentChar;
currentLineWidthTillNow = width;
@@ -376,11 +391,11 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
currentLine += currentChar;
}
}
+
// push current line if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
push(currentLine);
- currentLine = "";
- currentLineWidthTillNow = 0;
+ resetParams();
} else {
// space needs to be appended before next word
// as currentLine contains chars which couldn't be appended
@@ -388,7 +403,6 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
currentLine += " ";
currentLineWidthTillNow += spaceWidth;
}
-
index++;
} else {
// Start appending words in a line till max width reached
@@ -398,8 +412,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
if (currentLineWidthTillNow > maxWidth) {
push(currentLine);
- currentLineWidthTillNow = 0;
- currentLine = "";
+ resetParams();
break;
}
@@ -410,22 +423,15 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
const word = currentLine.slice(0, -1);
push(word);
- currentLine = "";
- currentLineWidthTillNow = 0;
+ resetParams();
break;
}
}
- if (currentLineWidthTillNow === maxWidth) {
- currentLine = "";
- currentLineWidthTillNow = 0;
- }
}
}
- if (currentLine) {
+ if (currentLine.slice(-1) === " ") {
// only remove last trailing space which we have added when joining words
- if (currentLine.slice(-1) === " ") {
- currentLine = currentLine.slice(0, -1);
- }
+ currentLine = currentLine.slice(0, -1);
push(currentLine);
}
});
@@ -652,14 +658,24 @@ export const shouldAllowVerticalAlign = (
}
return true;
}
- const boundTextElement = getBoundTextElement(element);
- if (boundTextElement) {
- if (isArrowElement(element)) {
+ return false;
+ });
+};
+
+export const suppportsHorizontalAlign = (
+ selectedElements: NonDeletedExcalidrawElement[],
+) => {
+ return selectedElements.some((element) => {
+ const hasBoundContainer = isBoundToContainer(element);
+ if (hasBoundContainer) {
+ const container = getContainerElement(element);
+ if (isTextElement(element) && isArrowElement(container)) {
return false;
}
return true;
}
- return false;
+
+ return isTextElement(element);
});
};
@@ -775,3 +791,14 @@ export const getContainerMaxHeight = (
}
return height - BOUND_TEXT_PADDING * 2;
};
+
+export const isMeasureTextSupported = () => {
+ const width = getTextWidth(
+ DUMMY_TEXT,
+ getFontString({
+ fontSize: DEFAULT_FONT_SIZE,
+ fontFamily: DEFAULT_FONT_FAMILY,
+ }),
+ );
+ return width > 0;
+};
diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx
index 30c9846c83..e48484ab64 100644
--- a/src/excalidraw-app/collab/Collab.tsx
+++ b/src/excalidraw-app/collab/Collab.tsx
@@ -838,10 +838,9 @@ class Collab extends PureComponent {
/>
)}
{errorMessage && (
- this.setState({ errorMessage: "" })}
- />
+ this.setState({ errorMessage: "" })}>
+ {errorMessage}
+
)}
>
);
diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts
index 393c51580d..7f13bc6153 100644
--- a/src/excalidraw-app/data/index.ts
+++ b/src/excalidraw-app/data/index.ts
@@ -263,7 +263,7 @@ export const loadScene = async (
await importFromBackend(id, privateKey),
localDataState?.appState,
localDataState?.elements,
- { repairBindings: true },
+ { repairBindings: true, refreshDimensions: true },
);
} else {
data = restore(localDataState || null, null, null, {
diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx
index 1c51637e06..930143f98e 100644
--- a/src/excalidraw-app/index.tsx
+++ b/src/excalidraw-app/index.tsx
@@ -673,10 +673,9 @@ const ExcalidrawWrapper = () => {
{excalidrawAPI && }
{errorMessage && (
- setErrorMessage("")}
- />
+ setErrorMessage("")}>
+ {errorMessage}
+
)}
);
diff --git a/src/locales/en.json b/src/locales/en.json
index f5ae003f37..3021014eaa 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -120,7 +120,6 @@
"edit": "Edit line",
"exit": "Exit line editor"
},
-
"elementLock": {
"lock": "Lock",
"unlock": "Unlock",
@@ -206,7 +205,22 @@
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
"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."
+ "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.",
+ "brave_measure_text_error": {
+ "start": "Looks like you are using Brave browser with the",
+ "aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
+ "setting_enabled": "setting enabled",
+ "break": "This could result in breaking the",
+ "text_elements": "Text Elements",
+ "in_your_drawings": "in your drawings",
+ "strongly_recommend": "We strongly recommend disabling this setting. You can follow",
+ "steps": "these steps",
+ "how": "on how to do so",
+ "disable_setting": " If disabling this setting doesn't fix the display of text elements, please open an",
+ "issue": "issue",
+ "write": "on our GitHub, or write us on",
+ "discord": "Discord"
+ }
},
"toolBar": {
"selection": "Selection",
diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx
index f3543c41bc..48b438e490 100644
--- a/src/tests/linearElementEditor.test.tsx
+++ b/src/tests/linearElementEditor.test.tsx
@@ -1181,5 +1181,17 @@ describe("Test Linear Elements", () => {
easy"
`);
});
+
+ it("should not render horizontal align tool when element selected", () => {
+ createTwoPointerLinearElement("arrow");
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+
+ createBoundTextElement(DEFAULT_TEXT, arrow);
+ API.setSelectedElements([arrow]);
+
+ expect(queryByTestId(container, "align-left")).toBeNull();
+ expect(queryByTestId(container, "align-horizontal-center")).toBeNull();
+ expect(queryByTestId(container, "align-right")).toBeNull();
+ });
});
});
diff --git a/src/types.ts b/src/types.ts
index e40476eadf..09848df1dc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -32,6 +32,7 @@ import type { FileSystemHandle } from "./data/filesystem";
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
import { ContextMenuItems } from "./components/ContextMenu";
import { Merge, ForwardRef } from "./utility-types";
+import React from "react";
export type Point = Readonly;
@@ -101,7 +102,7 @@ export type AppState = {
} | null;
showWelcomeScreen: boolean;
isLoading: boolean;
- errorMessage: string | null;
+ errorMessage: React.ReactNode;
draggingElement: NonDeletedExcalidrawElement | null;
resizingElement: NonDeletedExcalidrawElement | null;
multiElement: NonDeleted | null;