docs: add next js with app router example (#7552)

* move the existing example to with-script-in-browser

* Add example with next js app router

* disable ssr for excalidraw client comp

* typo

* update output dir

* don't include nextjs example in tsconfig

* remove meta.json

* lint

* remove example.ts

* port

* move the examples outside packages and use the deps as workspaces in examples

* update gitignore

* fix example

* update path of build dir

* fix

* fix scripts

* try local path

* fix

* update commands

* fix

* fix

* fix script

* skip ts

* disable ts

* add vercel.json

* install

* update tsconfig

* fix lint

* remove console.log

* lets see if this works

* revert

* remove ts nocheck

* add types and some utils in nextjs example

* fix types

* updatw example and remove nextjs dynamic syntax so we don't import excal twice

* move both examples to workspaces and create generic example to be used by browser and next js both

* copy the static assets to nextjs

* fix ts config

* render custom menu items

* fix custom footer

* fix types in browser example

* use regular imports for importing excal and import it using dynamic next js in app router instead

* Add example for pages router

* fix css discrepancies

* fix css

* configure output dir

* fix

* fix css

* rename to with-nextjs

* move components to examples/excalidraw/components
This commit is contained in:
Aakansha Doshi 2024-01-24 17:07:54 +05:30 committed by GitHub
parent f3f8217125
commit 4f0a2a9593
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 1431 additions and 229 deletions

View file

@ -1,4 +1,2 @@
node_modules
types
bundle.js
bundle.css

View file

@ -5780,7 +5780,10 @@ class App extends React.Component<AppProps, AppState> {
event.preventDefault();
let nextPastePrevented = false;
const isLinux = /Linux/.test(window.navigator.platform);
const isLinux =
typeof window === undefined
? false
: /Linux/.test(window.navigator.platform);
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING);
let { clientX: lastX, clientY: lastY } = event;

View file

@ -2,7 +2,6 @@ import cssVariables from "./css/variables.module.scss";
import { AppProps } from "./types";
import { ExcalidrawElement, FontFamilyValues } from "./element/types";
import { COLOR_PALETTE } from "./colors";
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
export const isWindows = /^Win/.test(navigator.platform);
export const isAndroid = /\b(android)\b/i.test(navigator.userAgent);

View file

@ -1,83 +0,0 @@
.App {
font-family: sans-serif;
text-align: center;
.comment-avatar {
background: #faa2c1;
border-radius: 66px 67px 67px 0px;
width: 2rem;
height: 2rem;
padding: 4px;
margin: 4px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
}
.button-wrapper button {
z-index: 1;
height: 40px;
max-width: 200px;
margin: 10px;
padding: 5px;
}
.excalidraw .App-menu_top .buttonList {
display: flex;
}
.excalidraw-wrapper {
height: 800px;
margin: 50px;
position: relative;
overflow: hidden;
}
:root[dir="ltr"]
.excalidraw
.layer-ui__wrapper
.zen-mode-transition.App-menu_bottom--transition-left {
transform: none;
}
.excalidraw .panelColumn {
text-align: left;
}
.export-wrapper {
display: flex;
flex-direction: column;
margin: 50px;
&__checkbox {
display: flex;
}
}
.excalidraw {
--color-primary: #faa2c1;
--color-primary-darker: #f783ac;
--color-primary-darkest: #e64980;
--color-primary-light: #fcc2d7;
button.custom-element {
width: 2rem;
height: 2rem;
}
.custom-footer,
.custom-element {
padding: 0.1rem;
margin: 0 8px;
}
.layer-ui__wrapper__footer.App-menu_bottom {
align-items: stretch;
}
// till its merged in OSS
.App-toolbar-container .mobile-misc-tools-container {
position: absolute;
}
}

View file

@ -1,920 +0,0 @@
import ExampleSidebar from "./sidebar/ExampleSidebar";
import type * as TExcalidraw from "../index";
import "./App.scss";
import initialData from "./initialData";
import { nanoid } from "nanoid";
import { resolvablePromise, ResolvablePromise } from "../utils";
import { EVENT, ROUNDNESS } from "../constants";
import { distance2d } from "../math";
import { fileOpen } from "../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils";
import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
ExcalidrawInitialDataState,
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "../types";
import type { NonDeletedExcalidrawElement, Theme } from "../element/types";
import { ImportedLibraryData } from "../data/types";
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import { KEYS } from "../keys";
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
declare global {
interface Window {
ExcalidrawLib: typeof TExcalidraw;
}
}
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 { useEffect, useState, useRef, useCallback } = window.React;
// This is so that we use the bundled excalidraw.development.js file instead
// of the actual source code
const {
exportToCanvas,
exportToSvg,
exportToBlob,
exportToClipboard,
Excalidraw,
useHandleLibrary,
MIME_TYPES,
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
Footer,
WelcomeScreen,
MainMenu,
LiveCollaborationTrigger,
convertToExcalidrawElements,
TTDDialog,
TTDDialogTrigger,
} = window.ExcalidrawLib;
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[];
}
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
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]);
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(EVENT.POINTER_MOVE, pointerDownState.onMove);
window.removeEventListener(EVENT.POINTER_UP, 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(EVENT.POINTER_MOVE, onPointerMove);
window.addEventListener(EVENT.POINTER_UP, 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 === KEYS.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 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">
<Excalidraw
excalidrawAPI={(api: ExcalidrawImperativeAPI) =>
setExcalidrawAPI(api)
}
initialData={initialStatePromiseRef.current.promise}
onChange={(elements, state) => {
// console.info("Elements :", elements, "State : ", state);
}}
onPointerUpdate={(payload: {
pointer: { x: number; y: number };
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => setPointerData(payload)}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
theme={theme}
name="Custom name of drawing"
UIOptions={{
canvasActions: {
loadScene: false,
},
tools: { image: !disableImageTool },
}}
renderTopRightUI={renderTopRightUI}
onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown}
onScrollChange={rerenderCommentIcons}
// allow all urls
validateEmbeddable={true}
>
{excalidrawAPI && (
<Footer>
<CustomFooter excalidrawAPI={excalidrawAPI} />
</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";
}}
/>
</Excalidraw>
{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 Virgil";
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 Virgil";
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>
);
}

View file

@ -1,73 +0,0 @@
import type { ExcalidrawImperativeAPI } from "../types";
const { Button, MIME_TYPES } = window.ExcalidrawLib;
const COMMENT_SVG = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-message-circle"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
</svg>
);
const CustomFooter = ({
excalidrawAPI,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
}) => {
return (
<>
<Button
onSelect={() => alert("General Kenobi!")}
className="you are a bold one"
style={{ marginLeft: "1rem" }}
title="Hello there!"
>
{COMMENT_SVG}
</Button>
<button
className="custom-element"
onClick={() => {
excalidrawAPI?.setActiveTool({
type: "custom",
customType: "comment",
});
const url = `data:${MIME_TYPES.svg},${encodeURIComponent(
`<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-message-circle"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
</svg>`,
)}`;
excalidrawAPI?.setCursor(`url(${url}), auto`);
}}
>
{COMMENT_SVG}
</button>
<button
className="custom-footer"
onClick={() => alert("This is dummy footer")}
>
custom footer
</button>
</>
);
};
export default CustomFooter;

View file

@ -1,20 +0,0 @@
import type { ExcalidrawImperativeAPI } from "../types";
import CustomFooter from "./CustomFooter";
const { useDevice, Footer } = window.ExcalidrawLib;
const MobileFooter = ({
excalidrawAPI,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
}) => {
const device = useDevice();
if (device.editor.isMobile) {
return (
<Footer>
<CustomFooter excalidrawAPI={excalidrawAPI} />
</Footer>
);
}
return null;
};
export default MobileFooter;

View file

@ -1,17 +0,0 @@
import App from "./App";
const { StrictMode } = window.React;
//@ts-ignore
const { createRoot } = window.ReactDOM;
const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App
appTitle={"Excalidraw Example"}
useCustom={(api: any, args?: any[]) => {}}
/>
</StrictMode>,
);

