Merge branch 'master' into mrazator/canvas-partitioning

# Conflicts:
#	yarn.lock
This commit is contained in:
dwelle 2023-08-06 22:10:19 +02:00
commit a502e49f78
28 changed files with 322 additions and 142 deletions

5
.codesandbox/Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM node:18-bullseye
# Vite wants to open the browser using `open`, so we
# need to install those utils.
RUN apt update -y && apt install -y xdg-utils

View File

@ -27,7 +27,10 @@
"start": { "start": {
"name": "Start Excalidraw", "name": "Start Excalidraw",
"command": "yarn start", "command": "yarn start",
"runAtStart": true "runAtStart": true,
"preview": {
"port": 3000
}
}, },
"test": { "test": {
"name": "Run Tests", "name": "Run Tests",
@ -37,7 +40,11 @@
"install-deps": { "install-deps": {
"name": "Install Dependencies", "name": "Install Dependencies",
"command": "yarn install", "command": "yarn install",
"restartOn": { "files": ["yarn.lock"] } "restartOn": {
"files": ["yarn.lock"],
"branch": false,
"resume": false
}
} }
} }
} }

View File

@ -1,5 +1,5 @@
REACT_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/ VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
REACT_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries

26
.github/workflows/test-coverage-pr.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Test Coverage PR
on:
pull_request:
jobs:
coverage:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v2
- name: "Install Node"
uses: actions/setup-node@v2
with:
node-version: "18.x"
- name: "Install Deps"
run: yarn --frozen-lockfile
- name: "Test Coverage"
run: yarn test:coverage
- name: "Report Coverage"
if: always() # Also generate the report if tests are failing
uses: davelosert/vitest-coverage-report-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

2
.nvmrc
View File

@ -1 +1 @@
14 18

View File

@ -69,6 +69,10 @@ It's also a good idea to consider if your change should include additional tests
Finally - always manually test your changes using the convenient staging environment deployed for each pull request. As much as local development attempts to replicate production, there can still be subtle differences in behavior. For larger features consider testing your change in multiple browsers as well. Finally - always manually test your changes using the convenient staging environment deployed for each pull request. As much as local development attempts to replicate production, there can still be subtle differences in behavior. For larger features consider testing your change in multiple browsers as well.
:::note
Some checks, such as the `lint` and `test`, require approval from the maintainers to run.
They will appear as `Expected — Waiting for status to be reported` in the PR checks when they are waiting for approval.
:::
## Translating ## Translating

View File

@ -6611,19 +6611,19 @@ semver@7.0.0:
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@^5.4.1: semver@^5.4.1:
version "5.7.1" version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
version "6.3.0" version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
version "7.3.7" version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"

View File

@ -70,6 +70,7 @@
"@types/resize-observer-browser": "0.1.7", "@types/resize-observer-browser": "0.1.7",
"@types/socket.io-client": "1.4.36", "@types/socket.io-client": "1.4.36",
"@vitejs/plugin-react": "3.1.0", "@vitejs/plugin-react": "3.1.0",
"@vitest/coverage-v8": "0.33.0",
"@vitest/ui": "0.32.2", "@vitest/ui": "0.32.2",
"chai": "4.3.6", "chai": "4.3.6",
"dotenv": "16.0.1", "dotenv": "16.0.1",
@ -101,8 +102,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build-node": "node ./scripts/build-node.js", "build-node": "node ./scripts/build-node.js",
"build:app:docker": "cross-env REACT_APP_DISABLE_SENTRY=true REACT_APP_DISABLE_TRACKING=true vite build", "build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build",
"build:app": "cross-env REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build", "build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
"build:version": "node ./scripts/build-version.js", "build:version": "node ./scripts/build-version.js",
"build": "yarn build:app && yarn build:version", "build": "yarn build:app && yarn build:version",
"fix:code": "yarn test:code --fix", "fix:code": "yarn test:code --fix",
@ -114,14 +115,15 @@
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore", "prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"start": "vite", "start": "vite",
"start:production": "npm run build && npx http-server build -a localhost -p 5001 -o", "start:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false", "test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
"test:app": "vitest --config vitest.config.ts", "test:app": "vitest --config vitest.config.ts",
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .", "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
"test:other": "yarn prettier --list-different", "test:other": "yarn prettier --list-different",
"test:typecheck": "tsc", "test:typecheck": "tsc",
"test:update": "yarn test:app --update --watch=false", "test:update": "yarn test:app --update --watch=false",
"test": "yarn test:app", "test": "yarn test:app",
"test:coverage": "vitest --coverage --watchAll", "test:coverage": "vitest --coverage",
"test:coverage:watch": "vitest --coverage --watch",
"test:ui": "yarn test --ui", "test:ui": "yarn test --ui",
"autorelease": "node scripts/autorelease.js", "autorelease": "node scripts/autorelease.js",
"prerelease": "node scripts/prerelease.js", "prerelease": "node scripts/prerelease.js",

