Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax
This commit is contained in:
commit
089aaa8792
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ src/packages/excalidraw/types
|
|||||||
src/packages/excalidraw/example/public/bundle.js
|
src/packages/excalidraw/example/public/bundle.js
|
||||||
src/packages/excalidraw/example/public/excalidraw-assets-dev
|
src/packages/excalidraw/example/public/excalidraw-assets-dev
|
||||||
src/packages/excalidraw/example/public/excalidraw.development.js
|
src/packages/excalidraw/example/public/excalidraw.development.js
|
||||||
|
coverage
|
||||||
|
@ -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.
|
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
|
||||||
|

|
||||||
|
|
||||||
|
<div style={{width:'30rem'}}>
|
||||||
|
|
||||||
|
2. Once opened, look for **Aggressively Block Fingerprinting**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Switch to **Block Fingerprinting**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Thats all. All text elements should be fixed now 🎉
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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?
|
## 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).
|
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).
|
||||||
|
BIN
dev-docs/docs/assets/aggressive-block-fingerprint.png
Normal file
BIN
dev-docs/docs/assets/aggressive-block-fingerprint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 214 KiB |
BIN
dev-docs/docs/assets/block-fingerprint.png
Normal file
BIN
dev-docs/docs/assets/block-fingerprint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
BIN
dev-docs/docs/assets/brave-shield.png
Normal file
BIN
dev-docs/docs/assets/brave-shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
@ -2,6 +2,9 @@ const fs = require("fs");
|
|||||||
|
|
||||||
const THRESSHOLD = 85;
|
const THRESSHOLD = 85;
|
||||||
|
|
||||||
|
// we're using BCP 47 language tags as keys
|
||||||
|
// e.g. https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1
|
||||||
|
|
||||||
const crowdinMap = {
|
const crowdinMap = {
|
||||||
"ar-SA": "en-ar",
|
"ar-SA": "en-ar",
|
||||||
"bg-BG": "en-bg",
|
"bg-BG": "en-bg",
|
||||||
@ -52,6 +55,7 @@ const crowdinMap = {
|
|||||||
"kk-KZ": "en-kk",
|
"kk-KZ": "en-kk",
|
||||||
"vi-VN": "en-vi",
|
"vi-VN": "en-vi",
|
||||||
"mr-IN": "en-mr",
|
"mr-IN": "en-mr",
|
||||||
|
"th-TH": "en-th",
|
||||||
};
|
};
|
||||||
|
|
||||||
const flags = {
|
const flags = {
|
||||||
@ -104,6 +108,7 @@ const flags = {
|
|||||||
"eu-ES": "🇪🇦",
|
"eu-ES": "🇪🇦",
|
||||||
"vi-VN": "🇻🇳",
|
"vi-VN": "🇻🇳",
|
||||||
"mr-IN": "🇮🇳",
|
"mr-IN": "🇮🇳",
|
||||||
|
"th-TH": "🇹🇭",
|
||||||
};
|
};
|
||||||
|
|
||||||
const languages = {
|
const languages = {
|
||||||
@ -156,6 +161,7 @@ const languages = {
|
|||||||
"zh-TW": "繁體中文",
|
"zh-TW": "繁體中文",
|
||||||
"vi-VN": "Tiếng Việt",
|
"vi-VN": "Tiếng Việt",
|
||||||
"mr-IN": "मराठी",
|
"mr-IN": "मराठी",
|
||||||
|
"th-TH": "ภาษาไทย",
|
||||||
};
|
};
|
||||||
|
|
||||||
const percentages = fs.readFileSync(
|
const percentages = fs.readFileSync(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { isDarwin } from "../constants";
|
import { isDarwin } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { SubtypeOf } from "../utility-types";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { ActionName } from "./types";
|
import { ActionName } from "./types";
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
ExcalidrawProps,
|
ExcalidrawProps,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
import { MarkOptional } from "../utility-types";
|
||||||
|
|
||||||
export type ActionSource = "ui" | "keyboard" | "contextMenu" | "api";
|
export type ActionSource = "ui" | "keyboard" | "contextMenu" | "api";
|
||||||
|
|
||||||
|
45
src/components/App.test.tsx
Normal file
45
src/components/App.test.tsx
Normal file
@ -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 <App/>", () => {
|
||||||
|
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(<ExcalidrawApp />);
|
||||||
|
expect(
|
||||||
|
queryByTestId(
|
||||||
|
document.querySelector(".excalidraw-modal-container")!,
|
||||||
|
"brave-measure-text-error",
|
||||||
|
),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -62,6 +62,7 @@ import {
|
|||||||
GRID_SIZE,
|
GRID_SIZE,
|
||||||
IMAGE_RENDER_TIMEOUT,
|
IMAGE_RENDER_TIMEOUT,
|
||||||
isAndroid,
|
isAndroid,
|
||||||
|
isBrave,
|
||||||
LINE_CONFIRM_THRESHOLD,
|
LINE_CONFIRM_THRESHOLD,
|
||||||
MAX_ALLOWED_FILE_BYTES,
|
MAX_ALLOWED_FILE_BYTES,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
@ -276,6 +277,7 @@ import {
|
|||||||
getContainerDims,
|
getContainerDims,
|
||||||
getContainerElement,
|
getContainerElement,
|
||||||
getTextBindableContainerAtPosition,
|
getTextBindableContainerAtPosition,
|
||||||
|
isMeasureTextSupported,
|
||||||
isValidTextContainer,
|
isValidTextContainer,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||||
@ -294,6 +296,7 @@ import { actionToggleHandTool } from "../actions/actionCanvas";
|
|||||||
import { jotaiStore } from "../jotai";
|
import { jotaiStore } from "../jotai";
|
||||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||||
|
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||||
|
|
||||||
const deviceContextInitialValue = {
|
const deviceContextInitialValue = {
|
||||||
isSmScreen: false,
|
isSmScreen: false,
|
||||||
@ -439,7 +442,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
|
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
this.actionManager = new ActionManager(
|
this.actionManager = new ActionManager(
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
@ -757,6 +759,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const theme =
|
const theme =
|
||||||
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
||||||
let name = actionResult?.appState?.name ?? this.state.name;
|
let name = actionResult?.appState?.name ?? this.state.name;
|
||||||
|
const errorMessage =
|
||||||
|
actionResult?.appState?.errorMessage ?? this.state.errorMessage;
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
viewModeEnabled = this.props.viewModeEnabled;
|
viewModeEnabled = this.props.viewModeEnabled;
|
||||||
}
|
}
|
||||||
@ -772,7 +776,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (typeof this.props.name !== "undefined") {
|
if (typeof this.props.name !== "undefined") {
|
||||||
name = this.props.name;
|
name = this.props.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
(state) => {
|
(state) => {
|
||||||
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
||||||
@ -790,6 +793,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
gridSize,
|
gridSize,
|
||||||
theme,
|
theme,
|
||||||
name,
|
name,
|
||||||
|
errorMessage,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@ -918,7 +922,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// FontFaceSet loadingdone event we listen on may not always fire
|
// FontFaceSet loadingdone event we listen on may not always fire
|
||||||
// (looking at you Safari), so on init we manually load fonts for current
|
// (looking at you Safari), so on init we manually load fonts for current
|
||||||
// text elements on canvas, and rerender them once done. This also
|
// text elements on canvas, and rerender them once done. This also
|
||||||
@ -1046,6 +1049,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
} else {
|
} else {
|
||||||
this.updateDOMRect(this.initializeScene);
|
this.updateDOMRect(this.initializeScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note that this check seems to always pass in localhost
|
||||||
|
if (isBrave() && !isMeasureTextSupported()) {
|
||||||
|
this.setState({
|
||||||
|
errorMessage: <BraveMeasureTextError />,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
|
42
src/components/BraveMeasureTextError.tsx
Normal file
42
src/components/BraveMeasureTextError.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { t } from "../i18n";
|
||||||
|
const BraveMeasureTextError = () => {
|
||||||
|
return (
|
||||||
|
<div data-testid="brave-measure-text-error">
|
||||||
|
<p>
|
||||||
|
{t("errors.brave_measure_text_error.start")}
|
||||||
|
<span style={{ fontWeight: 600 }}>
|
||||||
|
{t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
|
||||||
|
</span>{" "}
|
||||||
|
{t("errors.brave_measure_text_error.setting_enabled")}.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{t("errors.brave_measure_text_error.break")}{" "}
|
||||||
|
<span style={{ fontWeight: 600 }}>
|
||||||
|
{t("errors.brave_measure_text_error.text_elements")}
|
||||||
|
</span>{" "}
|
||||||
|
{t("errors.brave_measure_text_error.in_your_drawings")}.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{t("errors.brave_measure_text_error.strongly_recommend")}{" "}
|
||||||
|
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
|
||||||
|
{" "}
|
||||||
|
{t("errors.brave_measure_text_error.steps")}
|
||||||
|
</a>{" "}
|
||||||
|
{t("errors.brave_measure_text_error.how")}.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{t("errors.brave_measure_text_error.disable_setting")}{" "}
|
||||||
|
<a href="https://github.com/excalidraw/excalidraw/issues/new">
|
||||||
|
{t("errors.brave_measure_text_error.issue")}
|
||||||
|
</a>{" "}
|
||||||
|
{t("errors.brave_measure_text_error.write")}{" "}
|
||||||
|
<a href="https://discord.gg/UexuTaE">
|
||||||
|
{t("errors.brave_measure_text_error.discord")}
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BraveMeasureTextError;
|
@ -5,13 +5,13 @@ import { Dialog } from "./Dialog";
|
|||||||
import { useExcalidrawContainer } from "./App";
|
import { useExcalidrawContainer } from "./App";
|
||||||
|
|
||||||
export const ErrorDialog = ({
|
export const ErrorDialog = ({
|
||||||
message,
|
children,
|
||||||
onClose,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
message: string;
|
children?: React.ReactNode;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [modalIsShown, setModalIsShown] = useState(!!message);
|
const [modalIsShown, setModalIsShown] = useState(!!children);
|
||||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||||
|
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
@ -32,7 +32,7 @@ export const ErrorDialog = ({
|
|||||||
onCloseRequest={handleClose}
|
onCloseRequest={handleClose}
|
||||||
title={t("errorDialog.title")}
|
title={t("errorDialog.title")}
|
||||||
>
|
>
|
||||||
<div style={{ whiteSpace: "pre-wrap" }}>{message}</div>
|
<div style={{ whiteSpace: "pre-wrap" }}>{children}</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -364,10 +364,9 @@ const LayerUI = ({
|
|||||||
|
|
||||||
{appState.isLoading && <LoadingMessage delay={250} />}
|
{appState.isLoading && <LoadingMessage delay={250} />}
|
||||||
{appState.errorMessage && (
|
{appState.errorMessage && (
|
||||||
<ErrorDialog
|
<ErrorDialog onClose={() => setAppState({ errorMessage: null })}>
|
||||||
message={appState.errorMessage}
|
{appState.errorMessage}
|
||||||
onClose={() => setAppState({ errorMessage: null })}
|
</ErrorDialog>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{appState.openDialog === "help" && (
|
{appState.openDialog === "help" && (
|
||||||
<HelpDialog
|
<HelpDialog
|
||||||
|
63
src/components/__snapshots__/App.test.tsx.snap
Normal file
63
src/components/__snapshots__/App.test.tsx.snap
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Test <App/> should show error modal when using brave and measureText API is not working 1`] = `
|
||||||
|
<div
|
||||||
|
data-testid="brave-measure-text-error"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Looks like you are using Brave browser with the
|
||||||
|
|
||||||
|
<span
|
||||||
|
style="font-weight: 600;"
|
||||||
|
>
|
||||||
|
Aggressively Block Fingerprinting
|
||||||
|
</span>
|
||||||
|
|
||||||
|
setting enabled
|
||||||
|
.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
This could result in breaking the
|
||||||
|
|
||||||
|
<span
|
||||||
|
style="font-weight: 600;"
|
||||||
|
>
|
||||||
|
Text Elements
|
||||||
|
</span>
|
||||||
|
|
||||||
|
in your drawings
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We strongly recommend disabling this setting. You can follow
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
|
||||||
|
>
|
||||||
|
|
||||||
|
these steps
|
||||||
|
</a>
|
||||||
|
|
||||||
|
on how to do so
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If disabling this setting doesn't fix the display of text elements, please open an
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://github.com/excalidraw/excalidraw/issues/new"
|
||||||
|
>
|
||||||
|
issue
|
||||||
|
</a>
|
||||||
|
|
||||||
|
on our GitHub, or write us on
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/UexuTaE"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -12,6 +12,9 @@ export const isFirefox =
|
|||||||
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
|
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
|
||||||
export const isSafari =
|
export const isSafari =
|
||||||
!isChrome && navigator.userAgent.indexOf("Safari") !== -1;
|
!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";
|
export const APP_NAME = "Excalidraw";
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { CanvasError } 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";
|
||||||
|
import { ValueOf } from "../utility-types";
|
||||||
import { bytesToHexString } from "../utils";
|
import { bytesToHexString } from "../utils";
|
||||||
import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
|
import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
|
||||||
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
||||||
|
@ -35,6 +35,7 @@ import { getUpdatedTimestamp, updateActiveTool } from "../utils";
|
|||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import { isValidSubtype } from "../subtypes";
|
import { isValidSubtype } from "../subtypes";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
|
import { MarkOptional, Mutable } from "../utility-types";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
import { rescalePoints } from "../points";
|
import { rescalePoints } from "../points";
|
||||||
import { getBoundTextElement, getContainerElement } from "./textElement";
|
import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
import { Mutable } from "../utility-types";
|
||||||
|
|
||||||
// x and y position of top left corner, x and y position of bottom right corner
|
// x and y position of top left corner, x and y position of bottom right corner
|
||||||
export type Bounds = readonly [number, number, number, number];
|
export type Bounds = readonly [number, number, number, number];
|
||||||
|
@ -38,6 +38,7 @@ import { isTextElement } from ".";
|
|||||||
import { isTransparent } from "../utils";
|
import { isTransparent } from "../utils";
|
||||||
import { shouldShowBoundingBox } from "./transformHandles";
|
import { shouldShowBoundingBox } from "./transformHandles";
|
||||||
import { getBoundTextElement } from "./textElement";
|
import { getBoundTextElement } from "./textElement";
|
||||||
|
import { Mutable } from "../utility-types";
|
||||||
|
|
||||||
const isElementDraggableFromInside = (
|
const isElementDraggableFromInside = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
@ -41,6 +41,7 @@ import { shouldRotateWithDiscreteAngle } from "../keys";
|
|||||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||||
import { getShapeForElement } from "../renderer/renderElement";
|
import { getShapeForElement } from "../renderer/renderElement";
|
||||||
import { DRAGGING_THRESHOLD } from "../constants";
|
import { DRAGGING_THRESHOLD } from "../constants";
|
||||||
|
import { Mutable } from "../utility-types";
|
||||||
|
|
||||||
const editorMidPointsCache: {
|
const editorMidPointsCache: {
|
||||||
version: number | null;
|
version: number | null;
|
||||||
|
@ -5,6 +5,7 @@ import { getSizeFromPoints } from "../points";
|
|||||||
import { randomInteger } from "../random";
|
import { randomInteger } from "../random";
|
||||||
import { Point } from "../types";
|
import { Point } from "../types";
|
||||||
import { getUpdatedTimestamp } from "../utils";
|
import { getUpdatedTimestamp } from "../utils";
|
||||||
|
import { Mutable } from "../utility-types";
|
||||||
import { maybeGetSubtypeProps } from "./newElement";
|
import { maybeGetSubtypeProps } from "./newElement";
|
||||||
import { getSubtypeMethods } from "../subtypes";
|
import { getSubtypeMethods } from "../subtypes";
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
import { VERTICAL_ALIGN } from "../constants";
|
import { VERTICAL_ALIGN } from "../constants";
|
||||||
import { isArrowElement } from "./typeChecks";
|
import { isArrowElement } from "./typeChecks";
|
||||||
|
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||||
import { getSubtypeMethods, isValidSubtype } from "../subtypes";
|
import { getSubtypeMethods, isValidSubtype } from "../subtypes";
|
||||||
|
|
||||||
export const maybeGetSubtypeProps = (
|
export const maybeGetSubtypeProps = (
|
||||||
|
@ -9,7 +9,13 @@ import {
|
|||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { mutateElement } from "./mutateElement";
|
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 { MaybeTransformHandleType } from "./transformHandles";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { isTextElement } from ".";
|
import { isTextElement } from ".";
|
||||||
@ -24,6 +30,7 @@ import {
|
|||||||
resetOriginalContainerCache,
|
resetOriginalContainerCache,
|
||||||
updateOriginalContainerCache,
|
updateOriginalContainerCache,
|
||||||
} from "./textWysiwyg";
|
} from "./textWysiwyg";
|
||||||
|
import { ExtractSetType } from "../utility-types";
|
||||||
|
|
||||||
export const measureTextElement = function (element, next) {
|
export const measureTextElement = function (element, next) {
|
||||||
const map = getSubtypeMethods(element.subtype);
|
const map = getSubtypeMethods(element.subtype);
|
||||||
@ -817,3 +824,14 @@ export const getMaxContainerHeight = (container: ExcalidrawElement) => {
|
|||||||
}
|
}
|
||||||
return height - BOUND_TEXT_PADDING * 2;
|
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;
|
||||||
|
};
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { CLASSES, isFirefox, isSafari, VERTICAL_ALIGN } from "../constants";
|
import { CLASSES, VERTICAL_ALIGN } from "../constants";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@ -304,8 +304,7 @@ export const textWysiwyg = ({
|
|||||||
if (!container) {
|
if (!container) {
|
||||||
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
||||||
textElementWidth = Math.min(textElementWidth, maxWidth);
|
textElementWidth = Math.min(textElementWidth, maxWidth);
|
||||||
} else if (isFirefox || isSafari) {
|
} else {
|
||||||
// As firefox, Safari needs little higher dimensions on DOM
|
|
||||||
textElementWidth += 0.5;
|
textElementWidth += 0.5;
|
||||||
transformWidth += 0.5;
|
transformWidth += 0.5;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ROUNDNESS } from "../constants";
|
import { ROUNDNESS } from "../constants";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { MarkNonNullable } from "../utility-types";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
THEME,
|
THEME,
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
|
import { MarkNonNullable, ValueOf } from "../utility-types";
|
||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||||
|
@ -838,10 +838,9 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<ErrorDialog
|
<ErrorDialog onClose={() => this.setState({ errorMessage: "" })}>
|
||||||
message={errorMessage}
|
{errorMessage}
|
||||||
onClose={() => this.setState({ errorMessage: "" })}
|
</ErrorDialog>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,7 @@ import { encryptData, decryptData } from "../../data/encryption";
|
|||||||
import { MIME_TYPES } from "../../constants";
|
import { MIME_TYPES } from "../../constants";
|
||||||
import { reconcileElements } from "../collab/reconciliation";
|
import { reconcileElements } from "../collab/reconciliation";
|
||||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||||
|
import { ResolutionType } from "../../utility-types";
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -86,6 +86,7 @@ import { useAtomWithInitialValue } from "../jotai";
|
|||||||
import { appJotaiStore } from "./app-jotai";
|
import { appJotaiStore } from "./app-jotai";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
import { ResolutionType } from "../utility-types";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@ -677,10 +678,9 @@ const ExcalidrawWrapper = () => {
|
|||||||
</Excalidraw>
|
</Excalidraw>
|
||||||
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
|
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<ErrorDialog
|
<ErrorDialog onClose={() => setErrorMessage("")}>
|
||||||
message={errorMessage}
|
{errorMessage}
|
||||||
onClose={() => setErrorMessage("")}
|
</ErrorDialog>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
49
src/global.d.ts
vendored
49
src/global.d.ts
vendored
@ -51,36 +51,6 @@ interface Clipboard extends EventTarget {
|
|||||||
write(data: any[]): Promise<void>;
|
write(data: any[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutable<T> = {
|
|
||||||
-readonly [P in keyof T]: T[P];
|
|
||||||
};
|
|
||||||
|
|
||||||
type ValueOf<T> = T[keyof T];
|
|
||||||
|
|
||||||
type Merge<M, N> = Omit<M, keyof N> & N;
|
|
||||||
|
|
||||||
/** utility type to assert that the second type is a subtype of the first type.
|
|
||||||
* Returns the subtype. */
|
|
||||||
type SubtypeOf<Supertype, Subtype extends Supertype> = Subtype;
|
|
||||||
|
|
||||||
type ResolutionType<T extends (...args: any) => any> = T extends (
|
|
||||||
...args: any
|
|
||||||
) => Promise<infer R>
|
|
||||||
? R
|
|
||||||
: any;
|
|
||||||
|
|
||||||
// https://github.com/krzkaczor/ts-essentials
|
|
||||||
type MarkOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
||||||
|
|
||||||
type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> &
|
|
||||||
Required<Pick<T, RK>>;
|
|
||||||
|
|
||||||
type MarkNonNullable<T, K extends keyof T> = {
|
|
||||||
[P in K]-?: P extends K ? NonNullable<T[P]> : T[P];
|
|
||||||
} & { [P in keyof T]: T[P] };
|
|
||||||
|
|
||||||
type NonOptional<T> = Exclude<T, undefined>;
|
|
||||||
|
|
||||||
// PNG encoding/decoding
|
// PNG encoding/decoding
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
type TEXtChunk = { name: "tEXt"; data: Uint8Array };
|
type TEXtChunk = { name: "tEXt"; data: Uint8Array };
|
||||||
@ -102,23 +72,6 @@ declare module "png-chunks-extract" {
|
|||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// type getter for interface's callable type
|
|
||||||
// src: https://stackoverflow.com/a/58658851/927631
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
type SignatureType<T> = T extends (...args: infer R) => any ? R : never;
|
|
||||||
type CallableType<T extends (...args: any[]) => any> = (
|
|
||||||
...args: SignatureType<T>
|
|
||||||
) => ReturnType<T>;
|
|
||||||
// --------------------------------------------------------------------------—
|
|
||||||
|
|
||||||
// Type for React.forwardRef --- supply only the first generic argument T
|
|
||||||
type ForwardRef<T, P = any> = Parameters<
|
|
||||||
CallableType<React.ForwardRefRenderFunction<T, P>>
|
|
||||||
>[1];
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------—
|
|
||||||
|
|
||||||
interface Blob {
|
interface Blob {
|
||||||
handle?: import("browser-fs-acces").FileSystemHandle;
|
handle?: import("browser-fs-acces").FileSystemHandle;
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -166,5 +119,3 @@ declare module "image-blob-reduce" {
|
|||||||
const reduce: ImageBlobReduce.ImageBlobReduceStatic;
|
const reduce: ImageBlobReduce.ImageBlobReduceStatic;
|
||||||
export = reduce;
|
export = reduce;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtractSetType<T extends Set<any>> = T extends Set<infer U> ? U : never;
|
|
||||||
|
@ -2,6 +2,7 @@ import { AppState } from "./types";
|
|||||||
import { ExcalidrawElement } from "./element/types";
|
import { ExcalidrawElement } from "./element/types";
|
||||||
import { isLinearElement } from "./element/typeChecks";
|
import { isLinearElement } from "./element/typeChecks";
|
||||||
import { deepCopyElement } from "./element/newElement";
|
import { deepCopyElement } from "./element/newElement";
|
||||||
|
import { Mutable } from "./utility-types";
|
||||||
|
|
||||||
export interface HistoryEntry {
|
export interface HistoryEntry {
|
||||||
appState: ReturnType<typeof clearAppStatePropertiesForHistory>;
|
appState: ReturnType<typeof clearAppStatePropertiesForHistory>;
|
||||||
|
@ -121,7 +121,6 @@
|
|||||||
"edit": "Edit line",
|
"edit": "Edit line",
|
||||||
"exit": "Exit line editor"
|
"exit": "Exit line editor"
|
||||||
},
|
},
|
||||||
|
|
||||||
"elementLock": {
|
"elementLock": {
|
||||||
"lock": "Lock",
|
"lock": "Lock",
|
||||||
"unlock": "Unlock",
|
"unlock": "Unlock",
|
||||||
@ -207,7 +206,22 @@
|
|||||||
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
|
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
|
||||||
"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.",
|
||||||
|
"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": {
|
"toolBar": {
|
||||||
"selection": "Selection",
|
"selection": "Selection",
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
import { getShapeForElement } from "./renderer/renderElement";
|
import { getShapeForElement } from "./renderer/renderElement";
|
||||||
import { getCurvePathOps } from "./element/bounds";
|
import { getCurvePathOps } from "./element/bounds";
|
||||||
|
import { Mutable } from "./utility-types";
|
||||||
|
|
||||||
export const rotate = (
|
export const rotate = (
|
||||||
x1: number,
|
x1: number,
|
||||||
|
@ -5,7 +5,7 @@ exports[`Test Linear Elements Test bound text element should match styles for te
|
|||||||
class="excalidraw-wysiwyg"
|
class="excalidraw-wysiwyg"
|
||||||
data-type="wysiwyg"
|
data-type="wysiwyg"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10px; height: 24px; left: 35px; top: 8px; transform-origin: 5px 12px; transform: translate(0px, 0px) scale(1) rotate(0deg) translate(0px, 0px); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -8px; font: Emoji 20px 20px; line-height: 24px; font-family: Virgil, Segoe UI Emoji;"
|
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 24px; left: 35px; top: 8px; transform-origin: 5px 12px; transform: translate(0px, 0px) scale(1) rotate(0deg) translate(0px, 0px); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -8px; font: Emoji 20px 20px; line-height: 24px; font-family: Virgil, Segoe UI Emoji;"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
wrap="off"
|
wrap="off"
|
||||||
/>
|
/>
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
import { Point } from "../../types";
|
import { Point } from "../../types";
|
||||||
import { getSelectedElements } from "../../scene/selection";
|
import { getSelectedElements } from "../../scene/selection";
|
||||||
import { isLinearElementType } from "../../element/typeChecks";
|
import { isLinearElementType } from "../../element/typeChecks";
|
||||||
|
import { Mutable } from "../../utility-types";
|
||||||
|
|
||||||
const readFile = util.promisify(fs.readFile);
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ import {
|
|||||||
import type { FileSystemHandle } from "./data/filesystem";
|
import type { FileSystemHandle } from "./data/filesystem";
|
||||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||||
import { ContextMenuItems } from "./components/ContextMenu";
|
import { ContextMenuItems } from "./components/ContextMenu";
|
||||||
|
import { Merge, ForwardRef } from "./utility-types";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export type Point = Readonly<RoughPoint>;
|
export type Point = Readonly<RoughPoint>;
|
||||||
|
|
||||||
@ -107,7 +109,7 @@ export type AppState = {
|
|||||||
} | null;
|
} | null;
|
||||||
showWelcomeScreen: boolean;
|
showWelcomeScreen: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
errorMessage: string | null;
|
errorMessage: React.ReactNode;
|
||||||
draggingElement: NonDeletedExcalidrawElement | null;
|
draggingElement: NonDeletedExcalidrawElement | null;
|
||||||
resizingElement: NonDeletedExcalidrawElement | null;
|
resizingElement: NonDeletedExcalidrawElement | null;
|
||||||
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
|
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
|
||||||
|
49
src/utility-types.ts
Normal file
49
src/utility-types.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
export type Mutable<T> = {
|
||||||
|
-readonly [P in keyof T]: T[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ValueOf<T> = T[keyof T];
|
||||||
|
|
||||||
|
export type Merge<M, N> = Omit<M, keyof N> & N;
|
||||||
|
|
||||||
|
/** utility type to assert that the second type is a subtype of the first type.
|
||||||
|
* Returns the subtype. */
|
||||||
|
export type SubtypeOf<Supertype, Subtype extends Supertype> = Subtype;
|
||||||
|
|
||||||
|
export type ResolutionType<T extends (...args: any) => any> = T extends (
|
||||||
|
...args: any
|
||||||
|
) => Promise<infer R>
|
||||||
|
? R
|
||||||
|
: any;
|
||||||
|
|
||||||
|
// https://github.com/krzkaczor/ts-essentials
|
||||||
|
export type MarkOptional<T, K extends keyof T> = Omit<T, K> &
|
||||||
|
Partial<Pick<T, K>>;
|
||||||
|
|
||||||
|
export type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> &
|
||||||
|
Required<Pick<T, RK>>;
|
||||||
|
|
||||||
|
export type MarkNonNullable<T, K extends keyof T> = {
|
||||||
|
[P in K]-?: P extends K ? NonNullable<T[P]> : T[P];
|
||||||
|
} & { [P in keyof T]: T[P] };
|
||||||
|
|
||||||
|
export type NonOptional<T> = Exclude<T, undefined>;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// type getter for interface's callable type
|
||||||
|
// src: https://stackoverflow.com/a/58658851/927631
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
export type SignatureType<T> = T extends (...args: infer R) => any ? R : never;
|
||||||
|
export type CallableType<T extends (...args: any[]) => any> = (
|
||||||
|
...args: SignatureType<T>
|
||||||
|
) => ReturnType<T>;
|
||||||
|
// --------------------------------------------------------------------------—
|
||||||
|
|
||||||
|
// Type for React.forwardRef --- supply only the first generic argument T
|
||||||
|
export type ForwardRef<T, P = any> = Parameters<
|
||||||
|
CallableType<React.ForwardRefRenderFunction<T, P>>
|
||||||
|
>[1];
|
||||||
|
|
||||||
|
export type ExtractSetType<T extends Set<any>> = T extends Set<infer U>
|
||||||
|
? U
|
||||||
|
: never;
|
@ -16,6 +16,7 @@ import { AppState, DataURL, LastActiveTool, Zoom } from "./types";
|
|||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
import { SHAPES } from "./shapes";
|
import { SHAPES } from "./shapes";
|
||||||
import { isEraserActive, isHandToolActive } from "./appState";
|
import { isEraserActive, isHandToolActive } from "./appState";
|
||||||
|
import { ResolutionType } from "./utility-types";
|
||||||
|
|
||||||
let mockDateTime: string | null = null;
|
let mockDateTime: string | null = null;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user