View file

@ -1,994 +0,0 @@
import type { ExcalidrawElementSkeleton } from "../data/transform";
import type { FileId } from "../element/types";
const elements: ExcalidrawElementSkeleton[] = [
{
type: "rectangle",
x: 10,
y: 10,
strokeWidth: 2,
id: "1",
},
{
type: "diamond",
x: 120,
y: 20,
backgroundColor: "#fff3bf",
strokeWidth: 2,
label: {
text: "HELLO EXCALIDRAW",
strokeColor: "#099268",
fontSize: 30,
},
id: "2",
},
{
type: "arrow",
x: 100,
y: 200,
label: { text: "HELLO WORLD!!" },
start: { type: "rectangle" },
end: { type: "ellipse" },
},
{
type: "image",
x: 606.1042326312408,
y: 153.57729779411773,
width: 230,
height: 230,
fileId: "rocket" as FileId,
},
{
type: "frame",
children: ["1", "2"],
name: "My frame",
},
];
export default {
elements,
appState: { viewBackgroundColor: "#AFEEEE", currentItemFontFamily: 1 },
scrollToContent: true,
libraryItems: [
[
{
type: "line",
x: 209.72304760646858,
y: 338.83587294718825,
strokeColor: "#881fa3",
backgroundColor: "#be4bdb",
width: 116.42036295658873,
height: 103.65107323746608,
strokeSharpness: "sharp",
points: [
[-92.28090097254909, 7.105427357601002e-15],
[-154.72281841151394, 19.199290805487394],
[-155.45758928571422, 79.43840749607878],
[-99.89923520113778, 103.6510732374661],
[-40.317783799181804, 79.1587107641305],
[-39.037226329125524, 21.285677238400705],
[-92.28090097254909, 7.105427357601002e-15],
],
},
],
[
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
x: -249.48446738689245,
y: 374.851387389359,
strokeColor: "#0a11d3",
backgroundColor: "#228be6",
width: 88.21658171083376,
height: 113.8575037534261,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
points: [
[-0.22814350714115691, -43.414939319563715],
[0.06274947619197979, 42.63794490105306],
[-0.21453039840335475, 52.43469208825097],
[4.315205554872581, 56.66774540453215],
[20.089784992984285, 60.25027917349701],
[46.7532926683984, 61.365826671969444],
[72.22851104292477, 59.584691681394986],
[85.76368213524371, 55.325139565662596],
[87.67263486434864, 51.7342924478499],
[87.94074036468018, 43.84700272879395],
[87.73030872197806, -36.195582644606276],
[87.2559282533682, -43.758132174307036],
[81.5915337527493, -47.984890854524416],
[69.66352776578219, -50.4328058257654],
[42.481213744224995, -52.49167708145666],
[20.68789182864576, -51.26396751574663],
[3.5475921483286084, -47.099726468136254],
[-0.2758413461535838, -43.46664538034193],
[-0.22814350714115691, -43.414939319563715],
],
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -249.02524930453623,
y: 398.8804363713438,
strokeColor: "#0a11d3",
backgroundColor: "transparent",
width: 88.30808627974527,
height: 9.797916664247975,
seed: 683951089,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "round",
points: [
[0, -2.1538602707609424],
[2.326538897826852, 1.751753055375216],
[12.359939318521995, 5.028526743934819],
[25.710950037209347, 7.012921076245119],
[46.6269757640547, 7.193749997581346],
[71.03526003420632, 5.930375670950649],
[85.2899738827162, 1.3342483900732343],
[88.30808627974527, -2.6041666666666288],
],
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -250.11899081659772,
y: 365.80628180927204,
strokeColor: "#0a11d3",
backgroundColor: "transparent",
width: 88.30808627974527,
height: 9.797916664247975,
seed: 1817746897,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "round",
points: [
[0, -2.1538602707609424],
[2.326538897826852, 1.751753055375216],
[12.359939318521995, 5.028526743934819],
[25.710950037209347, 7.012921076245119],
[46.6269757640547, 7.193749997581346],
[71.03526003420632, 5.930375670950649],
[85.2899738827162, 1.3342483900732343],
[88.30808627974527, -2.6041666666666288],
],
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -251.23981350275943,
y: 323.4117518426986,
strokeColor: "#0a11d3",
backgroundColor: "#fff",
width: 87.65074610854188,
height: 17.72670397681366,
seed: 1409727409,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "sharp",
boundElementIds: ["bxuMGTzXLn7H-uBCptINx"],
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -179.73008120217884,
y: 347.98755471983213,
strokeColor: "#0a11d3",
backgroundColor: "#fff",
width: 12.846057046979809,
height: 13.941904362416096,
seed: 1073094033,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -179.73008120217884,
y: 378.5900085788926,
strokeColor: "#0a11d3",
backgroundColor: "#fff",
width: 12.846057046979809,
height: 13.941904362416096,
seed: 526271345,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -179.73008120217884,
y: 411.8508097533892,
strokeColor: "#0a11d3",
backgroundColor: "#fff",
width: 12.846057046979809,
height: 13.941904362416096,
seed: 243707217,
groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
strokeSharpness: "sharp",
},
],
[
{
type: "diamond",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -109.55894395256101,
y: 381.22641397493356,
strokeColor: "#c92a2a",
backgroundColor: "#fd8888",
width: 112.64736525303451,
height: 36.77344700318558,
seed: 511870335,
groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
strokeSharpness: "sharp",
},
{
type: "diamond",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -109.55894395256101,
y: 372.354634046675,
strokeColor: "#c92a2a",
backgroundColor: "#fd8888",
width: 112.64736525303451,
height: 36.77344700318558,
seed: 1283079231,
groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
strokeSharpness: "sharp",
},
{
type: "diamond",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -109.55894395256101,
y: 359.72407445196296,
strokeColor: "#c92a2a",
backgroundColor: "#fd8888",
width: 112.64736525303451,
height: 36.77344700318558,
seed: 996251633,
groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
strokeSharpness: "sharp",
},
{
type: "diamond",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -109.55894395256101,
y: 347.1924021546656,
strokeColor: "#c92a2a",
backgroundColor: "#fd8888",
width: 112.64736525303451,
height: 36.77344700318558,
seed: 1764842481,
groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
strokeSharpness: "sharp",
},
],
[
{
type: "line",
fillStyle: "hachure",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 1.5707963267948957,
x: -471.6208001976387,
y: 520.7681448415112,
strokeColor: "#087f5b",
backgroundColor: "#40c057",
width: 52.317507746132115,
height: 154.56722543646003,
seed: 1424381745,
groupIds: ["HSrtfEf-CssQTf160Fb6R"],
strokeSharpness: "round",
points: [
[-0.24755378372925183, -40.169554027464216],
[-0.07503751055611152, 76.6515171914404],
[-0.23948042713317108, 89.95108885873196],
[2.446913573036335, 95.69766931810295],
[11.802146636255692, 100.56113713047068],
[27.615140546177496, 102.07554835500338],
[42.72341054254274, 99.65756899883291],
[50.75054563137204, 93.87501510096598],
[51.88266441510958, 89.00026150397161],
[52.04166639997853, 78.29287333983132],
[51.916868330459295, -30.36891819848148],
[51.635533423123285, -40.63545540065934],
[48.27622163143906, -46.37349057843314],
[41.202227904674494, -49.69665692879073],
[25.081551986374073, -52.49167708145666],
[12.15685839679867, -50.825000270901],
[1.9916746648394732, -45.171835889467935],
[-0.2758413461535838, -40.23974757720194],
[-0.24755378372925183, -40.169554027464216],
],
},
{
type: "line",
version: 2405,
versionNonce: 2120341087,
isDeleted: false,
id: "TYsYe2VvJ60T_yKa3kyOw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 1.5707963267948957,
x: -496.3957643857249,
y: 541.7241190920508,
strokeColor: "#087f5b",
backgroundColor: "transparent",
width: 50.7174766392476,
height: 12.698053371678215,
seed: 726657713,
groupIds: ["HSrtfEf-CssQTf160Fb6R"],
strokeSharpness: "round",
points: [
[0, -2.0205717204386002],
[1.3361877396713384, 3.0410845646550486],
[7.098613049589299, 7.287767671898479],
[14.766422451441104, 9.859533283467512],
[26.779003528407447, 10.093886705011586],
[40.79727342221974, 8.456559589697127],
[48.98410145879092, 2.500000505196364],
[50.7174766392476, -2.6041666666666288],
],
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 1.5707963267948957,
x: -450.969983237283,
y: 542.1789894334747,
strokeColor: "#087f5b",
backgroundColor: "transparent",
width: 50.57247907260371,
height: 10.178760037658167,
seed: 1977326481,
groupIds: ["HSrtfEf-CssQTf160Fb6R"],
strokeSharpness: "round",
points: [
[0, -2.136356936862347],
[1.332367676378171, 1.9210669226078037],
[7.078318632616268, 5.325208253515953],
[14.724206326638113, 7.386735659885842],
[26.70244431044034, 7.574593370991538],
[40.68063699304561, 6.262111896696538],
[48.84405948536458, 1.4873339211608216],
[50.57247907260371, -2.6041666666666288],
],
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 1.5707963267948957,
x: -404.36521010516793,
y: 534.1894365757241,
strokeColor: "#087f5b",
backgroundColor: "#fff",
width: 51.27812853552538,
height: 22.797152568995934,
seed: 1774660383,
groupIds: ["HSrtfEf-CssQTf160Fb6R"],
strokeSharpness: "sharp",
boundElementIds: ["bxuMGTzXLn7H-uBCptINx"],
},
],
[
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 2,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -393.3000561423187,
y: 338.9742643666818,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 70.67858069123133,
height: 107.25081879410921,
seed: 371096063,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "sharp",
boundElementIds: [
"CFu0B4Mw_1wC1Hbgx8Fs0",
"XIl_NhaFtRO00pX5Pq6VU",
"EndiSTFlx1AT7vcBVjgve",
],
},
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 2,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -400.8474891780329,
y: 331.95417508096745,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 70.67858069123133,
height: 107.25081879410921,
seed: 685932433,
groupIds: ["0RJwA-yKP5dqk5oMiSeot", "9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "sharp",
boundElementIds: [
"CFu0B4Mw_1wC1Hbgx8Fs0",
"XIl_NhaFtRO00pX5Pq6VU",
"EndiSTFlx1AT7vcBVjgve",
],
},
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 2,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -410.24257846374826,
y: 323.7002688309677,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 70.67858069123133,
height: 107.25081879410921,
seed: 58634943,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "sharp",
boundElementIds: [
"CFu0B4Mw_1wC1Hbgx8Fs0",
"XIl_NhaFtRO00pX5Pq6VU",
"EndiSTFlx1AT7vcBVjgve",
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -398.2561518768373,
y: 371.84603609547054,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 46.57983585730082,
height: 3.249953844290203,
seed: 1673003743,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0.6014697828497827],
[40.42449133807562, 0.7588628355182573],
[46.57983585730082, -2.491091008771946],
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -396.400899638823,
y: 340.9822185794818,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 45.567415680676426,
height: 2.8032978840147194,
seed: 1821527807,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0],
[16.832548902953302, -2.8032978840147194],
[45.567415680676426, -0.3275477042019195],
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -396.4774991551924,
y: 408.37659284983897,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 48.33668263438425,
height: 4.280657518731036,
seed: 1485707039,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0],
[26.41225578429045, -0.2552319773002338],
[37.62000339651456, 2.3153712935189787],
[48.33668263438425, -1.9652862252120569],
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -399.6615463367227,
y: 419.61974125811776,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 54.40694982784246,
height: 2.9096445412231735,
seed: 1042012991,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0],
[10.166093050596771, -1.166642430373031],
[16.130660965377448, -0.8422655250909383],
[46.26079588567538, 0.6125567455206506],
[54.40694982784246, -2.297087795702523],
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -399.3767034411569,
y: 356.042820132743,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 46.92865289294453,
height: 2.4757501798128,
seed: 295443295,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0],
[18.193786115221407, -0.5912874140789839],
[46.92865289294453, 1.884462765733816],
],
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -399.26921524500654,
y: 390.5261491685826,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 46.92865289294453,
height: 2.4757501798128,
seed: 1734301567,
groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
strokeSharpness: "round",
points: [
[0, 0],
[8.093938105125233, 1.4279702913643746],
[18.193786115221407, -0.5912874140789839],
[46.92865289294453, 1.884462765733816],
],
},
],
[
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -593.9896997899341,
y: 343.9798351106279,
strokeColor: "#000000",
backgroundColor: "transparent",
width: 127.88383573213892,
height: 76.53703389977764,
seed: 106569279,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -595.0652975408293,
y: 354.6963695028721,
strokeColor: "#000000",
backgroundColor: "transparent",
width: 128.84193229844433,
height: 0,
seed: 73916127,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "round",
points: [
[0, 0],
[128.84193229844433, 0],
],
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 0,
opacity: 100,
angle: 0,
x: -589.5016643209792,
y: 348.2514049106367,
strokeColor: "#000000",
backgroundColor: "#fa5252",
width: 5.001953125,
height: 5.001953125,
seed: 387857791,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 0,
opacity: 100,
angle: 0,
x: -579.2389690084792,
y: 348.2514049106367,
strokeColor: "#000000",
backgroundColor: "#fab005",
width: 5.001953125,
height: 5.001953125,
seed: 1486370207,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 0,
opacity: 100,
angle: 0,
x: -568.525552542133,
y: 348.7021260644829,
strokeColor: "#000000",
backgroundColor: "#40c057",
width: 5.001953125,
height: 5.001953125,
seed: 610150847,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 90,
angle: 0,
x: -552.4984915525058,
y: 364.75449494249875,
strokeColor: "#000000",
backgroundColor: "#04aaf7",
width: 42.72020253937572,
height: 42.72020253937572,
seed: 144280593,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "draw",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
x: -530.327851842306,
y: 378.9357912947449,
strokeColor: "#087f5b",
backgroundColor: "#40c057",
width: 28.226201983883442,
height: 24.44112284281997,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "round",
points: [
[4.907524351775825, 2.043055712211473],
[3.0769604829149455, 1.6284171290602836],
[-2.66472604008681, -4.228569719133945],
[-6.450168189798415, -2.304577297733668],
[-7.704241049212052, 4.416384506147983],
[-6.361372181234263, 8.783101300254884],
[-12.516984713388897, 10.9291595737194],
[-12.295677738198286, 15.686226498407976],
[-7.473371426945252, 15.393030178104425],
[-3.787654025313423, 11.5207568827343],
[1.2873793872375165, 19.910682356036197],
[4.492232250183542, 20.212553123686025],
[1.1302787567009416, 6.843494873631317],
[6.294108177816019, 6.390688722156585],
[8.070028349098962, 7.910451897221202],
[14.143675334886687, 7.910451897221202],
[15.709217270494545, 2.6780252579576427],
[9.128749989671498, 3.1533849725326517],
[10.393751588600717, -3.7167773257046695],
[7.380151667177483, -3.30213874255348],
[4.669824267311791, 1.1200945145694894],
[4.907524351775825, 2.043055712211473],
],
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 90,
angle: 0,
x: -551.4394290784783,
y: 385.71736850567976,
strokeColor: "#000000",
backgroundColor: "#99bcff",
width: 42.095115772272244,
height: 0,
seed: 1443027377,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "round",
points: [
[0, 0],
[42.095115772272244, 0],
],
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 0,
opacity: 90,
angle: 0,
x: -546.3441000487039,
y: 372.6245229061568,
strokeColor: "#000000",
backgroundColor: "#99bcff",
width: 29.31860660384862,
height: 5.711199931375845,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "round",
points: [
[0, -2.341683327443203],
[0.7724193963150375, -0.06510358900749044],
[4.103544916365185, 1.84492589414448],
[8.536129150893453, 3.0016281808630056],
[15.480325949120388, 3.1070332647092163],
[23.583965316012858, 2.3706131055211244],
[28.316582284417855, -0.3084668090492442],
[29.31860660384862, -2.6041666666666288],
],
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 90,
angle: 0,
x: -538.2701841247845,
y: 363.37196531290607,
strokeColor: "#000000",
backgroundColor: "transparent",
width: 15.528434353116108,
height: 44.82230388130942,
seed: 683572113,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "sharp",
},
{
type: "line",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
opacity: 90,
x: -544.828148539078,
y: 402.0199316371545,
strokeColor: "#000000",
backgroundColor: "#99bcff",
width: 29.31860660384862,
height: 5.896061363392446,
seed: 318798801,
groupIds: ["TC0RSM64Cxmu17MlE12-o"],
strokeSharpness: "round",
points: [
[0, 0],
[4.103544916365185, -4.322122351104391],
[8.536129150893453, -5.516265043290966],
[15.480325949120388, -5.625081903117008],
[23.583965316012858, -4.8648251269605955],
[28.316582284417855, -2.0990281379671547],
[29.31860660384862, 0.2709794602754383],
],
},
],
[
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -715.1043446306466,
y: 330.4231266309418,
strokeColor: "#000000",
backgroundColor: "#ced4da",
width: 70.81644178885557,
height: 108.30428902193904,
seed: 1914896753,
groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
strokeSharpness: "sharp",
},
{
type: "rectangle",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -706.996640540555,
y: 338.68030798133873,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 55.801163535143246,
height: 82.83278895375764,
seed: 1306468145,
groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
strokeSharpness: "sharp",
},
{
type: "ellipse",
fillStyle: "solid",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -684.8099707762028,
y: 425.0579911039235,
strokeColor: "#000000",
backgroundColor: "#fff",
width: 11.427824006438863,
height: 11.427824006438863,
seed: 93422161,
groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
strokeSharpness: "sharp",
},
{
type: "rectangle",
fillStyle: "cross-hatch",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: -698.7169501405845,
y: 349.2244646574789,
strokeColor: "#000000",
backgroundColor: "#fab005",
width: 39.2417827352022,
height: 19.889460471185775,
seed: 11646495,
groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
strokeSharpness: "sharp",
},
{
type: "rectangle",
fillStyle: "cross-hatch",
strokeWidth: 1,
strokeStyle: "solid",
x: -698.7169501405845,
y: 384.7822247024333,
strokeColor: "#000000",
backgroundColor: "#fab005",
width: 39.2417827352022,
height: 19.889460471185775,
seed: 291717649,
groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
strokeSharpness: "sharp",
},
],
],
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View file

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>React App</title>
<script>
window.name = "codesandbox";
</script>
<link rel="stylesheet" href="/dist/browser/dev/index.css" />
<link rel="stylesheet" href="bundle.css" />
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<!-- This is so that we use the bundled excalidraw.development.js file instead
of the actual source code -->
<script type="module">
import * as ExcalidrawLib from "/dist/browser/dev/index.js";
window.ExcalidrawLib = ExcalidrawLib;
</script>
<script type="module" src="bundle.js"></script>
</body>
</html>

