mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
* feat: support creating text containers programatically
* fix
* fix
* fix
* fix
* update api to use label
* fix api and support individual shapes and text element
* update test case in package example
* support creating arrows and line
* support labelled arrows
* add in package example
* fix alignment
* better types
* fix
* keep element as is unless we support prog api
* fix tests
* fix lint
* ignore
* support arrow bindings via start and end in api
* fix lint
* fix coords
* support id as well for elements
* preserve bindings if present and fix testcases
* preserve bindings for labelled arrows
* support ids, clean up code and move the api related stuff to transform.ts
* allow multiple arrows to bind to single element
* fix singular elements
* fix single text element, unique id and tests
* fix lint
* fix
* support binding arrow to text element
* fix creation of regular text
* use same stroke color as parent for text containers and height 0 for linear element by default
* fix types
* fix
* remove more ts ignore
* remove ts ignore
* remove
* Add coverage script
* Add tests
* fix tests
* make type optional when id present
* remove type when id provided in tests
* Add more tests
* tweak
* let host call convertToExcalidrawElements when using programmatic API
* remove convertToExcalidrawElements call from restore
* lint
* update snaps
* Add new type excalidraw-api/clipboard for programmatic api
* cleanup
* rename tweak
* tweak
* make image attributes optional and better ts check
* support image via programmatic API
* fix lint
* more types
* make fileId mandatory for image and export convertToExcalidrawElements
* fix
* small tweaks
* update snaps
* fix
* use Object.assign instead of mutateElement
* lint
* preserve z-index by pushing all elements first and then add bindings
* instantiate instead of closure for storing elements
* use element API to create regular text, diamond, ellipse and rectangle
* fix snaps
* udpdate api
* ts fixes
* make `convertToExcalidrawElements` more typesafe
* update snaps
* refactor the approach so that order of elements doesn't matter
* Revert "update snaps"
This reverts commit 621dfadccf
.
* review fixes
* rename ExcalidrawProgrammaticElement -> ExcalidrawELementSkeleton
* Add tests
* give preference to first element when duplicate ids found
* use console.error
---------
Co-authored-by: dwelle <luzar.david@gmail.com>
256 lines
7.1 KiB
TypeScript
256 lines
7.1 KiB
TypeScript
import React, { useEffect, forwardRef } from "react";
|
|
import { InitializeApp } from "../../components/InitializeApp";
|
|
import App from "../../components/App";
|
|
import { isShallowEqual } from "../../utils";
|
|
|
|
import "../../css/app.scss";
|
|
import "../../css/styles.scss";
|
|
|
|
import { AppProps, ExcalidrawAPIRefValue, ExcalidrawProps } from "../../types";
|
|
import { defaultLang } from "../../i18n";
|
|
import { DEFAULT_UI_OPTIONS } from "../../constants";
|
|
import { Provider } from "jotai";
|
|
import { jotaiScope, jotaiStore } from "../../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";
|
|
|
|
const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|
const {
|
|
onChange,
|
|
initialData,
|
|
excalidrawRef,
|
|
isCollaborating = false,
|
|
onPointerUpdate,
|
|
renderTopRightUI,
|
|
langCode = defaultLang.code,
|
|
viewModeEnabled,
|
|
zenModeEnabled,
|
|
gridModeEnabled,
|
|
libraryReturnUrl,
|
|
theme,
|
|
name,
|
|
renderCustomStats,
|
|
onPaste,
|
|
detectScroll = true,
|
|
handleKeyboardGlobally = false,
|
|
onLibraryChange,
|
|
autoFocus = false,
|
|
generateIdForFile,
|
|
onLinkOpen,
|
|
onPointerDown,
|
|
onScrollChange,
|
|
children,
|
|
validateEmbeddable,
|
|
renderEmbeddable,
|
|
} = 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,
|
|
},
|
|
};
|
|
|
|
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(() => {
|
|
// 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 (
|
|
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
|
|
<InitializeApp langCode={langCode} theme={theme}>
|
|
<App
|
|
onChange={onChange}
|
|
initialData={initialData}
|
|
excalidrawRef={excalidrawRef}
|
|
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}
|
|
onPointerDown={onPointerDown}
|
|
onScrollChange={onScrollChange}
|
|
validateEmbeddable={validateEmbeddable}
|
|
renderEmbeddable={renderEmbeddable}
|
|
>
|
|
{children}
|
|
</App>
|
|
</InitializeApp>
|
|
</Provider>
|
|
);
|
|
};
|
|
|
|
type PublicExcalidrawProps = Omit<ExcalidrawProps, "forwardedRef">;
|
|
|
|
const areEqual = (
|
|
prevProps: PublicExcalidrawProps,
|
|
nextProps: PublicExcalidrawProps,
|
|
) => {
|
|
// 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);
|
|
};
|
|
|
|
const forwardedRefComp = forwardRef<
|
|
ExcalidrawAPIRefValue,
|
|
PublicExcalidrawProps
|
|
>((props, ref) => <ExcalidrawBase {...props} excalidrawRef={ref} />);
|
|
|
|
export const Excalidraw = React.memo(forwardedRefComp, areEqual);
|
|
Excalidraw.displayName = "Excalidraw";
|
|
|
|
export {
|
|
getSceneVersion,
|
|
isInvisiblySmallElement,
|
|
getNonDeletedElements,
|
|
} from "../../element";
|
|
export { defaultLang, useI18n, languages } from "../../i18n";
|
|
export {
|
|
restore,
|
|
restoreAppState,
|
|
restoreElements,
|
|
restoreLibraryItems,
|
|
} from "../../data/restore";
|
|
export {
|
|
exportToCanvas,
|
|
exportToBlob,
|
|
exportToSvg,
|
|
serializeAsJSON,
|
|
serializeLibraryAsJSON,
|
|
loadLibraryFromBlob,
|
|
loadFromBlob,
|
|
loadSceneOrLibraryFromBlob,
|
|
getFreeDrawSvgPath,
|
|
exportToClipboard,
|
|
mergeLibraryItems,
|
|
} from "../../packages/utils";
|
|
export { isLinearElement } from "../../element/typeChecks";
|
|
|
|
export { FONT_FAMILY, THEME, MIME_TYPES } from "../../constants";
|
|
|
|
export {
|
|
mutateElement,
|
|
newElementWith,
|
|
bumpVersion,
|
|
} from "../../element/mutateElement";
|
|
|
|
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 { DefaultSidebar } from "../../components/DefaultSidebar";
|
|
|
|
export { normalizeLink } from "../../data/url";
|
|
export { convertToExcalidrawElements } from "../../data/transform";
|