20
public/service-worker.js Normal file
View File

@ -0,0 +1,20 @@
// Since we migrated to Vite, the service worker strategy changed, in CRA it was a custom service worker named service-worker.js and in Vite its sw.js handled by vite-plugin-pwa
// Due to this the existing CRA users were not able to migrate to Vite or any new changes post Vite unless browser is hard refreshed
// Hence adding a self destroying worker so all CRA service workers are destroyed and migrated to Vite
// We should remove this code after sometime when we are confident that
// all users have migrated to Vite
self.addEventListener("install", () => {
self.skipWaiting();
});
self.addEventListener("activate", () => {
self.registration
.unregister()
.then(() => {
return self.clients.matchAll();
})
.then((clients) => {
clients.forEach((client) => client.navigate(client.url));
});
});

View File

@ -297,7 +297,6 @@ import {
getApproxMinLineWidth, getApproxMinLineWidth,
getBoundTextElement, getBoundTextElement,
getContainerCenter, getContainerCenter,
getContainerDims,
getContainerElement, getContainerElement,
getDefaultLineHeight, getDefaultLineHeight,
getLineHeightInPx, getLineHeightInPx,
@ -3464,9 +3463,8 @@ class App extends React.Component<AppProps, AppState> {
lineHeight, lineHeight,
); );
const minHeight = getApproxMinLineHeight(fontSize, lineHeight); const minHeight = getApproxMinLineHeight(fontSize, lineHeight);
const containerDims = getContainerDims(container); const newHeight = Math.max(container.height, minHeight);
const newHeight = Math.max(containerDims.height, minHeight); const newWidth = Math.max(container.width, minWidth);
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;
@ -5309,7 +5307,7 @@ class App extends React.Component<AppProps, AppState> {
width: embedLink.aspectRatio.w, width: embedLink.aspectRatio.w,
height: embedLink.aspectRatio.h, height: embedLink.aspectRatio.h,
link, link,
validated: undefined, validated: null,
}); });
this.scene.replaceAllElements([ this.scene.replaceAllElements([
@ -5497,7 +5495,7 @@ class App extends React.Component<AppProps, AppState> {
} }
private createGenericElementOnPointerDown = ( private createGenericElementOnPointerDown = (
elementType: ExcalidrawGenericElement["type"], elementType: ExcalidrawGenericElement["type"] | "embeddable",
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
): void => { ): void => {
const [gridX, gridY] = getGridPoint( const [gridX, gridY] = getGridPoint(
@ -5511,8 +5509,7 @@ class App extends React.Component<AppProps, AppState> {
y: gridY, y: gridY,
}); });
const element = newElement({ const baseElementAttributes = {
type: elementType,
x: gridX, x: gridX,
y: gridY, y: gridY,
strokeColor: this.state.currentItemStrokeColor, strokeColor: this.state.currentItemStrokeColor,
@ -5525,8 +5522,21 @@ class App extends React.Component<AppProps, AppState> {
roundness: this.getCurrentItemRoundness(elementType), roundness: this.getCurrentItemRoundness(elementType),
locked: false, locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null, frameId: topLayerFrame ? topLayerFrame.id : null,
...(elementType === "embeddable" ? { validated: false } : {}), } as const;
});
let element;
if (elementType === "embeddable") {
element = newEmbeddableElement({
type: "embeddable",
validated: null,
...baseElementAttributes,
});
} else {
element = newElement({
type: elementType,
...baseElementAttributes,
});
}
if (element.type === "selection") { if (element.type === "selection") {
this.setState({ this.setState({

View File

@ -77,8 +77,8 @@ export const EyeDropper: React.FC<{
colorPreviewDiv.style.left = `${clientX + 20}px`; colorPreviewDiv.style.left = `${clientX + 20}px`;
const pixel = ctx.getImageData( const pixel = ctx.getImageData(
clientX * window.devicePixelRatio - appState.offsetLeft, (clientX - appState.offsetLeft) * window.devicePixelRatio,
clientY * window.devicePixelRatio - appState.offsetTop, (clientY - appState.offsetTop) * window.devicePixelRatio,
1, 1,
1, 1,
).data; ).data;

View File

@ -117,6 +117,7 @@ export const FRAME_STYLE = {
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji"; export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
export const MIN_FONT_SIZE = 1;
export const DEFAULT_FONT_SIZE = 20; export const DEFAULT_FONT_SIZE = 20;
export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Virgil; export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Virgil;
export const DEFAULT_TEXT_ALIGN = "left"; export const DEFAULT_TEXT_ALIGN = "left";
@ -239,6 +240,8 @@ export const VERSIONS = {
} as const; } as const;
export const BOUND_TEXT_PADDING = 5; export const BOUND_TEXT_PADDING = 5;
export const ARROW_LABEL_WIDTH_FRACTION = 0.7;
export const ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO = 11;
export const VERTICAL_ALIGN = { export const VERTICAL_ALIGN = {
TOP: "top", TOP: "top",

View File

@ -286,7 +286,7 @@ const restoreElement = (
return restoreElementWithProperties(element, {}); return restoreElementWithProperties(element, {});
case "embeddable": case "embeddable":
return restoreElementWithProperties(element, { return restoreElementWithProperties(element, {
validated: undefined, validated: null,
}); });
case "frame": case "frame":
return restoreElementWithProperties(element, { return restoreElementWithProperties(element, {

View File

@ -269,11 +269,11 @@ export class LinearElementEditor {
}; };
}), }),
); );
}
const boundTextElement = getBoundTextElement(element); const boundTextElement = getBoundTextElement(element);
if (boundTextElement) { if (boundTextElement) {
handleBindTextResize(element, false); handleBindTextResize(element, false);
}
} }
// suggest bindings for first and last point if selected // suggest bindings for first and last point if selected

View File

@ -134,7 +134,7 @@ export const newElement = (
export const newEmbeddableElement = ( export const newEmbeddableElement = (
opts: { opts: {
type: "embeddable"; type: "embeddable";
validated: boolean | undefined; validated: ExcalidrawEmbeddableElement["validated"];
} & ElementConstructorOpts, } & ElementConstructorOpts,
): NonDeleted<ExcalidrawEmbeddableElement> => { ): NonDeleted<ExcalidrawEmbeddableElement> => {
return { return {

View File

@ -1,4 +1,4 @@
import { SHIFT_LOCKING_ANGLE } from "../constants"; import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
import { rescalePoints } from "../points"; import { rescalePoints } from "../points";
import { import {
@ -204,8 +204,6 @@ const rescalePointsInElement = (
} }
: {}; : {};
const MIN_FONT_SIZE = 1;
const measureFontSizeFromWidth = ( const measureFontSizeFromWidth = (
element: NonDeleted<ExcalidrawTextElement>, element: NonDeleted<ExcalidrawTextElement>,
nextWidth: number, nextWidth: number,
@ -589,24 +587,42 @@ export const resizeSingleElement = (
}); });
} }
if (
isArrowElement(element) &&
boundTextElement &&
shouldMaintainAspectRatio
) {
const fontSize =
(resizedElement.width / element.width) * boundTextElement.fontSize;
if (fontSize < MIN_FONT_SIZE) {
return;
}
boundTextFont.fontSize = fontSize;
}
if ( if (
resizedElement.width !== 0 && resizedElement.width !== 0 &&
resizedElement.height !== 0 && resizedElement.height !== 0 &&
Number.isFinite(resizedElement.x) && Number.isFinite(resizedElement.x) &&
Number.isFinite(resizedElement.y) Number.isFinite(resizedElement.y)
) { ) {
mutateElement(element, resizedElement);
updateBoundElements(element, { updateBoundElements(element, {
newSize: { width: resizedElement.width, height: resizedElement.height }, newSize: { width: resizedElement.width, height: resizedElement.height },
}); });
mutateElement(element, resizedElement);
if (boundTextElement && boundTextFont != null) { if (boundTextElement && boundTextFont != null) {
mutateElement(boundTextElement, { mutateElement(boundTextElement, {
fontSize: boundTextFont.fontSize, fontSize: boundTextFont.fontSize,
baseline: boundTextFont.baseline, baseline: boundTextFont.baseline,
}); });
} }
handleBindTextResize(element, transformHandleDirection); handleBindTextResize(
element,
transformHandleDirection,
shouldMaintainAspectRatio,
);
} }
}; };
@ -722,12 +738,8 @@ export const resizeMultipleElements = (
fontSize?: ExcalidrawTextElement["fontSize"]; fontSize?: ExcalidrawTextElement["fontSize"];
baseline?: ExcalidrawTextElement["baseline"]; baseline?: ExcalidrawTextElement["baseline"];
scale?: ExcalidrawImageElement["scale"]; scale?: ExcalidrawImageElement["scale"];
boundTextFontSize?: ExcalidrawTextElement["fontSize"];
}; };
boundText: {
element: ExcalidrawTextElementWithContainer;
fontSize: ExcalidrawTextElement["fontSize"];
baseline: ExcalidrawTextElement["baseline"];
} | null;
}[] = []; }[] = [];
for (const { orig, latest } of targetElements) { for (const { orig, latest } of targetElements) {
@ -798,50 +810,39 @@ export const resizeMultipleElements = (
} }
} }
let boundText: typeof elementsAndUpdates[0]["boundText"] = null; if (isTextElement(orig)) {
const metrics = measureFontSizeFromWidth(orig, width, height);
const boundTextElement = getBoundTextElement(latest);
if (boundTextElement || isTextElement(orig)) {
const updatedElement = {
...latest,
width,
height,
};
const metrics = measureFontSizeFromWidth(
boundTextElement ?? (orig as ExcalidrawTextElement),
boundTextElement
? getBoundTextMaxWidth(updatedElement)
: updatedElement.width,
boundTextElement
? getBoundTextMaxHeight(updatedElement, boundTextElement)
: updatedElement.height,
);
if (!metrics) { if (!metrics) {
return; return;
} }
update.fontSize = metrics.size;
if (isTextElement(orig)) { update.baseline = metrics.baseline;
update.fontSize = metrics.size;
update.baseline = metrics.baseline;
}
if (boundTextElement) {
boundText = {
element: boundTextElement,
fontSize: metrics.size,
baseline: metrics.baseline,
};
}
} }
elementsAndUpdates.push({ element: latest, update, boundText }); const boundTextElement = pointerDownState.originalElements.get(
getBoundTextElementId(orig) ?? "",
) as ExcalidrawTextElementWithContainer | undefined;
if (boundTextElement) {
const newFontSize = boundTextElement.fontSize * scale;
if (newFontSize < MIN_FONT_SIZE) {
return;
}
update.boundTextFontSize = newFontSize;
}
elementsAndUpdates.push({
element: latest,
update,
});
} }
const elementsToUpdate = elementsAndUpdates.map(({ element }) => element); const elementsToUpdate = elementsAndUpdates.map(({ element }) => element);
for (const { element, update, boundText } of elementsAndUpdates) { for (const {
element,
update: { boundTextFontSize, ...update },
} of elementsAndUpdates) {
const { width, height, angle } = update; const { width, height, angle } = update;
mutateElement(element, update, false); mutateElement(element, update, false);
@ -851,17 +852,17 @@ export const resizeMultipleElements = (
newSize: { width, height }, newSize: { width, height },
}); });
if (boundText) { const boundTextElement = getBoundTextElement(element);
const { element: boundTextElement, ...boundTextUpdates } = boundText; if (boundTextElement && boundTextFontSize) {
mutateElement( mutateElement(
boundTextElement, boundTextElement,
{ {
...boundTextUpdates, fontSize: boundTextFontSize,
angle: isLinearElement(element) ? undefined : angle, angle: isLinearElement(element) ? undefined : angle,
}, },
false, false,
); );
handleBindTextResize(element, transformHandleType); handleBindTextResize(element, transformHandleType, true);
} }
} }

View File

@ -10,6 +10,8 @@ import {
} from "./types"; } from "./types";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { import {
ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO,
ARROW_LABEL_WIDTH_FRACTION,
BOUND_TEXT_PADDING, BOUND_TEXT_PADDING,
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
@ -65,7 +67,7 @@ export const redrawTextBoundingBox = (
boundTextUpdates.text = textElement.text; boundTextUpdates.text = textElement.text;
if (container) { if (container) {
maxWidth = getBoundTextMaxWidth(container); maxWidth = getBoundTextMaxWidth(container, textElement);
boundTextUpdates.text = wrapText( boundTextUpdates.text = wrapText(
textElement.originalText, textElement.originalText,
getFontString(textElement), getFontString(textElement),
@ -83,13 +85,12 @@ export const redrawTextBoundingBox = (
boundTextUpdates.baseline = metrics.baseline; boundTextUpdates.baseline = metrics.baseline;
if (container) { if (container) {
const containerDims = getContainerDims(container);
const maxContainerHeight = getBoundTextMaxHeight( const maxContainerHeight = getBoundTextMaxHeight(
container, container,
textElement as ExcalidrawTextElementWithContainer, textElement as ExcalidrawTextElementWithContainer,
); );
let nextHeight = containerDims.height; let nextHeight = container.height;
if (metrics.height > maxContainerHeight) { if (metrics.height > maxContainerHeight) {
nextHeight = computeContainerDimensionForBoundText( nextHeight = computeContainerDimensionForBoundText(
metrics.height, metrics.height,
@ -155,6 +156,7 @@ export const bindTextToShapeAfterDuplication = (
export const handleBindTextResize = ( export const handleBindTextResize = (
container: NonDeletedExcalidrawElement, container: NonDeletedExcalidrawElement,
transformHandleType: MaybeTransformHandleType, transformHandleType: MaybeTransformHandleType,
shouldMaintainAspectRatio = false,
) => { ) => {
const boundTextElementId = getBoundTextElementId(container); const boundTextElementId = getBoundTextElementId(container);
if (!boundTextElementId) { if (!boundTextElementId) {
@ -175,15 +177,17 @@ export const handleBindTextResize = (
let text = textElement.text; let text = textElement.text;
let nextHeight = textElement.height; let nextHeight = textElement.height;
let nextWidth = textElement.width; let nextWidth = textElement.width;
const containerDims = getContainerDims(container);
const maxWidth = getBoundTextMaxWidth(container); const maxWidth = getBoundTextMaxWidth(container);
const maxHeight = getBoundTextMaxHeight( const maxHeight = getBoundTextMaxHeight(
container, container,
textElement as ExcalidrawTextElementWithContainer, textElement as ExcalidrawTextElementWithContainer,
); );
let containerHeight = containerDims.height; let containerHeight = container.height;
let nextBaseLine = textElement.baseline; let nextBaseLine = textElement.baseline;
if (transformHandleType !== "n" && transformHandleType !== "s") { if (
shouldMaintainAspectRatio ||
(transformHandleType !== "n" && transformHandleType !== "s")
) {
if (text) { if (text) {
text = wrapText( text = wrapText(
textElement.originalText, textElement.originalText,
@ -207,7 +211,7 @@ export const handleBindTextResize = (
container.type, container.type,
); );
const diff = containerHeight - containerDims.height; const diff = containerHeight - container.height;
// fix the y coord when resizing from ne/nw/n // fix the y coord when resizing from ne/nw/n
const updatedY = const updatedY =
!isArrowElement(container) && !isArrowElement(container) &&
@ -687,16 +691,6 @@ export const getContainerElement = (
return null; return null;
}; };
export const getContainerDims = (element: ExcalidrawElement) => {
const MIN_WIDTH = 300;
if (isArrowElement(element)) {
const width = Math.max(element.width, MIN_WIDTH);
const height = element.height;
return { width, height };
}
return { width: element.width, height: element.height };
};
export const getContainerCenter = ( export const getContainerCenter = (
container: ExcalidrawElement, container: ExcalidrawElement,
appState: AppState, appState: AppState,
@ -887,12 +881,19 @@ export const computeContainerDimensionForBoundText = (
return dimension + padding; return dimension + padding;
}; };
export const getBoundTextMaxWidth = (container: ExcalidrawElement) => { export const getBoundTextMaxWidth = (
const width = getContainerDims(container).width; container: ExcalidrawElement,
boundTextElement: ExcalidrawTextElement | null = getBoundTextElement(
container,
),
) => {
const { width } = container;
if (isArrowElement(container)) { if (isArrowElement(container)) {
return width - BOUND_TEXT_PADDING * 8 * 2; const minWidth =
(boundTextElement?.fontSize ?? DEFAULT_FONT_SIZE) *
ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO;
return Math.max(ARROW_LABEL_WIDTH_FRACTION * width, minWidth);
} }
if (container.type === "ellipse") { if (container.type === "ellipse") {
// The width of the largest rectangle inscribed inside an ellipse is // The width of the largest rectangle inscribed inside an ellipse is
// Math.round((ellipse.width / 2) * Math.sqrt(2)) which is derived from // Math.round((ellipse.width / 2) * Math.sqrt(2)) which is derived from
@ -911,7 +912,7 @@ export const getBoundTextMaxHeight = (
container: ExcalidrawElement, container: ExcalidrawElement,
boundTextElement: ExcalidrawTextElementWithContainer, boundTextElement: ExcalidrawTextElementWithContainer,
) => { ) => {
const height = getContainerDims(container).height; const { height } = container;
if (isArrowElement(container)) { if (isArrowElement(container)) {
const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2; const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2;
if (containerHeight <= 0) { if (containerHeight <= 0) {

View File

@ -23,7 +23,6 @@ import { AppState } from "../types";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { import {
getBoundTextElementId, getBoundTextElementId,
getContainerDims,
getContainerElement, getContainerElement,
getTextElementAngle, getTextElementAngle,
getTextWidth, getTextWidth,
@ -177,20 +176,19 @@ export const textWysiwyg = ({
updatedTextElement, updatedTextElement,
editable, editable,
); );
const containerDims = getContainerDims(container);
let originalContainerData; let originalContainerData;
if (propertiesUpdated) { if (propertiesUpdated) {
originalContainerData = updateOriginalContainerCache( originalContainerData = updateOriginalContainerCache(
container.id, container.id,
containerDims.height, container.height,
); );
} else { } else {
originalContainerData = originalContainerCache[container.id]; originalContainerData = originalContainerCache[container.id];
if (!originalContainerData) { if (!originalContainerData) {
originalContainerData = updateOriginalContainerCache( originalContainerData = updateOriginalContainerCache(
container.id, container.id,
containerDims.height, container.height,
); );
} }
} }
@ -214,7 +212,7 @@ export const textWysiwyg = ({
// 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
!isArrowElement(container) && !isArrowElement(container) &&
containerDims.height > originalContainerData.height && container.height > originalContainerData.height &&
textElementHeight < maxHeight textElementHeight < maxHeight
) { ) {
const targetContainerHeight = computeContainerDimensionForBoundText( const targetContainerHeight = computeContainerDimensionForBoundText(

View File

@ -86,15 +86,15 @@ export type ExcalidrawEllipseElement = _ExcalidrawElementBase & {
export type ExcalidrawEmbeddableElement = _ExcalidrawElementBase & export type ExcalidrawEmbeddableElement = _ExcalidrawElementBase &
Readonly<{ Readonly<{
type: "embeddable";
/** /**
* indicates whether the embeddable src (url) has been validated for rendering. * indicates whether the embeddable src (url) has been validated for rendering.
* nullish value indicates that the validation is pending. We reset the * null value indicates that the validation is pending. We reset the
* value on each restore (or url change) so that we can guarantee * value on each restore (or url change) so that we can guarantee
* the validation came from a trusted source (the editor). Also because we * the validation came from a trusted source (the editor). Also because we
* may not have access to host-app supplied url validator during restore. * may not have access to host-app supplied url validator during restore.
*/ */
validated?: boolean; validated: boolean | null;
type: "embeddable";
}>; }>;
export type ExcalidrawImageElement = _ExcalidrawElementBase & export type ExcalidrawImageElement = _ExcalidrawElementBase &
@ -123,7 +123,6 @@ export type ExcalidrawFrameElement = _ExcalidrawElementBase & {
export type ExcalidrawGenericElement = export type ExcalidrawGenericElement =
| ExcalidrawSelectionElement | ExcalidrawSelectionElement
| ExcalidrawRectangleElement | ExcalidrawRectangleElement
| ExcalidrawEmbeddableElement
| ExcalidrawDiamondElement | ExcalidrawDiamondElement
| ExcalidrawEllipseElement; | ExcalidrawEllipseElement;
@ -138,7 +137,8 @@ export type ExcalidrawElement =
| ExcalidrawLinearElement | ExcalidrawLinearElement
| ExcalidrawFreeDrawElement | ExcalidrawFreeDrawElement
| ExcalidrawImageElement | ExcalidrawImageElement
| ExcalidrawFrameElement; | ExcalidrawFrameElement
| ExcalidrawEmbeddableElement;
export type NonDeleted<TElement extends ExcalidrawElement> = TElement & { export type NonDeleted<TElement extends ExcalidrawElement> = TElement & {
isDeleted: boolean; isDeleted: boolean;

View File

@ -6,12 +6,11 @@ const SentryEnvHostnameMap: { [key: string]: string } = {
"vercel.app": "staging", "vercel.app": "staging",
}; };
const REACT_APP_DISABLE_SENTRY = const SENTRY_DISABLED = import.meta.env.VITE_APP_DISABLE_SENTRY === "true";
import.meta.env.VITE_APP_DISABLE_SENTRY === "true";
// Disable Sentry locally or inside the Docker to avoid noise/respect privacy // Disable Sentry locally or inside the Docker to avoid noise/respect privacy
const onlineEnv = const onlineEnv =
!REACT_APP_DISABLE_SENTRY && !SENTRY_DISABLED &&
Object.keys(SentryEnvHostnameMap).find( Object.keys(SentryEnvHostnameMap).find(
(item) => window.location.hostname.indexOf(item) >= 0, (item) => window.location.hostname.indexOf(item) >= 0,
); );

View File

@ -2,8 +2,8 @@ const path = require("path");
const webpack = require("webpack"); const webpack = require("webpack");
const autoprefixer = require("autoprefixer"); const autoprefixer = require("autoprefixer");
const { parseEnvVariables } = require("./env"); const { parseEnvVariables } = require("./env");
const outputDir = process.env.EXAMPLE === "true" ? "example/public" : "dist"; const outputDir = process.env.EXAMPLE === "true" ? "example/public" : "dist";
module.exports = { module.exports = {
mode: "development", mode: "development",
devtool: false, devtool: false,
@ -17,7 +17,6 @@ module.exports = {
filename: "[name].js", filename: "[name].js",
chunkFilename: "excalidraw-assets-dev/[name]-[contenthash].js", chunkFilename: "excalidraw-assets-dev/[name]-[contenthash].js",
assetModuleFilename: "excalidraw-assets-dev/[name][ext]", assetModuleFilename: "excalidraw-assets-dev/[name][ext]",
publicPath: "", publicPath: "",
}, },
resolve: { resolve: {
@ -45,7 +44,7 @@ module.exports = {
{ {
test: /\.(ts|tsx|js|jsx|mjs)$/, test: /\.(ts|tsx|js|jsx|mjs)$/,
exclude: exclude:
/node_modules\/(?!(browser-fs-access|canvas-roundrect-polyfill))/, /node_modules[\\/](?!(browser-fs-access|canvas-roundrect-polyfill))/,
use: [ use: [
{ {
loader: "import-meta-loader", loader: "import-meta-loader",

View File

@ -1,10 +1,10 @@
const path = require("path"); const path = require("path");
const webpack = require("webpack");
const autoprefixer = require("autoprefixer");
const { parseEnvVariables } = require("./env");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const BundleAnalyzerPlugin = const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin; require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const autoprefixer = require("autoprefixer");
const webpack = require("webpack");
const { parseEnvVariables } = require("./env");
module.exports = { module.exports = {
mode: "production", mode: "production",
@ -47,8 +47,7 @@ module.exports = {
{ {
test: /\.(ts|tsx|js|jsx|mjs)$/, test: /\.(ts|tsx|js|jsx|mjs)$/,
exclude: exclude:
/node_modules\/(?!(browser-fs-access|canvas-roundrect-polyfill))/, /node_modules[\\/](?!(browser-fs-access|canvas-roundrect-polyfill))/,
use: [ use: [
{ {
loader: "import-meta-loader", loader: "import-meta-loader",

View File

@ -34,6 +34,7 @@ export const rectangleFixture: ExcalidrawElement = {
export const embeddableFixture: ExcalidrawElement = { export const embeddableFixture: ExcalidrawElement = {
...elementBase, ...elementBase,
type: "embeddable", type: "embeddable",
validated: null,
}; };
export const ellipseFixture: ExcalidrawElement = { export const ellipseFixture: ExcalidrawElement = {
...elementBase, ...elementBase,

View File

@ -15,7 +15,11 @@ import fs from "fs";
import util from "util"; import util from "util";
import path from "path"; import path from "path";
import { getMimeType } from "../../data/blob"; import { getMimeType } from "../../data/blob";
import { newFreeDrawElement, newImageElement } from "../../element/newElement"; import {
newEmbeddableElement,
newFreeDrawElement,
newImageElement,
} from "../../element/newElement";
import { Point } from "../../types"; import { Point } from "../../types";
import { getSelectedElements } from "../../scene/selection"; import { getSelectedElements } from "../../scene/selection";
import { isLinearElementType } from "../../element/typeChecks"; import { isLinearElementType } from "../../element/typeChecks";
@ -178,14 +182,20 @@ export class API {
case "rectangle": case "rectangle":
case "diamond": case "diamond":
case "ellipse": case "ellipse":
case "embeddable":
element = newElement({ element = newElement({
type: type as "rectangle" | "diamond" | "ellipse" | "embeddable", type: type as "rectangle" | "diamond" | "ellipse",
width, width,
height, height,
...base, ...base,
}); });
break; break;
case "embeddable":
element = newEmbeddableElement({
type: "embeddable",
...base,
validated: null,
});
break;
case "text": case "text":
const fontSize = rest.fontSize ?? appState.currentItemFontSize; const fontSize = rest.fontSize ?? appState.currentItemFontSize;
const fontFamily = rest.fontFamily ?? appState.currentItemFontFamily; const fontFamily = rest.fontFamily ?? appState.currentItemFontFamily;

View File

@ -1143,7 +1143,7 @@ describe("Test Linear Elements", () => {
height: 500, height: 500,
}); });
const arrow = UI.createElement("arrow", { const arrow = UI.createElement("arrow", {
x: 210, x: -10,
y: 250, y: 250,
width: 400, width: 400,
height: 1, height: 1,
@ -1167,8 +1167,8 @@ describe("Test Linear Elements", () => {
expect( expect(
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"Online whiteboard collaboration "Online whiteboard
made easy" collaboration made easy"
`); `);
const handleBindTextResizeSpy = vi.spyOn( const handleBindTextResizeSpy = vi.spyOn(
textElementUtils, textElementUtils,
@ -1180,7 +1180,7 @@ describe("Test Linear Elements", () => {
mouse.moveTo(200, 0); mouse.moveTo(200, 0);
mouse.upAt(200, 0); mouse.upAt(200, 0);
expect(arrow.width).toBe(170); expect(arrow.width).toBe(200);
expect(rect.x).toBe(200); expect(rect.x).toBe(200);
expect(rect.y).toBe(0); expect(rect.y).toBe(0);
expect(handleBindTextResizeSpy).toHaveBeenCalledWith( expect(handleBindTextResizeSpy).toHaveBeenCalledWith(

View File

@ -61,7 +61,7 @@ export default defineConfig({
workbox: { workbox: {
// Don't push fonts and locales to app precache // Don't push fonts and locales to app precache
globIgnores: ["fonts.css", "**/locales/**"], globIgnores: ["fonts.css", "**/locales/**", "service-worker.js"],
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: new RegExp("/.+.(ttf|woff2|otf)"), urlPattern: new RegExp("/.+.(ttf|woff2|otf)"),

View File

@ -5,5 +5,12 @@ export default defineConfig({
setupFiles: ["./src/setupTests.ts"], setupFiles: ["./src/setupTests.ts"],
globals: true, globals: true,
environment: "jsdom", environment: "jsdom",
coverage: {
reporter: ["text", "json-summary", "json"],
lines: 70,
branches: 70,
functions: 68,
statements: 70,
},
}, },
}); });

100
yarn.lock
View File

@ -2,7 +2,7 @@
# yarn lockfile v1 # yarn lockfile v1
"@ampproject/remapping@^2.2.0": "@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.2.1":
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
@ -1260,6 +1260,11 @@
"@babel/helper-validator-identifier" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@braintree/sanitize-url@6.0.2": "@braintree/sanitize-url@6.0.2":
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f" resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
@ -1812,6 +1817,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@istanbuljs/schema@^0.1.2":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
"@jest/expect-utils@^29.5.0": "@jest/expect-utils@^29.5.0":
version "29.5.0" version "29.5.0"
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036"
@ -1875,7 +1885,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.18" version "0.3.18"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
@ -2500,7 +2510,7 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==
@ -2786,6 +2796,23 @@
magic-string "^0.27.0" magic-string "^0.27.0"
react-refresh "^0.14.0" react-refresh "^0.14.0"
"@vitest/coverage-v8@0.33.0":
version "0.33.0"
resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-0.33.0.tgz#dfcb36cf51624a89d33ab0962a6eec8a41346ef2"
integrity sha512-Rj5IzoLF7FLj6yR7TmqsfRDSeaFki6NAJ/cQexqhbWkHEV2htlVGrmuOde3xzvFsCbLCagf4omhcIaVmfU8Okg==
dependencies:
"@ampproject/remapping" "^2.2.1"
"@bcoe/v8-coverage" "^0.2.3"
istanbul-lib-coverage "^3.2.0"
istanbul-lib-report "^3.0.0"
istanbul-lib-source-maps "^4.0.1"
istanbul-reports "^3.1.5"
magic-string "^0.30.1"
picocolors "^1.0.0"
std-env "^3.3.3"
test-exclude "^6.0.0"
v8-to-istanbul "^9.1.0"
"@vitest/expect@0.34.1": "@vitest/expect@0.34.1":
version "0.34.1" version "0.34.1"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.1.tgz#2ba6cb96695f4b4388c6d955423a81afc79b8da0" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.1.tgz#2ba6cb96695f4b4388c6d955423a81afc79b8da0"
@ -3491,7 +3518,7 @@ confusing-browser-globals@^1.0.11:
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
convert-source-map@^1.7.0: convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.9.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
@ -4536,7 +4563,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies: dependencies:
is-glob "^4.0.1" is-glob "^4.0.1"
glob@^7.1.3, glob@^7.1.6: glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3" version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -4671,6 +4698,11 @@ html-encoding-sniffer@^3.0.0:
dependencies: dependencies:
whatwg-encoding "^2.0.0" whatwg-encoding "^2.0.0"
html-escaper@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
http-parser-js@>=0.5.1: http-parser-js@>=0.5.1:
version "0.5.8" version "0.5.8"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
@ -5060,6 +5092,37 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
istanbul-lib-report@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
dependencies:
istanbul-lib-coverage "^3.0.0"
make-dir "^4.0.0"
supports-color "^7.1.0"
istanbul-lib-source-maps@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
dependencies:
debug "^4.1.1"
istanbul-lib-coverage "^3.0.0"
source-map "^0.6.1"
istanbul-reports@^3.1.5:
version "3.1.6"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a"
integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==
dependencies:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
jake@^10.8.5: jake@^10.8.5:
version "10.8.5" version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@ -5477,6 +5540,13 @@ magic-string@^0.30.1:
dependencies: dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15" "@jridgewell/sourcemap-codec" "^1.4.15"
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
dependencies:
semver "^7.5.3"
merge-stream@^2.0.0: merge-stream@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@ -6420,7 +6490,7 @@ semver@^7.2.1, semver@^7.3.7:
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
semver@^7.3.4, semver@^7.5.0: semver@^7.3.4, semver@^7.5.0, semver@^7.5.3:
version "7.5.4" version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@ -6799,6 +6869,15 @@ terser@^5.0.0:
commander "^2.20.0" commander "^2.20.0"
source-map-support "~0.5.20" source-map-support "~0.5.20"
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
dependencies:
"@istanbuljs/schema" "^0.1.2"
glob "^7.1.4"
minimatch "^3.0.4"
text-table@^0.2.0: text-table@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -7096,6 +7175,15 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
v8-to-istanbul@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265"
integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==
dependencies:
"@jridgewell/trace-mapping" "^0.3.12"
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0"
vite-node@0.34.1: vite-node@0.34.1:
version "0.34.1" version "0.34.1"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.1.tgz#144900ca4bd54cc419c501d671350bcbc07eb1ee" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.1.tgz#144900ca4bd54cc419c501d671350bcbc07eb1ee"