View file

@ -1,66 +0,0 @@
.sidebar {
height: 100%;
width: 0;
position: absolute;
z-index: 1;
top: 0;
left: 0;
background-color: #111;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
&.open {
width: 300px;
}
&-links {
display: flex;
flex-direction: column;
padding: 20px;
button {
padding: 10px;
margin: 10px;
background: #faa2c1;
color: #fff;
border: none;
cursor: pointer;
}
}
}
.sidebar a {
padding: 8px 8px 8px 32px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s;
}
.sidebar a:hover {
color: #f1f1f1;
}
.sidebar .closebtn {
position: absolute;
top: 0;
right: 0;
font-size: 36px;
margin-left: 50px;
}
.openbtn {
font-size: 20px;
cursor: pointer;
background-color: #111;
color: white;
padding: 10px 15px;
border: none;
display: flex;
margin-left: 50px;
}
.sidebar-open {
margin-left: 300px;
}

View file

@ -1,32 +0,0 @@
import "./ExampleSidebar.scss";
const React = window.React;
export default function Sidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = React.useState(false);
return (
<>
<div id="mySidebar" className={`sidebar ${open ? "open" : ""}`}>
<button className="closebtn" onClick={() => setOpen(false)}>
x
</button>
<div className="sidebar-links">
<button>Empty Home</button>
<button>Empty About</button>
</div>
</div>
<div className={`${open ? "sidebar-open" : ""}`}>
<button
className="openbtn"
onClick={() => {
setOpen(!open);
}}
>
Open Sidebar
</button>
{children}
</div>
</>
);
}

View file

@ -80,6 +80,13 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
}
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
@ -223,7 +230,7 @@ export {
} from "../utils/export";
export { isLinearElement } from "./element/typeChecks";
export { FONT_FAMILY, THEME, MIME_TYPES } from "./constants";
export { FONT_FAMILY, THEME, MIME_TYPES, ROUNDNESS } from "./constants";
export {
mutateElement,

View file

@ -82,7 +82,6 @@ import {
getTargetFrame,
isElementInFrame,
} from "../frame";
import "canvas-roundrect-polyfill";
export const DEFAULT_SPACING = 2;

View file

@ -1,5 +1,5 @@
{
"exclude": ["**/*.test.*", "tests", "types", "example", "dist"],
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"],
"compilerOptions": {
"target": "ESNext",
"strict": true,

View file

@ -1,4 +0,0 @@
{
"outputDirectory": "example/public",
"installCommand": "yarn install"
}

View file

@ -1,15 +0,0 @@
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
// To load .env.local variables
const envVars = loadEnv("", `../../`);
// https://vitejs.dev/config/
export default defineConfig({
root: "example/public",
server: {
port: 3001,
// open the browser
open: true,
},
publicDir: "public",
});