Compare commits

...

4 Commits

Author SHA1 Message Date
David Luzar 3ae8af5a13 v0.16.2 2024-04-12 20:15:53 +02:00
David Luzar 053ca9058a fix: Gist embed allowing unsafe html (#7883) 2024-04-12 15:14:18 +02:00
David Luzar f8b3692262 fix: more eye-droper fixes (#7019) 2023-09-21 09:54:03 +05:30
Aakansha Doshi 741d5f1a18 refactor: move excalidraw-app outside src (#6987)
* refactor: move excalidraw-app outside src

* move some tests to excal app and fix some

* fix tests

* fix

* port remaining tests

* fix

* update snap

* move tests inside test folder

* fix

* fix
2023-09-21 09:28:48 +05:30
71 changed files with 785 additions and 521 deletions
@@ -1,14 +1,14 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { debounce, getVersion, nFormatter } from "../utils"; import { debounce, getVersion, nFormatter } from "../src/utils";
import { import {
getElementsStorageSize, getElementsStorageSize,
getTotalStorageSize, getTotalStorageSize,
} from "./data/localStorage"; } from "./data/localStorage";
import { DEFAULT_VERSION } from "../constants"; import { DEFAULT_VERSION } from "../src/constants";
import { t } from "../i18n"; import { t } from "../src/i18n";
import { copyTextToSystemClipboard } from "../clipboard"; import { copyTextToSystemClipboard } from "../src/clipboard";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../src/element/types";
import { UIAppState } from "../types"; import { UIAppState } from "../src/types";
type StorageSizes = { scene: number; total: number }; type StorageSizes = { scene: number; total: number };
@@ -1,23 +1,23 @@
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import { PureComponent } from "react"; import { PureComponent } from "react";
import { ExcalidrawImperativeAPI } from "../../types"; import { ExcalidrawImperativeAPI } from "../../src/types";
import { ErrorDialog } from "../../components/ErrorDialog"; import { ErrorDialog } from "../../src/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../constants"; import { APP_NAME, ENV, EVENT } from "../../src/constants";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
import { import {
ExcalidrawElement, ExcalidrawElement,
InitializedExcalidrawImageElement, InitializedExcalidrawImageElement,
} from "../../element/types"; } from "../../src/element/types";
import { import {
getSceneVersion, getSceneVersion,
restoreElements, restoreElements,
} from "../../packages/excalidraw/index"; } from "../../src/packages/excalidraw/index";
import { Collaborator, Gesture } from "../../types"; import { Collaborator, Gesture } from "../../src/types";
import { import {
preventUnload, preventUnload,
resolvablePromise, resolvablePromise,
withBatchedUpdates, withBatchedUpdates,
} from "../../utils"; } from "../../src/utils";
import { import {
CURSOR_SYNC_TIMEOUT, CURSOR_SYNC_TIMEOUT,
FILE_UPLOAD_MAX_BYTES, FILE_UPLOAD_MAX_BYTES,
@@ -48,25 +48,25 @@ import {
} from "../data/localStorage"; } from "../data/localStorage";
import Portal from "./Portal"; import Portal from "./Portal";
import RoomDialog from "./RoomDialog"; import RoomDialog from "./RoomDialog";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { UserIdleState } from "../../types"; import { UserIdleState } from "../../src/types";
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants"; import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../src/constants";
import { import {
encodeFilesForUpload, encodeFilesForUpload,
FileManager, FileManager,
updateStaleImageStatuses, updateStaleImageStatuses,
} from "../data/FileManager"; } from "../data/FileManager";
import { AbortError } from "../../errors"; import { AbortError } from "../../src/errors";
import { import {
isImageElement, isImageElement,
isInitializedImageElement, isInitializedImageElement,
} from "../../element/typeChecks"; } from "../../src/element/typeChecks";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { import {
ReconciledElements, ReconciledElements,
reconcileElements as _reconcileElements, reconcileElements as _reconcileElements,
} from "./reconciliation"; } from "./reconciliation";
import { decryptData } from "../../data/encryption"; import { decryptData } from "../../src/data/encryption";
import { resetBrowserStateVersions } from "../data/tabSync"; import { resetBrowserStateVersions } from "../data/tabSync";
import { LocalData } from "../data/LocalData"; import { LocalData } from "../data/LocalData";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
@@ -6,19 +6,19 @@ import {
import { TCollabClass } from "./Collab"; import { TCollabClass } from "./Collab";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { import {
WS_EVENTS, WS_EVENTS,
FILE_UPLOAD_TIMEOUT, FILE_UPLOAD_TIMEOUT,
WS_SCENE_EVENT_TYPES, WS_SCENE_EVENT_TYPES,
} from "../app_constants"; } from "../app_constants";
import { UserIdleState } from "../../types"; import { UserIdleState } from "../../src/types";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { BroadcastedExcalidrawElement } from "./reconciliation"; import { BroadcastedExcalidrawElement } from "./reconciliation";
import { encryptData } from "../../data/encryption"; import { encryptData } from "../../src/data/encryption";
import { PRECEDING_ELEMENT_KEY } from "../../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
class Portal { class Portal {
collab: TCollabClass; collab: TCollabClass;
@@ -1,4 +1,4 @@
@import "../../css/variables.module"; @import "../../src/css/variables.module";
.excalidraw { .excalidraw {
.RoomDialog { .RoomDialog {
@@ -1,13 +1,13 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { copyTextToSystemClipboard } from "../../clipboard"; import { copyTextToSystemClipboard } from "../../src/clipboard";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import { getFrame } from "../../utils"; import { getFrame } from "../../src/utils";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { KEYS } from "../../keys"; import { KEYS } from "../../src/keys";
import { Dialog } from "../../components/Dialog"; import { Dialog } from "../../src/components/Dialog";
import { import {
copyIcon, copyIcon,
playerPlayIcon, playerPlayIcon,
@@ -16,11 +16,11 @@ import {
shareIOS, shareIOS,
shareWindows, shareWindows,
tablerCheckIcon, tablerCheckIcon,
} from "../../components/icons"; } from "../../src/components/icons";
import { TextField } from "../../components/TextField"; import { TextField } from "../../src/components/TextField";
import { FilledButton } from "../../components/FilledButton"; import { FilledButton } from "../../src/components/FilledButton";
import { ReactComponent as CollabImage } from "../../assets/lock.svg"; import { ReactComponent as CollabImage } from "../../src/assets/lock.svg";
import "./RoomDialog.scss"; import "./RoomDialog.scss";
const getShareIcon = () => { const getShareIcon = () => {
@@ -1,7 +1,7 @@
import { PRECEDING_ELEMENT_KEY } from "../../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { AppState } from "../../types"; import { AppState } from "../../src/types";
import { arrayToMapWithIndex } from "../../utils"; import { arrayToMapWithIndex } from "../../src/utils";
export type ReconciledElements = readonly ExcalidrawElement[] & { export type ReconciledElements = readonly ExcalidrawElement[] & {
_brand: "reconciledElements"; _brand: "reconciledElements";
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Footer } from "../../packages/excalidraw/index"; import { Footer } from "../../src/packages/excalidraw/index";
import { EncryptedIcon } from "./EncryptedIcon"; import { EncryptedIcon } from "./EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink"; import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
import { isExcalidrawPlusSignedUser } from "../app_constants"; import { isExcalidrawPlusSignedUser } from "../app_constants";
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { PlusPromoIcon } from "../../components/icons"; import { PlusPromoIcon } from "../../src/components/icons";
import { MainMenu } from "../../packages/excalidraw/index"; import { MainMenu } from "../../src/packages/excalidraw/index";
import { LanguageList } from "./LanguageList"; import { LanguageList } from "./LanguageList";
export const AppMainMenu: React.FC<{ export const AppMainMenu: React.FC<{
@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { PlusPromoIcon } from "../../components/icons"; import { PlusPromoIcon } from "../../src/components/icons";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { WelcomeScreen } from "../../packages/excalidraw/index"; import { WelcomeScreen } from "../../src/packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants"; import { isExcalidrawPlusSignedUser } from "../app_constants";
import { POINTER_EVENTS } from "../../constants"; import { POINTER_EVENTS } from "../../src/constants";
export const AppWelcomeScreen: React.FC<{ export const AppWelcomeScreen: React.FC<{
setCollabDialogShown: (toggle: boolean) => any; setCollabDialogShown: (toggle: boolean) => any;
@@ -1,6 +1,6 @@
import { shield } from "../../components/icons"; import { shield } from "../../src/components/icons";
import { Tooltip } from "../../components/Tooltip"; import { Tooltip } from "../../src/components/Tooltip";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
export const EncryptedIcon = () => { export const EncryptedIcon = () => {
const { t } = useI18n(); const { t } = useI18n();
@@ -1,20 +1,20 @@
import React from "react"; import React from "react";
import { Card } from "../../components/Card"; import { Card } from "../../src/components/Card";
import { ToolButton } from "../../components/ToolButton"; import { ToolButton } from "../../src/components/ToolButton";
import { serializeAsJSON } from "../../data/json"; import { serializeAsJSON } from "../../src/data/json";
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase"; import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
import { FileId, NonDeletedExcalidrawElement } from "../../element/types"; import { FileId, NonDeletedExcalidrawElement } from "../../src/element/types";
import { AppState, BinaryFileData, BinaryFiles } from "../../types"; import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { encryptData, generateEncryptionKey } from "../../data/encryption"; import { encryptData, generateEncryptionKey } from "../../src/data/encryption";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants"; import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
import { encodeFilesForUpload } from "../data/FileManager"; import { encodeFilesForUpload } from "../data/FileManager";
import { MIME_TYPES } from "../../constants"; import { MIME_TYPES } from "../../src/constants";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import { getFrame } from "../../utils"; import { getFrame } from "../../src/utils";
import { ExcalidrawLogo } from "../../components/ExcalidrawLogo"; import { ExcalidrawLogo } from "../../src/components/ExcalidrawLogo";
export const exportToExcalidrawPlus = async ( export const exportToExcalidrawPlus = async (
elements: readonly NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],
@@ -1,7 +1,7 @@
import oc from "open-color"; import oc from "open-color";
import React from "react"; import React from "react";
import { THEME } from "../../constants"; import { THEME } from "../../src/constants";
import { Theme } from "../../element/types"; import { Theme } from "../../src/element/types";
// https://github.com/tholman/github-corners // https://github.com/tholman/github-corners
export const GitHubCorner = React.memo( export const GitHubCorner = React.memo(
@@ -1,8 +1,8 @@
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import React from "react"; import React from "react";
import { appLangCodeAtom } from ".."; import { appLangCodeAtom } from "..";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { languages } from "../../i18n"; import { languages } from "../../src/i18n";
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => { export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
const { t, langCode } = useI18n(); const { t, langCode } = useI18n();
@@ -1,19 +1,19 @@
import { compressData } from "../../data/encode"; import { compressData } from "../../src/data/encode";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { import {
ExcalidrawElement, ExcalidrawElement,
ExcalidrawImageElement, ExcalidrawImageElement,
FileId, FileId,
InitializedExcalidrawImageElement, InitializedExcalidrawImageElement,
} from "../../element/types"; } from "../../src/element/types";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { import {
BinaryFileData, BinaryFileData,
BinaryFileMetadata, BinaryFileMetadata,
ExcalidrawImperativeAPI, ExcalidrawImperativeAPI,
BinaryFiles, BinaryFiles,
} from "../../types"; } from "../../src/types";
export class FileManager { export class FileManager {
/** files being fetched */ /** files being fetched */
@@ -11,11 +11,11 @@
*/ */
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval"; import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
import { clearAppStateForLocalStorage } from "../../appState"; import { clearAppStateForLocalStorage } from "../../src/appState";
import { clearElementsForLocalStorage } from "../../element"; import { clearElementsForLocalStorage } from "../../src/element";
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { AppState, BinaryFileData, BinaryFiles } from "../../types"; import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
import { debounce } from "../../utils"; import { debounce } from "../../src/utils";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants"; import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
import { FileManager } from "./FileManager"; import { FileManager } from "./FileManager";
import { Locker } from "./Locker"; import { Locker } from "./Locker";
@@ -1,20 +1,20 @@
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { getSceneVersion } from "../../element"; import { getSceneVersion } from "../../src/element";
import Portal from "../collab/Portal"; import Portal from "../collab/Portal";
import { restoreElements } from "../../data/restore"; import { restoreElements } from "../../src/data/restore";
import { import {
AppState, AppState,
BinaryFileData, BinaryFileData,
BinaryFileMetadata, BinaryFileMetadata,
DataURL, DataURL,
} from "../../types"; } from "../../src/types";
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants"; import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
import { decompressData } from "../../data/encode"; import { decompressData } from "../../src/data/encode";
import { encryptData, decryptData } from "../../data/encryption"; import { encryptData, decryptData } from "../../src/data/encryption";
import { MIME_TYPES } from "../../constants"; import { MIME_TYPES } from "../../src/constants";
import { reconcileElements } from "../collab/reconciliation"; import { reconcileElements } from "../collab/reconciliation";
import { getSyncableElements, SyncableExcalidrawElement } from "."; import { getSyncableElements, SyncableExcalidrawElement } from ".";
import { ResolutionType } from "../../utility-types"; import { ResolutionType } from "../../src/utility-types";
// private // private
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -1,23 +1,23 @@
import { compressData, decompressData } from "../../data/encode"; import { compressData, decompressData } from "../../src/data/encode";
import { import {
decryptData, decryptData,
generateEncryptionKey, generateEncryptionKey,
IV_LENGTH_BYTES, IV_LENGTH_BYTES,
} from "../../data/encryption"; } from "../../src/data/encryption";
import { serializeAsJSON } from "../../data/json"; import { serializeAsJSON } from "../../src/data/json";
import { restore } from "../../data/restore"; import { restore } from "../../src/data/restore";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
import { isInvisiblySmallElement } from "../../element/sizeHelpers"; import { isInvisiblySmallElement } from "../../src/element/sizeHelpers";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { import {
AppState, AppState,
BinaryFileData, BinaryFileData,
BinaryFiles, BinaryFiles,
UserIdleState, UserIdleState,
} from "../../types"; } from "../../src/types";
import { bytesToHexString } from "../../utils"; import { bytesToHexString } from "../../src/utils";
import { import {
DELETED_ELEMENT_TIMEOUT, DELETED_ELEMENT_TIMEOUT,
FILE_UPLOAD_MAX_BYTES, FILE_UPLOAD_MAX_BYTES,
@@ -1,12 +1,12 @@
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { AppState } from "../../types"; import { AppState } from "../../src/types";
import { import {
clearAppStateForLocalStorage, clearAppStateForLocalStorage,
getDefaultAppState, getDefaultAppState,
} from "../../appState"; } from "../../src/appState";
import { clearElementsForLocalStorage } from "../../element"; import { clearElementsForLocalStorage } from "../../src/element";
import { STORAGE_KEYS } from "../app_constants"; import { STORAGE_KEYS } from "../app_constants";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
export const saveUsernameToLocalStorage = (username: string) => { export const saveUsernameToLocalStorage = (username: string) => {
try { try {
@@ -1,31 +1,31 @@
import polyfill from "../polyfill"; import polyfill from "../src/polyfill";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { trackEvent } from "../analytics"; import { trackEvent } from "../src/analytics";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../src/appState";
import { ErrorDialog } from "../components/ErrorDialog"; import { ErrorDialog } from "../src/components/ErrorDialog";
import { TopErrorBoundary } from "../components/TopErrorBoundary"; import { TopErrorBoundary } from "../src/components/TopErrorBoundary";
import { import {
APP_NAME, APP_NAME,
EVENT, EVENT,
THEME, THEME,
TITLE_TIMEOUT, TITLE_TIMEOUT,
VERSION_TIMEOUT, VERSION_TIMEOUT,
} from "../constants"; } from "../src/constants";
import { loadFromBlob } from "../data/blob"; import { loadFromBlob } from "../src/data/blob";
import { import {
ExcalidrawElement, ExcalidrawElement,
FileId, FileId,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
Theme, Theme,
} from "../element/types"; } from "../src/element/types";
import { useCallbackRefState } from "../hooks/useCallbackRefState"; import { useCallbackRefState } from "../src/hooks/useCallbackRefState";
import { t } from "../i18n"; import { t } from "../src/i18n";
import { import {
Excalidraw, Excalidraw,
defaultLang, defaultLang,
LiveCollaborationTrigger, LiveCollaborationTrigger,
} from "../packages/excalidraw/index"; } from "../src/packages/excalidraw/index";
import { import {
AppState, AppState,
LibraryItems, LibraryItems,
@@ -33,7 +33,7 @@ import {
BinaryFiles, BinaryFiles,
ExcalidrawInitialDataState, ExcalidrawInitialDataState,
UIAppState, UIAppState,
} from "../types"; } from "../src/types";
import { import {
debounce, debounce,
getVersion, getVersion,
@@ -43,7 +43,7 @@ import {
ResolvablePromise, ResolvablePromise,
resolvablePromise, resolvablePromise,
isRunningInIframe, isRunningInIframe,
} from "../utils"; } from "../src/utils";
import { import {
FIREBASE_STORAGE_PREFIXES, FIREBASE_STORAGE_PREFIXES,
STORAGE_KEYS, STORAGE_KEYS,
@@ -68,33 +68,40 @@ import {
importUsernameFromLocalStorage, importUsernameFromLocalStorage,
} from "./data/localStorage"; } from "./data/localStorage";
import CustomStats from "./CustomStats"; import CustomStats from "./CustomStats";
import { restore, restoreAppState, RestoredDataState } from "../data/restore"; import {
restore,
restoreAppState,
RestoredDataState,
} from "../src/data/restore";
import { import {
ExportToExcalidrawPlus, ExportToExcalidrawPlus,
exportToExcalidrawPlus, exportToExcalidrawPlus,
} from "./components/ExportToExcalidrawPlus"; } from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager"; import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../element/mutateElement"; import { newElementWith } from "../src/element/mutateElement";
import { isInitializedImageElement } from "../element/typeChecks"; import { isInitializedImageElement } from "../src/element/typeChecks";
import { loadFilesFromFirebase } from "./data/firebase"; import { loadFilesFromFirebase } from "./data/firebase";
import { LocalData } from "./data/LocalData"; import { LocalData } from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync"; import { isBrowserStorageStateNewer } from "./data/tabSync";
import clsx from "clsx"; import clsx from "clsx";
import { reconcileElements } from "./collab/reconciliation"; import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library"; import {
parseLibraryTokensFromUrl,
useHandleLibrary,
} from "../src/data/library";
import { AppMainMenu } from "./components/AppMainMenu"; import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen"; import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter"; import { AppFooter } from "./components/AppFooter";
import { atom, Provider, useAtom, useAtomValue } from "jotai"; import { atom, Provider, useAtom, useAtomValue } from "jotai";
import { useAtomWithInitialValue } from "../jotai"; import { useAtomWithInitialValue } from "../src/jotai";
import { appJotaiStore } from "./app-jotai"; import { appJotaiStore } from "./app-jotai";
import "./index.scss"; import "./index.scss";
import { ResolutionType } from "../utility-types"; import { ResolutionType } from "../src/utility-types";
import { ShareableLinkDialog } from "../components/ShareableLinkDialog"; import { ShareableLinkDialog } from "../src/components/ShareableLinkDialog";
import { openConfirmModal } from "../components/OverwriteConfirm/OverwriteConfirmState"; import { openConfirmModal } from "../src/components/OverwriteConfirm/OverwriteConfirmState";
import { OverwriteConfirmDialog } from "../components/OverwriteConfirm/OverwriteConfirm"; import { OverwriteConfirmDialog } from "../src/components/OverwriteConfirm/OverwriteConfirm";
import Trans from "../components/Trans"; import Trans from "../src/components/Trans";
polyfill(); polyfill();
@@ -0,0 +1,29 @@
import { defaultLang } from "../../src/i18n";
import { UI } from "../../src/tests/helpers/ui";
import { screen, fireEvent, waitFor, render } from "../../src/tests/test-utils";
import ExcalidrawApp from "../../excalidraw-app";
describe("Test LanguageList", () => {
it("rerenders UI on language change", async () => {
await render(<ExcalidrawApp />);
// select rectangle tool to show properties menu
UI.clickTool("rectangle");
// english lang should display `thin` label
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: "de-DE" },
});
// switching to german, `thin` label should no longer exist
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
// reset language
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: defaultLang.code },
});
// switching back to English
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
});
});
@@ -1,11 +1,11 @@
import ExcalidrawApp from "../excalidraw-app"; import ExcalidrawApp from "../../excalidraw-app";
import { import {
mockBoundingClientRect, mockBoundingClientRect,
render, render,
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
} from "./test-utils"; } from "../../src/tests/test-utils";
import { UI } from "./helpers/ui"; import { UI } from "../../src/tests/helpers/ui";
describe("Test MobileMenu", () => { describe("Test MobileMenu", () => {
const { h } = window; const { h } = window;
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
import { vi } from "vitest"; import { vi } from "vitest";
import { render, updateSceneData, waitFor } from "./test-utils"; import { render, updateSceneData, waitFor } from "../../src/tests/test-utils";
import ExcalidrawApp from "../excalidraw-app"; import ExcalidrawApp from "../../excalidraw-app";
import { API } from "./helpers/api"; import { API } from "../../src/tests/helpers/api";
import { createUndoAction } from "../actions/actionHistory"; import { createUndoAction } from "../../src/actions/actionHistory";
const { h } = window; const { h } = window;
Object.defineProperty(window, "crypto", { Object.defineProperty(window, "crypto", {
@@ -16,7 +16,7 @@ Object.defineProperty(window, "crypto", {
}, },
}); });
vi.mock("../excalidraw-app/data/index.ts", async (importActual) => { vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => {
const module = (await importActual()) as any; const module = (await importActual()) as any;
return { return {
__esmodule: true, __esmodule: true,
@@ -27,7 +27,7 @@ vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
}; };
}); });
vi.mock("../excalidraw-app/data/firebase.ts", () => { vi.mock("../../excalidraw-app/data/firebase.ts", () => {
const loadFromFirebase = async () => null; const loadFromFirebase = async () => null;
const saveToFirebase = () => {}; const saveToFirebase = () => {};
const isSavedToFirebase = () => true; const isSavedToFirebase = () => true;
@@ -1,13 +1,13 @@
import { expect } from "chai"; import { expect } from "chai";
import { PRECEDING_ELEMENT_KEY } from "../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { import {
BroadcastedExcalidrawElement, BroadcastedExcalidrawElement,
ReconciledElements, ReconciledElements,
reconcileElements, reconcileElements,
} from "../excalidraw-app/collab/reconciliation"; } from "../../excalidraw-app/collab/reconciliation";
import { randomInteger } from "../random"; import { randomInteger } from "../../src/random";
import { AppState } from "../types"; import { AppState } from "../../src/types";
type Id = string; type Id = string;
type ElementLike = { type ElementLike = {
+13 -6
View File
@@ -894,7 +894,11 @@ class App extends React.Component<AppProps, AppState> {
title="Excalidraw Embedded Content" title="Excalidraw Embedded Content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen={true} allowFullScreen={true}
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads" sandbox={`${
embedLink?.sandbox?.allowSameOrigin
? "allow-same-origin"
: ""
} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`}
/> />
)} )}
</div> </div>
@@ -1330,7 +1334,8 @@ class App extends React.Component<AppProps, AppState> {
private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => { private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => {
jotaiStore.set(activeEyeDropperAtom, { jotaiStore.set(activeEyeDropperAtom, {
swapPreviewOnAlt: true, swapPreviewOnAlt: true,
previewType: type === "stroke" ? "strokeColor" : "backgroundColor", colorPickerType:
type === "stroke" ? "elementStroke" : "elementBackground",
onSelect: (color, event) => { onSelect: (color, event) => {
const shouldUpdateStrokeColor = const shouldUpdateStrokeColor =
(type === "background" && event.altKey) || (type === "background" && event.altKey) ||
@@ -1341,12 +1346,14 @@ class App extends React.Component<AppProps, AppState> {
this.state.activeTool.type !== "selection" this.state.activeTool.type !== "selection"
) { ) {
if (shouldUpdateStrokeColor) { if (shouldUpdateStrokeColor) {
this.setState({ this.syncActionResult({
currentItemStrokeColor: color, appState: { ...this.state, currentItemStrokeColor: color },
commitToHistory: true,
}); });
} else { } else {
this.setState({ this.syncActionResult({
currentItemBackgroundColor: color, appState: { ...this.state, currentItemBackgroundColor: color },
commitToHistory: true,
}); });
} }
} else { } else {
+7 -4
View File
@@ -1,7 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { getColor } from "./ColorPicker"; import { getColor } from "./ColorPicker";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { activeColorPickerSectionAtom } from "./colorPickerUtils"; import {
ColorPickerType,
activeColorPickerSectionAtom,
} from "./colorPickerUtils";
import { eyeDropperIcon } from "../icons"; import { eyeDropperIcon } from "../icons";
import { jotaiScope } from "../../jotai"; import { jotaiScope } from "../../jotai";
import { KEYS } from "../../keys"; import { KEYS } from "../../keys";
@@ -15,14 +18,14 @@ interface ColorInputProps {
color: string; color: string;
onChange: (color: string) => void; onChange: (color: string) => void;
label: string; label: string;
eyeDropperType: "strokeColor" | "backgroundColor"; colorPickerType: ColorPickerType;
} }
export const ColorInput = ({ export const ColorInput = ({
color, color,
onChange, onChange,
label, label,
eyeDropperType, colorPickerType,
}: ColorInputProps) => { }: ColorInputProps) => {
const device = useDevice(); const device = useDevice();
const [innerValue, setInnerValue] = useState(color); const [innerValue, setInnerValue] = useState(color);
@@ -116,7 +119,7 @@ export const ColorInput = ({
: { : {
keepOpenOnAlt: false, keepOpenOnAlt: false,
onSelect: (color) => onChange(color), onSelect: (color) => onChange(color),
previewType: eyeDropperType, colorPickerType,
}, },
) )
} }
+5 -12
View File
@@ -82,14 +82,7 @@ const ColorPickerPopupContent = ({
const { container } = useExcalidrawContainer(); const { container } = useExcalidrawContainer();
const { isMobile, isLandscape } = useDevice(); const { isMobile, isLandscape } = useDevice();
const eyeDropperType = const colorInputJSX = (
type === "canvasBackground"
? undefined
: type === "elementBackground"
? "backgroundColor"
: "strokeColor";
const colorInputJSX = eyeDropperType && (
<div> <div>
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading> <PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
<ColorInput <ColorInput
@@ -98,7 +91,7 @@ const ColorPickerPopupContent = ({
onChange={(color) => { onChange={(color) => {
onChange(color); onChange(color);
}} }}
eyeDropperType={eyeDropperType} colorPickerType={type}
/> />
</div> </div>
); );
@@ -160,7 +153,7 @@ const ColorPickerPopupContent = ({
"0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)", "0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)",
}} }}
> >
{palette && eyeDropperType ? ( {palette ? (
<Picker <Picker
palette={palette} palette={palette}
color={color} color={color}
@@ -173,7 +166,7 @@ const ColorPickerPopupContent = ({
state = state || { state = state || {
keepOpenOnAlt: true, keepOpenOnAlt: true,
onSelect: onChange, onSelect: onChange,
previewType: eyeDropperType, colorPickerType: type,
}; };
state.keepOpenOnAlt = true; state.keepOpenOnAlt = true;
return state; return state;
@@ -184,7 +177,7 @@ const ColorPickerPopupContent = ({
: { : {
keepOpenOnAlt: false, keepOpenOnAlt: false,
onSelect: onChange, onSelect: onChange,
previewType: eyeDropperType, colorPickerType: type,
}; };
}); });
}} }}
+50 -36
View File
@@ -1,35 +1,47 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { rgbToHex } from "../colors"; import { rgbToHex } from "../colors";
import { EVENT } from "../constants"; import { EVENT } from "../constants";
import { useUIAppState } from "../context/ui-appState"; import { useUIAppState } from "../context/ui-appState";
import { mutateElement } from "../element/mutateElement";
import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer"; import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer";
import { useOutsideClick } from "../hooks/useOutsideClick"; import { useOutsideClick } from "../hooks/useOutsideClick";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { getSelectedElements } from "../scene"; import { getSelectedElements } from "../scene";
import Scene from "../scene/Scene";
import { ShapeCache } from "../scene/ShapeCache";
import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App"; import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App";
import { useStable } from "../hooks/useStable";
import "./EyeDropper.scss"; import "./EyeDropper.scss";
import { ColorPickerType } from "./ColorPicker/colorPickerUtils";
import { ExcalidrawElement } from "../element/types";
type EyeDropperProperties = { export type EyeDropperProperties = {
keepOpenOnAlt: boolean; keepOpenOnAlt: boolean;
swapPreviewOnAlt?: boolean; swapPreviewOnAlt?: boolean;
/** called when user picks color (on pointerup) */
onSelect: (color: string, event: PointerEvent) => void; onSelect: (color: string, event: PointerEvent) => void;
previewType: "strokeColor" | "backgroundColor"; /**
* property of selected elements to update live when alt-dragging.
* Supply `null` if not applicable (e.g. updating the canvas bg instead of
* elements)
**/
colorPickerType: ColorPickerType;
}; };
export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null); export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null);
export const EyeDropper: React.FC<{ export const EyeDropper: React.FC<{
onCancel: () => void; onCancel: () => void;
onSelect: Required<EyeDropperProperties>["onSelect"]; onSelect: EyeDropperProperties["onSelect"];
swapPreviewOnAlt?: EyeDropperProperties["swapPreviewOnAlt"]; /** called when color changes, on pointerdown for preview */
previewType: EyeDropperProperties["previewType"]; onChange: (
}> = ({ onCancel, onSelect, swapPreviewOnAlt, previewType }) => { type: ColorPickerType,
color: string,
selectedElements: ExcalidrawElement[],
event: { altKey: boolean },
) => void;
colorPickerType: EyeDropperProperties["colorPickerType"];
}> = ({ onCancel, onChange, onSelect, colorPickerType }) => {
const eyeDropperContainer = useCreatePortalContainer({ const eyeDropperContainer = useCreatePortalContainer({
className: "excalidraw-eye-dropper-backdrop", className: "excalidraw-eye-dropper-backdrop",
parentSelector: ".excalidraw-eye-dropper-container", parentSelector: ".excalidraw-eye-dropper-container",
@@ -40,9 +52,13 @@ export const EyeDropper: React.FC<{
const selectedElements = getSelectedElements(elements, appState); const selectedElements = getSelectedElements(elements, appState);
const metaStuffRef = useRef({ selectedElements, app }); const stableProps = useStable({
metaStuffRef.current.selectedElements = selectedElements; app,
metaStuffRef.current.app = app; onCancel,
onChange,
onSelect,
selectedElements,
});
const { container: excalidrawContainer } = useExcalidrawContainer(); const { container: excalidrawContainer } = useExcalidrawContainer();
@@ -90,28 +106,28 @@ export const EyeDropper: React.FC<{
const currentColor = getCurrentColor({ clientX, clientY }); const currentColor = getCurrentColor({ clientX, clientY });
if (isHoldingPointerDown) { if (isHoldingPointerDown) {
for (const element of metaStuffRef.current.selectedElements) { stableProps.onChange(
mutateElement( colorPickerType,
element, currentColor,
{ stableProps.selectedElements,
[altKey && swapPreviewOnAlt { altKey },
? previewType === "strokeColor" );
? "backgroundColor"
: "strokeColor"
: previewType]: currentColor,
},
false,
);
ShapeCache.delete(element);
}
Scene.getScene(
metaStuffRef.current.selectedElements[0],
)?.informMutation();
} }
colorPreviewDiv.style.background = currentColor; colorPreviewDiv.style.background = currentColor;
}; };
const onCancel = () => {
stableProps.onCancel();
};
const onSelect: Required<EyeDropperProperties>["onSelect"] = (
color,
event,
) => {
stableProps.onSelect(color, event);
};
const pointerDownListener = (event: PointerEvent) => { const pointerDownListener = (event: PointerEvent) => {
isHoldingPointerDown = true; isHoldingPointerDown = true;
// NOTE we can't event.preventDefault() as that would stop // NOTE we can't event.preventDefault() as that would stop
@@ -148,8 +164,8 @@ export const EyeDropper: React.FC<{
// init color preview else it would show only after the first mouse move // init color preview else it would show only after the first mouse move
mouseMoveListener({ mouseMoveListener({
clientX: metaStuffRef.current.app.lastViewportPosition.x, clientX: stableProps.app.lastViewportPosition.x,
clientY: metaStuffRef.current.app.lastViewportPosition.y, clientY: stableProps.app.lastViewportPosition.y,
altKey: false, altKey: false,
}); });
@@ -179,12 +195,10 @@ export const EyeDropper: React.FC<{
window.removeEventListener(EVENT.BLUR, onCancel); window.removeEventListener(EVENT.BLUR, onCancel);
}; };
}, [ }, [
stableProps,
app.canvas, app.canvas,
eyeDropperContainer, eyeDropperContainer,
onCancel, colorPickerType,
onSelect,
swapPreviewOnAlt,
previewType,
excalidrawContainer, excalidrawContainer,
appState.offsetLeft, appState.offsetLeft,
appState.offsetTop, appState.offsetTop,
+38 -2
View File
@@ -52,6 +52,9 @@ import { EyeDropper, activeEyeDropperAtom } from "./EyeDropper";
import "./LayerUI.scss"; import "./LayerUI.scss";
import "./Toolbar.scss"; import "./Toolbar.scss";
import { mutateElement } from "../element/mutateElement";
import { ShapeCache } from "../scene/ShapeCache";
import Scene from "../scene/Scene";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
@@ -368,11 +371,44 @@ const LayerUI = ({
)} )}
{eyeDropperState && !device.isMobile && ( {eyeDropperState && !device.isMobile && (
<EyeDropper <EyeDropper
swapPreviewOnAlt={eyeDropperState.swapPreviewOnAlt} colorPickerType={eyeDropperState.colorPickerType}
previewType={eyeDropperState.previewType}
onCancel={() => { onCancel={() => {
setEyeDropperState(null); setEyeDropperState(null);
}} }}
onChange={(colorPickerType, color, selectedElements, { altKey }) => {
if (
colorPickerType !== "elementBackground" &&
colorPickerType !== "elementStroke"
) {
return;
}
if (selectedElements.length) {
for (const element of selectedElements) {
mutateElement(
element,
{
[altKey && eyeDropperState.swapPreviewOnAlt
? colorPickerType === "elementBackground"
? "strokeColor"
: "backgroundColor"
: colorPickerType === "elementBackground"
? "backgroundColor"
: "strokeColor"]: color,
},
false,
);
ShapeCache.delete(element);
}
Scene.getScene(selectedElements[0])?.informMutation();
} else if (colorPickerType === "elementBackground") {
setAppState({
currentItemBackgroundColor: color,
});
} else {
setAppState({ currentItemStrokeColor: color });
}
}}
onSelect={(color, event) => { onSelect={(color, event) => {
setEyeDropperState((state) => { setEyeDropperState((state) => {
return state?.keepOpenOnAlt && event.altKey ? state : null; return state?.keepOpenOnAlt && event.altKey ? state : null;
+26 -45
View File
@@ -17,6 +17,7 @@ type EmbeddedLink =
| ({ | ({
aspectRatio: { w: number; h: number }; aspectRatio: { w: number; h: number };
warning?: string; warning?: string;
sandbox?: { allowSameOrigin?: boolean };
} & ( } & (
| { type: "video" | "generic"; link: string } | { type: "video" | "generic"; link: string }
| { type: "document"; srcdoc: (theme: Theme) => string } | { type: "document"; srcdoc: (theme: Theme) => string }
@@ -28,20 +29,20 @@ const embeddedLinkCache = new Map<string, EmbeddedLink>();
const RE_YOUTUBE = const RE_YOUTUBE =
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/; /^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
const RE_VIMEO = const RE_VIMEO =
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/; /^(?:http(?:s)?:\/\/)?(?:(?:w){3}\.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/; const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
const RE_GH_GIST = /^https:\/\/gist\.github\.com/; const RE_GH_GIST = /^https:\/\/gist\.github\.com/;
const RE_GH_GIST_EMBED = const RE_GH_GIST_EMBED =
/^<script[\s\S]*?\ssrc=["'](https:\/\/gist.github.com\/.*?)\.js["']/i; /https?:\/\/gist\.github\.com\/([\w_-]+)\/([\w_-]+)\.js["']/i;
// not anchored to start to allow <blockquote> twitter embeds // not anchored to start to allow <blockquote> twitter embeds
const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?twitter.com/; const RE_TWITTER = /(?:https?:\/\/)?(?:(?:w){3}\.)?(?:twitter|x)\.com/;
const RE_TWITTER_EMBED = const RE_TWITTER_EMBED =
/^<blockquote[\s\S]*?\shref=["'](https:\/\/twitter.com\/[^"']*)/i; /^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:twitter|x)\.com\/[^"']*)/i;
const RE_VALTOWN = const RE_VALTOWN =
/^https:\/\/(?:www\.)?val.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/; /^https:\/\/(?:www\.)?val\.town\/(v|embed)\/[a-zA-Z_$][0-9a-zA-Z_$]+\.[a-zA-Z_$][0-9a-zA-Z_$]+/;
const RE_GENERIC_EMBED = const RE_GENERIC_EMBED =
/^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i; /^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
@@ -136,46 +137,27 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
} }
if (RE_TWITTER.test(link)) { if (RE_TWITTER.test(link)) {
let ret: EmbeddedLink; // the embed srcdoc still supports twitter.com domain only
// assume embed code link = link.replace(/\bx.com\b/, "twitter.com");
if (/<blockquote/.test(link)) {
const srcDoc = createSrcDoc(link); const ret: EmbeddedLink = {
ret = { type: "document",
type: "document", srcdoc: (theme: string) =>
srcdoc: () => srcDoc, createSrcDoc(
aspectRatio: { w: 480, h: 480 }, `<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
}; ),
// assume regular tweet url aspectRatio: { w: 480, h: 480 },
} else { sandbox: { allowSameOrigin: true },
ret = { };
type: "document",
srcdoc: (theme: string) =>
createSrcDoc(
`<blockquote class="twitter-tweet" data-dnt="true" data-theme="${theme}"><a href="${link}"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`,
),
aspectRatio: { w: 480, h: 480 },
};
}
embeddedLinkCache.set(originalLink, ret); embeddedLinkCache.set(originalLink, ret);
return ret; return ret;
} }
if (RE_GH_GIST.test(link)) { if (RE_GH_GIST.test(link)) {
let ret: EmbeddedLink; const ret: EmbeddedLink = {
// assume embed code type: "document",
if (/<script>/.test(link)) { srcdoc: () =>
const srcDoc = createSrcDoc(link); createSrcDoc(`
ret = {
type: "document",
srcdoc: () => srcDoc,
aspectRatio: { w: 550, h: 720 },
};
// assume regular url
} else {
ret = {
type: "document",
srcdoc: () =>
createSrcDoc(`
<script src="${link}.js"></script> <script src="${link}.js"></script>
<style type="text/css"> <style type="text/css">
* { margin: 0px; } * { margin: 0px; }
@@ -183,9 +165,8 @@ export const getEmbedLink = (link: string | null | undefined): EmbeddedLink => {
.gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; } .gist .gist-file { height: calc(100vh - 2px); padding: 0px; display: grid; grid-template-rows: 1fr auto; }
</style> </style>
`), `),
aspectRatio: { w: 550, h: 720 }, aspectRatio: { w: 550, h: 720 },
}; };
}
embeddedLinkCache.set(link, ret); embeddedLinkCache.set(link, ret);
return ret; return ret;
} }
@@ -303,8 +284,8 @@ export const extractSrc = (htmlString: string): string => {
} }
const gistMatch = htmlString.match(RE_GH_GIST_EMBED); const gistMatch = htmlString.match(RE_GH_GIST_EMBED);
if (gistMatch && gistMatch.length === 2) { if (gistMatch && gistMatch.length === 3) {
return gistMatch[1]; return `https://gist.github.com/${gistMatch[1]}/${gistMatch[2]}`;
} }
const match = htmlString.match(RE_GENERIC_EMBED); const match = htmlString.match(RE_GENERIC_EMBED);
-1
View File
@@ -203,7 +203,6 @@ describe("duplicating multiple elements", () => {
); );
clonedArrows.forEach((arrow) => { clonedArrows.forEach((arrow) => {
// console.log(arrow);
expect( expect(
clonedRectangle.boundElements!.find((e) => e.id === arrow.id), clonedRectangle.boundElements!.find((e) => e.id === arrow.id),
).toEqual( ).toEqual(
+5 -5
View File
@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { GlobalTestState, render, screen } from "../tests/test-utils"; import { GlobalTestState, render, screen } from "../tests/test-utils";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
@@ -41,7 +41,7 @@ describe("textWysiwyg", () => {
describe("start text editing", () => { describe("start text editing", () => {
const { h } = window; const { h } = window;
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
}); });
@@ -243,7 +243,7 @@ describe("textWysiwyg", () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
//@ts-ignore //@ts-ignore
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!); h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
@@ -477,7 +477,7 @@ describe("textWysiwyg", () => {
const { h } = window; const { h } = window;
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
rectangle = UI.createElement("rectangle", { rectangle = UI.createElement("rectangle", {
@@ -1511,7 +1511,7 @@ describe("textWysiwyg", () => {
}); });
it("should bump the version of labelled arrow when label updated", async () => { it("should bump the version of labelled arrow when label updated", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const arrow = UI.createElement("arrow", { const arrow = UI.createElement("arrow", {
width: 300, width: 300,
height: 0, height: 0,
+7
View File
@@ -0,0 +1,7 @@
import { useRef } from "react";
export const useStable = <T extends Record<string, any>>(value: T) => {
const ref = useRef<T>(value);
Object.assign(ref.current, value);
return ref.current;
};
+2 -2
View File
@@ -1,9 +1,9 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import ExcalidrawApp from "./excalidraw-app"; import ExcalidrawApp from "../excalidraw-app";
import { registerSW } from "virtual:pwa-register"; import { registerSW } from "virtual:pwa-register";
import "./excalidraw-app/sentry"; import "../excalidraw-app/sentry";
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA; window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
const rootElement = document.getElementById("root")!; const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement); const root = createRoot(rootElement);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@excalidraw/excalidraw", "name": "@excalidraw/excalidraw",
"version": "0.16.0", "version": "0.16.2",
"main": "main.js", "main": "main.js",
"types": "types/packages/excalidraw/index.d.ts", "types": "types/packages/excalidraw/index.d.ts",
"files": [ "files": [
@@ -3,7 +3,7 @@ import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { render, queryByTestId } from "../tests/test-utils"; import { render, queryByTestId } from "../tests/test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { vi } from "vitest"; import { vi } from "vitest";
const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene"); const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene");
@@ -35,7 +35,7 @@ describe("Test <App/>", () => {
}; };
}; };
await render(<ExcalidrawApp />); await render(<Excalidraw />);
expect( expect(
queryByTestId( queryByTestId(
document.querySelector(".excalidraw-modal-container")!, document.querySelector(".excalidraw-modal-container")!,
+50
View File
@@ -0,0 +1,50 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
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.
</p>
<p>
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>
`;
@@ -13089,126 +13089,6 @@ exports[`regression tests > pinch-to-zoom works > [end of test] number of elemen
exports[`regression tests > pinch-to-zoom works > [end of test] number of renders 1`] = `7`; exports[`regression tests > pinch-to-zoom works > [end of test] number of renders 1`] = `7`;
exports[`regression tests > rerenders UI on language change > [end of test] appState 1`] = `
{
"activeEmbeddable": null,
"activeTool": {
"customType": null,
"lastActiveTool": null,
"locked": false,
"type": "rectangle",
},
"collaborators": Map {},
"contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#1e1e1e",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
"cursorButton": "up",
"defaultSidebarDockedPreference": false,
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
"editingLinearElement": null,
"elementsToHighlight": null,
"errorMessage": null,
"exportBackground": true,
"exportEmbedScene": false,
"exportScale": 1,
"exportWithDarkMode": false,
"fileHandle": null,
"frameRendering": {
"clip": true,
"enabled": true,
"name": true,
"outline": true,
},
"frameToHighlight": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"offsetLeft": 0,
"offsetTop": 0,
"openDialog": null,
"openMenu": "canvas",
"openPopup": null,
"openSidebar": null,
"pasteDialog": {
"data": null,
"shown": false,
},
"penDetected": false,
"penMode": false,
"pendingImageElementId": null,
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
"scrolledOutside": false,
"selectedElementIds": {},
"selectedElementsAreBeingDragged": false,
"selectedGroupIds": {},
"selectedLinearElement": null,
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"showHyperlinkPopup": false,
"showStats": false,
"showWelcomeScreen": true,
"startBoundElement": null,
"suggestedBindings": [],
"theme": "light",
"toast": null,
"viewBackgroundColor": "#ffffff",
"viewModeEnabled": false,
"width": 1024,
"zenModeEnabled": false,
"zoom": {
"value": 1,
},
}
`;
exports[`regression tests > rerenders UI on language change > [end of test] history 1`] = `
{
"recording": false,
"redoStack": [],
"stateHistory": [
{
"appState": {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {},
"selectedGroupIds": {},
"viewBackgroundColor": "#ffffff",
},
"elements": [],
},
],
}
`;
exports[`regression tests > rerenders UI on language change > [end of test] number of elements 1`] = `0`;
exports[`regression tests > rerenders UI on language change > [end of test] number of renders 1`] = `5`;
exports[`regression tests > shift click on selected element should deselect it on pointer up > [end of test] appState 1`] = ` exports[`regression tests > shift click on selected element should deselect it on pointer up > [end of test] appState 1`] = `
{ {
"activeEmbeddable": null, "activeEmbeddable": null,
@@ -1,4 +1,4 @@
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { CODES } from "../keys"; import { CODES } from "../keys";
import { API } from "../tests/helpers/api"; import { API } from "../tests/helpers/api";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
@@ -9,7 +9,7 @@ import {
screen, screen,
togglePopover, togglePopover,
} from "../tests/test-utils"; } from "../tests/test-utils";
import { copiedStyles } from "./actionStyles"; import { copiedStyles } from "../actions/actionStyles";
const { h } = window; const { h } = window;
@@ -17,7 +17,7 @@ const mouse = new Pointer("mouse");
describe("actionStyles", () => { describe("actionStyles", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
afterEach(async () => { afterEach(async () => {
+2 -2
View File
@@ -1,6 +1,6 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render } from "./test-utils"; import { render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../../src/packages/excalidraw/index";
import { defaultLang, setLanguage } from "../i18n"; import { defaultLang, setLanguage } from "../i18n";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
@@ -60,7 +60,7 @@ describe("aligning", () => {
mouse.reset(); mouse.reset();
await setLanguage(defaultLang); await setLanguage(defaultLang);
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
it("aligns two objects correctly to the top", () => { it("aligns two objects correctly to the top", () => {
+21 -16
View File
@@ -1,6 +1,6 @@
import { queryByTestId, render, waitFor } from "./test-utils"; import { queryByTestId, render, waitFor } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
@@ -14,14 +14,17 @@ describe("appState", () => {
const defaultAppState = getDefaultAppState(); const defaultAppState = getDefaultAppState();
const exportBackground = !defaultAppState.exportBackground; const exportBackground = !defaultAppState.exportBackground;
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
appState: { initialData={{
exportBackground, appState: {
viewBackgroundColor: "#F00", exportBackground,
}, viewBackgroundColor: "#F00",
}, },
}); }}
/>,
{},
);
await waitFor(() => { await waitFor(() => {
expect(h.state.exportBackground).toBe(exportBackground); expect(h.state.exportBackground).toBe(exportBackground);
@@ -53,13 +56,15 @@ describe("appState", () => {
}); });
it("changing fontSize with text tool selected (no element created yet)", async () => { it("changing fontSize with text tool selected (no element created yet)", async () => {
const { container } = await render(<ExcalidrawApp />, { const { container } = await render(
localStorageData: { <Excalidraw
appState: { initialData={{
currentItemFontSize: 30, appState: {
}, currentItemFontSize: 30,
}, },
}); }}
/>,
);
UI.clickTool("text"); UI.clickTool("text");
+2 -2
View File
@@ -1,5 +1,5 @@
import { fireEvent, render } from "./test-utils"; import { fireEvent, render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../../src/packages/excalidraw/index";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
import { getTransformHandles } from "../element/transformHandles"; import { getTransformHandles } from "../element/transformHandles";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
@@ -12,7 +12,7 @@ const mouse = new Pointer("mouse");
describe("element binding", () => { describe("element binding", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
it("should create valid binding if duplicate start/end points", async () => { it("should create valid binding if duplicate start/end points", async () => {
+8 -4
View File
@@ -7,7 +7,7 @@ import {
createPasteEvent, createPasteEvent,
} from "./test-utils"; } from "./test-utils";
import { Pointer, Keyboard } from "./helpers/ui"; import { Pointer, Keyboard } from "./helpers/ui";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { import {
getDefaultLineHeight, getDefaultLineHeight,
@@ -79,8 +79,13 @@ beforeEach(async () => {
mouse.reset(); mouse.reset();
await render(<ExcalidrawApp />); await render(
h.app.setAppState({ zoom: { value: 1 as NormalizedZoomValue } }); <Excalidraw
autoFocus={true}
handleKeyboardGlobally={true}
initialData={{ appState: { zoom: { value: 1 as NormalizedZoomValue } } }}
/>,
);
setClipboardText(""); setClipboardText("");
Object.assign(document, { Object.assign(document, {
elementFromPoint: () => GlobalTestState.canvas, elementFromPoint: () => GlobalTestState.canvas,
@@ -91,7 +96,6 @@ describe("general paste behavior", () => {
it("should randomize seed on paste", async () => { it("should randomize seed on paste", async () => {
const rectangle = API.createElement({ type: "rectangle" }); const rectangle = API.createElement({ type: "rectangle" });
const clipboardJSON = (await copyToClipboard([rectangle], null))!; const clipboardJSON = (await copyToClipboard([rectangle], null))!;
pasteWithCtrlCmdV(clipboardJSON); pasteWithCtrlCmdV(clipboardJSON);
await waitFor(() => { await waitFor(() => {
+5 -8
View File
@@ -11,7 +11,7 @@ import {
waitFor, waitFor,
togglePopover, togglePopover,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -20,7 +20,6 @@ import { ShortcutName } from "../actions/shortcuts";
import { copiedStyles } from "../actions/actionStyles"; import { copiedStyles } from "../actions/actionStyles";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { setDateTimeForTests } from "../utils"; import { setDateTimeForTests } from "../utils";
import { LibraryItem } from "../types";
import { vi } from "vitest"; import { vi } from "vitest";
const checkpoint = (name: string) => { const checkpoint = (name: string) => {
@@ -56,7 +55,7 @@ describe("contextMenu element", () => {
reseed(7); reseed(7);
setDateTimeForTests("201933152653"); setDateTimeForTests("201933152653");
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
beforeAll(() => { beforeAll(() => {
@@ -394,11 +393,9 @@ describe("contextMenu element", () => {
const contextMenu = UI.queryContextMenu(); const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByText(contextMenu!, "Add to library")!); fireEvent.click(queryByText(contextMenu!, "Add to library")!);
await waitFor(() => { await waitFor(async () => {
const library = localStorage.getItem("excalidraw-library"); const libraryItems = await h.app.library.getLatestLibrary();
expect(library).not.toBeNull(); expect(libraryItems[0].elements[0]).toEqual(h.elements[0]);
const addedElement = JSON.parse(library!)[0] as LibraryItem;
expect(addedElement.elements[0]).toEqual(h.elements[0]);
}); });
}); });
+15 -11
View File
@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { import {
@@ -30,7 +30,7 @@ const { h } = window;
describe("Test dragCreate", () => { describe("Test dragCreate", () => {
describe("add element to the scene when pointer dragging long enough", () => { describe("add element to the scene when pointer dragging long enough", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
@@ -62,7 +62,7 @@ describe("Test dragCreate", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -95,7 +95,7 @@ describe("Test dragCreate", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -127,7 +127,7 @@ describe("Test dragCreate", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -163,7 +163,7 @@ describe("Test dragCreate", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);
@@ -207,7 +207,7 @@ describe("Test dragCreate", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
@@ -227,7 +227,7 @@ describe("Test dragCreate", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -247,7 +247,7 @@ describe("Test dragCreate", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -267,7 +267,9 @@ describe("Test dragCreate", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -292,7 +294,9 @@ describe("Test dragCreate", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);
+2 -2
View File
@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { render } from "../tests/test-utils"; import { render } from "../tests/test-utils";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
@@ -15,7 +15,7 @@ const h = window.h;
describe("element locking", () => { describe("element locking", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
}); });
+2 -2
View File
@@ -1,5 +1,5 @@
import { render, waitFor } from "./test-utils"; import { render, waitFor } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { import {
encodePngMetadata, encodePngMetadata,
@@ -42,7 +42,7 @@ Object.defineProperty(window, "TextDecoder", {
describe("export", () => { describe("export", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("export embedded png and reimport", async () => { it("export embedded png and reimport", async () => {
+6 -6
View File
@@ -1,14 +1,14 @@
import { render } from "./test-utils"; import { render } from "./test-utils";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { vi } from "vitest"; import { vi } from "vitest";
const { h } = window; const { h } = window;
describe("fitToContent", () => { describe("fitToContent", () => {
it("should zoom to fit the selected element", async () => { it("should zoom to fit the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -30,7 +30,7 @@ describe("fitToContent", () => {
}); });
it("should zoom to fit multiple elements", async () => { it("should zoom to fit multiple elements", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
const topLeft = API.createElement({ const topLeft = API.createElement({
width: 20, width: 20,
@@ -61,7 +61,7 @@ describe("fitToContent", () => {
}); });
it("should scroll the viewport to the selected element", async () => { it("should scroll the viewport to the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -106,7 +106,7 @@ describe("fitToContent animated", () => {
}); });
it("should ease scroll the viewport to the selected element", async () => { it("should ease scroll the viewport to the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -142,7 +142,7 @@ describe("fitToContent animated", () => {
}); });
it("should animate the scroll but not the zoom", async () => { it("should animate the scroll but not the zoom", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 50; h.state.width = 50;
h.state.height = 50; h.state.height = 50;
+2 -2
View File
@@ -19,7 +19,7 @@ import {
FileId, FileId,
} from "../element/types"; } from "../element/types";
import { newLinearElement } from "../element"; import { newLinearElement } from "../element";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { mutateElement } from "../element/mutateElement"; import { mutateElement } from "../element/mutateElement";
import { NormalizedZoomValue } from "../types"; import { NormalizedZoomValue } from "../types";
import { ROUNDNESS } from "../constants"; import { ROUNDNESS } from "../constants";
@@ -52,7 +52,7 @@ beforeEach(async () => {
Object.assign(document, { Object.assign(document, {
elementFromPoint: () => GlobalTestState.canvas, elementFromPoint: () => GlobalTestState.canvas,
}); });
await render(<ExcalidrawApp />); await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
h.setState({ h.setState({
zoom: { zoom: {
value: 1 as NormalizedZoomValue, value: 1 as NormalizedZoomValue,
+22 -18
View File
@@ -1,5 +1,5 @@
import { assertSelectedElements, render } from "./test-utils"; import { assertSelectedElements, render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { Keyboard, Pointer, UI } from "./helpers/ui"; import { Keyboard, Pointer, UI } from "./helpers/ui";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
@@ -13,14 +13,16 @@ const mouse = new Pointer("mouse");
describe("history", () => { describe("history", () => {
it("initializing scene should end up with single history entry", async () => { it("initializing scene should end up with single history entry", async () => {
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
elements: [API.createElement({ type: "rectangle", id: "A" })], initialData={{
appState: { elements: [API.createElement({ type: "rectangle", id: "A" })],
zenModeEnabled: true, appState: {
}, zenModeEnabled: true,
}, },
}); }}
/>,
);
await waitFor(() => expect(h.state.zenModeEnabled).toBe(true)); await waitFor(() => expect(h.state.zenModeEnabled).toBe(true));
await waitFor(() => await waitFor(() =>
@@ -60,14 +62,16 @@ describe("history", () => {
}); });
it("scene import via drag&drop should create new history entry", async () => { it("scene import via drag&drop should create new history entry", async () => {
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
elements: [API.createElement({ type: "rectangle", id: "A" })], initialData={{
appState: { elements: [API.createElement({ type: "rectangle", id: "A" })],
viewBackgroundColor: "#FFF", appState: {
}, viewBackgroundColor: "#FFF",
}, },
}); }}
/>,
);
await waitFor(() => expect(h.state.viewBackgroundColor).toBe("#FFF")); await waitFor(() => expect(h.state.viewBackgroundColor).toBe("#FFF"));
await waitFor(() => await waitFor(() =>
@@ -113,7 +117,7 @@ describe("history", () => {
}); });
it("undo/redo works properly with groups", async () => { it("undo/redo works properly with groups", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] }); const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] });
const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] }); const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] });
+3 -3
View File
@@ -2,7 +2,7 @@ import { vi } from "vitest";
import { fireEvent, render, waitFor } from "./test-utils"; import { fireEvent, render, waitFor } from "./test-utils";
import { queryByTestId } from "@testing-library/react"; import { queryByTestId } from "@testing-library/react";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { MIME_TYPES } from "../constants"; import { MIME_TYPES } from "../constants";
import { LibraryItem, LibraryItems } from "../types"; import { LibraryItem, LibraryItems } from "../types";
@@ -42,7 +42,7 @@ vi.mock("../data/filesystem.ts", async (importOriginal) => {
describe("library", () => { describe("library", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.app.library.resetLibrary(); h.app.library.resetLibrary();
}); });
@@ -189,7 +189,7 @@ describe("library", () => {
describe("library menu", () => { describe("library menu", () => {
it("should load library from file picker", async () => { it("should load library from file picker", async () => {
const { container } = await render(<ExcalidrawApp />); const { container } = await render(<Excalidraw />);
const latestLibrary = await h.app.library.getLatestLibrary(); const latestLibrary = await h.app.library.getLatestLibrary();
expect(latestLibrary.length).toBe(0); expect(latestLibrary.length).toBe(0);
+2 -2
View File
@@ -5,7 +5,7 @@ import {
ExcalidrawTextElementWithContainer, ExcalidrawTextElementWithContainer,
FontString, FontString,
} from "../element/types"; } from "../element/types";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { centerPoint } from "../math"; import { centerPoint } from "../math";
import { reseed } from "../random"; import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
@@ -43,7 +43,7 @@ describe("Test Linear Elements", () => {
renderInteractiveScene.mockClear(); renderInteractiveScene.mockClear();
renderStaticScene.mockClear(); renderStaticScene.mockClear();
reseed(7); reseed(7);
const comp = await render(<ExcalidrawApp />); const comp = await render(<Excalidraw handleKeyboardGlobally={true} />);
h.state.width = 1000; h.state.width = 1000;
h.state.height = 1000; h.state.height = 1000;
container = comp.container; container = comp.container;
+4 -4
View File
@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils"; import { render, fireEvent } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { bindOrUnbindLinearElement } from "../element/binding"; import { bindOrUnbindLinearElement } from "../element/binding";
@@ -31,7 +31,7 @@ const { h } = window;
describe("move element", () => { describe("move element", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
@@ -67,7 +67,7 @@ describe("move element", () => {
}); });
it("rectangles with binding arrow", async () => { it("rectangles with binding arrow", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
// create elements // create elements
const rectA = UI.createElement("rectangle", { size: 100 }); const rectA = UI.createElement("rectangle", { size: 100 });
@@ -119,7 +119,7 @@ describe("move element", () => {
describe("duplicate element on move when ALT is clicked", () => { describe("duplicate element on move when ALT is clicked", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
+12 -11
View File
@@ -5,7 +5,7 @@ import {
mockBoundingClientRect, mockBoundingClientRect,
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { ExcalidrawLinearElement } from "../element/types"; import { ExcalidrawLinearElement } from "../element/types";
@@ -29,7 +29,7 @@ const { h } = window;
describe("remove shape in non linear elements", () => { describe("remove shape in non linear elements", () => {
beforeAll(() => { beforeAll(() => {
mockBoundingClientRect(); mockBoundingClientRect({ width: 1000, height: 1000 });
}); });
afterAll(() => { afterAll(() => {
@@ -37,12 +37,13 @@ describe("remove shape in non linear elements", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
@@ -52,7 +53,7 @@ describe("remove shape in non linear elements", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -67,7 +68,7 @@ describe("remove shape in non linear elements", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -84,7 +85,7 @@ describe("remove shape in non linear elements", () => {
describe("multi point mode in linear elements", () => { describe("multi point mode in linear elements", () => {
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -109,8 +110,8 @@ describe("multi point mode in linear elements", () => {
key: KEYS.ENTER, key: KEYS.ENTER,
}); });
expect(renderInteractiveScene).toHaveBeenCalledTimes(10); expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
expect(renderStaticScene).toHaveBeenCalledTimes(11); expect(renderStaticScene).toHaveBeenCalledTimes(10);
expect(h.elements.length).toEqual(1); expect(h.elements.length).toEqual(1);
const element = h.elements[0] as ExcalidrawLinearElement; const element = h.elements[0] as ExcalidrawLinearElement;
@@ -128,7 +129,7 @@ describe("multi point mode in linear elements", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);
@@ -153,8 +154,8 @@ describe("multi point mode in linear elements", () => {
key: KEYS.ENTER, key: KEYS.ENTER,
}); });
expect(renderInteractiveScene).toHaveBeenCalledTimes(10); expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
expect(renderStaticScene).toHaveBeenCalledTimes(11); expect(renderStaticScene).toHaveBeenCalledTimes(10);
expect(h.elements.length).toEqual(1); expect(h.elements.length).toEqual(1);
const element = h.elements[0] as ExcalidrawLinearElement; const element = h.elements[0] as ExcalidrawLinearElement;
+2 -24
View File
@@ -1,7 +1,7 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { reseed } from "../random"; import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { setDateTimeForTests } from "../utils"; import { setDateTimeForTests } from "../utils";
@@ -13,9 +13,7 @@ import {
render, render,
screen, screen,
togglePopover, togglePopover,
waitFor,
} from "./test-utils"; } from "./test-utils";
import { defaultLang } from "../i18n";
import { FONT_FAMILY } from "../constants"; import { FONT_FAMILY } from "../constants";
import { vi } from "vitest"; import { vi } from "vitest";
@@ -56,7 +54,7 @@ beforeEach(async () => {
finger1.reset(); finger1.reset();
finger2.reset(); finger2.reset();
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.setState({ height: 768, width: 1024 }); h.setState({ height: 768, width: 1024 });
}); });
@@ -443,26 +441,6 @@ describe("regression tests", () => {
expect(h.state.zoom.value).toBe(1); expect(h.state.zoom.value).toBe(1);
}); });
it("rerenders UI on language change", async () => {
// select rectangle tool to show properties menu
UI.clickTool("rectangle");
// english lang should display `thin` label
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: "de-DE" },
});
// switching to german, `thin` label should no longer exist
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
// reset language
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: defaultLang.code },
});
// switching back to English
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
});
it("make a group and duplicate it", () => { it("make a group and duplicate it", () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(10, 10); mouse.down(10, 10);
+2 -2
View File
@@ -6,7 +6,7 @@ import { reseed } from "../random";
import { UI, Keyboard } from "./helpers/ui"; import { UI, Keyboard } from "./helpers/ui";
import { resize } from "./utils"; import { resize } from "./utils";
import { ExcalidrawTextElement } from "../element/types"; import { ExcalidrawTextElement } from "../element/types";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { vi } from "vitest"; import { vi } from "vitest";
@@ -126,7 +126,7 @@ describe("resize rectangle ellipses and diamond elements", () => {
describe("Test text element", () => { describe("Test text element", () => {
it("should update font size via keyboard", async () => { it("should update font size via keyboard", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const textElement = API.createElement({ const textElement = API.createElement({
type: "text", type: "text",
+1 -2
View File
@@ -8,7 +8,6 @@ import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { Keyboard } from "./helpers/ui"; import { Keyboard } from "./helpers/ui";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import ExcalidrawApp from "../excalidraw-app";
const { h } = window; const { h } = window;
@@ -56,7 +55,7 @@ describe("appState", () => {
it("moving by page up/down/left/right", async () => { it("moving by page up/down/left/right", async () => {
mockBoundingClientRect(); mockBoundingClientRect();
await render(<ExcalidrawApp />, {}); await render(<Excalidraw handleKeyboardGlobally={true} />, {});
const scrollTest = () => { const scrollTest = () => {
const initialScrollY = h.state.scrollY; const initialScrollY = h.state.scrollY;
+23 -13
View File
@@ -6,7 +6,7 @@ import {
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
assertSelectedElements, assertSelectedElements,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { reseed } from "../random"; import { reseed } from "../random";
@@ -34,7 +34,7 @@ const mouse = new Pointer("mouse");
describe("box-selection", () => { describe("box-selection", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("should allow adding to selection via box-select when holding shift", async () => { it("should allow adding to selection via box-select when holding shift", async () => {
@@ -102,7 +102,7 @@ describe("box-selection", () => {
describe("inner box-selection", () => { describe("inner box-selection", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("selecting elements visually nested inside another", async () => { it("selecting elements visually nested inside another", async () => {
const rect1 = API.createElement({ const rect1 = API.createElement({
@@ -218,7 +218,7 @@ describe("inner box-selection", () => {
describe("selection element", () => { describe("selection element", () => {
it("create selection element on pointer down", async () => { it("create selection element on pointer down", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -239,7 +239,7 @@ describe("selection element", () => {
}); });
it("resize selection element on pointer move", async () => { it("resize selection element on pointer move", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -261,7 +261,7 @@ describe("selection element", () => {
}); });
it("remove selection element on pointer up", async () => { it("remove selection element on pointer up", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -287,7 +287,9 @@ describe("select single element on the scene", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -317,7 +319,9 @@ describe("select single element on the scene", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -347,7 +351,9 @@ describe("select single element on the scene", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -377,7 +383,9 @@ describe("select single element on the scene", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -419,7 +427,9 @@ describe("select single element on the scene", () => {
}); });
it("arrow escape", async () => { it("arrow escape", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -464,7 +474,7 @@ describe("select single element on the scene", () => {
describe("tool locking & selection", () => { describe("tool locking & selection", () => {
it("should not select newly created element while tool is locked", async () => { it("should not select newly created element while tool is locked", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
UI.clickTool("lock"); UI.clickTool("lock");
expect(h.state.activeTool.locked).toBe(true); expect(h.state.activeTool.locked).toBe(true);
@@ -480,7 +490,7 @@ describe("tool locking & selection", () => {
describe("selectedElementIds stability", () => { describe("selectedElementIds stability", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("box-selection should be stable when not changing selection", () => { it("box-selection should be stable when not changing selection", () => {
+1 -1
View File
@@ -11,7 +11,7 @@ import {
import * as toolQueries from "./queries/toolQueries"; import * as toolQueries from "./queries/toolQueries";
import { ImportedDataState } from "../data/types"; import { ImportedDataState } from "../data/types";
import { STORAGE_KEYS } from "../excalidraw-app/app_constants"; import { STORAGE_KEYS } from "../../excalidraw-app/app_constants";
import { SceneData } from "../types"; import { SceneData } from "../types";
import { getSelectedElements } from "../scene/selection"; import { getSelectedElements } from "../scene/selection";
+2 -2
View File
@@ -1,5 +1,5 @@
import { render, GlobalTestState } from "./test-utils"; import { render, GlobalTestState } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { Keyboard, Pointer, UI } from "./helpers/ui"; import { Keyboard, Pointer, UI } from "./helpers/ui";
import { CURSOR_TYPE } from "../constants"; import { CURSOR_TYPE } from "../constants";
@@ -12,7 +12,7 @@ const pointerTypes = [mouse, touch, pen];
describe("view mode", () => { describe("view mode", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("after switching to view mode cursor type should be pointer", async () => { it("after switching to view mode cursor type should be pointer", async () => {
+2 -3
View File
@@ -1,6 +1,6 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render } from "./test-utils"; import { render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { reseed } from "../random"; import { reseed } from "../random";
import { import {
actionSendBackward, actionSendBackward,
@@ -121,7 +121,6 @@ const assertZindex = ({
operations: [Actions, string[]][]; operations: [Actions, string[]][];
}) => { }) => {
const selectedElementIds = populateElements(elements, appState); const selectedElementIds = populateElements(elements, appState);
operations.forEach(([action, expected]) => { operations.forEach(([action, expected]) => {
h.app.actionManager.executeAction(action); h.app.actionManager.executeAction(action);
expect(h.elements.map((element) => element.id)).toEqual(expected); expect(h.elements.map((element) => element.id)).toEqual(expected);
@@ -131,7 +130,7 @@ const assertZindex = ({
describe("z-index manipulation", () => { describe("z-index manipulation", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("send back", () => { it("send back", () => {
+1 -1
View File
@@ -96,9 +96,9 @@ const getTargetIndexAccountingForBinding = (
if (direction === "left") { if (direction === "left") {
return elements.indexOf(nextElement); return elements.indexOf(nextElement);
} }
const boundTextElement = const boundTextElement =
Scene.getScene(nextElement)!.getElement(boundElementId); Scene.getScene(nextElement)!.getElement(boundElementId);
if (boundTextElement) { if (boundTextElement) {
return elements.indexOf(boundTextElement); return elements.indexOf(boundTextElement);
} }
+1 -1
View File
@@ -16,6 +16,6 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"], "include": ["src", "excalidraw-app"],
"exclude": ["src/packages/excalidraw/types"] "exclude": ["src/packages/excalidraw/types"]
} }