diff options
Diffstat (limited to 'packages/excalidraw/index.tsx')
| -rw-r--r-- | packages/excalidraw/index.tsx | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx new file mode 100644 index 0000000..b4b8574 --- /dev/null +++ b/packages/excalidraw/index.tsx @@ -0,0 +1,300 @@ +import React, { useEffect } from "react"; +import { InitializeApp } from "./components/InitializeApp"; +import App from "./components/App"; +import { isShallowEqual } from "./utils"; +import polyfill from "./polyfill"; + +import "./css/app.scss"; +import "./css/styles.scss"; +import "./fonts/fonts.css"; + +import type { AppProps, ExcalidrawProps } from "./types"; +import { defaultLang } from "./i18n"; +import { DEFAULT_UI_OPTIONS } from "./constants"; +import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai"; +import Footer from "./components/footer/FooterCenter"; +import MainMenu from "./components/main-menu/MainMenu"; +import WelcomeScreen from "./components/welcome-screen/WelcomeScreen"; +import LiveCollaborationTrigger from "./components/live-collaboration/LiveCollaborationTrigger"; + +polyfill(); + +const ExcalidrawBase = (props: ExcalidrawProps) => { + const { + onChange, + initialData, + excalidrawAPI, + isCollaborating = false, + onPointerUpdate, + renderTopRightUI, + langCode = defaultLang.code, + viewModeEnabled, + zenModeEnabled, + gridModeEnabled, + libraryReturnUrl, + theme, + name, + renderCustomStats, + onPaste, + detectScroll = true, + handleKeyboardGlobally = false, + onLibraryChange, + autoFocus = false, + generateIdForFile, + onLinkOpen, + generateLinkForSelection, + onPointerDown, + onPointerUp, + onScrollChange, + onDuplicate, + children, + validateEmbeddable, + renderEmbeddable, + aiEnabled, + showDeprecatedFonts, + } = props; + + const canvasActions = props.UIOptions?.canvasActions; + + // FIXME normalize/set defaults in parent component so that the memo resolver + // compares the same values + const UIOptions: AppProps["UIOptions"] = { + ...props.UIOptions, + canvasActions: { + ...DEFAULT_UI_OPTIONS.canvasActions, + ...canvasActions, + }, + tools: { + image: props.UIOptions?.tools?.image ?? true, + }, + }; + + if (canvasActions?.export) { + UIOptions.canvasActions.export.saveFileToDisk = + canvasActions.export?.saveFileToDisk ?? + DEFAULT_UI_OPTIONS.canvasActions.export.saveFileToDisk; + } + + if ( + UIOptions.canvasActions.toggleTheme === null && + typeof theme === "undefined" + ) { + UIOptions.canvasActions.toggleTheme = true; + } + + useEffect(() => { + const importPolyfill = async () => { + //@ts-ignore + await import("canvas-roundrect-polyfill"); + }; + + importPolyfill(); + + // Block pinch-zooming on iOS outside of the content area + const handleTouchMove = (event: TouchEvent) => { + // @ts-ignore + if (typeof event.scale === "number" && event.scale !== 1) { + event.preventDefault(); + } + }; + + document.addEventListener("touchmove", handleTouchMove, { + passive: false, + }); + + return () => { + document.removeEventListener("touchmove", handleTouchMove); + }; + }, []); + + return ( + <EditorJotaiProvider store={editorJotaiStore}> + <InitializeApp langCode={langCode} theme={theme}> + <App + onChange={onChange} + initialData={initialData} + excalidrawAPI={excalidrawAPI} + isCollaborating={isCollaborating} + onPointerUpdate={onPointerUpdate} + renderTopRightUI={renderTopRightUI} + langCode={langCode} + viewModeEnabled={viewModeEnabled} + zenModeEnabled={zenModeEnabled} + gridModeEnabled={gridModeEnabled} + libraryReturnUrl={libraryReturnUrl} + theme={theme} + name={name} + renderCustomStats={renderCustomStats} + UIOptions={UIOptions} + onPaste={onPaste} + detectScroll={detectScroll} + handleKeyboardGlobally={handleKeyboardGlobally} + onLibraryChange={onLibraryChange} + autoFocus={autoFocus} + generateIdForFile={generateIdForFile} + onLinkOpen={onLinkOpen} + generateLinkForSelection={generateLinkForSelection} + onPointerDown={onPointerDown} + onPointerUp={onPointerUp} + onScrollChange={onScrollChange} + onDuplicate={onDuplicate} + validateEmbeddable={validateEmbeddable} + renderEmbeddable={renderEmbeddable} + aiEnabled={aiEnabled !== false} + showDeprecatedFonts={showDeprecatedFonts} + > + {children} + </App> + </InitializeApp> + </EditorJotaiProvider> + ); +}; + +const areEqual = (prevProps: ExcalidrawProps, nextProps: ExcalidrawProps) => { + // short-circuit early + if (prevProps.children !== nextProps.children) { + return false; + } + + const { + initialData: prevInitialData, + UIOptions: prevUIOptions = {}, + ...prev + } = prevProps; + const { + initialData: nextInitialData, + UIOptions: nextUIOptions = {}, + ...next + } = nextProps; + + // comparing UIOptions + const prevUIOptionsKeys = Object.keys(prevUIOptions) as (keyof Partial< + typeof DEFAULT_UI_OPTIONS + >)[]; + const nextUIOptionsKeys = Object.keys(nextUIOptions) as (keyof Partial< + typeof DEFAULT_UI_OPTIONS + >)[]; + + if (prevUIOptionsKeys.length !== nextUIOptionsKeys.length) { + return false; + } + + const isUIOptionsSame = prevUIOptionsKeys.every((key) => { + if (key === "canvasActions") { + const canvasOptionKeys = Object.keys( + prevUIOptions.canvasActions!, + ) as (keyof Partial<typeof DEFAULT_UI_OPTIONS.canvasActions>)[]; + return canvasOptionKeys.every((key) => { + if ( + key === "export" && + prevUIOptions?.canvasActions?.export && + nextUIOptions?.canvasActions?.export + ) { + return ( + prevUIOptions.canvasActions.export.saveFileToDisk === + nextUIOptions.canvasActions.export.saveFileToDisk + ); + } + return ( + prevUIOptions?.canvasActions?.[key] === + nextUIOptions?.canvasActions?.[key] + ); + }); + } + return prevUIOptions[key] === nextUIOptions[key]; + }); + + return isUIOptionsSame && isShallowEqual(prev, next); +}; + +export const Excalidraw = React.memo(ExcalidrawBase, areEqual); +Excalidraw.displayName = "Excalidraw"; + +export { + getSceneVersion, + hashElementsVersion, + hashString, + isInvisiblySmallElement, + getNonDeletedElements, + getTextFromElements, +} from "./element"; +export { defaultLang, useI18n, languages } from "./i18n"; +export { + restore, + restoreAppState, + restoreElements, + restoreLibraryItems, +} from "./data/restore"; + +export { reconcileElements } from "./data/reconcile"; + +export { + exportToCanvas, + exportToBlob, + exportToSvg, + exportToClipboard, +} from "@excalidraw/utils/export"; + +export { serializeAsJSON, serializeLibraryAsJSON } from "./data/json"; +export { + loadFromBlob, + loadSceneOrLibraryFromBlob, + loadLibraryFromBlob, +} from "./data/blob"; +export { getFreeDrawSvgPath } from "./renderer/renderElement"; +export { mergeLibraryItems, getLibraryItemsHash } from "./data/library"; +export { isLinearElement } from "./element/typeChecks"; + +export { + FONT_FAMILY, + THEME, + MIME_TYPES, + ROUNDNESS, + DEFAULT_LASER_COLOR, + UserIdleState, +} from "./constants"; + +export { + mutateElement, + newElementWith, + bumpVersion, +} from "./element/mutateElement"; + +export { CaptureUpdateAction } from "./store"; + +export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library"; + +export { + sceneCoordsToViewportCoords, + viewportCoordsToSceneCoords, +} from "./utils"; + +export { Sidebar } from "./components/Sidebar/Sidebar"; +export { Button } from "./components/Button"; +export { Footer }; +export { MainMenu }; +export { useDevice } from "./components/App"; +export { WelcomeScreen }; +export { LiveCollaborationTrigger }; +export { Stats } from "./components/Stats"; + +export { DefaultSidebar } from "./components/DefaultSidebar"; +export { TTDDialog } from "./components/TTDDialog/TTDDialog"; +export { TTDDialogTrigger } from "./components/TTDDialog/TTDDialogTrigger"; + +export { normalizeLink } from "./data/url"; +export { zoomToFitBounds } from "./actions/actionCanvas"; +export { convertToExcalidrawElements } from "./data/transform"; +export { getCommonBounds, getVisibleSceneBounds } from "./element/bounds"; + +export { + elementsOverlappingBBox, + isElementInsideBBox, + elementPartiallyOverlapsWithOrContainsBBox, +} from "@excalidraw/utils/withinBounds"; + +export { DiagramToCodePlugin } from "./components/DiagramToCodePlugin/DiagramToCodePlugin"; +export { getDataURL } from "./data/blob"; +export { isElementLink } from "./element/elementLink"; + +export { setCustomTextMetricsProvider } from "./element/textMeasurements"; |
