diff --git a/src/components/App.tsx b/src/components/App.tsx index df5a82493b..73075e6e2b 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -130,7 +130,6 @@ import { import { LinearElementEditor } from "../element/linearElementEditor"; import { mutateElement, newElementWith } from "../element/mutateElement"; import { - convertToExcalidrawElements, deepCopyElement, duplicateElements, newFreeDrawElement, @@ -305,6 +304,7 @@ import { jotaiStore } from "../jotai"; import { activeConfirmDialogAtom } from "./ActiveConfirmDialog"; import { actionWrapTextInContainer } from "../actions/actionBoundText"; import BraveMeasureTextError from "./BraveMeasureTextError"; +import { convertToExcalidrawElements } from "../data/transform"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); @@ -2044,6 +2044,8 @@ class App extends React.Component { } if (sceneData.elements) { + console.log("HEYYYYYYYY", sceneData.elements); + this.scene.replaceAllElements( convertToExcalidrawElements(sceneData.elements), ); diff --git a/src/data/blob.ts b/src/data/blob.ts index e500bbf8fe..3aa8fe4093 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -13,7 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem"; import { isValidExcalidrawData, isValidLibrary } from "./json"; import { restore, restoreLibraryItems } from "./restore"; import { ImportedLibraryData } from "./types"; -import { convertToExcalidrawElements } from "../element/newElement"; +import { convertToExcalidrawElements } from "../data/transform"; const parseFileContents = async (blob: Blob | File) => { let contents: string; diff --git a/src/data/restore.ts b/src/data/restore.ts index e6cb89c816..d9662a888c 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -41,7 +41,7 @@ import { getDefaultLineHeight, measureBaseline, } from "../element/textElement"; -import { convertToExcalidrawElements } from "../element/newElement"; +import { convertToExcalidrawElements } from "../data/transform"; type RestoredAppState = Omit< AppState, diff --git a/src/data/transform.ts b/src/data/transform.ts new file mode 100644 index 0000000000..eae8108681 --- /dev/null +++ b/src/data/transform.ts @@ -0,0 +1,310 @@ +import { TEXT_ALIGN, VERTICAL_ALIGN } from "../constants"; +import { + newElement, + newLinearElement, + redrawTextBoundingBox, +} from "../element"; +import { bindLinearElement } from "../element/binding"; +import { mutateElement } from "../element/mutateElement"; +import { ElementConstructorOpts, newTextElement } from "../element/newElement"; +import { VALID_CONTAINER_TYPES } from "../element/textElement"; +import { + ExcalidrawBindableElement, + ExcalidrawElement, + ExcalidrawGenericElement, + ExcalidrawLinearElement, + ExcalidrawTextElement, + FontFamilyValues, + TextAlign, + VerticalAlign, +} from "../element/types"; +import { MarkOptional } from "../utility-types"; +import { ImportedDataState } from "./types"; + +export const ELEMENTS_SUPPORTING_PROGRAMMATIC_API = [ + "rectangle", + "ellipse", + "diamond", + "text", + "arrow", + "line", +]; + +const bindTextToContainer = ( + containerProps: + | { + type: + | Exclude + | ExcalidrawLinearElement["type"]; + } & MarkOptional, + textProps: { text: string } & MarkOptional, +) => { + let container; + if (containerProps.type === "arrow") { + container = newLinearElement({ + width: containerProps.width || 300, + height: containerProps.height || 24, + //@ts-ignore + type: containerProps.type, + //@ts-ignore, + endArrowhead: containerProps.type === "arrow" ? "arrow" : null, + //@ts-ignore + points: [ + [0, 0], + [300, 0], + ], + ...containerProps, + }); + } else { + //@ts-ignore + container = newElement({ + ...containerProps, + }); + } + const textElement: ExcalidrawTextElement = newTextElement({ + x: 0, + y: 0, + textAlign: TEXT_ALIGN.CENTER, + verticalAlign: VERTICAL_ALIGN.MIDDLE, + ...textProps, + containerId: container.id, + }); + + mutateElement(container, { + boundElements: (container.boundElements || []).concat({ + type: "text", + id: textElement.id, + }), + }); + + redrawTextBoundingBox(textElement, container); + + return [container, textElement]; +}; + +const bindLinearElementToElement = ( + linearElement: { + type: ExcalidrawLinearElement["type"]; + x: number; + y: number; + label?: { + text: string; + fontSize?: number; + fontFamily?: FontFamilyValues; + textAlign?: TextAlign; + verticalAlign?: VerticalAlign; + } & MarkOptional; + start?: { + type: Exclude< + ExcalidrawBindableElement["type"], + "image" | "selection" | "text" + >; + id?: ExcalidrawGenericElement["id"]; + } & MarkOptional; + end?: { + type: ExcalidrawGenericElement["type"]; + id?: ExcalidrawGenericElement["id"]; + } & MarkOptional; + } & Partial, + elements: ImportedDataState["elements"], +): { + linearElement: ExcalidrawLinearElement; + startBoundElement?: ExcalidrawElement; + endBoundElement?: ExcalidrawElement; +} => { + const { + start, + end, + type, + endArrowhead = linearElement.type === "arrow" ? "arrow" : null, + ...rest + } = linearElement; + + const excliadrawLinearElement = newLinearElement({ + type, + width: 200, + height: 24, + points: [ + [0, 0], + [200, 0], + ], + endArrowhead, + ...rest, + }); + + if (!elements || !elements.length) { + return { linearElement: excliadrawLinearElement }; + } + + let startBoundElement; + let endBoundElement; + + mutateElement(excliadrawLinearElement, { + startBinding: linearElement?.startBinding || null, + endBinding: linearElement.endBinding || null, + }); + + if (start) { + const width = start?.width ?? 100; + const height = start?.height ?? 100; + const existingElement = start.id + ? elements.find((ele) => ele?.id === start.id) + : undefined; + startBoundElement = newElement({ + x: start.x || excliadrawLinearElement.x - width, + y: start.y || excliadrawLinearElement.y - height / 2, + width, + height, + ...existingElement, + ...start, + }); + + bindLinearElement( + excliadrawLinearElement, + startBoundElement as ExcalidrawBindableElement, + "start", + ); + } + if (end) { + const height = end?.height ?? 100; + const existingElement = end.id + ? elements.find((ele) => ele?.id === end.id) + : undefined; + endBoundElement = newElement({ + x: end.x || excliadrawLinearElement.x + excliadrawLinearElement.width, + y: end.y || excliadrawLinearElement.y - height / 2, + width: end?.width ?? 100, + height, + ...existingElement, + ...end, + }) as ExcalidrawBindableElement; + + bindLinearElement( + excliadrawLinearElement, + endBoundElement as ExcalidrawBindableElement, + "end", + ); + } + return { + linearElement: excliadrawLinearElement, + //@ts-ignore + startBoundElement, + //@ts-ignore + endBoundElement, + }; +}; + +const excalidrawElements = (() => { + const res: ExcalidrawElement[] = []; + const elementMap = new Map(); + + const push = (ele?: ExcalidrawElement) => { + if (!ele) { + return; + } + const index = elementMap.get(ele.id); + if (index !== undefined && index >= 0) { + res[index] = ele; + } else { + res.push(ele); + const index = res.length - 1; + elementMap.set(ele.id, index); + } + }; + const clear = () => { + res.length = 0; + }; + const get = () => { + return res; + }; + return { + push, + clear, + get, + }; +})(); + +export const convertToExcalidrawElements = ( + elements: ImportedDataState["elements"], +): ExcalidrawElement[] => { + excalidrawElements.clear(); + if (!elements) { + return []; + } + elements.forEach((element) => { + if (!element) { + return; + } + if (!ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)) { + excalidrawElements.push(element as ExcalidrawElement); + + return; + } + //@ts-ignore + if (VALID_CONTAINER_TYPES.has(element.type) && element?.label?.text) { + //@ts-ignore + let [container, text] = bindTextToContainer(element, element.label); + excalidrawElements.push(container); + excalidrawElements.push(text); + + if (container.type === "arrow") { + const { linearElement, startBoundElement, endBoundElement } = + bindLinearElementToElement( + { + ...container, + //@ts-ignore + start: element?.start, + //@ts-ignore + end: element?.end, + }, + elements, + ); + container = linearElement; + excalidrawElements.push(linearElement); + excalidrawElements.push(startBoundElement); + excalidrawElements.push(endBoundElement); + } + } else { + let excalidrawElement; + if (element.type === "text") { + excalidrawElement = { + ...element, + } as ExcalidrawTextElement; + excalidrawElements.push(excalidrawElement); + } else if (element.type === "arrow" || element.type === "line") { + const { linearElement, startBoundElement, endBoundElement } = + //@ts-ignore + bindLinearElementToElement(element, elements); + excalidrawElements.push(linearElement); + excalidrawElements.push(startBoundElement); + excalidrawElements.push(endBoundElement); + //@ts-ignore + if (startBoundElement && !element.start.id) { + //@ts-ignore + excalidrawElements.push(startBoundElement); + } + //@ts-ignore + if (endBoundElement && !element.end.id) { + //@ts-ignore + excalidrawElements.push(endBoundElement); + } + } else { + excalidrawElement = { + ...element, + width: + element?.width || + (ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type) + ? 100 + : 0), + height: + element?.height || + (ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type) + ? 100 + : 0), + } as ExcalidrawGenericElement; + excalidrawElements.push(excalidrawElement); + } + } + }); + return excalidrawElements.get(); +}; diff --git a/src/data/types.ts b/src/data/types.ts index 56a2ddbc89..c4ddf35005 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -1,7 +1,8 @@ import { - Arrowhead, ExcalidrawBindableElement, ExcalidrawElement, + ExcalidrawGenericElement, + ExcalidrawLinearElement, FontFamilyValues, TextAlign, VerticalAlign, @@ -15,10 +16,8 @@ import { import type { cleanAppStateForExport } from "../appState"; import { VERSIONS } from "../constants"; import { MarkOptional } from "../utility-types"; -import { - ElementConstructorOpts, - ELEMENTS_SUPPORTING_PROGRAMMATIC_API, -} from "../element/newElement"; +import { ElementConstructorOpts } from "../element/newElement"; +import { ELEMENTS_SUPPORTING_PROGRAMMATIC_API } from "./transform"; export interface ExportedDataState { type: string; @@ -74,9 +73,12 @@ export interface ImportedDataState { | ({ type: "text"; text: string; + id?: ExcalidrawBindableElement["id"]; } & ElementConstructorOpts) | ({ - type: "arrow"; + type: ExcalidrawLinearElement["type"]; + x: number; + y: number; label?: { text: string; fontSize?: number; @@ -85,13 +87,17 @@ export interface ImportedDataState { verticalAlign?: VerticalAlign; } & MarkOptional; start?: { - type: ExcalidrawBindableElement["type"]; + type: Exclude< + ExcalidrawBindableElement["type"], + "image" | "selection" | "text" + >; + id?: ExcalidrawGenericElement["id"]; } & MarkOptional; end?: { - type: ExcalidrawBindableElement["type"]; + type: ExcalidrawGenericElement["type"]; + id?: ExcalidrawGenericElement["id"]; } & MarkOptional; - endArrowhead?: Arrowhead | null; - } & ElementConstructorOpts) + } & Partial) )[] | null; appState?: Readonly< diff --git a/src/element/newElement.ts b/src/element/newElement.ts index e1afc0b560..09b6b47e4e 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -12,7 +12,6 @@ import { ExcalidrawFreeDrawElement, FontFamilyValues, ExcalidrawTextContainer, - ExcalidrawBindableElement, } from "../element/types"; import { arrayToMap, @@ -36,8 +35,6 @@ import { wrapText, getBoundTextMaxWidth, getDefaultLineHeight, - bindTextToContainer, - VALID_CONTAINER_TYPES, } from "./textElement"; import { DEFAULT_ELEMENT_PROPS, @@ -49,17 +46,7 @@ import { } from "../constants"; import { isArrowElement } from "./typeChecks"; import { MarkOptional, Merge, Mutable } from "../utility-types"; -import { ImportedDataState } from "../data/types"; -import { bindLinearElement } from "./binding"; -export const ELEMENTS_SUPPORTING_PROGRAMMATIC_API = [ - "rectangle", - "ellipse", - "diamond", - "text", - "arrow", - "line", -]; export type ElementConstructorOpts = MarkOptional< Omit, | "width" @@ -656,169 +643,3 @@ export const duplicateElements = ( return clonedElements; }; - -export const convertToExcalidrawElements = ( - elements: ImportedDataState["elements"], -) => { - const res: ExcalidrawElement[] = []; - if (!elements) { - return []; - } - elements.forEach((element) => { - if (!element) { - return; - } - if (!ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)) { - res.push(element as ExcalidrawElement); - return; - } - let startBoundElement; - let endBoundElement; - //@ts-ignore - if (VALID_CONTAINER_TYPES.has(element.type) && element?.label?.text) { - //@ts-ignore - const elements = bindTextToContainer(element, element.label); - const [container, text] = elements; - - if (container.type === "arrow") { - //@ts-ignore - const { start, end } = element; - mutateElement(container, { - //@ts-ignore - startBinding: element?.startBinding || null, - //@ts-ignore - endBinding: element.endBinding || null, - }); - if (start) { - const width = start?.width ?? 100; - const height = start?.height ?? 100; - - startBoundElement = newElement({ - x: start.x || container.x - width, - y: start.y || container.y - height / 2, - width, - height, - ...start, - }) as ExcalidrawBindableElement; - bindLinearElement( - container as ExcalidrawLinearElement, - startBoundElement, - "start", - ); - } - if (end) { - const height = end?.height ?? 100; - - endBoundElement = newElement({ - x: end.x || container.x + container.width, - y: end.y || container.y - height / 2, - width: end?.width ?? 100, - height, - ...end, - }) as ExcalidrawBindableElement; - bindLinearElement( - container as ExcalidrawLinearElement, - endBoundElement, - "end", - ); - } - } - res.push(container); - res.push(text); - if (startBoundElement) { - res.push(startBoundElement); - } - if (endBoundElement) { - res.push(endBoundElement); - } - } else { - let excalidrawElement; - if (element.type === "text") { - excalidrawElement = { - ...element, - } as ExcalidrawTextElement; - res.push(excalidrawElement); - } else if (element.type === "arrow" || element.type === "line") { - const { - //@ts-ignore - start, - //@ts-ignore - end, - type, - //@ts-ignore - endArrowhead = element.type === "arrow" ? "arrow" : null, - ...rest - } = element; - - excalidrawElement = newLinearElement({ - type, - width: 200, - height: 24, - points: [ - [0, 0], - [200, 0], - ], - endArrowhead, - ...rest, - }); - - mutateElement(excalidrawElement, { - //@ts-ignore - startBinding: element?.startBinding || null, - //@ts-ignore - endBinding: element.endBinding || null, - }); - let startBoundElement; - let endBoundElement; - if (start) { - const width = start?.width ?? 100; - const height = start?.height ?? 100; - startBoundElement = newElement({ - x: start.x || excalidrawElement.x - width, - y: start.y || excalidrawElement.y - height / 2, - width, - height, - ...start, - }) as ExcalidrawBindableElement; - bindLinearElement(excalidrawElement, startBoundElement, "start"); - } - if (end) { - const height = end?.height ?? 100; - - endBoundElement = newElement({ - x: end.x || excalidrawElement.x + excalidrawElement.width, - y: end.y || excalidrawElement.y - height / 2, - width: end?.width ?? 100, - height, - ...end, - }) as ExcalidrawBindableElement; - bindLinearElement(excalidrawElement, endBoundElement, "end"); - } - - res.push(excalidrawElement); - if (startBoundElement) { - res.push(startBoundElement); - } - if (endBoundElement) { - res.push(endBoundElement); - } - } else { - excalidrawElement = { - ...element, - width: - element?.width || - (ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type) - ? 100 - : 0), - height: - element?.height || - (ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type) - ? 100 - : 0), - } as ExcalidrawGenericElement; - res.push(excalidrawElement); - } - } - }); - return res; -}; diff --git a/src/element/textElement.ts b/src/element/textElement.ts index dddd82aa1f..6e76c1f7d0 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -980,55 +980,3 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => { } return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY]; }; - -export const bindTextToContainer = ( - containerProps: - | { - type: - | Exclude - | ExcalidrawLinearElement["type"]; - } & MarkOptional, - textProps: { text: string } & MarkOptional, -) => { - let container; - if (containerProps.type === "arrow") { - container = newLinearElement({ - width: containerProps.width || 300, - height: containerProps.height || 24, - //@ts-ignore - type: containerProps.type, - //@ts-ignore, - endArrowhead: containerProps.type === "arrow" ? "arrow" : null, - //@ts-ignore - points: [ - [0, 0], - [300, 0], - ], - ...containerProps, - }); - } else { - //@ts-ignore - container = newElement({ - ...containerProps, - }); - } - const textElement: ExcalidrawTextElement = newTextElement({ - x: 0, - y: 0, - textAlign: TEXT_ALIGN.CENTER, - verticalAlign: VERTICAL_ALIGN.MIDDLE, - ...textProps, - containerId: container.id, - }); - - mutateElement(container, { - boundElements: (container.boundElements || []).concat({ - type: "text", - id: textElement.id, - }), - }); - - redrawTextBoundingBox(textElement, container); - - return [container, textElement]; -}; diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts index 91b817b7d0..32603afd2e 100644 --- a/src/excalidraw-app/data/index.ts +++ b/src/excalidraw-app/data/index.ts @@ -7,7 +7,7 @@ import { import { serializeAsJSON } from "../../data/json"; import { restore } from "../../data/restore"; import { ImportedDataState } from "../../data/types"; -import { convertToExcalidrawElements } from "../../element/newElement"; +import { convertToExcalidrawElements } from "../../data/transform"; import { isInvisiblySmallElement } from "../../element/sizeHelpers"; import { isInitializedImageElement } from "../../element/typeChecks"; import { ExcalidrawElement, FileId } from "../../element/types"; diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index dcd15f1cbe..31c545c934 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -87,7 +87,7 @@ import { appJotaiStore } from "./app-jotai"; import "./index.scss"; import { ResolutionType } from "../utility-types"; -import { convertToExcalidrawElements } from "../element/newElement"; +import { convertToExcalidrawElements } from "../data/transform"; polyfill(); diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx index dc82954895..20c99d8e1b 100644 --- a/src/packages/excalidraw/example/App.tsx +++ b/src/packages/excalidraw/example/App.tsx @@ -207,9 +207,9 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { { type: "arrow", - x: -160, - y: 300, - start: { type: "rectangle", width: 300, height: 300 }, + x: 300, + y: 150, + start: { type: "rectangle", id: "rect-1" }, end: { type: "ellipse" }, }, ], diff --git a/src/tests/packages/utils.unmocked.test.ts b/src/tests/packages/utils.unmocked.test.ts index a51f7744d6..3b1c1d9c0a 100644 --- a/src/tests/packages/utils.unmocked.test.ts +++ b/src/tests/packages/utils.unmocked.test.ts @@ -1,6 +1,6 @@ import { decodePngMetadata, decodeSvgMetadata } from "../../data/image"; import { ImportedDataState } from "../../data/types"; -import { convertToExcalidrawElements } from "../../element/newElement"; +import { convertToExcalidrawElements } from "../../data/transform"; import * as utils from "../../packages/utils"; import { API } from "../helpers/api";