From 15b5295baff57282e758d1cb0051046a4a20fb85 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 2 May 2023 22:10:04 +0530 Subject: [PATCH] feat: support creating text containers programatically --- src/data/restore.ts | 15 +++++++++- src/data/types.ts | 4 ++- src/element/newElement.ts | 21 ++++++++++++-- src/element/textElement.ts | 54 +++++++++++++++++++++++++++++++----- src/element/types.ts | 6 +++- src/excalidraw-app/index.tsx | 18 +++++++++++- 6 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/data/restore.ts b/src/data/restore.ts index fcf5fa1325..c4c041f21b 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { ExcalidrawElement, ExcalidrawSelectionElement, @@ -40,6 +41,7 @@ import { getDefaultLineHeight, measureBaseline, } from "../element/textElement"; +import { updateElementChildren } from "../element/newElement"; type RestoredAppState = Omit< AppState, @@ -194,6 +196,7 @@ const restoreElement = ( lineHeight, ); element = restoreElementWithProperties(element, { + type: "text", fontSize, fontFamily, text, @@ -390,7 +393,16 @@ export const restoreElements = ( migratedElement = { ...migratedElement, id: randomId() }; } existingIds.add(migratedElement.id); - elements.push(migratedElement); + if (element.children?.length) { + const newElements = updateElementChildren(element); + if (newElements) { + elements.push(...newElements); + } else { + elements.push(migratedElement); + } + } else { + elements.push(migratedElement); + } } } return elements; @@ -540,6 +552,7 @@ export const restore = ( localElements: readonly ExcalidrawElement[] | null | undefined, elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean }, ): RestoredDataState => { + console.log(restoreElements(data?.elements, localElements, elementsConfig)); return { elements: restoreElements(data?.elements, localElements, elementsConfig), appState: restoreAppState(data?.appState, localAppState || null), diff --git a/src/data/types.ts b/src/data/types.ts index b8c9592185..fd09530855 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -1,4 +1,4 @@ -import { ExcalidrawElement } from "../element/types"; +import { ExcalidrawElement, ExcalidrawTextContainer } from "../element/types"; import { AppState, BinaryFiles, @@ -8,6 +8,8 @@ import { import type { cleanAppStateForExport } from "../appState"; import { VERSIONS } from "../constants"; +import { ElementConstructorOpts } from "../element/newElement"; + export interface ExportedDataState { type: string; version: number; diff --git a/src/element/newElement.ts b/src/element/newElement.ts index 4922a5b4ee..2c9f68e48d 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -35,6 +35,8 @@ import { wrapText, getBoundTextMaxWidth, getDefaultLineHeight, + bindTextToContainer, + isValidTextContainer, } from "./textElement"; import { DEFAULT_ELEMENT_PROPS, @@ -44,10 +46,10 @@ import { DEFAULT_VERTICAL_ALIGN, VERTICAL_ALIGN, } from "../constants"; -import { isArrowElement } from "./typeChecks"; +import { isArrowElement, isTextElement } from "./typeChecks"; import { MarkOptional, Merge, Mutable } from "../utility-types"; -type ElementConstructorOpts = MarkOptional< +export type ElementConstructorOpts = MarkOptional< Omit, | "width" | "height" @@ -140,6 +142,7 @@ const getTextElementPositionOffsets = ( height: number; }, ) => { + console.log("metrics", metrics); return { x: opts.textAlign === "center" @@ -643,3 +646,17 @@ export const duplicateElements = ( return clonedElements; }; + +export const updateElementChildren = ( + element: { + type: ExcalidrawElement["type"]; + } & MarkOptional, +) => { + const textElement = element.children!.find((child) => child.text !== null); + if (isValidTextContainer(element) && textElement) { + //@ts-ignore + const elements = bindTextToContainer(element, textElement); + return elements; + } + return null; +}; diff --git a/src/element/textElement.ts b/src/element/textElement.ts index a6d0c3271c..d7f97d42c8 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -1,6 +1,7 @@ import { getFontString, arrayToMap, isTestEnv } from "../utils"; import { ExcalidrawElement, + ExcalidrawGenericElement, ExcalidrawTextContainer, ExcalidrawTextElement, ExcalidrawTextElementWithContainer, @@ -20,7 +21,7 @@ import { } from "../constants"; import { MaybeTransformHandleType } from "./transformHandles"; import Scene from "../scene/Scene"; -import { isTextElement } from "."; +import { isTextElement, newElement, newTextElement } from "."; import { isBoundToContainer, isArrowElement } from "./typeChecks"; import { LinearElementEditor } from "./linearElementEditor"; import { AppState } from "../types"; @@ -32,7 +33,8 @@ import { resetOriginalContainerCache, updateOriginalContainerCache, } from "./textWysiwyg"; -import { ExtractSetType } from "../utility-types"; +import { ExtractSetType, MarkOptional } from "../utility-types"; +import { ElementConstructorOpts } from "./newElement"; export const normalizeText = (text: string) => { return ( @@ -83,21 +85,27 @@ export const redrawTextBoundingBox = ( boundTextUpdates.baseline = metrics.baseline; if (container) { - const containerDims = getContainerDims(container); const maxContainerHeight = getBoundTextMaxHeight( container, textElement as ExcalidrawTextElementWithContainer, ); + const maxContainerWidth = getBoundTextMaxWidth(container); - let nextHeight = containerDims.height; if (metrics.height > maxContainerHeight) { - nextHeight = computeContainerDimensionForBoundText( + const nextHeight = computeContainerDimensionForBoundText( metrics.height, container.type, ); mutateElement(container, { height: nextHeight }); updateOriginalContainerCache(container.id, nextHeight); } + if (metrics.width > maxContainerWidth) { + const nextWidth = computeContainerDimensionForBoundText( + metrics.width, + container.type, + ); + mutateElement(container, { width: nextWidth }); + } const updatedTextElement = { ...textElement, ...boundTextUpdates, @@ -864,8 +872,9 @@ const VALID_CONTAINER_TYPES = new Set([ "arrow", ]); -export const isValidTextContainer = (element: ExcalidrawElement) => - VALID_CONTAINER_TYPES.has(element.type); +export const isValidTextContainer = (element: { + type: ExcalidrawElement["type"]; +}) => VALID_CONTAINER_TYPES.has(element.type); export const computeContainerDimensionForBoundText = ( dimension: number, @@ -971,3 +980,34 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => { } return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY]; }; + +export const bindTextToContainer = ( + containerProps: { + type: ExcalidrawGenericElement["type"]; + } & MarkOptional, + textProps: { text: string } & MarkOptional, +) => { + const container = newElement({ + x: 0, + y: 0, + ...containerProps, + }); + const textElement: ExcalidrawTextElement = newTextElement({ + x: 0, + y: 0, + ...textProps, + containerId: container.id, + textAlign: TEXT_ALIGN.CENTER, + verticalAlign: VERTICAL_ALIGN.MIDDLE, + }); + mutateElement(container, { + boundElements: (container.boundElements || []).concat({ + type: "text", + id: textElement.id, + }), + }); + + redrawTextBoundingBox(textElement, container); + + return [container, textElement]; +}; diff --git a/src/element/types.ts b/src/element/types.ts index 4a4db7e8b2..2fd18f9017 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -6,7 +6,8 @@ import { THEME, VERTICAL_ALIGN, } from "../constants"; -import { MarkNonNullable, ValueOf } from "../utility-types"; +import { MarkNonNullable, MarkOptional, ValueOf } from "../utility-types"; +import { ElementConstructorOpts } from "./newElement"; export type ChartType = "bar" | "line"; export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag"; @@ -65,6 +66,9 @@ type _ExcalidrawElementBase = Readonly<{ link: string | null; locked: boolean; customData?: Record; + children?: [ + { text: string } & MarkOptional, + ]; }>; export type ExcalidrawSelectionElement = _ExcalidrawElementBase & { diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index 930143f98e..36129c9085 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -344,7 +344,23 @@ const ExcalidrawWrapper = () => { initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => { loadImages(data, /* isInitialLoad */ true); - initialStatePromiseRef.current.promise.resolve(data.scene); + initialStatePromiseRef.current.promise.resolve({ + type: "excalidraw", + version: 2, + source: "http://localhost:3000", + elements: [ + //@ts-ignore + { + type: "rectangle", + children: [{ text: "HELLO DAMMMMY" }], + }, + ], + appState: { + gridSize: null, + viewBackgroundColor: "#ffffff", + }, + files: {}, + }); }); const onHashChange = async (event: HashChangeEvent) => {