From 8636ef1017fc43995b4d371ce37496511f8c8b59 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 19 Sep 2022 15:30:37 +0530 Subject: [PATCH] refactor: create a util to compute container dimensions for bound text container (#5708) --- src/components/App.tsx | 6 ++- src/element/newElement.ts | 20 ++++++--- src/element/textElement.ts | 24 +++++++---- src/element/textWysiwyg.tsx | 82 +++++++++++++++++++------------------ 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 06115155..d3149f60 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -252,6 +252,7 @@ import { getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, + getContainerDims, } from "../element/textElement"; import { isHittingElementNotConsideringBoundingBox } from "../element/collision"; import { @@ -2385,8 +2386,9 @@ class App extends React.Component { }; const minWidth = getApproxMinLineWidth(getFontString(fontString)); const minHeight = getApproxMinLineHeight(getFontString(fontString)); - const newHeight = Math.max(container.height, minHeight); - const newWidth = Math.max(container.width, minWidth); + const containerDims = getContainerDims(container); + const newHeight = Math.max(containerDims.height, minHeight); + const newWidth = Math.max(containerDims.width, minWidth); mutateElement(container, { height: newHeight, width: newWidth }); sceneX = container.x + newWidth / 2; sceneY = container.y + newHeight / 2; diff --git a/src/element/newElement.ts b/src/element/newElement.ts index ad5161b7..ee8fe3bf 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -21,7 +21,12 @@ import { AppState } from "../types"; import { getElementAbsoluteCoords } from "."; import { adjustXYWithRotation } from "../math"; import { getResizedElementAbsoluteCoords } from "./bounds"; -import { getContainerElement, measureText, wrapText } from "./textElement"; +import { + getContainerDims, + getContainerElement, + measureText, + wrapText, +} from "./textElement"; import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants"; type ElementConstructorOpts = MarkOptional< @@ -164,7 +169,8 @@ const getAdjustedDimensions = ( let maxWidth = null; const container = getContainerElement(element); if (container) { - maxWidth = container.width - BOUND_TEXT_PADDING * 2; + const containerDims = getContainerDims(container); + maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2; } const { width: nextWidth, @@ -224,15 +230,16 @@ const getAdjustedDimensions = ( // make sure container dimensions are set properly when // text editor overflows beyond viewport dimensions if (container) { - let height = container.height; - let width = container.width; + const containerDims = getContainerDims(container); + let height = containerDims.height; + let width = containerDims.width; if (nextHeight > height - BOUND_TEXT_PADDING * 2) { height = nextHeight + BOUND_TEXT_PADDING * 2; } if (nextWidth > width - BOUND_TEXT_PADDING * 2) { width = nextWidth + BOUND_TEXT_PADDING * 2; } - if (height !== container.height || width !== container.width) { + if (height !== containerDims.height || width !== containerDims.width) { mutateElement(container, { height, width }); } } @@ -259,7 +266,8 @@ export const updateTextElement = ( ): ExcalidrawTextElement => { const container = getContainerElement(element); if (container) { - text = wrapText(text, getFontString(element), container.width); + const containerDims = getContainerDims(container); + text = wrapText(text, getFontString(element), containerDims.width); } const dimensions = getAdjustedDimensions(element, text); return newElementWith(element, { diff --git a/src/element/textElement.ts b/src/element/textElement.ts index f1969a89..49112f0e 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -16,16 +16,16 @@ export const redrawTextBoundingBox = ( element: ExcalidrawTextElement, container: ExcalidrawElement | null, ) => { - const maxWidth = container - ? container.width - BOUND_TEXT_PADDING * 2 - : undefined; + let maxWidth = undefined; let text = element.text; if (container) { + const containerDims = getContainerDims(container); + maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2; text = wrapText( element.originalText, getFontString(element), - container.width, + containerDims.width, ); } const metrics = measureText( @@ -37,16 +37,20 @@ export const redrawTextBoundingBox = ( let coordX = element.x; // Resize container and vertically center align the text if (container) { - let nextHeight = container.height; + const containerDims = getContainerDims(container); + let nextHeight = containerDims.height; coordX = container.x + BOUND_TEXT_PADDING; if (element.verticalAlign === VERTICAL_ALIGN.TOP) { coordY = container.y + BOUND_TEXT_PADDING; } else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) { coordY = - container.y + container.height - metrics.height - BOUND_TEXT_PADDING; + container.y + + containerDims.height - + metrics.height - + BOUND_TEXT_PADDING; } else { - coordY = container.y + container.height / 2 - metrics.height / 2; - if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) { + coordY = container.y + containerDims.height / 2 - metrics.height / 2; + if (metrics.height > containerDims.height - BOUND_TEXT_PADDING * 2) { nextHeight = metrics.height + BOUND_TEXT_PADDING * 2; coordY = container.y + nextHeight / 2 - metrics.height / 2; } @@ -474,3 +478,7 @@ export const getContainerElement = ( } return null; }; + +export const getContainerDims = (element: ExcalidrawElement) => { + return { width: element.width, height: element.height }; +}; diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 1bd940de..a61e0b0c 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -18,6 +18,7 @@ import { mutateElement } from "./mutateElement"; import { getApproxLineHeight, getBoundTextElementId, + getContainerDims, getContainerElement, wrapText, } from "./textElement"; @@ -83,17 +84,17 @@ export const textWysiwyg = ({ app: App; }) => { const textPropertiesUpdated = ( - updatedElement: ExcalidrawTextElement, + updatedTextElement: ExcalidrawTextElement, editable: HTMLTextAreaElement, ) => { const currentFont = editable.style.fontFamily.replace(/"/g, ""); if ( - getFontFamilyString({ fontFamily: updatedElement.fontFamily }) !== + getFontFamilyString({ fontFamily: updatedTextElement.fontFamily }) !== currentFont ) { return true; } - if (`${updatedElement.fontSize}px` !== editable.style.fontSize) { + if (`${updatedTextElement.fontSize}px` !== editable.style.fontSize) { return true; } return false; @@ -102,46 +103,49 @@ export const textWysiwyg = ({ const updateWysiwygStyle = () => { const appState = app.state; - const updatedElement = + const updatedTextElement = Scene.getScene(element)?.getElement(id); - if (!updatedElement) { + if (!updatedTextElement) { return; } - const { textAlign, verticalAlign } = updatedElement; + const { textAlign, verticalAlign } = updatedTextElement; - const approxLineHeight = getApproxLineHeight(getFontString(updatedElement)); - if (updatedElement && isTextElement(updatedElement)) { - let coordX = updatedElement.x; - let coordY = updatedElement.y; - const container = getContainerElement(updatedElement); - let maxWidth = updatedElement.width; + const approxLineHeight = getApproxLineHeight( + getFontString(updatedTextElement), + ); + if (updatedTextElement && isTextElement(updatedTextElement)) { + let coordX = updatedTextElement.x; + let coordY = updatedTextElement.y; + const container = getContainerElement(updatedTextElement); + let maxWidth = updatedTextElement.width; - let maxHeight = updatedElement.height; - let width = updatedElement.width; + let maxHeight = updatedTextElement.height; + let width = updatedTextElement.width; // Set to element height by default since that's // what is going to be used for unbounded text - let height = updatedElement.height; - if (container && updatedElement.containerId) { + let height = updatedTextElement.height; + if (container && updatedTextElement.containerId) { const propertiesUpdated = textPropertiesUpdated( - updatedElement, + updatedTextElement, editable, ); + const containerDims = getContainerDims(container); // using editor.style.height to get the accurate height of text editor const editorHeight = Number(editable.style.height.slice(0, -2)); if (editorHeight > 0) { height = editorHeight; } if (propertiesUpdated) { - originalContainerHeight = container.height; + originalContainerHeight = containerDims.height; // update height of the editor after properties updated - height = updatedElement.height; + height = updatedTextElement.height; } if (!originalContainerHeight) { - originalContainerHeight = container.height; + originalContainerHeight = containerDims.height; } - maxWidth = container.width - BOUND_TEXT_PADDING * 2; - maxHeight = container.height - BOUND_TEXT_PADDING * 2; + maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2; + maxHeight = containerDims.height - BOUND_TEXT_PADDING * 2; width = maxWidth; // The coordinates of text box set a distance of // 5px to preserve padding @@ -149,27 +153,27 @@ export const textWysiwyg = ({ // autogrow container height if text exceeds if (height > maxHeight) { const diff = Math.min(height - maxHeight, approxLineHeight); - mutateElement(container, { height: container.height + diff }); + mutateElement(container, { height: containerDims.height + diff }); return; } else if ( // autoshrink container height until original container height // is reached when text is removed - container.height > originalContainerHeight && + containerDims.height > originalContainerHeight && height < maxHeight ) { const diff = Math.min(maxHeight - height, approxLineHeight); - mutateElement(container, { height: container.height - diff }); + mutateElement(container, { height: containerDims.height - diff }); } // Start pushing text upward until a diff of 30px (padding) // is reached else { // vertically center align the text if (verticalAlign === VERTICAL_ALIGN.MIDDLE) { - coordY = container.y + container.height / 2 - height / 2; + coordY = container.y + containerDims.height / 2 - height / 2; } if (verticalAlign === VERTICAL_ALIGN.BOTTOM) { coordY = - container.y + container.height - height - BOUND_TEXT_PADDING; + container.y + containerDims.height - height - BOUND_TEXT_PADDING; } } } @@ -177,7 +181,7 @@ export const textWysiwyg = ({ const initialSelectionStart = editable.selectionStart; const initialSelectionEnd = editable.selectionEnd; const initialLength = editable.value.length; - editable.value = updatedElement.originalText; + editable.value = updatedTextElement.originalText; // restore cursor position after value updated so it doesn't // go to the end of text when container auto expanded @@ -192,10 +196,10 @@ export const textWysiwyg = ({ editable.selectionEnd = editable.value.length - diff; } - const lines = updatedElement.originalText.split("\n"); - const lineHeight = updatedElement.containerId + const lines = updatedTextElement.originalText.split("\n"); + const lineHeight = updatedTextElement.containerId ? approxLineHeight - : updatedElement.height / lines.length; + : updatedTextElement.height / lines.length; if (!container) { maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value; } @@ -203,9 +207,9 @@ export const textWysiwyg = ({ // Make sure text editor height doesn't go beyond viewport const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value; - const angle = container ? container.angle : updatedElement.angle; + const angle = container ? container.angle : updatedTextElement.angle; Object.assign(editable.style, { - font: getFontString(updatedElement), + font: getFontString(updatedTextElement), // must be defined *after* font ¯\_(ツ)_/¯ lineHeight: `${lineHeight}px`, width: `${width}px`, @@ -222,8 +226,8 @@ export const textWysiwyg = ({ ), textAlign, verticalAlign, - color: updatedElement.strokeColor, - opacity: updatedElement.opacity / 100, + color: updatedTextElement.strokeColor, + opacity: updatedTextElement.opacity / 100, filter: "var(--theme-filter)", maxWidth: `${maxWidth}px`, maxHeight: `${editorMaxHeight}px`, @@ -231,9 +235,9 @@ export const textWysiwyg = ({ // For some reason updating font attribute doesn't set font family // hence updating font family explicitly for test environment if (isTestEnv()) { - editable.style.fontFamily = getFontFamilyString(updatedElement); + editable.style.fontFamily = getFontFamilyString(updatedTextElement); } - mutateElement(updatedElement, { x: coordX, y: coordY }); + mutateElement(updatedTextElement, { x: coordX, y: coordY }); } }; @@ -276,10 +280,10 @@ export const textWysiwyg = ({ if (onChange) { editable.oninput = () => { - const updatedElement = Scene.getScene(element)?.getElement( + const updatedTextElement = Scene.getScene(element)?.getElement( id, ) as ExcalidrawTextElement; - const font = getFontString(updatedElement); + const font = getFontString(updatedTextElement); // using scrollHeight here since we need to calculate // number of lines so cannot use editable.style.height // as that gets updated below