123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import {
- ExcalidrawElement,
- ExcalidrawGenericElement,
- ExcalidrawTextElement,
- ExcalidrawLinearElement,
- ExcalidrawFreeDrawElement,
- ExcalidrawImageElement,
- FileId,
- } from "../../element/types";
- import { newElement, newTextElement, newLinearElement } from "../../element";
- import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
- import { getDefaultAppState } from "../../appState";
- import { GlobalTestState, createEvent, fireEvent } from "../test-utils";
- import fs from "fs";
- import util from "util";
- import path from "path";
- import { getMimeType } from "../../data/blob";
- import { newFreeDrawElement, newImageElement } from "../../element/newElement";
- import { Point } from "../../types";
- import { getSelectedElements } from "../../scene/selection";
- import { isLinearElementType } from "../../element/typeChecks";
- const readFile = util.promisify(fs.readFile);
- const { h } = window;
- export class API {
- static setSelectedElements = (elements: ExcalidrawElement[]) => {
- h.setState({
- selectedElementIds: elements.reduce((acc, element) => {
- acc[element.id] = true;
- return acc;
- }, {} as Record<ExcalidrawElement["id"], true>),
- });
- };
- static getSelectedElements = (
- includeBoundTextElement: boolean = false,
- ): ExcalidrawElement[] => {
- return getSelectedElements(h.elements, h.state, includeBoundTextElement);
- };
- static getSelectedElement = (): ExcalidrawElement => {
- const selectedElements = API.getSelectedElements();
- if (selectedElements.length !== 1) {
- throw new Error(
- `expected 1 selected element; got ${selectedElements.length}`,
- );
- }
- return selectedElements[0];
- };
- static getStateHistory = () => {
- // @ts-ignore
- return h.history.stateHistory;
- };
- static clearSelection = () => {
- // @ts-ignore
- h.app.clearSelection(null);
- expect(API.getSelectedElements().length).toBe(0);
- };
- static createElement = <
- T extends Exclude<ExcalidrawElement["type"], "selection"> = "rectangle",
- >({
- // @ts-ignore
- type = "rectangle",
- id,
- x = 0,
- y = x,
- width = 100,
- height = width,
- isDeleted = false,
- groupIds = [],
- ...rest
- }: {
- type?: T;
- x?: number;
- y?: number;
- height?: number;
- width?: number;
- angle?: number;
- id?: string;
- isDeleted?: boolean;
- groupIds?: string[];
- // generic element props
- strokeColor?: ExcalidrawGenericElement["strokeColor"];
- backgroundColor?: ExcalidrawGenericElement["backgroundColor"];
- fillStyle?: ExcalidrawGenericElement["fillStyle"];
- strokeWidth?: ExcalidrawGenericElement["strokeWidth"];
- strokeStyle?: ExcalidrawGenericElement["strokeStyle"];
- roundness?: ExcalidrawGenericElement["roundness"];
- roughness?: ExcalidrawGenericElement["roughness"];
- opacity?: ExcalidrawGenericElement["opacity"];
- // text props
- text?: T extends "text" ? ExcalidrawTextElement["text"] : never;
- fontSize?: T extends "text" ? ExcalidrawTextElement["fontSize"] : never;
- fontFamily?: T extends "text" ? ExcalidrawTextElement["fontFamily"] : never;
- textAlign?: T extends "text" ? ExcalidrawTextElement["textAlign"] : never;
- verticalAlign?: T extends "text"
- ? ExcalidrawTextElement["verticalAlign"]
- : never;
- boundElements?: ExcalidrawGenericElement["boundElements"];
- containerId?: T extends "text"
- ? ExcalidrawTextElement["containerId"]
- : never;
- points?: T extends "arrow" | "line" ? readonly Point[] : never;
- locked?: boolean;
- fileId?: T extends "image" ? string : never;
- scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
- status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
- endBinding?: T extends "arrow"
- ? ExcalidrawLinearElement["endBinding"]
- : never;
- }): T extends "arrow" | "line"
- ? ExcalidrawLinearElement
- : T extends "freedraw"
- ? ExcalidrawFreeDrawElement
- : T extends "text"
- ? ExcalidrawTextElement
- : T extends "image"
- ? ExcalidrawImageElement
- : ExcalidrawGenericElement => {
- let element: Mutable<ExcalidrawElement> = null!;
- const appState = h?.state || getDefaultAppState();
- const base: Omit<
- ExcalidrawGenericElement,
- | "id"
- | "width"
- | "height"
- | "type"
- | "seed"
- | "version"
- | "versionNonce"
- | "isDeleted"
- | "groupIds"
- | "link"
- | "updated"
- > = {
- x,
- y,
- angle: rest.angle ?? 0,
- strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
- backgroundColor:
- rest.backgroundColor ?? appState.currentItemBackgroundColor,
- fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
- strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth,
- strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
- roundness: (
- rest.roundness === undefined
- ? appState.currentItemRoundness === "round"
- : rest.roundness
- )
- ? {
- type: isLinearElementType(type)
- ? ROUNDNESS.PROPORTIONAL_RADIUS
- : ROUNDNESS.ADAPTIVE_RADIUS,
- }
- : null,
- roughness: rest.roughness ?? appState.currentItemRoughness,
- opacity: rest.opacity ?? appState.currentItemOpacity,
- boundElements: rest.boundElements ?? null,
- locked: rest.locked ?? false,
- };
- switch (type) {
- case "rectangle":
- case "diamond":
- case "ellipse":
- element = newElement({
- type: type as "rectangle" | "diamond" | "ellipse",
- width,
- height,
- ...base,
- });
- break;
- case "text":
- element = newTextElement({
- ...base,
- text: rest.text || "test",
- fontSize: rest.fontSize ?? appState.currentItemFontSize,
- fontFamily: rest.fontFamily ?? appState.currentItemFontFamily,
- textAlign: rest.textAlign ?? appState.currentItemTextAlign,
- verticalAlign: rest.verticalAlign ?? DEFAULT_VERTICAL_ALIGN,
- containerId: rest.containerId ?? undefined,
- });
- element.width = width;
- element.height = height;
- break;
- case "freedraw":
- element = newFreeDrawElement({
- type: type as "freedraw",
- simulatePressure: true,
- ...base,
- });
- break;
- case "arrow":
- case "line":
- element = newLinearElement({
- ...base,
- width,
- height,
- type,
- startArrowhead: null,
- endArrowhead: null,
- points: rest.points ?? [],
- });
- break;
- case "image":
- element = newImageElement({
- ...base,
- width,
- height,
- type,
- fileId: (rest.fileId as string as FileId) ?? null,
- status: rest.status || "saved",
- scale: rest.scale || [1, 1],
- });
- break;
- }
- if (id) {
- element.id = id;
- }
- if (isDeleted) {
- element.isDeleted = isDeleted;
- }
- if (groupIds) {
- element.groupIds = groupIds;
- }
- return element as any;
- };
- static readFile = async <T extends "utf8" | null>(
- filepath: string,
- encoding?: T,
- ): Promise<T extends "utf8" ? string : Buffer> => {
- filepath = path.isAbsolute(filepath)
- ? filepath
- : path.resolve(path.join(__dirname, "../", filepath));
- return readFile(filepath, { encoding }) as any;
- };
- static loadFile = async (filepath: string) => {
- const { base, ext } = path.parse(filepath);
- return new File([await API.readFile(filepath, null)], base, {
- type: getMimeType(ext),
- });
- };
- static drop = async (blob: Blob) => {
- const fileDropEvent = createEvent.drop(GlobalTestState.canvas);
- const text = await new Promise<string>((resolve, reject) => {
- try {
- const reader = new FileReader();
- reader.onload = () => {
- resolve(reader.result as string);
- };
- reader.readAsText(blob);
- } catch (error: any) {
- reject(error);
- }
- });
- const files = [blob] as File[] & { item: (index: number) => File };
- files.item = (index: number) => files[index];
- Object.defineProperty(fileDropEvent, "dataTransfer", {
- value: {
- files,
- getData: (type: string) => {
- if (type === blob.type) {
- return text;
- }
- return "";
- },
- },
- });
- fireEvent(GlobalTestState.canvas, fileDropEvent);
- };
- }
|