From 1815cf3213b1d19cb6aa1a54dabd969196a65a28 Mon Sep 17 00:00:00 2001 From: Nainterceptor Date: Tue, 25 Apr 2023 13:21:25 +0200 Subject: [PATCH 1/5] build: Add version tags to Docker build (#6508) ci: Add version tags --- .github/workflows/publish-docker.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index a4a8a4c5f..3602bb660 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -12,14 +12,24 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + excalidraw/excalidraw + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . push: true - tags: excalidraw/excalidraw:latest + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From 1e9943323ad5f34bb4efc48c5b5a9b01387dbc36 Mon Sep 17 00:00:00 2001 From: suwalkanishka Date: Tue, 25 Apr 2023 17:35:19 +0545 Subject: [PATCH 2/5] style: fix font family inconsistencies (#6501) style: font fix for four components The browser default font was showing up in various locations. Fixed them to show the desired ui font. --- src/components/ColorPicker.scss | 1 + src/components/ContextMenu.scss | 1 + src/components/Tooltip.scss | 3 +++ src/css/styles.scss | 2 ++ 4 files changed, 7 insertions(+) diff --git a/src/components/ColorPicker.scss b/src/components/ColorPicker.scss index 52ea20a19..b816b2553 100644 --- a/src/components/ColorPicker.scss +++ b/src/components/ColorPicker.scss @@ -183,6 +183,7 @@ width: 100%; margin: 0; font-size: 0.875rem; + font-family: inherit; background-color: transparent; color: var(--text-primary-color); border: 0; diff --git a/src/components/ContextMenu.scss b/src/components/ContextMenu.scss index 579763119..81ced3880 100644 --- a/src/components/ContextMenu.scss +++ b/src/components/ContextMenu.scss @@ -30,6 +30,7 @@ background-color: transparent; border: none; white-space: nowrap; + font-family: inherit; display: grid; grid-template-columns: 1fr 0.2fr; diff --git a/src/components/Tooltip.scss b/src/components/Tooltip.scss index bb2b2f72e..490e25578 100644 --- a/src/components/Tooltip.scss +++ b/src/components/Tooltip.scss @@ -2,6 +2,9 @@ // container in body where the actual tooltip is appended to .excalidraw-tooltip { + --ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI, + Roboto, Helvetica, Arial, sans-serif; + font-family: var(--ui-font); position: fixed; z-index: 1000; diff --git a/src/css/styles.scss b/src/css/styles.scss index 8dafbfbdf..29e52011e 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -354,6 +354,7 @@ border-radius: var(--space-factor); border: 1px solid var(--button-gray-2); font-size: 0.8rem; + font-family: inherit; outline: none; appearance: none; background-image: var(--dropdown-icon); @@ -413,6 +414,7 @@ bottom: 30px; transform: translateX(-50%); pointer-events: all; + font-family: inherit; &:hover { background-color: var(--button-hover-bg); From dae81c0a2cb8c21381830635d5bb992eb30b6f36 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 25 Apr 2023 17:57:53 +0530 Subject: [PATCH 3/5] fix: cleanup redrawTextBoundingBox (#6518) * chore: cleanup redrawTextBoundingBox * fix --- src/element/textElement.ts | 44 +++++++++++++++----------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/element/textElement.ts b/src/element/textElement.ts index f01ba3e1b..339f68b96 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -83,35 +83,25 @@ export const redrawTextBoundingBox = ( boundTextUpdates.baseline = metrics.baseline; if (container) { - if (isArrowElement(container)) { - const centerX = textElement.x + textElement.width / 2; - const centerY = textElement.y + textElement.height / 2; - const diffWidth = metrics.width - textElement.width; - const diffHeight = metrics.height - textElement.height; - boundTextUpdates.x = centerY - (textElement.height + diffHeight) / 2; - boundTextUpdates.y = centerX - (textElement.width + diffWidth) / 2; - } else { - const containerDims = getContainerDims(container); - let maxContainerHeight = getMaxContainerHeight(container); + const containerDims = getContainerDims(container); + const maxContainerHeight = getMaxContainerHeight(container); - let nextHeight = containerDims.height; - if (metrics.height > maxContainerHeight) { - nextHeight = computeContainerDimensionForBoundText( - metrics.height, - container.type, - ); - mutateElement(container, { height: nextHeight }); - maxContainerHeight = getMaxContainerHeight(container); - updateOriginalContainerCache(container.id, nextHeight); - } - const updatedTextElement = { - ...textElement, - ...boundTextUpdates, - } as ExcalidrawTextElementWithContainer; - const { x, y } = computeBoundTextPosition(container, updatedTextElement); - boundTextUpdates.x = x; - boundTextUpdates.y = y; + let nextHeight = containerDims.height; + if (metrics.height > maxContainerHeight) { + nextHeight = computeContainerDimensionForBoundText( + metrics.height, + container.type, + ); + mutateElement(container, { height: nextHeight }); + updateOriginalContainerCache(container.id, nextHeight); } + const updatedTextElement = { + ...textElement, + ...boundTextUpdates, + } as ExcalidrawTextElementWithContainer; + const { x, y } = computeBoundTextPosition(container, updatedTextElement); + boundTextUpdates.x = x; + boundTextUpdates.y = y; } mutateElement(textElement, boundTextUpdates); From da8dd389a9dd7e6528fbd6bf85b12e8ec052f325 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 25 Apr 2023 18:06:23 +0530 Subject: [PATCH 4/5] fix: cleanup getMaxContainerHeight and getMaxContainerWidth (#6519) * fix: cleanup getMaxContainerHeight and getMaxContainerWidth * rename getMaxContainerWidth -> getBoundTextMaxMaxWidth and getMaxContainerHeight -> getBoundTextMaxHeight * add specs --- src/element/newElement.ts | 4 +- src/element/resizeElements.ts | 14 +++--- src/element/textElement.test.ts | 59 +++++++++++++++++++++----- src/element/textElement.ts | 41 +++++++++--------- src/element/textWysiwyg.tsx | 15 ++++--- src/renderer/renderElement.ts | 11 +++-- src/tests/linearElementEditor.test.tsx | 8 ++-- 7 files changed, 96 insertions(+), 56 deletions(-) diff --git a/src/element/newElement.ts b/src/element/newElement.ts index c1b0f17bf..4922a5b4e 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -33,7 +33,7 @@ import { measureText, normalizeText, wrapText, - getMaxContainerWidth, + getBoundTextMaxWidth, getDefaultLineHeight, } from "./textElement"; import { @@ -310,7 +310,7 @@ export const refreshTextDimensions = ( text = wrapText( text, getFontString(textElement), - getMaxContainerWidth(container), + getBoundTextMaxWidth(container), ); } const dimensions = getAdjustedDimensions(textElement, text); diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 69b8afae7..67a6346be 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -44,10 +44,10 @@ import { getBoundTextElementId, getContainerElement, handleBindTextResize, - getMaxContainerWidth, + getBoundTextMaxWidth, getApproxMinLineHeight, measureText, - getMaxContainerHeight, + getBoundTextMaxHeight, } from "./textElement"; export const normalizeAngle = (angle: number): number => { @@ -204,7 +204,7 @@ const measureFontSizeFromWidth = ( if (hasContainer) { const container = getContainerElement(element); if (container) { - width = getMaxContainerWidth(container); + width = getBoundTextMaxWidth(container); } } const nextFontSize = element.fontSize * (nextWidth / width); @@ -435,8 +435,8 @@ export const resizeSingleElement = ( const nextFont = measureFontSizeFromWidth( boundTextElement, - getMaxContainerWidth(updatedElement), - getMaxContainerHeight(updatedElement), + getBoundTextMaxWidth(updatedElement), + getBoundTextMaxHeight(updatedElement, boundTextElement), ); if (nextFont === null) { return; @@ -718,10 +718,10 @@ const resizeMultipleElements = ( const metrics = measureFontSizeFromWidth( boundTextElement ?? (element.orig as ExcalidrawTextElement), boundTextElement - ? getMaxContainerWidth(updatedElement) + ? getBoundTextMaxWidth(updatedElement) : updatedElement.width, boundTextElement - ? getMaxContainerHeight(updatedElement) + ? getBoundTextMaxHeight(updatedElement, boundTextElement) : updatedElement.height, ); diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index f83eafd1b..b6221336d 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -3,15 +3,15 @@ import { API } from "../tests/helpers/api"; import { computeContainerDimensionForBoundText, getContainerCoords, - getMaxContainerWidth, - getMaxContainerHeight, + getBoundTextMaxWidth, + getBoundTextMaxHeight, wrapText, detectLineHeight, getLineHeightInPx, getDefaultLineHeight, parseTokens, } from "./textElement"; -import { FontString } from "./types"; +import { ExcalidrawTextElementWithContainer, FontString } from "./types"; describe("Test wrapText", () => { const font = "20px Cascadia, width: Segoe UI Emoji" as FontString; @@ -311,7 +311,7 @@ describe("Test measureText", () => { }); }); - describe("Test getMaxContainerWidth", () => { + describe("Test getBoundTextMaxWidth", () => { const params = { width: 178, height: 194, @@ -319,39 +319,76 @@ describe("Test measureText", () => { it("should return max width when container is rectangle", () => { const container = API.createElement({ type: "rectangle", ...params }); - expect(getMaxContainerWidth(container)).toBe(168); + expect(getBoundTextMaxWidth(container)).toBe(168); }); it("should return max width when container is ellipse", () => { const container = API.createElement({ type: "ellipse", ...params }); - expect(getMaxContainerWidth(container)).toBe(116); + expect(getBoundTextMaxWidth(container)).toBe(116); }); it("should return max width when container is diamond", () => { const container = API.createElement({ type: "diamond", ...params }); - expect(getMaxContainerWidth(container)).toBe(79); + expect(getBoundTextMaxWidth(container)).toBe(79); }); }); - describe("Test getMaxContainerHeight", () => { + describe("Test getBoundTextMaxHeight", () => { const params = { width: 178, height: 194, + id: '"container-id', }; + const boundTextElement = API.createElement({ + type: "text", + id: "text-id", + x: 560.51171875, + y: 202.033203125, + width: 154, + height: 175, + fontSize: 20, + fontFamily: 1, + text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams", + textAlign: "center", + verticalAlign: "middle", + containerId: params.id, + }) as ExcalidrawTextElementWithContainer; + it("should return max height when container is rectangle", () => { const container = API.createElement({ type: "rectangle", ...params }); - expect(getMaxContainerHeight(container)).toBe(184); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(184); }); it("should return max height when container is ellipse", () => { const container = API.createElement({ type: "ellipse", ...params }); - expect(getMaxContainerHeight(container)).toBe(127); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(127); }); it("should return max height when container is diamond", () => { const container = API.createElement({ type: "diamond", ...params }); - expect(getMaxContainerHeight(container)).toBe(87); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(87); + }); + + it("should return max height when container is arrow", () => { + const container = API.createElement({ + type: "arrow", + ...params, + }); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(194); + }); + + it("should return max height when container is arrow and height is less than threshold", () => { + const container = API.createElement({ + type: "arrow", + ...params, + height: 70, + boundElements: [{ type: "text", id: "text-id" }], + }); + + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe( + boundTextElement.height, + ); }); }); }); diff --git a/src/element/textElement.ts b/src/element/textElement.ts index 339f68b96..a6d0c3271 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -65,7 +65,7 @@ export const redrawTextBoundingBox = ( boundTextUpdates.text = textElement.text; if (container) { - maxWidth = getMaxContainerWidth(container); + maxWidth = getBoundTextMaxWidth(container); boundTextUpdates.text = wrapText( textElement.originalText, getFontString(textElement), @@ -84,7 +84,10 @@ export const redrawTextBoundingBox = ( if (container) { const containerDims = getContainerDims(container); - const maxContainerHeight = getMaxContainerHeight(container); + const maxContainerHeight = getBoundTextMaxHeight( + container, + textElement as ExcalidrawTextElementWithContainer, + ); let nextHeight = containerDims.height; if (metrics.height > maxContainerHeight) { @@ -173,8 +176,11 @@ export const handleBindTextResize = ( let nextHeight = textElement.height; let nextWidth = textElement.width; const containerDims = getContainerDims(container); - const maxWidth = getMaxContainerWidth(container); - const maxHeight = getMaxContainerHeight(container); + const maxWidth = getBoundTextMaxWidth(container); + const maxHeight = getBoundTextMaxHeight( + container, + textElement as ExcalidrawTextElementWithContainer, + ); let containerHeight = containerDims.height; let nextBaseLine = textElement.baseline; if (transformHandleType !== "n" && transformHandleType !== "s") { @@ -246,8 +252,8 @@ export const computeBoundTextPosition = ( ); } const containerCoords = getContainerCoords(container); - const maxContainerHeight = getMaxContainerHeight(container); - const maxContainerWidth = getMaxContainerWidth(container); + const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement); + const maxContainerWidth = getBoundTextMaxWidth(container); let x; let y; @@ -880,18 +886,10 @@ export const computeContainerDimensionForBoundText = ( return dimension + padding; }; -export const getMaxContainerWidth = (container: ExcalidrawElement) => { +export const getBoundTextMaxWidth = (container: ExcalidrawElement) => { const width = getContainerDims(container).width; if (isArrowElement(container)) { - const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2; - if (containerWidth <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.width; - } - return BOUND_TEXT_PADDING * 8 * 2; - } - return containerWidth; + return width - BOUND_TEXT_PADDING * 8 * 2; } if (container.type === "ellipse") { @@ -908,16 +906,15 @@ export const getMaxContainerWidth = (container: ExcalidrawElement) => { return width - BOUND_TEXT_PADDING * 2; }; -export const getMaxContainerHeight = (container: ExcalidrawElement) => { +export const getBoundTextMaxHeight = ( + container: ExcalidrawElement, + boundTextElement: ExcalidrawTextElementWithContainer, +) => { const height = getContainerDims(container).height; if (isArrowElement(container)) { const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2; if (containerHeight <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.height; - } - return BOUND_TEXT_PADDING * 8 * 2; + return boundTextElement.height; } return height; } diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index ef4f7c926..63bc9e4a4 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -32,8 +32,8 @@ import { normalizeText, redrawTextBoundingBox, wrapText, - getMaxContainerHeight, - getMaxContainerWidth, + getBoundTextMaxHeight, + getBoundTextMaxWidth, computeContainerDimensionForBoundText, detectLineHeight, } from "./textElement"; @@ -205,8 +205,11 @@ export const textWysiwyg = ({ } } - maxWidth = getMaxContainerWidth(container); - maxHeight = getMaxContainerHeight(container); + maxWidth = getBoundTextMaxWidth(container); + maxHeight = getBoundTextMaxHeight( + container, + updatedTextElement as ExcalidrawTextElementWithContainer, + ); // autogrow container height if text exceeds if (!isArrowElement(container) && textElementHeight > maxHeight) { @@ -377,7 +380,7 @@ export const textWysiwyg = ({ const wrappedText = wrapText( `${editable.value}${data}`, font, - getMaxContainerWidth(container), + getBoundTextMaxWidth(container), ); const width = getTextWidth(wrappedText, font); editable.style.width = `${width}px`; @@ -394,7 +397,7 @@ export const textWysiwyg = ({ const wrappedText = wrapText( normalizeText(editable.value), font, - getMaxContainerWidth(container!), + getBoundTextMaxWidth(container!), ); const { width, height } = measureText( wrappedText, diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index f85c83a6b..77ea14587 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -44,8 +44,8 @@ import { getContainerCoords, getContainerElement, getLineHeightInPx, - getMaxContainerHeight, - getMaxContainerWidth, + getBoundTextMaxHeight, + getBoundTextMaxWidth, } from "../element/textElement"; import { LinearElementEditor } from "../element/linearElementEditor"; @@ -868,14 +868,17 @@ const drawElementFromCanvas = ( "true" && hasBoundTextElement(element) ) { + const textElement = getBoundTextElement( + element, + ) as ExcalidrawTextElementWithContainer; const coords = getContainerCoords(element); context.strokeStyle = "#c92a2a"; context.lineWidth = 3; context.strokeRect( (coords.x + renderConfig.scrollX) * window.devicePixelRatio, (coords.y + renderConfig.scrollY) * window.devicePixelRatio, - getMaxContainerWidth(element) * window.devicePixelRatio, - getMaxContainerHeight(element) * window.devicePixelRatio, + getBoundTextMaxWidth(element) * window.devicePixelRatio, + getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio, ); } } diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index 15fd105ec..c71283a4c 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -20,7 +20,7 @@ import { resize, rotate } from "./utils"; import { getBoundTextElementPosition, wrapText, - getMaxContainerWidth, + getBoundTextMaxWidth, } from "../element/textElement"; import * as textElementUtils from "../element/textElement"; import { ROUNDNESS, VERTICAL_ALIGN } from "../constants"; @@ -729,7 +729,7 @@ describe("Test Linear Elements", () => { type: "text", x: 0, y: 0, - text: wrapText(text, font, getMaxContainerWidth(container)), + text: wrapText(text, font, getBoundTextMaxWidth(container)), containerId: container.id, width: 30, height: 20, @@ -1149,7 +1149,7 @@ describe("Test Linear Elements", () => { expect(rect.x).toBe(400); expect(rect.y).toBe(0); expect( - wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)), + wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made easy" @@ -1172,7 +1172,7 @@ describe("Test Linear Elements", () => { false, ); expect( - wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)), + wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made From 45a57d70de75fbb47f7c3c45cae675341c9bcab5 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 26 Apr 2023 21:35:06 +0530 Subject: [PATCH 5/5] fix: don't refresh dimensions for text containers on font load (#6523) --- src/data/blob.ts | 2 +- src/excalidraw-app/data/index.ts | 2 +- src/scene/Fonts.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/blob.ts b/src/data/blob.ts index 4565b5cb5..c0aa66ee7 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -155,7 +155,7 @@ export const loadSceneOrLibraryFromBlob = async ( }, localAppState, localElements, - { repairBindings: true, refreshDimensions: true }, + { repairBindings: true, refreshDimensions: false }, ), }; } else if (isValidLibrary(data)) { diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts index 7f13bc615..2e50abf1f 100644 --- a/src/excalidraw-app/data/index.ts +++ b/src/excalidraw-app/data/index.ts @@ -263,7 +263,7 @@ export const loadScene = async ( await importFromBackend(id, privateKey), localDataState?.appState, localDataState?.elements, - { repairBindings: true, refreshDimensions: true }, + { repairBindings: true, refreshDimensions: false }, ); } else { data = restore(localDataState || null, null, null, { diff --git a/src/scene/Fonts.ts b/src/scene/Fonts.ts index cc206c776..e245eb16e 100644 --- a/src/scene/Fonts.ts +++ b/src/scene/Fonts.ts @@ -1,5 +1,6 @@ import { isTextElement, refreshTextDimensions } from "../element"; import { newElementWith } from "../element/mutateElement"; +import { isBoundToContainer } from "../element/typeChecks"; import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; import { invalidateShapeForElement } from "../renderer/renderElement"; import { getFontString } from "../utils"; @@ -52,7 +53,7 @@ export class Fonts { let didUpdate = false; this.scene.mapElements((element) => { - if (isTextElement(element)) { + if (isTextElement(element) && !isBoundToContainer(element)) { invalidateShapeForElement(element); didUpdate = true; return newElementWith(element, {