refactor: create a util to compute container dimensions for bound text container (#5708)

This commit is contained in:
Aakansha Doshi 2022-09-19 15:30:37 +05:30 committed by GitHub
parent 3a776f8795
commit 8636ef1017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 55 deletions

View File

@ -252,6 +252,7 @@ import {
getApproxMinLineHeight, getApproxMinLineHeight,
getApproxMinLineWidth, getApproxMinLineWidth,
getBoundTextElement, getBoundTextElement,
getContainerDims,
} from "../element/textElement"; } from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision"; import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
import { import {
@ -2385,8 +2386,9 @@ class App extends React.Component<AppProps, AppState> {
}; };
const minWidth = getApproxMinLineWidth(getFontString(fontString)); const minWidth = getApproxMinLineWidth(getFontString(fontString));
const minHeight = getApproxMinLineHeight(getFontString(fontString)); const minHeight = getApproxMinLineHeight(getFontString(fontString));
const newHeight = Math.max(container.height, minHeight); const containerDims = getContainerDims(container);
const newWidth = Math.max(container.width, minWidth); const newHeight = Math.max(containerDims.height, minHeight);
const newWidth = Math.max(containerDims.width, minWidth);
mutateElement(container, { height: newHeight, width: newWidth }); mutateElement(container, { height: newHeight, width: newWidth });
sceneX = container.x + newWidth / 2; sceneX = container.x + newWidth / 2;
sceneY = container.y + newHeight / 2; sceneY = container.y + newHeight / 2;

View File

@ -21,7 +21,12 @@ import { AppState } from "../types";
import { getElementAbsoluteCoords } from "."; import { getElementAbsoluteCoords } from ".";
import { adjustXYWithRotation } from "../math"; import { adjustXYWithRotation } from "../math";
import { getResizedElementAbsoluteCoords } from "./bounds"; 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"; import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
type ElementConstructorOpts = MarkOptional< type ElementConstructorOpts = MarkOptional<
@ -164,7 +169,8 @@ const getAdjustedDimensions = (
let maxWidth = null; let maxWidth = null;
const container = getContainerElement(element); const container = getContainerElement(element);
if (container) { if (container) {
maxWidth = container.width - BOUND_TEXT_PADDING * 2; const containerDims = getContainerDims(container);
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
} }
const { const {
width: nextWidth, width: nextWidth,
@ -224,15 +230,16 @@ const getAdjustedDimensions = (
// make sure container dimensions are set properly when // make sure container dimensions are set properly when
// text editor overflows beyond viewport dimensions // text editor overflows beyond viewport dimensions
if (container) { if (container) {
let height = container.height; const containerDims = getContainerDims(container);
let width = container.width; let height = containerDims.height;
let width = containerDims.width;
if (nextHeight > height - BOUND_TEXT_PADDING * 2) { if (nextHeight > height - BOUND_TEXT_PADDING * 2) {
height = nextHeight + BOUND_TEXT_PADDING * 2; height = nextHeight + BOUND_TEXT_PADDING * 2;
} }
if (nextWidth > width - BOUND_TEXT_PADDING * 2) { if (nextWidth > width - BOUND_TEXT_PADDING * 2) {
width = nextWidth + 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 }); mutateElement(container, { height, width });
} }
} }
@ -259,7 +266,8 @@ export const updateTextElement = (
): ExcalidrawTextElement => { ): ExcalidrawTextElement => {
const container = getContainerElement(element); const container = getContainerElement(element);
if (container) { 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); const dimensions = getAdjustedDimensions(element, text);
return newElementWith(element, { return newElementWith(element, {

View File

@ -16,16 +16,16 @@ export const redrawTextBoundingBox = (
element: ExcalidrawTextElement, element: ExcalidrawTextElement,
container: ExcalidrawElement | null, container: ExcalidrawElement | null,
) => { ) => {
const maxWidth = container let maxWidth = undefined;
? container.width - BOUND_TEXT_PADDING * 2
: undefined;
let text = element.text; let text = element.text;
if (container) { if (container) {
const containerDims = getContainerDims(container);
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
text = wrapText( text = wrapText(
element.originalText, element.originalText,
getFontString(element), getFontString(element),
container.width, containerDims.width,
); );
} }
const metrics = measureText( const metrics = measureText(
@ -37,16 +37,20 @@ export const redrawTextBoundingBox = (
let coordX = element.x; let coordX = element.x;
// Resize container and vertically center align the text // Resize container and vertically center align the text
if (container) { if (container) {
let nextHeight = container.height; const containerDims = getContainerDims(container);
let nextHeight = containerDims.height;
coordX = container.x + BOUND_TEXT_PADDING; coordX = container.x + BOUND_TEXT_PADDING;
if (element.verticalAlign === VERTICAL_ALIGN.TOP) { if (element.verticalAlign === VERTICAL_ALIGN.TOP) {
coordY = container.y + BOUND_TEXT_PADDING; coordY = container.y + BOUND_TEXT_PADDING;
} else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) { } else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
coordY = coordY =
container.y + container.height - metrics.height - BOUND_TEXT_PADDING; container.y +
containerDims.height -
metrics.height -
BOUND_TEXT_PADDING;
} else { } else {
coordY = container.y + container.height / 2 - metrics.height / 2; coordY = container.y + containerDims.height / 2 - metrics.height / 2;
if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) { if (metrics.height > containerDims.height - BOUND_TEXT_PADDING * 2) {
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2; nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
coordY = container.y + nextHeight / 2 - metrics.height / 2; coordY = container.y + nextHeight / 2 - metrics.height / 2;
} }
@ -474,3 +478,7 @@ export const getContainerElement = (
} }
return null; return null;
}; };
export const getContainerDims = (element: ExcalidrawElement) => {
return { width: element.width, height: element.height };
};

View File

@ -18,6 +18,7 @@ import { mutateElement } from "./mutateElement";
import { import {
getApproxLineHeight, getApproxLineHeight,
getBoundTextElementId, getBoundTextElementId,
getContainerDims,
getContainerElement, getContainerElement,
wrapText, wrapText,
} from "./textElement"; } from "./textElement";
@ -83,17 +84,17 @@ export const textWysiwyg = ({
app: App; app: App;
}) => { }) => {
const textPropertiesUpdated = ( const textPropertiesUpdated = (
updatedElement: ExcalidrawTextElement, updatedTextElement: ExcalidrawTextElement,
editable: HTMLTextAreaElement, editable: HTMLTextAreaElement,
) => { ) => {
const currentFont = editable.style.fontFamily.replace(/"/g, ""); const currentFont = editable.style.fontFamily.replace(/"/g, "");
if ( if (
getFontFamilyString({ fontFamily: updatedElement.fontFamily }) !== getFontFamilyString({ fontFamily: updatedTextElement.fontFamily }) !==
currentFont currentFont
) { ) {
return true; return true;
} }
if (`${updatedElement.fontSize}px` !== editable.style.fontSize) { if (`${updatedTextElement.fontSize}px` !== editable.style.fontSize) {
return true; return true;
} }
return false; return false;
@ -102,46 +103,49 @@ export const textWysiwyg = ({
const updateWysiwygStyle = () => { const updateWysiwygStyle = () => {
const appState = app.state; const appState = app.state;
const updatedElement = const updatedTextElement =
Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id); Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
if (!updatedElement) { if (!updatedTextElement) {
return; return;
} }
const { textAlign, verticalAlign } = updatedElement; const { textAlign, verticalAlign } = updatedTextElement;
const approxLineHeight = getApproxLineHeight(getFontString(updatedElement)); const approxLineHeight = getApproxLineHeight(
if (updatedElement && isTextElement(updatedElement)) { getFontString(updatedTextElement),
let coordX = updatedElement.x; );
let coordY = updatedElement.y; if (updatedTextElement && isTextElement(updatedTextElement)) {
const container = getContainerElement(updatedElement); let coordX = updatedTextElement.x;
let maxWidth = updatedElement.width; let coordY = updatedTextElement.y;
const container = getContainerElement(updatedTextElement);
let maxWidth = updatedTextElement.width;
let maxHeight = updatedElement.height; let maxHeight = updatedTextElement.height;
let width = updatedElement.width; let width = updatedTextElement.width;
// Set to element height by default since that's // Set to element height by default since that's
// what is going to be used for unbounded text // what is going to be used for unbounded text
let height = updatedElement.height; let height = updatedTextElement.height;
if (container && updatedElement.containerId) { if (container && updatedTextElement.containerId) {
const propertiesUpdated = textPropertiesUpdated( const propertiesUpdated = textPropertiesUpdated(
updatedElement, updatedTextElement,
editable, editable,
); );
const containerDims = getContainerDims(container);
// using editor.style.height to get the accurate height of text editor // using editor.style.height to get the accurate height of text editor
const editorHeight = Number(editable.style.height.slice(0, -2)); const editorHeight = Number(editable.style.height.slice(0, -2));
if (editorHeight > 0) { if (editorHeight > 0) {
height = editorHeight; height = editorHeight;
} }
if (propertiesUpdated) { if (propertiesUpdated) {
originalContainerHeight = container.height; originalContainerHeight = containerDims.height;
// update height of the editor after properties updated // update height of the editor after properties updated
height = updatedElement.height; height = updatedTextElement.height;
} }
if (!originalContainerHeight) { if (!originalContainerHeight) {
originalContainerHeight = container.height; originalContainerHeight = containerDims.height;
} }
maxWidth = container.width - BOUND_TEXT_PADDING * 2; maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
maxHeight = container.height - BOUND_TEXT_PADDING * 2; maxHeight = containerDims.height - BOUND_TEXT_PADDING * 2;
width = maxWidth; width = maxWidth;
// The coordinates of text box set a distance of // The coordinates of text box set a distance of
// 5px to preserve padding // 5px to preserve padding
@ -149,27 +153,27 @@ export const textWysiwyg = ({
// autogrow container height if text exceeds // autogrow container height if text exceeds
if (height > maxHeight) { if (height > maxHeight) {
const diff = Math.min(height - maxHeight, approxLineHeight); const diff = Math.min(height - maxHeight, approxLineHeight);
mutateElement(container, { height: container.height + diff }); mutateElement(container, { height: containerDims.height + diff });
return; return;
} else if ( } else if (
// autoshrink container height until original container height // autoshrink container height until original container height
// is reached when text is removed // is reached when text is removed
container.height > originalContainerHeight && containerDims.height > originalContainerHeight &&
height < maxHeight height < maxHeight
) { ) {
const diff = Math.min(maxHeight - height, approxLineHeight); 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) // Start pushing text upward until a diff of 30px (padding)
// is reached // is reached
else { else {
// vertically center align the text // vertically center align the text
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) { 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) { if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
coordY = 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 initialSelectionStart = editable.selectionStart;
const initialSelectionEnd = editable.selectionEnd; const initialSelectionEnd = editable.selectionEnd;
const initialLength = editable.value.length; const initialLength = editable.value.length;
editable.value = updatedElement.originalText; editable.value = updatedTextElement.originalText;
// restore cursor position after value updated so it doesn't // restore cursor position after value updated so it doesn't
// go to the end of text when container auto expanded // go to the end of text when container auto expanded
@ -192,10 +196,10 @@ export const textWysiwyg = ({
editable.selectionEnd = editable.value.length - diff; editable.selectionEnd = editable.value.length - diff;
} }
const lines = updatedElement.originalText.split("\n"); const lines = updatedTextElement.originalText.split("\n");
const lineHeight = updatedElement.containerId const lineHeight = updatedTextElement.containerId
? approxLineHeight ? approxLineHeight
: updatedElement.height / lines.length; : updatedTextElement.height / lines.length;
if (!container) { if (!container) {
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value; 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 // Make sure text editor height doesn't go beyond viewport
const editorMaxHeight = const editorMaxHeight =
(appState.height - viewportY) / appState.zoom.value; (appState.height - viewportY) / appState.zoom.value;
const angle = container ? container.angle : updatedElement.angle; const angle = container ? container.angle : updatedTextElement.angle;
Object.assign(editable.style, { Object.assign(editable.style, {
font: getFontString(updatedElement), font: getFontString(updatedTextElement),
// must be defined *after* font ¯\_(ツ)_/¯ // must be defined *after* font ¯\_(ツ)_/¯
lineHeight: `${lineHeight}px`, lineHeight: `${lineHeight}px`,
width: `${width}px`, width: `${width}px`,
@ -222,8 +226,8 @@ export const textWysiwyg = ({
), ),
textAlign, textAlign,
verticalAlign, verticalAlign,
color: updatedElement.strokeColor, color: updatedTextElement.strokeColor,
opacity: updatedElement.opacity / 100, opacity: updatedTextElement.opacity / 100,
filter: "var(--theme-filter)", filter: "var(--theme-filter)",
maxWidth: `${maxWidth}px`, maxWidth: `${maxWidth}px`,
maxHeight: `${editorMaxHeight}px`, maxHeight: `${editorMaxHeight}px`,
@ -231,9 +235,9 @@ export const textWysiwyg = ({
// For some reason updating font attribute doesn't set font family // For some reason updating font attribute doesn't set font family
// hence updating font family explicitly for test environment // hence updating font family explicitly for test environment
if (isTestEnv()) { 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) { if (onChange) {
editable.oninput = () => { editable.oninput = () => {
const updatedElement = Scene.getScene(element)?.getElement( const updatedTextElement = Scene.getScene(element)?.getElement(
id, id,
) as ExcalidrawTextElement; ) as ExcalidrawTextElement;
const font = getFontString(updatedElement); const font = getFontString(updatedTextElement);
// using scrollHeight here since we need to calculate // using scrollHeight here since we need to calculate
// number of lines so cannot use editable.style.height // number of lines so cannot use editable.style.height
// as that gets updated below // as that gets updated below