Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax

This commit is contained in:
Daniel J. Geiger 2023-04-26 16:43:42 -05:00
commit 91fe07d9c5
16 changed files with 141 additions and 93 deletions

View File

@ -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 }}

View File

@ -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;

View File

@ -30,6 +30,7 @@
background-color: transparent;
border: none;
white-space: nowrap;
font-family: inherit;
display: grid;
grid-template-columns: 1fr 0.2fr;

View File

@ -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;

View File

@ -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);

View File

@ -155,7 +155,7 @@ export const loadSceneOrLibraryFromBlob = async (
},
localAppState,
localElements,
{ repairBindings: true, refreshDimensions: true },
{ repairBindings: true, refreshDimensions: false },
),
};
} else if (isValidLibrary(data)) {

View File

@ -28,7 +28,7 @@ import {
measureTextElement,
normalizeText,
wrapTextElement,
getMaxContainerWidth,
getBoundTextMaxWidth,
getDefaultLineHeight,
} from "./textElement";
import {
@ -333,7 +333,7 @@ export const refreshTextDimensions = (
}
const container = getContainerElement(textElement);
if (container) {
text = wrapTextElement(textElement, getMaxContainerWidth(container), {
text = wrapTextElement(textElement, getBoundTextMaxWidth(container), {
text,
});
}

View File

@ -44,10 +44,10 @@ import {
getBoundTextElementId,
getContainerElement,
handleBindTextResize,
getMaxContainerWidth,
getBoundTextMaxWidth,
getApproxMinLineHeight,
measureTextElement,
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);
@ -431,8 +431,8 @@ export const resizeSingleElement = (
const nextFont = measureFontSizeFromWidth(
boundTextElement,
getMaxContainerWidth(updatedElement),
getMaxContainerHeight(updatedElement),
getBoundTextMaxWidth(updatedElement),
getBoundTextMaxHeight(updatedElement, boundTextElement),
);
if (nextFont === null) {
return;
@ -714,10 +714,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,
);

View File

@ -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,
);
});
});
});

View File

@ -90,7 +90,7 @@ export const redrawTextBoundingBox = (
boundTextUpdates.text = textElement.text;
if (container) {
maxWidth = getMaxContainerWidth(container);
maxWidth = getBoundTextMaxWidth(container);
boundTextUpdates.text = wrapTextElement(textElement, maxWidth);
}
const metrics = measureTextElement(textElement, {
@ -110,35 +110,28 @@ export const redrawTextBoundingBox = (
}
}
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 = getBoundTextMaxHeight(
container,
textElement as ExcalidrawTextElementWithContainer,
);
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);
@ -210,8 +203,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") {
@ -275,8 +271,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;
@ -909,18 +905,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") {
@ -937,16 +925,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;
}

View File

@ -32,8 +32,8 @@ import {
normalizeText,
redrawTextBoundingBox,
wrapText,
getMaxContainerHeight,
getMaxContainerWidth,
getBoundTextMaxHeight,
getBoundTextMaxWidth,
computeContainerDimensionForBoundText,
detectLineHeight,
} from "./textElement";
@ -174,7 +174,7 @@ export const textWysiwyg = ({
? wrapText(
updatedTextElement.originalText,
getFontString(updatedTextElement),
getMaxContainerWidth(container),
getBoundTextMaxWidth(container),
)
: updatedTextElement.originalText,
getFontString(updatedTextElement),
@ -189,7 +189,7 @@ export const textWysiwyg = ({
if (container && updatedTextElement.containerId) {
textElementHeight = Math.min(
getMaxContainerHeight(container),
getBoundTextMaxWidth(container),
textElementHeight,
);
if (isArrowElement(container)) {
@ -221,7 +221,7 @@ export const textWysiwyg = ({
wrapText(
updatedTextElement.originalText,
font,
getMaxContainerWidth(container),
getBoundTextMaxWidth(container),
).split("\n").length;
textElementHeight = Math.max(
textElementHeight,
@ -245,8 +245,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) {
@ -437,7 +440,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`;
@ -454,7 +457,7 @@ export const textWysiwyg = ({
const wrappedText = wrapText(
normalizeText(editable.value),
font,
getMaxContainerWidth(container!),
getBoundTextMaxWidth(container!),
);
const { width, height } = measureText(
wrappedText,

View File

@ -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, {

View File

@ -4,9 +4,9 @@ import { FONT_FAMILY, SVG_NS } from "../../../../constants";
import { getFontString, getFontFamilyString, isRTL } from "../../../../utils";
import {
getBoundTextElement,
getBoundTextMaxWidth,
getContainerElement,
getDefaultLineHeight,
getMaxContainerWidth,
getTextWidth,
measureText,
wrapText,
@ -1018,7 +1018,7 @@ const renderMathElement = function (element, context, renderCb) {
}
};
const container = getContainerElement(_element);
const parentWidth = container ? getMaxContainerWidth(container) : undefined;
const parentWidth = container ? getBoundTextMaxWidth(container) : undefined;
const offsetX =
(_element.width - (container ? parentWidth! : _element.width)) *
@ -1095,7 +1095,7 @@ const renderSvgMathElement = function (svgRoot, root, element, opt) {
tempSvg.appendChild(groupNode);
const container = getContainerElement(_element);
const parentWidth = container ? getMaxContainerWidth(container) : undefined;
const parentWidth = container ? getBoundTextMaxWidth(container) : undefined;
const offsetX =
(_element.width - (container ? parentWidth! : _element.width)) *

View File

@ -45,8 +45,8 @@ import {
getContainerCoords,
getContainerElement,
getLineHeightInPx,
getMaxContainerHeight,
getMaxContainerWidth,
getBoundTextMaxHeight,
getBoundTextMaxWidth,
} from "../element/textElement";
import { LinearElementEditor } from "../element/linearElementEditor";
@ -875,14 +875,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,
);
}
}

View File

@ -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, {

View File

@ -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