123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961 |
- import React, {
- useEffect,
- useState,
- useRef,
- useCallback,
- Children,
- cloneElement,
- } from "react";
- import ExampleSidebar from "./sidebar/ExampleSidebar";
- import type * as TExcalidraw from "@excalidraw/excalidraw";
- import { nanoid } from "nanoid";
- import type { ResolvablePromise } from "../utils";
- import {
- resolvablePromise,
- distance2d,
- fileOpen,
- withBatchedUpdates,
- withBatchedUpdatesThrottled,
- } from "../utils";
- import CustomFooter from "./CustomFooter";
- import MobileFooter from "./MobileFooter";
- import initialData from "../initialData";
- import type {
- AppState,
- BinaryFileData,
- ExcalidrawImperativeAPI,
- ExcalidrawInitialDataState,
- Gesture,
- LibraryItems,
- PointerDownState as ExcalidrawPointerDownState,
- } from "@excalidraw/excalidraw/types";
- import type {
- NonDeletedExcalidrawElement,
- Theme,
- } from "@excalidraw/excalidraw/element/types";
- import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
- import "./ExampleApp.scss";
- type Comment = {
- x: number;
- y: number;
- value: string;
- id?: string;
- };
- type PointerDownState = {
- x: number;
- y: number;
- hitElement: Comment;
- onMove: any;
- onUp: any;
- hitElementOffsets: {
- x: number;
- y: number;
- };
- };
- const COMMENT_ICON_DIMENSION = 32;
- const COMMENT_INPUT_HEIGHT = 50;
- const COMMENT_INPUT_WIDTH = 150;
- export interface AppProps {
- appTitle: string;
- useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
- customArgs?: any[];
- children: React.ReactNode;
- excalidrawLib: typeof TExcalidraw;
- }
- export default function ExampleApp({
- appTitle,
- useCustom,
- customArgs,
- children,
- excalidrawLib,
- }: AppProps) {
- const {
- exportToCanvas,
- exportToSvg,
- exportToBlob,
- exportToClipboard,
- useHandleLibrary,
- MIME_TYPES,
- sceneCoordsToViewportCoords,
- viewportCoordsToSceneCoords,
- restoreElements,
- Sidebar,
- Footer,
- WelcomeScreen,
- MainMenu,
- LiveCollaborationTrigger,
- convertToExcalidrawElements,
- TTDDialog,
- TTDDialogTrigger,
- ROUNDNESS,
- loadSceneOrLibraryFromBlob,
- } = excalidrawLib;
- const appRef = useRef<any>(null);
- const [viewModeEnabled, setViewModeEnabled] = useState(false);
- const [zenModeEnabled, setZenModeEnabled] = useState(false);
- const [gridModeEnabled, setGridModeEnabled] = useState(false);
- const [blobUrl, setBlobUrl] = useState<string>("");
- const [canvasUrl, setCanvasUrl] = useState<string>("");
- const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
- const [exportEmbedScene, setExportEmbedScene] = useState(false);
- const [theme, setTheme] = useState<Theme>("light");
- const [disableImageTool, setDisableImageTool] = useState(false);
- const [isCollaborating, setIsCollaborating] = useState(false);
- const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
- {},
- );
- const [comment, setComment] = useState<Comment | null>(null);
- const initialStatePromiseRef = useRef<{
- promise: ResolvablePromise<ExcalidrawInitialDataState | null>;
- }>({ promise: null! });
- if (!initialStatePromiseRef.current.promise) {
- initialStatePromiseRef.current.promise =
- resolvablePromise<ExcalidrawInitialDataState | null>();
- }
- const [excalidrawAPI, setExcalidrawAPI] =
- useState<ExcalidrawImperativeAPI | null>(null);
- useCustom(excalidrawAPI, customArgs);
- useHandleLibrary({ excalidrawAPI });
- useEffect(() => {
- if (!excalidrawAPI) {
- return;
- }
- const fetchData = async () => {
- const res = await fetch("/images/rocket.jpeg");
- const imageData = await res.blob();
- const reader = new FileReader();
- reader.readAsDataURL(imageData);
- reader.onload = function () {
- const imagesArray: BinaryFileData[] = [
- {
- id: "rocket" as BinaryFileData["id"],
- dataURL: reader.result as BinaryFileData["dataURL"],
- mimeType: MIME_TYPES.jpg,
- created: 1644915140367,
- lastRetrieved: 1644915140367,
- },
- ];
- //@ts-ignore
- initialStatePromiseRef.current.promise.resolve({
- ...initialData,
- elements: convertToExcalidrawElements(initialData.elements),
- });
- excalidrawAPI.addFiles(imagesArray);
- };
- };
- fetchData();
- }, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]);
- const renderExcalidraw = (children: React.ReactNode) => {
- const Excalidraw: any = Children.toArray(children).find(
- (child) =>
- React.isValidElement(child) &&
- typeof child.type !== "string" &&
- //@ts-ignore
- child.type.displayName === "Excalidraw",
- );
- if (!Excalidraw) {
- return;
- }
- const newElement = cloneElement(
- Excalidraw,
- {
- excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api),
- initialData: initialStatePromiseRef.current.promise,
- onChange: (
- elements: NonDeletedExcalidrawElement[],
- state: AppState,
- ) => {
- console.info("Elements :", elements, "State : ", state);
- },
- onPointerUpdate: (payload: {
- pointer: { x: number; y: number };
- button: "down" | "up";
- pointersMap: Gesture["pointers"];
- }) => setPointerData(payload),
- viewModeEnabled,
- zenModeEnabled,
- gridModeEnabled,
- theme,
- name: "Custom name of drawing",
- UIOptions: {
- canvasActions: {
- loadScene: false,
- },
- tools: { image: !disableImageTool },
- },
- renderTopRightUI,
- onLinkOpen,
- onPointerDown,
- onScrollChange: rerenderCommentIcons,
- validateEmbeddable: true,
- },
- <>
- {excalidrawAPI && (
- <Footer>
- <CustomFooter
- excalidrawAPI={excalidrawAPI}
- excalidrawLib={excalidrawLib}
- />
- </Footer>
- )}
- <WelcomeScreen />
- <Sidebar name="custom">
- <Sidebar.Tabs>
- <Sidebar.Header />
- <Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
- <Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
- <Sidebar.TabTriggers>
- <Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
- <Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
- </Sidebar.TabTriggers>
- </Sidebar.Tabs>
- </Sidebar>
- <Sidebar.Trigger
- name="custom"
- tab="one"
- style={{
- position: "absolute",
- left: "50%",
- transform: "translateX(-50%)",
- bottom: "20px",
- zIndex: 9999999999999999,
- }}
- >
- Toggle Custom Sidebar
- </Sidebar.Trigger>
- {renderMenu()}
- {excalidrawAPI && (
- <TTDDialogTrigger icon={<span>😀</span>}>
- Text to diagram
- </TTDDialogTrigger>
- )}
- <TTDDialog
- onTextSubmit={async (_) => {
- console.info("submit");
- // sleep for 2s
- await new Promise((resolve) => setTimeout(resolve, 2000));
- throw new Error("error, go away now");
- // return "dummy";
- }}
- />
- </>,
- );
- return newElement;
- };
- const renderTopRightUI = (isMobile: boolean) => {
- return (
- <>
- {!isMobile && (
- <LiveCollaborationTrigger
- isCollaborating={isCollaborating}
- onSelect={() => {
- window.alert("Collab dialog clicked");
- }}
- />
- )}
- <button
- onClick={() => alert("This is an empty top right UI")}
- style={{ height: "2.5rem" }}
- >
- Click me
- </button>
- </>
- );
- };
- const loadSceneOrLibrary = async () => {
- const file = await fileOpen({ description: "Excalidraw or library file" });
- const contents = await loadSceneOrLibraryFromBlob(file, null, null);
- if (contents.type === MIME_TYPES.excalidraw) {
- excalidrawAPI?.updateScene(contents.data as any);
- } else if (contents.type === MIME_TYPES.excalidrawlib) {
- excalidrawAPI?.updateLibrary({
- libraryItems: (contents.data as ImportedLibraryData).libraryItems!,
- openLibraryMenu: true,
- });
- }
- };
- const updateScene = () => {
- const sceneData = {
- elements: restoreElements(
- convertToExcalidrawElements([
- {
- type: "rectangle",
- id: "rect-1",
- fillStyle: "hachure",
- strokeWidth: 1,
- strokeStyle: "solid",
- roughness: 1,
- angle: 0,
- x: 100.50390625,
- y: 93.67578125,
- strokeColor: "#c92a2a",
- width: 186.47265625,
- height: 141.9765625,
- seed: 1968410350,
- roundness: {
- type: ROUNDNESS.ADAPTIVE_RADIUS,
- value: 32,
- },
- },
- {
- type: "arrow",
- x: 300,
- y: 150,
- start: { id: "rect-1" },
- end: { type: "ellipse" },
- },
- {
- type: "text",
- x: 300,
- y: 100,
- text: "HELLO WORLD!",
- },
- ]),
- null,
- ),
- appState: {
- viewBackgroundColor: "#edf2ff",
- },
- };
- excalidrawAPI?.updateScene(sceneData);
- };
- const onLinkOpen = useCallback(
- (
- element: NonDeletedExcalidrawElement,
- event: CustomEvent<{
- nativeEvent: MouseEvent | React.PointerEvent<HTMLCanvasElement>;
- }>,
- ) => {
- const link = element.link!;
- const { nativeEvent } = event.detail;
- const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
- const isNewWindow = nativeEvent.shiftKey;
- const isInternalLink =
- link.startsWith("/") || link.includes(window.location.origin);
- if (isInternalLink && !isNewTab && !isNewWindow) {
- // signal that we're handling the redirect ourselves
- event.preventDefault();
- // do a custom redirect, such as passing to react-router
- // ...
- }
- },
- [],
- );
- const onCopy = async (type: "png" | "svg" | "json") => {
- if (!excalidrawAPI) {
- return false;
- }
- await exportToClipboard({
- elements: excalidrawAPI.getSceneElements(),
- appState: excalidrawAPI.getAppState(),
- files: excalidrawAPI.getFiles(),
- type,
- });
- window.alert(`Copied to clipboard as ${type} successfully`);
- };
- const [pointerData, setPointerData] = useState<{
- pointer: { x: number; y: number };
- button: "down" | "up";
- pointersMap: Gesture["pointers"];
- } | null>(null);
- const onPointerDown = (
- activeTool: AppState["activeTool"],
- pointerDownState: ExcalidrawPointerDownState,
- ) => {
- if (activeTool.type === "custom" && activeTool.customType === "comment") {
- const { x, y } = pointerDownState.origin;
- setComment({ x, y, value: "" });
- }
- };
- const rerenderCommentIcons = () => {
- if (!excalidrawAPI) {
- return false;
- }
- const commentIconsElements = appRef.current.querySelectorAll(
- ".comment-icon",
- ) as HTMLElement[];
- commentIconsElements.forEach((ele) => {
- const id = ele.id;
- const appstate = excalidrawAPI.getAppState();
- const { x, y } = sceneCoordsToViewportCoords(
- { sceneX: commentIcons[id].x, sceneY: commentIcons[id].y },
- appstate,
- );
- ele.style.left = `${
- x - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetLeft
- }px`;
- ele.style.top = `${
- y - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetTop
- }px`;
- });
- };
- const onPointerMoveFromPointerDownHandler = (
- pointerDownState: PointerDownState,
- ) => {
- return withBatchedUpdatesThrottled((event) => {
- if (!excalidrawAPI) {
- return false;
- }
- const { x, y } = viewportCoordsToSceneCoords(
- {
- clientX: event.clientX - pointerDownState.hitElementOffsets.x,
- clientY: event.clientY - pointerDownState.hitElementOffsets.y,
- },
- excalidrawAPI.getAppState(),
- );
- setCommentIcons({
- ...commentIcons,
- [pointerDownState.hitElement.id!]: {
- ...commentIcons[pointerDownState.hitElement.id!],
- x,
- y,
- },
- });
- });
- };
- const onPointerUpFromPointerDownHandler = (
- pointerDownState: PointerDownState,
- ) => {
- return withBatchedUpdates((event) => {
- window.removeEventListener("pointermove", pointerDownState.onMove);
- window.removeEventListener("pointerup", pointerDownState.onUp);
- excalidrawAPI?.setActiveTool({ type: "selection" });
- const distance = distance2d(
- pointerDownState.x,
- pointerDownState.y,
- event.clientX,
- event.clientY,
- );
- if (distance === 0) {
- if (!comment) {
- setComment({
- x: pointerDownState.hitElement.x + 60,
- y: pointerDownState.hitElement.y,
- value: pointerDownState.hitElement.value,
- id: pointerDownState.hitElement.id,
- });
- } else {
- setComment(null);
- }
- }
- });
- };
- const renderCommentIcons = () => {
- return Object.values(commentIcons).map((commentIcon) => {
- if (!excalidrawAPI) {
- return false;
- }
- const appState = excalidrawAPI.getAppState();
- const { x, y } = sceneCoordsToViewportCoords(
- { sceneX: commentIcon.x, sceneY: commentIcon.y },
- excalidrawAPI.getAppState(),
- );
- return (
- <div
- id={commentIcon.id}
- key={commentIcon.id}
- style={{
- top: `${y - COMMENT_ICON_DIMENSION / 2 - appState!.offsetTop}px`,
- left: `${x - COMMENT_ICON_DIMENSION / 2 - appState!.offsetLeft}px`,
- position: "absolute",
- zIndex: 1,
- width: `${COMMENT_ICON_DIMENSION}px`,
- height: `${COMMENT_ICON_DIMENSION}px`,
- cursor: "pointer",
- touchAction: "none",
- }}
- className="comment-icon"
- onPointerDown={(event) => {
- event.preventDefault();
- if (comment) {
- commentIcon.value = comment.value;
- saveComment();
- }
- const pointerDownState: any = {
- x: event.clientX,
- y: event.clientY,
- hitElement: commentIcon,
- hitElementOffsets: { x: event.clientX - x, y: event.clientY - y },
- };
- const onPointerMove =
- onPointerMoveFromPointerDownHandler(pointerDownState);
- const onPointerUp =
- onPointerUpFromPointerDownHandler(pointerDownState);
- window.addEventListener("pointermove", onPointerMove);
- window.addEventListener("pointerup", onPointerUp);
- pointerDownState.onMove = onPointerMove;
- pointerDownState.onUp = onPointerUp;
- excalidrawAPI?.setActiveTool({
- type: "custom",
- customType: "comment",
- });
- }}
- >
- <div className="comment-avatar">
- <img src="images/doremon.png" alt="doremon" />
- </div>
- </div>
- );
- });
- };
- const saveComment = () => {
- if (!comment) {
- return;
- }
- if (!comment.id && !comment.value) {
- setComment(null);
- return;
- }
- const id = comment.id || nanoid();
- setCommentIcons({
- ...commentIcons,
- [id]: {
- x: comment.id ? comment.x - 60 : comment.x,
- y: comment.y,
- id,
- value: comment.value,
- },
- });
- setComment(null);
- };
- const renderComment = () => {
- if (!comment) {
- return null;
- }
- const appState = excalidrawAPI?.getAppState()!;
- const { x, y } = sceneCoordsToViewportCoords(
- { sceneX: comment.x, sceneY: comment.y },
- appState,
- );
- let top = y - COMMENT_ICON_DIMENSION / 2 - appState.offsetTop;
- let left = x - COMMENT_ICON_DIMENSION / 2 - appState.offsetLeft;
- if (
- top + COMMENT_INPUT_HEIGHT <
- appState.offsetTop + COMMENT_INPUT_HEIGHT
- ) {
- top = COMMENT_ICON_DIMENSION / 2;
- }
- if (top + COMMENT_INPUT_HEIGHT > appState.height) {
- top = appState.height - COMMENT_INPUT_HEIGHT - COMMENT_ICON_DIMENSION / 2;
- }
- if (
- left + COMMENT_INPUT_WIDTH <
- appState.offsetLeft + COMMENT_INPUT_WIDTH
- ) {
- left = COMMENT_ICON_DIMENSION / 2;
- }
- if (left + COMMENT_INPUT_WIDTH > appState.width) {
- left = appState.width - COMMENT_INPUT_WIDTH - COMMENT_ICON_DIMENSION / 2;
- }
- return (
- <textarea
- className="comment"
- style={{
- top: `${top}px`,
- left: `${left}px`,
- position: "absolute",
- zIndex: 1,
- height: `${COMMENT_INPUT_HEIGHT}px`,
- width: `${COMMENT_INPUT_WIDTH}px`,
- }}
- ref={(ref) => {
- setTimeout(() => ref?.focus());
- }}
- placeholder={comment.value ? "Reply" : "Comment"}
- value={comment.value}
- onChange={(event) => {
- setComment({ ...comment, value: event.target.value });
- }}
- onBlur={saveComment}
- onKeyDown={(event) => {
- if (!event.shiftKey && event.key === "Enter") {
- event.preventDefault();
- saveComment();
- }
- }}
- />
- );
- };
- const renderMenu = () => {
- return (
- <MainMenu>
- <MainMenu.DefaultItems.SaveAsImage />
- <MainMenu.DefaultItems.Export />
- <MainMenu.Separator />
- <MainMenu.DefaultItems.LiveCollaborationTrigger
- isCollaborating={isCollaborating}
- onSelect={() => window.alert("You clicked on collab button")}
- />
- <MainMenu.Group title="Excalidraw links">
- <MainMenu.DefaultItems.Socials />
- </MainMenu.Group>
- <MainMenu.Separator />
- <MainMenu.ItemCustom>
- <button
- style={{ height: "2rem" }}
- onClick={() => window.alert("custom menu item")}
- >
- custom item
- </button>
- </MainMenu.ItemCustom>
- <MainMenu.DefaultItems.Help />
- {excalidrawAPI && (
- <MobileFooter
- excalidrawLib={excalidrawLib}
- excalidrawAPI={excalidrawAPI}
- />
- )}
- </MainMenu>
- );
- };
- return (
- <div className="App" ref={appRef}>
- <h1>{appTitle}</h1>
- {/* TODO fix type */}
- <ExampleSidebar>
- <div className="button-wrapper">
- <button onClick={loadSceneOrLibrary}>Load Scene or Library</button>
- <button className="update-scene" onClick={updateScene}>
- Update Scene
- </button>
- <button
- className="reset-scene"
- onClick={() => {
- excalidrawAPI?.resetScene();
- }}
- >
- Reset Scene
- </button>
- <button
- onClick={() => {
- const libraryItems: LibraryItems = [
- {
- status: "published",
- id: "1",
- created: 1,
- elements: initialData.libraryItems[1] as any,
- },
- {
- status: "unpublished",
- id: "2",
- created: 2,
- elements: initialData.libraryItems[1] as any,
- },
- ];
- excalidrawAPI?.updateLibrary({
- libraryItems,
- });
- }}
- >
- Update Library
- </button>
- <label>
- <input
- type="checkbox"
- checked={viewModeEnabled}
- onChange={() => setViewModeEnabled(!viewModeEnabled)}
- />
- View mode
- </label>
- <label>
- <input
- type="checkbox"
- checked={zenModeEnabled}
- onChange={() => setZenModeEnabled(!zenModeEnabled)}
- />
- Zen mode
- </label>
- <label>
- <input
- type="checkbox"
- checked={gridModeEnabled}
- onChange={() => setGridModeEnabled(!gridModeEnabled)}
- />
- Grid mode
- </label>
- <label>
- <input
- type="checkbox"
- checked={theme === "dark"}
- onChange={() => {
- setTheme(theme === "light" ? "dark" : "light");
- }}
- />
- Switch to Dark Theme
- </label>
- <label>
- <input
- type="checkbox"
- checked={disableImageTool === true}
- onChange={() => {
- setDisableImageTool(!disableImageTool);
- }}
- />
- Disable Image Tool
- </label>
- <label>
- <input
- type="checkbox"
- checked={isCollaborating}
- onChange={() => {
- if (!isCollaborating) {
- const collaborators = new Map();
- collaborators.set("id1", {
- username: "Doremon",
- avatarUrl: "images/doremon.png",
- });
- collaborators.set("id2", {
- username: "Excalibot",
- avatarUrl: "images/excalibot.png",
- });
- collaborators.set("id3", {
- username: "Pika",
- avatarUrl: "images/pika.jpeg",
- });
- collaborators.set("id4", {
- username: "fallback",
- avatarUrl: "https://example.com",
- });
- excalidrawAPI?.updateScene({ collaborators });
- } else {
- excalidrawAPI?.updateScene({
- collaborators: new Map(),
- });
- }
- setIsCollaborating(!isCollaborating);
- }}
- />
- Show collaborators
- </label>
- <div>
- <button onClick={onCopy.bind(null, "png")}>
- Copy to Clipboard as PNG
- </button>
- <button onClick={onCopy.bind(null, "svg")}>
- Copy to Clipboard as SVG
- </button>
- <button onClick={onCopy.bind(null, "json")}>
- Copy to Clipboard as JSON
- </button>
- </div>
- <div
- style={{
- display: "flex",
- gap: "1em",
- justifyContent: "center",
- marginTop: "1em",
- }}
- >
- <div>x: {pointerData?.pointer.x ?? 0}</div>
- <div>y: {pointerData?.pointer.y ?? 0}</div>
- </div>
- </div>
- <div className="excalidraw-wrapper">
- {renderExcalidraw(children)}
- {Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
- {comment && renderComment()}
- </div>
- <div className="export-wrapper button-wrapper">
- <label className="export-wrapper__checkbox">
- <input
- type="checkbox"
- checked={exportWithDarkMode}
- onChange={() => setExportWithDarkMode(!exportWithDarkMode)}
- />
- Export with dark mode
- </label>
- <label className="export-wrapper__checkbox">
- <input
- type="checkbox"
- checked={exportEmbedScene}
- onChange={() => setExportEmbedScene(!exportEmbedScene)}
- />
- Export with embed scene
- </label>
- <button
- onClick={async () => {
- if (!excalidrawAPI) {
- return;
- }
- const svg = await exportToSvg({
- elements: excalidrawAPI?.getSceneElements(),
- appState: {
- ...initialData.appState,
- exportWithDarkMode,
- exportEmbedScene,
- width: 300,
- height: 100,
- },
- files: excalidrawAPI?.getFiles(),
- });
- appRef.current.querySelector(".export-svg").innerHTML =
- svg.outerHTML;
- }}
- >
- Export to SVG
- </button>
- <div className="export export-svg"></div>
- <button
- onClick={async () => {
- if (!excalidrawAPI) {
- return;
- }
- const blob = await exportToBlob({
- elements: excalidrawAPI?.getSceneElements(),
- mimeType: "image/png",
- appState: {
- ...initialData.appState,
- exportEmbedScene,
- exportWithDarkMode,
- },
- files: excalidrawAPI?.getFiles(),
- });
- setBlobUrl(window.URL.createObjectURL(blob));
- }}
- >
- Export to Blob
- </button>
- <div className="export export-blob">
- <img src={blobUrl} alt="" />
- </div>
- <button
- onClick={async () => {
- if (!excalidrawAPI) {
- return;
- }
- const canvas = await exportToCanvas({
- elements: excalidrawAPI.getSceneElements(),
- appState: {
- ...initialData.appState,
- exportWithDarkMode,
- },
- files: excalidrawAPI.getFiles(),
- });
- const ctx = canvas.getContext("2d")!;
- ctx.font = "30px Excalifont";
- ctx.strokeText("My custom text", 50, 60);
- setCanvasUrl(canvas.toDataURL());
- }}
- >
- Export to Canvas
- </button>
- <button
- onClick={async () => {
- if (!excalidrawAPI) {
- return;
- }
- const canvas = await exportToCanvas({
- elements: excalidrawAPI.getSceneElements(),
- appState: {
- ...initialData.appState,
- exportWithDarkMode,
- },
- files: excalidrawAPI.getFiles(),
- });
- const ctx = canvas.getContext("2d")!;
- ctx.font = "30px Excalifont";
- ctx.strokeText("My custom text", 50, 60);
- setCanvasUrl(canvas.toDataURL());
- }}
- >
- Export to Canvas
- </button>
- <button
- type="button"
- onClick={() => {
- if (!excalidrawAPI) {
- return;
- }
- const elements = excalidrawAPI.getSceneElements();
- excalidrawAPI.scrollToContent(elements[0], {
- fitToViewport: true,
- });
- }}
- >
- Fit to viewport, first element
- </button>
- <button
- type="button"
- onClick={() => {
- if (!excalidrawAPI) {
- return;
- }
- const elements = excalidrawAPI.getSceneElements();
- excalidrawAPI.scrollToContent(elements[0], {
- fitToContent: true,
- });
- excalidrawAPI.scrollToContent(elements[0], {
- fitToContent: true,
- });
- }}
- >
- Fit to content, first element
- </button>
- <button
- type="button"
- onClick={() => {
- if (!excalidrawAPI) {
- return;
- }
- const elements = excalidrawAPI.getSceneElements();
- excalidrawAPI.scrollToContent(elements[0], {
- fitToContent: true,
- });
- excalidrawAPI.scrollToContent(elements[0]);
- }}
- >
- Scroll to first element, no fitToContent, no fitToViewport
- </button>
- <div className="export export-canvas">
- <img src={canvasUrl} alt="" />
- </div>
- </div>
- </ExampleSidebar>
- </div>
- );
- }
|