@apoorvdwi/excalidraw-collab
Version:
Excalidraw as a React component
438 lines (437 loc) • 15.5 kB
TypeScript
/// <reference types="react" />
import { PointerType, ExcalidrawLinearElement, NonDeletedExcalidrawElement, NonDeleted, TextAlign, ExcalidrawElement, GroupId, ExcalidrawBindableElement, Arrowhead, ChartType, FontFamilyValues, FileId, ExcalidrawImageElement, Theme, StrokeRoundness } from "./element/types";
import { SHAPES } from "./shapes";
import Video from 'twilio-video';
import { Point as RoughPoint } from "roughjs/bin/geometry";
import { LinearElementEditor } from "./element/linearElementEditor";
import { SuggestedBinding } from "./element/binding";
import { ImportedDataState } from "./data/types";
import type App from "./components/App";
import type { ResolvablePromise, throttleRAF } from "./utils";
import { Spreadsheet } from "./charts";
import { Language } from "./i18n";
import { ClipboardData } from "./clipboard";
import { isOverScrollBars } from "./scene";
import { MaybeTransformHandleType } from "./element/transformHandles";
import Library from "./data/library";
import type { FileSystemHandle } from "./data/filesystem";
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
import { ContextMenuItems } from "./components/ContextMenu";
export declare type Point = Readonly<RoughPoint>;
export declare type Collaborator = {
pointer?: {
x: number;
y: number;
};
button?: "up" | "down";
selectedElementIds?: AppState["selectedElementIds"];
name: string;
color: string;
userState?: UserIdleState;
avatarUrl?: string;
id?: string;
};
export declare type DataURL = string & {
_brand: "DataURL";
};
export declare type BinaryFileData = {
mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number] | typeof MIME_TYPES.binary;
id: FileId;
dataURL: DataURL;
/**
* Epoch timestamp in milliseconds
*/
created: number;
/**
* Indicates when the file was last retrieved from storage to be loaded
* onto the scene. We use this flag to determine whether to delete unused
* files from storage.
*
* Epoch timestamp in milliseconds.
*/
lastRetrieved?: number;
};
export declare type BinaryFileMetadata = Omit<BinaryFileData, "dataURL">;
export declare type BinaryFiles = Record<ExcalidrawElement["id"], BinaryFileData>;
export declare type LastActiveTool = {
type: typeof SHAPES[number]["value"] | "eraser" | "hand";
customType: null;
} | {
type: "custom";
customType: string;
} | null;
export declare type AppState = {
contextMenu: {
items: ContextMenuItems;
top: number;
left: number;
} | null;
showWelcomeScreen: boolean;
isLoading: boolean;
errorMessage: string | null;
draggingElement: NonDeletedExcalidrawElement | null;
resizingElement: NonDeletedExcalidrawElement | null;
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
selectionElement: NonDeletedExcalidrawElement | null;
isBindingEnabled: boolean;
startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
suggestedBindings: SuggestedBinding[];
editingElement: NonDeletedExcalidrawElement | null;
editingLinearElement: LinearElementEditor | null;
activeTool: {
/**
* indicates a previous tool we should revert back to if we deselect the
* currently active tool. At the moment applies to `eraser` and `hand` tool.
*/
lastActiveTool: LastActiveTool;
locked: boolean;
} & ({
type: typeof SHAPES[number]["value"] | "eraser" | "hand";
customType: null;
} | {
type: "custom";
customType: string;
});
penMode: boolean;
penDetected: boolean;
exportBackground: boolean;
exportEmbedScene: boolean;
exportWithDarkMode: boolean;
exportScale: number;
currentItemStrokeColor: string;
currentItemBackgroundColor: string;
currentItemFillStyle: ExcalidrawElement["fillStyle"];
currentItemStrokeWidth: number;
currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
currentItemRoughness: number;
currentItemOpacity: number;
currentItemFontFamily: FontFamilyValues;
currentItemFontSize: number;
currentItemTextAlign: TextAlign;
currentItemStartArrowhead: Arrowhead | null;
currentItemEndArrowhead: Arrowhead | null;
currentItemRoundness: StrokeRoundness;
viewBackgroundColor: string;
scrollX: number;
scrollY: number;
cursorButton: "up" | "down";
scrolledOutside: boolean;
name: string;
isResizing: boolean;
isRotating: boolean;
zoom: Zoom;
openMenu: "canvas" | "shape" | null;
openPopup: "canvasColorPicker" | "backgroundColorPicker" | "strokeColorPicker" | null;
openSidebar: "library" | "customSidebar" | null;
openDialog: "imageExport" | "help" | "jsonExport" | null;
isSidebarDocked: boolean;
lastPointerDownWith: PointerType;
selectedElementIds: {
[id: string]: boolean;
};
previousSelectedElementIds: {
[id: string]: boolean;
};
shouldCacheIgnoreZoom: boolean;
toast: {
message: string;
closable?: boolean;
duration?: number;
} | null;
zenModeEnabled: boolean;
theme: Theme;
gridSize: number | null;
viewModeEnabled: boolean;
/** top-most selected groups (i.e. does not include nested groups) */
selectedGroupIds: {
[groupId: string]: boolean;
};
/** group being edited when you drill down to its constituent element
(e.g. when you double-click on a group's element) */
editingGroupId: GroupId | null;
width: number;
height: number;
offsetTop: number;
offsetLeft: number;
fileHandle: FileSystemHandle | null;
collaborators: Map<string, Collaborator>;
showStats: boolean;
currentChartType: ChartType;
pasteDialog: {
shown: false;
data: null;
} | {
shown: true;
data: Spreadsheet;
};
/** imageElement waiting to be placed on canvas */
pendingImageElementId: ExcalidrawImageElement["id"] | null;
showHyperlinkPopup: false | "info" | "editor";
selectedLinearElement: LinearElementEditor | null;
};
export declare type NormalizedZoomValue = number & {
_brand: "normalizedZoom";
};
export declare type Zoom = Readonly<{
value: NormalizedZoomValue;
}>;
export declare type PointerCoords = Readonly<{
x: number;
y: number;
}>;
export declare type Gesture = {
pointers: Map<number, PointerCoords>;
lastCenter: {
x: number;
y: number;
} | null;
initialDistance: number | null;
initialScale: number | null;
};
export declare class GestureEvent extends UIEvent {
readonly rotation: number;
readonly scale: number;
}
/** @deprecated legacy: do not use outside of migration paths */
export declare type LibraryItem_v1 = readonly NonDeleted<ExcalidrawElement>[];
/** @deprecated legacy: do not use outside of migration paths */
declare type LibraryItems_v1 = readonly LibraryItem_v1[];
/** v2 library item */
export declare type LibraryItem = {
id: string;
status: "published" | "unpublished";
elements: readonly NonDeleted<ExcalidrawElement>[];
/** timestamp in epoch (ms) */
created: number;
name?: string;
error?: string;
};
export declare type LibraryItems = readonly LibraryItem[];
export declare type LibraryItems_anyVersion = LibraryItems | LibraryItems_v1;
export declare type LibraryItemsSource = ((currentLibraryItems: LibraryItems) => Blob | LibraryItems_anyVersion | Promise<LibraryItems_anyVersion | Blob>) | Blob | LibraryItems_anyVersion | Promise<LibraryItems_anyVersion | Blob>;
export declare type ExcalidrawAPIRefValue = ExcalidrawImperativeAPI | {
readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
ready?: false;
};
export declare type ExcalidrawInitialDataState = Merge<ImportedDataState, {
libraryItems?: Required<ImportedDataState>["libraryItems"] | Promise<Required<ImportedDataState>["libraryItems"]>;
}>;
export interface ExcalidrawProps {
onChange?: (elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles) => void;
initialData?: ExcalidrawInitialDataState | null | Promise<ExcalidrawInitialDataState | null>;
excalidrawRef?: ForwardRef<ExcalidrawAPIRefValue>;
isCollaborating?: boolean;
onPointerUpdate?: (payload: {
pointer: {
x: number;
y: number;
};
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => void;
onPaste?: (data: ClipboardData, event: ClipboardEvent | null) => Promise<boolean> | boolean;
renderTopRightUI?: (isMobile: boolean, appState: AppState) => JSX.Element | null;
langCode?: Language["code"];
viewModeEnabled?: boolean;
zenModeEnabled?: boolean;
gridModeEnabled?: boolean;
libraryReturnUrl?: string;
theme?: Theme;
name?: string;
renderCustomStats?: (elements: readonly NonDeletedExcalidrawElement[], appState: AppState) => JSX.Element;
UIOptions?: Partial<UIOptions>;
detectScroll?: boolean;
handleKeyboardGlobally?: boolean;
onLibraryChange?: (libraryItems: LibraryItems) => void | Promise<any>;
autoFocus?: boolean;
generateIdForFile?: (file: File) => string | Promise<string>;
onLinkOpen?: (element: NonDeletedExcalidrawElement, event: CustomEvent<{
nativeEvent: MouseEvent | React.PointerEvent<HTMLCanvasElement>;
}>) => void;
onPointerDown?: (activeTool: AppState["activeTool"], pointerDownState: PointerDownState) => void;
onScrollChange?: (scrollX: number, scrollY: number) => void;
/**
* Render function that renders custom <Sidebar /> component.
*/
renderSidebar?: () => JSX.Element | null;
width?: number;
height?: number;
setPositionUpdateCb?: (cb: () => void) => void;
show?: boolean;
children?: React.ReactNode;
}
export interface ExcalidrawWrapperProps {
urlHash: string;
encryptionKey: string;
width: number;
height: number;
onChange?: () => void;
initialData?: ImportedDataState;
user?: {
name: string;
color: string;
};
firebaseConfig: {
apiKey: string;
authDomain: string;
databaseURL: string;
projectId: string;
storageBucket: string | null;
messagingSenderId: string;
appId: string;
};
room?: Video.Room;
appearance: "dark" | "light";
forwardedRef?: ForwardRef<ExcalidrawImperativeAPI>;
setPositionUpdateCb?: (cb: () => void) => void;
show?: boolean;
}
export declare type SceneData = {
elements?: ImportedDataState["elements"];
appState?: ImportedDataState["appState"];
collaborators?: Map<string, Collaborator>;
commitToHistory?: boolean;
};
export declare enum UserIdleState {
ACTIVE = "active",
AWAY = "away",
IDLE = "idle"
}
export declare type ExportOpts = {
saveFileToDisk?: boolean;
onExportToBackend?: (exportedElements: readonly NonDeletedExcalidrawElement[], appState: AppState, files: BinaryFiles, canvas: HTMLCanvasElement | null) => void;
renderCustomUI?: (exportedElements: readonly NonDeletedExcalidrawElement[], appState: AppState, files: BinaryFiles, canvas: HTMLCanvasElement | null) => JSX.Element;
};
declare type CanvasActions = Partial<{
changeViewBackgroundColor: boolean;
clearCanvas: boolean;
export: false | ExportOpts;
loadScene: boolean;
saveToActiveFile: boolean;
toggleTheme: boolean | null;
saveAsImage: boolean;
}>;
declare type UIOptions = Partial<{
dockedSidebarBreakpoint: number;
canvasActions: CanvasActions;
/** @deprecated does nothing. Will be removed in 0.15 */
welcomeScreen?: boolean;
}>;
export declare type AppProps = Merge<ExcalidrawProps, {
UIOptions: Merge<UIOptions, {
canvasActions: Required<CanvasActions> & {
export: ExportOpts;
};
}>;
detectScroll: boolean;
handleKeyboardGlobally: boolean;
isCollaborating: boolean;
width: number;
height: number;
children?: React.ReactNode;
}>;
/** A subset of App class properties that we need to use elsewhere
* in the app, eg Manager. Factored out into a separate type to keep DRY. */
export declare type AppClassProperties = {
props: AppProps;
canvas: HTMLCanvasElement | null;
focusContainer(): void;
library: Library;
imageCache: Map<FileId, {
image: HTMLImageElement | Promise<HTMLImageElement>;
mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number];
}>;
files: BinaryFiles;
device: App["device"];
scene: App["scene"];
pasteFromClipboard: App["pasteFromClipboard"];
};
export declare type PointerDownState = Readonly<{
origin: Readonly<{
x: number;
y: number;
}>;
originInGrid: Readonly<{
x: number;
y: number;
}>;
scrollbars: ReturnType<typeof isOverScrollBars>;
lastCoords: {
x: number;
y: number;
};
originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
resize: {
handleType: MaybeTransformHandleType;
isResizing: boolean;
offset: {
x: number;
y: number;
};
arrowDirection: "origin" | "end";
center: {
x: number;
y: number;
};
};
hit: {
element: NonDeleted<ExcalidrawElement> | null;
allHitElements: NonDeleted<ExcalidrawElement>[];
wasAddedToSelection: boolean;
hasBeenDuplicated: boolean;
hasHitCommonBoundingBoxOfSelectedElements: boolean;
};
withCmdOrCtrl: boolean;
drag: {
hasOccurred: boolean;
offset: {
x: number;
y: number;
} | null;
};
eventListeners: {
onMove: null | ReturnType<typeof throttleRAF>;
onUp: null | ((event: PointerEvent) => void);
onKeyDown: null | ((event: KeyboardEvent) => void);
onKeyUp: null | ((event: KeyboardEvent) => void);
};
boxSelection: {
hasOccurred: boolean;
};
elementIdsToErase: {
[key: ExcalidrawElement["id"]]: {
opacity: ExcalidrawElement["opacity"];
erase: boolean;
};
};
}>;
export declare type ExcalidrawImperativeAPI = {
updateScene: InstanceType<typeof App>["updateScene"];
updateLibrary: InstanceType<typeof Library>["updateLibrary"];
resetScene: InstanceType<typeof App>["resetScene"];
getSceneElementsIncludingDeleted: InstanceType<typeof App>["getSceneElementsIncludingDeleted"];
history: {
clear: InstanceType<typeof App>["resetHistory"];
};
scrollToContent: InstanceType<typeof App>["scrollToContent"];
getSceneElements: InstanceType<typeof App>["getSceneElements"];
getAppState: () => InstanceType<typeof App>["state"];
getFiles: () => InstanceType<typeof App>["files"];
refresh: InstanceType<typeof App>["refresh"];
setToast: InstanceType<typeof App>["setToast"];
addFiles: (data: BinaryFileData[]) => void;
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
ready: true;
id: string;
setActiveTool: InstanceType<typeof App>["setActiveTool"];
setCursor: InstanceType<typeof App>["setCursor"];
resetCursor: InstanceType<typeof App>["resetCursor"];
toggleMenu: InstanceType<typeof App>["toggleMenu"];
};
export declare type Device = Readonly<{
isSmScreen: boolean;
isMobile: boolean;
isTouchScreen: boolean;
canDeviceFitSidebar: boolean;
}>;
export {};