excalidraw/src/packages/excalidraw/example/App.tsx

816 lines
24 KiB
TypeScript
Raw Normal View History

import { useEffect, useState, useRef, useCallback } from "react";
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,
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../../../utils";
import { EVENT, ROUNDNESS } from "../../../constants";
import { distance2d } from "../../../math";
import { fileOpen } from "../../../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils";
import {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
ExcalidrawInitialDataState,
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "../../../types";
import { NonDeletedExcalidrawElement } from "../../../element/types";
import { ImportedLibraryData } from "../../../data/types";
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import { KEYS } from "../../../keys";
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;
};
};
// 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,
docs: migrating dev docs to docusaurus :) (#6073) * docs: migrating existing docs to docosaraus :) * log broken links * lint :p * fix * divide the doc into diff categories * fix * order sidebars and more * fix lint * point to installation * making docs better :) * fix * renaming git * renaming git * fix links * fix * update readme * fix * resolve duplicate url and make /docs as base url * fix * move main docs as well * making docs better * support mdx * update og * fix title * upgrade docusarus to stable version * use draculla theme * fix * make entire sidebar collapsable * live editor for footer wohoo * render excalidraw only on client to fix the prod build * migrate MainMenu to live editor too :) * lint :p * cleanup integration and use live editor and tabs * fix * Add welcome screen doc * Live Collaboration comp docs * Add collaborator example * Add example * add more * remove isCollaborating * Rewrite ref and move to sidebar * change color of links inside pre * add initial data * fix lint * Add styling * fix lint * Add example for customizing styles * fix lint * fix * fix lint * Add link to livecollabtrigger * fix * rewrite UIOptions to sidebar * move initialdata to sidebar * move render props to sidebar and rewrite renderTopRightUI and renderCustomStats * rewrite renderSidebar * update og * update url for testing * fix url * update readme * fix style * tweaks * Add highlight comp to highlight text * Add bash syntax highlight * fix * tweaks * fix * rewrite export utilities * fix restore * rewrite utils * move constants to sidebar * update readme * add copyright * fix links style * Add linkedin * tweaks * rename package to @excalidraw/excalidraw * enable algolia with dummy creds * tweaks to integration doc * tweak WelcomeScreen docs to reflect upcoming API changes * tweak components intro * tweak nomenclature * fix admonition * rename `components` sidebar item and change order of components list * uncollapse package section in sidebar * show level 4 haeadings in TOC * remove algolia * remove unused assets * capitalize C * tweak * rename components to App * rename components -> children-components in the routes * move notable used tools to intro * update MainMenu docs with `onSelect` preventDefault behavior * change sidebar label for children components * use code * tweak README & docs intro * tweak package development doc * make scrollbar gutter stable * tweak api intro * add admonition for export utils * use next * wip * wip * make excalidraw examples use current color theme & prefer system * fix welcomescreen docs * use latest temp release * fix component order * revert wip changes * use next * tweak * increase height to fix welcome screen hint * tweak editor height * update excal version * wrap Excal with forwardRef to fix refs * migrate contributing.md * fix broken links --------- Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-01 14:27:54 +00:00
WelcomeScreen,
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
MainMenu,
LiveCollaborationTrigger,
} = 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("light");
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);
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(
[
{
type: "rectangle",
version: 141,
versionNonce: 361174001,
isDeleted: false,
id: "oDVXy8D6rom3H1-LLH2-f",
fillStyle: "hachure",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
angle: 0,
x: 100.50390625,
y: 93.67578125,
strokeColor: "#c92a2a",
backgroundColor: "transparent",
width: 186.47265625,
height: 141.9765625,
seed: 1968410350,
groupIds: [],
boundElements: null,
locked: false,
link: null,
updated: 1,
roundness: {
type: ROUNDNESS.ADAPTIVE_RADIUS,
value: 32,
},
},
],
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);
}
}
});
};
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
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();
}
}}
/>
);
};
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
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")}
/>
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
<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 />
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
{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={() => {
let newTheme = "light";
if (theme === "light") {
newTheme = "dark";
}
setTheme(newTheme);
}}
/>
Switch to Dark Theme
</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
ref={(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 } }}
renderTopRightUI={renderTopRightUI}
onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown}
onScrollChange={rerenderCommentIcons}
>
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
{excalidrawAPI && (
<Footer>
<CustomFooter excalidrawAPI={excalidrawAPI} />
</Footer>
)}
docs: migrating dev docs to docusaurus :) (#6073) * docs: migrating existing docs to docosaraus :) * log broken links * lint :p * fix * divide the doc into diff categories * fix * order sidebars and more * fix lint * point to installation * making docs better :) * fix * renaming git * renaming git * fix links * fix * update readme * fix * resolve duplicate url and make /docs as base url * fix * move main docs as well * making docs better * support mdx * update og * fix title * upgrade docusarus to stable version * use draculla theme * fix * make entire sidebar collapsable * live editor for footer wohoo * render excalidraw only on client to fix the prod build * migrate MainMenu to live editor too :) * lint :p * cleanup integration and use live editor and tabs * fix * Add welcome screen doc * Live Collaboration comp docs * Add collaborator example * Add example * add more * remove isCollaborating * Rewrite ref and move to sidebar * change color of links inside pre * add initial data * fix lint * Add styling * fix lint * Add example for customizing styles * fix lint * fix * fix lint * Add link to livecollabtrigger * fix * rewrite UIOptions to sidebar * move initialdata to sidebar * move render props to sidebar and rewrite renderTopRightUI and renderCustomStats * rewrite renderSidebar * update og * update url for testing * fix url * update readme * fix style * tweaks * Add highlight comp to highlight text * Add bash syntax highlight * fix * tweaks * fix * rewrite export utilities * fix restore * rewrite utils * move constants to sidebar * update readme * add copyright * fix links style * Add linkedin * tweaks * rename package to @excalidraw/excalidraw * enable algolia with dummy creds * tweaks to integration doc * tweak WelcomeScreen docs to reflect upcoming API changes * tweak components intro * tweak nomenclature * fix admonition * rename `components` sidebar item and change order of components list * uncollapse package section in sidebar * show level 4 haeadings in TOC * remove algolia * remove unused assets * capitalize C * tweak * rename components to App * rename components -> children-components in the routes * move notable used tools to intro * update MainMenu docs with `onSelect` preventDefault behavior * change sidebar label for children components * use code * tweak README & docs intro * tweak package development doc * make scrollbar gutter stable * tweak api intro * add admonition for export utils * use next * wip * wip * make excalidraw examples use current color theme & prefer system * fix welcomescreen docs * use latest temp release * fix component order * revert wip changes * use next * tweak * increase height to fix welcome screen hint * tweak editor height * update excal version * wrap Excal with forwardRef to fix refs * migrate contributing.md * fix broken links --------- Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-01 14:27:54 +00:00
<WelcomeScreen />
feat: sidebar tabs support (#6213) * feat: Sidebar tabs support [wip] * tab trigger styling tweaks * add `:hover` & `:active` states * replace `@dwelle/tunnel-rat` with `tunnel-rat` * make stuff more explicit - remove `Sidebar.Header` fallback (host apps need to render manually), and stop tunneling it (render in place) - make `docked` state explicit - stop tunneling `Sidebar.TabTriggers` (render in place) * redesign sidebar / library as per latest spec * support no label on `Sidebar.Trigger` * add Sidebar `props.onStateChange` * style fixes * make `appState.isSidebarDocked` into a soft user preference * px -> rem & refactor * remove `props.renderSidebar` * update tests * remove * refactor * rename constants * tab triggers styling fixes * factor out library-related logic from generic sidebar trigger * change `props.onClose` to `onToggle` * rename `props.value` -> `props.tab` * add displayNames * allow HTMLAttributes on applicable compos * fix example App * more styling tweaks and fixes * fix not setting `dockable` * more style fixes * fix and align sidebar header button styling * make DefaultSidebar dockable on if host apps supplies `onDock` * stop `Sidebar.Trigger` hiding label on mobile this should be only the default sidebar trigger behavior, and for that we don't need to use `device` hook as we handle in CSS * fix `dockable` prop of defaultSidebar * remove extra `typescript` dep * remove `defaultTab` prop in favor of explicit `tab` value in `<Sidebar.Trigger/>` and `toggleSidebar()`, to reduce API surface area and solve inconsistency of `appState.openSidebar.tab` not reflecting actual UI value if `defaultTab` was supported (without additional syncing logic which feels like the wrong solution). * remove `onToggle` in favor of `onStateChange` reducing API surface area * fix restore * comment no longer applies * reuse `Button` component in sidebar buttons * fix tests * split Sidebar sub-components into files * remove `props.dockable` in favor of `props.onDock` only * split tests * fix sidebar showing dock button if no `props.docked` supplied & add more tests * reorder and group sidebar tests * clarify * rename classes & dedupe css * refactor tests * update changelog * update changelog --------- Co-authored-by: barnabasmolnar <barnabas@excalidraw.com>
2023-05-04 17:33:31 +00:00
<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>
feat: new Menu Component API (#6034) * feat: new Menu Component API * allow valid children types * introduce menu group to group items * Add lang footer * use display name * displayName * define types inside * fix default menu * add json export to menu * fix * simplify expression * put open menu into own compo to optimize perf So that we don't rerun `useOutsideClickHook` (and rebind event listeners all the time) * naming tweaks * rename MenuComponents->MenuDefaultItems and export default items from Menu.Items * import Menu.scss in Menu.tsx * move menu scss to excal app * Don't filter children inside menu group * move E+ out of socials * support style prop for MenuItem and MenuGroup * Support header in menu group and add Excalidraw links header for default items in social section * rename header to title * fix padding for lang * render menu in mobile * review fixes * tweaks * Export collaborators and show in mobile menu * revert .env * lint :p * again lint * show correct actions in view mode for mobile * Whitelist Collaborators Comp * mobile styling * padding * don't show nerds when menu open in mobile * lint :( * hide shortcuts * refactor userlist to support mobile and keep a wrapper comp for excal app * use only UserList * render only on mobile for default items * remove unused hooks * Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false * fix tests * lint * inject userlist inside menu on mobile * revert userlist * move menu socials to default menu * fix collab * use meny in library * Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well * use appState.openMenu for mobile * fix tests * styling fixes and support style and class name in menu content * fix test * rename MenuDefaultItems->DefaultItems * move footer css to its own comp * rename HamburgerMenu -> MainMenu * rename menu -> dropdownMenu and update classes, onClick->onToggle * close main menu when dialog closes * by bye filtering * update docs * fix lint * update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere * spec * remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :) * [temp] remove cyclic depenedency to fix build * hack- update appstate to sync lang change * Add more specs * wip: rewrite MainMenu footer * fix margin * fix snaps * not needed as lang list no more imported * simplify custom footer rendering * Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs * fix `MainMenu.ItemCustom` * naming * use onSelect and base class for custom items * fix lint * fix snap * use custom item for lang * update docs * fix * properly use `MainMenu.ItemCustom` for `LanguageList` * add margin top to custom items * flex Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 16:34:23 +00:00
{renderMenu()}
</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>
<div className="export export-canvas">
<img src={canvasUrl} alt="" />
</div>
</div>
</ExampleSidebar>
</div>
);
}