From dd8e465304033681bf610f98d27cde833c39eba5 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 29 Dec 2021 16:49:52 +0530 Subject: [PATCH] feat: Support updating text properties by clicking on container (#4499) --- src/actions/actionProperties.tsx | 101 +++++++++++++++++++------------ src/actions/actionStyles.ts | 6 +- src/element/textElement.ts | 28 +++++++++ src/element/textWysiwyg.tsx | 17 ++---- src/scene/selection.ts | 2 +- 5 files changed, 99 insertions(+), 55 deletions(-) diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx index 70b06f1f..59200c80 100644 --- a/src/actions/actionProperties.tsx +++ b/src/actions/actionProperties.tsx @@ -57,21 +57,27 @@ import { canChangeSharpness, canHaveArrowheads, getCommonAttributeOfSelectedElements, + getSelectedElements, getTargetElements, isSomeElementSelected, } from "../scene"; import { hasStrokeColor } from "../scene/comparisons"; import Scene from "../scene/Scene"; +import { arrayToMap } from "../utils"; import { register } from "./register"; const changeProperty = ( elements: readonly ExcalidrawElement[], appState: AppState, callback: (element: ExcalidrawElement) => ExcalidrawElement, + includeBoundText = false, ) => { + const selectedElementIds = arrayToMap( + getSelectedElements(elements, appState, includeBoundText), + ); return elements.map((element) => { if ( - appState.selectedElementIds[element.id] || + selectedElementIds.get(element.id) || element.id === appState.editingElement?.id ) { return callback(element); @@ -427,21 +433,26 @@ export const actionChangeFontSize = register({ name: "changeFontSize", perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => { - if (isTextElement(el)) { - const element: ExcalidrawTextElement = newElementWith(el, { - fontSize: value, - }); - let container = null; - if (el.containerId) { - container = Scene.getScene(el)!.getElement(el.containerId); + elements: changeProperty( + elements, + appState, + (el) => { + if (isTextElement(el)) { + const element: ExcalidrawTextElement = newElementWith(el, { + fontSize: value, + }); + let container = null; + if (el.containerId) { + container = Scene.getScene(el)!.getElement(el.containerId); + } + redrawTextBoundingBox(element, container, appState); + return element; } - redrawTextBoundingBox(element, container); - return element; - } - return el; - }), + return el; + }, + true, + ), appState: { ...appState, currentItemFontSize: value, @@ -492,21 +503,26 @@ export const actionChangeFontFamily = register({ name: "changeFontFamily", perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => { - if (isTextElement(el)) { - const element: ExcalidrawTextElement = newElementWith(el, { - fontFamily: value, - }); - let container = null; - if (el.containerId) { - container = Scene.getScene(el)!.getElement(el.containerId); + elements: changeProperty( + elements, + appState, + (el) => { + if (isTextElement(el)) { + const element: ExcalidrawTextElement = newElementWith(el, { + fontFamily: value, + }); + let container = null; + if (el.containerId) { + container = Scene.getScene(el)!.getElement(el.containerId); + } + redrawTextBoundingBox(element, container, appState); + return element; } - redrawTextBoundingBox(element, container); - return element; - } - return el; - }), + return el; + }, + true, + ), appState: { ...appState, currentItemFontFamily: value, @@ -560,21 +576,26 @@ export const actionChangeTextAlign = register({ name: "changeTextAlign", perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => { - if (isTextElement(el)) { - const element: ExcalidrawTextElement = newElementWith(el, { - textAlign: value, - }); - let container = null; - if (el.containerId) { - container = Scene.getScene(el)!.getElement(el.containerId); + elements: changeProperty( + elements, + appState, + (el) => { + if (isTextElement(el)) { + const element: ExcalidrawTextElement = newElementWith(el, { + textAlign: value, + }); + let container = null; + if (el.containerId) { + container = Scene.getScene(el)!.getElement(el.containerId); + } + redrawTextBoundingBox(element, container, appState); + return element; } - redrawTextBoundingBox(element, container); - return element; - } - return el; - }), + return el; + }, + true, + ), appState: { ...appState, currentItemTextAlign: value, diff --git a/src/actions/actionStyles.ts b/src/actions/actionStyles.ts index 5b843c9f..40496cdf 100644 --- a/src/actions/actionStyles.ts +++ b/src/actions/actionStyles.ts @@ -71,7 +71,11 @@ export const actionPasteStyles = register({ element.containerId, ); } - redrawTextBoundingBox(element as ExcalidrawTextElement, container); + redrawTextBoundingBox( + element as ExcalidrawTextElement, + container, + appState, + ); } return newElement; } diff --git a/src/element/textElement.ts b/src/element/textElement.ts index 155ca201..43ccc40f 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -10,24 +10,52 @@ import { mutateElement } from "./mutateElement"; import { BOUND_TEXT_PADDING } from "../constants"; import { MaybeTransformHandleType } from "./transformHandles"; import Scene from "../scene/Scene"; +import { AppState } from "../types"; +import { isTextElement } from "."; export const redrawTextBoundingBox = ( element: ExcalidrawTextElement, container: ExcalidrawElement | null, + appState: AppState, ) => { const maxWidth = container ? container.width - BOUND_TEXT_PADDING * 2 : undefined; + let text = element.originalText; + + // Call wrapText only when updating text properties + // By clicking on the container + if (container && !isTextElement(appState.editingElement)) { + text = wrapText( + element.originalText, + getFontString(element), + container.width, + ); + } const metrics = measureText( element.originalText, getFontString(element), maxWidth, ); + let coordY = element.y; + // Resize container and vertically center align the text + if (container) { + coordY = container.y + container.height / 2 - metrics.height / 2; + let nextHeight = container.height; + if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) { + nextHeight = metrics.height + BOUND_TEXT_PADDING * 2; + coordY = container.y + nextHeight / 2 - metrics.height / 2; + } + mutateElement(container, { height: nextHeight }); + } + mutateElement(element, { width: metrics.width, height: metrics.height, baseline: metrics.baseline, + y: coordY, + text, }); }; diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 74bb24ff..276d8b51 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -102,7 +102,7 @@ export const textWysiwyg = ({ if (updatedElement && isTextElement(updatedElement)) { let coordX = updatedElement.x; let coordY = updatedElement.y; - let container = updatedElement?.containerId + const container = updatedElement?.containerId ? Scene.getScene(updatedElement)!.getElement(updatedElement.containerId) : null; let maxWidth = updatedElement.width; @@ -123,21 +123,12 @@ export const textWysiwyg = ({ height = editorHeight; } if (propertiesUpdated) { - const currentContainer = Scene.getScene(updatedElement)?.getElement( - updatedElement.containerId, - ) as ExcalidrawBindableElement; approxLineHeight = isTextElement(updatedElement) ? getApproxLineHeight(getFontString(updatedElement)) : 0; - if ( - updatedElement.height > - currentContainer.height - BOUND_TEXT_PADDING * 2 - ) { - const nextHeight = updatedElement.height + BOUND_TEXT_PADDING * 2; - originalContainerHeight = nextHeight; - mutateElement(container, { height: nextHeight }); - container = { ...container, height: nextHeight }; - } + + originalContainerHeight = container.height; + // update height of the editor after properties updated height = updatedElement.height; } diff --git a/src/scene/selection.ts b/src/scene/selection.ts index da8acc77..a5abf7e4 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -77,4 +77,4 @@ export const getTargetElements = ( ) => appState.editingElement ? [appState.editingElement] - : getSelectedElements(elements, appState); + : getSelectedElements(elements, appState, true);