From cd61f3111684244133ae02d42109e8f29eaf6cc9 Mon Sep 17 00:00:00 2001 From: zsviczian Date: Mon, 5 Sep 2022 12:30:47 +0200 Subject: [PATCH 01/11] fix: buttons jump around on the mobile menu (#5658) Update MobileMenu.tsx --- src/components/MobileMenu.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index f6e28f441..a20547546 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -121,7 +121,6 @@ export const MobileMenu = ({ const renderAppToolbar = () => { // Render eraser conditionally in mobile const showEraser = - !appState.viewModeEnabled && !appState.editingElement && getSelectedElements(elements, appState).length === 0; @@ -140,11 +139,11 @@ export const MobileMenu = ({ {actionManager.renderAction("undo")} {actionManager.renderAction("redo")} - {showEraser && actionManager.renderAction("eraser")} - - {actionManager.renderAction( - appState.multiElement ? "finalize" : "duplicateSelection", - )} + {showEraser + ? actionManager.renderAction("eraser") + : actionManager.renderAction( + appState.multiElement ? "finalize" : "duplicateSelection", + )} {actionManager.renderAction("deleteSelectedElements")} ); From 933c6a2237163ebf438bb1d9b387449d8e7e6216 Mon Sep 17 00:00:00 2001 From: Igor Berlenko Date: Wed, 7 Sep 2022 18:38:04 +0800 Subject: [PATCH 02/11] build: add missing dependencies: pica, lodash (#5656) * add missing dependencies: pica, lodash * remove lodash & fix yarn.lock * first * second Co-authored-by: dwelle --- package.json | 1 + src/components/LibraryMenuItems.tsx | 3 +-- src/excalidraw-app/collab/Portal.tsx | 2 +- yarn.lock | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 436e0de23..f5189072a 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "open-color": "1.9.1", "pako": "1.0.11", "perfect-freehand": "1.0.16", + "pica": "7.1.1", "png-chunk-text": "1.0.0", "png-chunks-encode": "1.0.0", "png-chunks-extract": "1.0.0", diff --git a/src/components/LibraryMenuItems.tsx b/src/components/LibraryMenuItems.tsx index 01223baed..b39003cda 100644 --- a/src/components/LibraryMenuItems.tsx +++ b/src/components/LibraryMenuItems.tsx @@ -1,4 +1,3 @@ -import { chunk } from "lodash"; import React, { useCallback, useState } from "react"; import { saveLibraryAsJSON, serializeLibraryAsJSON } from "../data/json"; import Library from "../data/library"; @@ -11,7 +10,7 @@ import { LibraryItem, LibraryItems, } from "../types"; -import { arrayToMap, muteFSAbortError } from "../utils"; +import { arrayToMap, chunk, muteFSAbortError } from "../utils"; import { useDevice } from "./App"; import ConfirmDialog from "./ConfirmDialog"; import { close, exportToFileIcon, load, publishIcon, trash } from "./icons"; diff --git a/src/excalidraw-app/collab/Portal.tsx b/src/excalidraw-app/collab/Portal.tsx index 95e0e7aa4..2d073c86a 100644 --- a/src/excalidraw-app/collab/Portal.tsx +++ b/src/excalidraw-app/collab/Portal.tsx @@ -14,7 +14,7 @@ import { } from "../app_constants"; import { UserIdleState } from "../../types"; import { trackEvent } from "../../analytics"; -import { throttle } from "lodash"; +import throttle from "lodash.throttle"; import { newElementWith } from "../../element/mutateElement"; import { BroadcastedExcalidrawElement } from "./reconciliation"; import { encryptData } from "../../data/encryption"; diff --git a/yarn.lock b/yarn.lock index 64e577c58..98fb485d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9032,6 +9032,17 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pica@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/pica/-/pica-7.1.1.tgz#c68b42f5cfa6cc26eaec5cfa10cc0a5299ef3b7a" + integrity sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ== + dependencies: + glur "^1.1.2" + inherits "^2.0.3" + multimath "^2.0.0" + object-assign "^4.1.1" + webworkify "^1.5.0" + pica@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/pica/-/pica-7.1.0.tgz" From 59ec1c6cee2e8150f70fea13464af5715ee2b9b8 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Fri, 9 Sep 2022 13:53:38 +0200 Subject: [PATCH 03/11] fix: zen-mode exit button not working (#5682) --- src/components/Actions.tsx | 6 +++--- src/components/Footer.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 8cd1d2abd..1ba40ba08 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -286,17 +286,17 @@ export const UndoRedoActions = ({ ); export const ExitZenModeAction = ({ - executeAction, + actionManager, showExitZenModeBtn, }: { - executeAction: ActionManager["executeAction"]; + actionManager: ActionManager; showExitZenModeBtn: boolean; }) => ( diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 420aeb6a8..0fbcd73d9 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -96,7 +96,7 @@ const Footer = ({ {actionManager.renderAction("toggleShortcuts")} From 7922ce129e569f0abed54158deba15506415af15 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Mon, 12 Sep 2022 06:50:51 +0900 Subject: [PATCH 04/11] chore: fix typo in blob.ts (#5664) Co-authored-by: David Luzar --- src/data/blob.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/blob.ts b/src/data/blob.ts index ceecd9345..b5ca1dcad 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -356,7 +356,7 @@ export const getFileHandle = async ( }; /** - * attemps to detect if a buffer is a valid image by checking its leading bytes + * attempts to detect if a buffer is a valid image by checking its leading bytes */ const getActualMimeTypeFromImage = (buffer: ArrayBuffer) => { let mimeType: ValueOf> | null = @@ -396,7 +396,7 @@ export const createFile = ( }); }; -/** attemps to detect correct mimeType if none is set, or if an image +/** attempts to detect correct mimeType if none is set, or if an image * has an incorrect extension. * Note: doesn't handle missing .excalidraw/.excalidrawlib extension */ export const normalizeFile = async (file: File) => { From 898789b9792ea9bbd4dc26b7a62874b6052b0a58 Mon Sep 17 00:00:00 2001 From: Ryan Di Date: Mon, 12 Sep 2022 13:49:22 +0800 Subject: [PATCH 05/11] chore: update lib menu click outside callback comment (#5687) --- src/components/LibraryMenu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/LibraryMenu.tsx b/src/components/LibraryMenu.tsx index c9fc75703..ebd609763 100644 --- a/src/components/LibraryMenu.tsx +++ b/src/components/LibraryMenu.tsx @@ -107,7 +107,8 @@ export const LibraryMenu = ({ ref, useCallback( (event) => { - // If click on the library icon, do nothing. + // If click on the library icon, do nothing so that LibraryButton + // can toggle library menu if ((event.target as Element).closest(".ToolIcon__library")) { return; } From 5c17751662e67b8b078eee3ab522b1e3b83d73f9 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 13 Sep 2022 16:29:56 +0530 Subject: [PATCH 06/11] fix: Move to release notes for v0.9.0 and after (#5686) * fix: Move to release notes for v0.9.0 and after * fix --- src/packages/excalidraw/CHANGELOG.md | 856 +-------------------------- 1 file changed, 4 insertions(+), 852 deletions(-) diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 4fab8e8d4..d239d25fa 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -26,869 +26,21 @@ Please add the latest change on the top under the correct section. ## 0.12.0 (2022-07-07) -### Excalidraw API - -#### Features - -- [`loadLibraryFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadLibraryFromBlob) now takes an additional parameter `defaultStatus` which sets the default status of library item if not present, defaults to `unpublished` [#5067](https://github.com/excalidraw/excalidraw/pull/5067). - -- Add [`UIOptions.dockedSidebarBreakpoint`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#dockedSidebarBreakpoint) to customize at which point to break from the docked sidebar [#5274](https://github.com/excalidraw/excalidraw/pull/5274). - -- Added support for supplying user `id` in the Collaborator object (see `collaborators` in [`updateScene()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene)), which will be used to deduplicate users when rendering collaborator avatar list. Cursors will still be rendered for every user. [#5309](https://github.com/excalidraw/excalidraw/pull/5309) - -- Export API to [set](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setCursor) and [reset](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#resetCursor) mouse cursor on the canvas [#5215](https://github.com/excalidraw/excalidraw/pull/5215). - -- Export [`sceneCoordsToViewportCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) and [`viewportCoordsToSceneCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) utilities [#5187](https://github.com/excalidraw/excalidraw/pull/5187). - -- Added [`useHandleLibrary`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#useHandleLibrary) hook to automatically handle importing of libraries when `#addLibrary` URL hash key is present, and potentially for initializing library as well [#5115](https://github.com/excalidraw/excalidraw/pull/5115). - - Also added [`parseLibraryTokensFromUrl`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#parseLibraryTokensFromUrl) to help in manually importing library from URL if desired. - - ##### BREAKING CHANGE - - - Libraries are no longer automatically initialized from URL when `#addLibrary` hash key is present. Host apps now need to handle this themselves with the help of either of the above APIs (`useHandleLibrary` is recommended). - -- Added [`updateLibrary`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateLibrary) API to update (replace/merge) the library [#5115](https://github.com/excalidraw/excalidraw/pull/5115). - - ##### BREAKING CHANGE - - - `updateScene` API no longer supports passing `libraryItems`. Instead, use the `updateLibrary` API. - -- Add support for integrating custom elements [#5164](https://github.com/excalidraw/excalidraw/pull/5164). - - - Add [`onPointerDown`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) callback which gets triggered on pointer down events. - - Add [`onScrollChange`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onScrollChange) callback which gets triggered when scrolling the canvas. - - Add API [`setActiveTool`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setActiveTool) which host can call to set the active tool. - -- Exported [`loadSceneOrLibraryFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadSceneOrLibraryFromBlob) function [#5057](https://github.com/excalidraw/excalidraw/pull/5057). -- Export [`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L92) supported by Excalidraw [#5135](https://github.com/excalidraw/excalidraw/pull/5135). -- Support [`avatarUrl`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L50) for collaborators. Now onwards host can pass `avatarUrl` to render the customized avatar for collaborators [#5114](https://github.com/excalidraw/excalidraw/pull/5114), renamed in [#5177](https://github.com/excalidraw/excalidraw/pull/5177). -- Support `libraryItems` argument in `initialData.libraryItems` and `updateScene({ libraryItems })` to be a Promise resolving to `LibraryItems`, and support functional update of `libraryItems` in [`updateScene({ libraryItems })`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene). [#5101](https://github.com/excalidraw/excalidraw/pull/5101). -- Expose util [`mergeLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#mergeLibraryItems) [#5101](https://github.com/excalidraw/excalidraw/pull/5101). -- Expose util [`exportToClipboard`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToClipboard) which allows to copy the scene contents to clipboard as `svg`, `png` or `json` [#5103](https://github.com/excalidraw/excalidraw/pull/5103). -- Expose `window.EXCALIDRAW_EXPORT_SOURCE` which you can use to overwrite the `source` field in exported data [#5095](https://github.com/excalidraw/excalidraw/pull/5095). -- The `exportToBlob` utility now supports the `exportEmbedScene` option when generating a png image [#5047](https://github.com/excalidraw/excalidraw/pull/5047). -- Exported [`restoreLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreLibraryItems) API [#4995](https://github.com/excalidraw/excalidraw/pull/4995). - -#### Fixes - -- Allow returning `null ` in [`renderFooter`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderFooter) prop [#5282](https://github.com/excalidraw/excalidraw/pull/5282). - -- Transpile `browser-fs-access` dependency so that its `for await` syntax doesn't force `es2018` requirement onto dependent projects [#5041](https://github.com/excalidraw/excalidraw/pull/5041). - -- Use `window.EXCALIDRAW_ASSET_PATH` for fonts when exporting to svg [#5065](https://github.com/excalidraw/excalidraw/pull/5065). -- Library menu now properly rerenders if open when library is updated using `updateScene({ libraryItems })` [#4995](https://github.com/excalidraw/excalidraw/pull/4995). - -#### Refactor - -- Rename `appState.elementLocked` to `appState.activeTool.locked` [#4983](https://github.com/excalidraw/excalidraw/pull/4983). -- Expose [`serializeLibraryAsJSON`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#serializeLibraryAsJSON) helper that we use when saving Excalidraw Library to a file. - -##### BREAKING CHANGE - -You will need to pass `activeTool.locked` instead of `elementType` from now onwards in `appState`. - -- Rename `appState.elementType` to [`appState.activeTool`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L80) which is now an object [#4698](https://github.com/excalidraw/excalidraw/pull/4968). - -##### BREAKING CHANGE - -You will need to pass `activeTool` instead of `elementType` from now onwards in `appState` - -### Build - -- Use only named exports [#5045](https://github.com/excalidraw/excalidraw/pull/5045). - -#### BREAKING CHANGE - -You will need to import the named export from now onwards to use the component - -Using bundler :point_down: - -```js -import { Excalidraw } from "@excalidraw/excalidraw"; -``` - -In Browser :point_down: - -```js -React.createElement(ExcalidrawLib.Excalidraw, opts); -``` - -## Excalidraw Library +Check out the [release notes](https://github.com/excalidraw/excalidraw/releases/tag/v0.12.0) ) **_This section lists the updates made to the excalidraw library and will not affect the integration._** -### Features - -- Throttle scene rendering to animation framerate [#5422](https://github.com/excalidraw/excalidraw/pull/5422) - -- Make toast closable and allow custom duration [#5308](https://github.com/excalidraw/excalidraw/pull/5308) - -- Collab component state handling rewrite & fixes [#5046](https://github.com/excalidraw/excalidraw/pull/5046) - -- Support debugging PWA in dev [#4853](https://github.com/excalidraw/excalidraw/pull/4853) - -- Redirect vscode.excalidraw.com to vscode marketplace [#5285](https://github.com/excalidraw/excalidraw/pull/5285) - -- Go-to-excalidrawplus button [#5202](https://github.com/excalidraw/excalidraw/pull/5202) - -- Autoredirect to Excalidraw+ if special cookie is present [#5183](https://github.com/excalidraw/excalidraw/pull/5183) - -- Support resubmitting published library items [#5174](https://github.com/excalidraw/excalidraw/pull/5174) - -- Support adding multiple library items on canvas [#5116](https://github.com/excalidraw/excalidraw/pull/5116) - -- Support customType in activeTool [#5144](https://github.com/excalidraw/excalidraw/pull/5144) - -- Stop event propagation when key handled [#5091](https://github.com/excalidraw/excalidraw/pull/5091) - -- Rewrite library state management & related refactor [#5067](https://github.com/excalidraw/excalidraw/pull/5067) - -- Delay initial loading message & tweak design [#5049](https://github.com/excalidraw/excalidraw/pull/5049) - -- Reconcile when saving to firebase [#4991](https://github.com/excalidraw/excalidraw/pull/4991) - -- Hide trash button during collaboration [#5037](https://github.com/excalidraw/excalidraw/pull/5037) - -- Refactor local persistence & fix race condition on SW reload [#5032](https://github.com/excalidraw/excalidraw/pull/5032) - -- Element locking [#4964](https://github.com/excalidraw/excalidraw/pull/4964) - -- Copy to clipboard all text nodes as text [#5013](https://github.com/excalidraw/excalidraw/pull/5013) - -- Create and expose serializeLibraryAsJSON [#5009](https://github.com/excalidraw/excalidraw/pull/5009) - -- Hide penMode button on reload if not enabled [#4992](https://github.com/excalidraw/excalidraw/pull/4992) - -- Eraser toggle to switch back to the previous tool [#4981](https://github.com/excalidraw/excalidraw/pull/4981) - -- Save penDetected and penMode, and detect pen already on ToolButton click [#4955](https://github.com/excalidraw/excalidraw/pull/4955) - -- Support binding text to container via context menu [#4935](https://github.com/excalidraw/excalidraw/pull/4935) - -- Map shortcut O to ellipse and Add eraser shortcut E [#4930](https://github.com/excalidraw/excalidraw/pull/4930) - -- Update eraser cursor [#4922](https://github.com/excalidraw/excalidraw/pull/4922) - -- Add Eraser 🎉 [#4887](https://github.com/excalidraw/excalidraw/pull/4887) - -- Added optional REACT_APP_WS_SERVER_URL for forks usecases [#4889](https://github.com/excalidraw/excalidraw/pull/4889) - -- Rewrite collab server connecting [#4881](https://github.com/excalidraw/excalidraw/pull/4881) - -- Support vertical text align for bound containers [#4852](https://github.com/excalidraw/excalidraw/pull/4852) - -- Support custom colors 🎉 [#4843](https://github.com/excalidraw/excalidraw/pull/4843) - -- Support Links in Exported SVG [#4791](https://github.com/excalidraw/excalidraw/pull/4791) - -- Scale font size when bound text containers resized with shift pressed [#4828](https://github.com/excalidraw/excalidraw/pull/4828) - -### Fixes - -- Autorelease job name [#5412](https://github.com/excalidraw/excalidraw/pull/5412) - -- Action name for autorelease [#5411](https://github.com/excalidraw/excalidraw/pull/5411) - -- Typecast file to fix the build [#5410](https://github.com/excalidraw/excalidraw/pull/5410) - -- File handle not persisted when importing excalidraw files [#5372](https://github.com/excalidraw/excalidraw/pull/5372) - -- Library not scrollable when no published items installed [#5352](https://github.com/excalidraw/excalidraw/pull/5352) - -- Focus traps inside popovers [#5317](https://github.com/excalidraw/excalidraw/pull/5317) - -- Unable to use cmd/ctrl-delete/backspace in inputs [#5348](https://github.com/excalidraw/excalidraw/pull/5348) - -- Delay loading until language imported [#5344](https://github.com/excalidraw/excalidraw/pull/5344) - -- Command to trigger release [#5347](https://github.com/excalidraw/excalidraw/pull/5347) - -- Remove unnecessary options passed to language detector [#5336](https://github.com/excalidraw/excalidraw/pull/5336) - -- Stale `appState.pendingImageElement` [#5322](https://github.com/excalidraw/excalidraw/pull/5322) - -- Non-letter shortcuts being swallowed by color picker [#5316](https://github.com/excalidraw/excalidraw/pull/5316) - -- Bind text to correct container when nested [#5307](https://github.com/excalidraw/excalidraw/pull/5307) - -- Copy bound text style when copying element having bound text [#5305](https://github.com/excalidraw/excalidraw/pull/5305) - -- Copy arrow head when using copy styles [#5303](https://github.com/excalidraw/excalidraw/pull/5303) - -- Unsafely accessing draggingElement [#5216](https://github.com/excalidraw/excalidraw/pull/5216) - -- Library load button does not work [#5205](https://github.com/excalidraw/excalidraw/pull/5205) - -- Do not deselect when not zooming using touchscreen pinch [#5181](https://github.com/excalidraw/excalidraw/pull/5181) - -- Wheel zoom normalization [#5165](https://github.com/excalidraw/excalidraw/pull/5165) - -- Hide sidebar when `custom` tool active [#5179](https://github.com/excalidraw/excalidraw/pull/5179) - -- Don't save deleted ExcalidrawElements to Firebase [#5108](https://github.com/excalidraw/excalidraw/pull/5108) - -- Eraser removed deleted elements [#5155](https://github.com/excalidraw/excalidraw/pull/5155) - -- Handle `ColorPicker` parentSelector being undefined [#5152](https://github.com/excalidraw/excalidraw/pull/5152) - -- Library multiselect not accounting for published state [#5132](https://github.com/excalidraw/excalidraw/pull/5132) - -- Chart display fix [#5154](https://github.com/excalidraw/excalidraw/pull/5154) - -- Update opacity of bound text when opacity of container updated [#5142](https://github.com/excalidraw/excalidraw/pull/5142) - -- Jumping of text when typing single line in bound text [#5139](https://github.com/excalidraw/excalidraw/pull/5139) - -- Remove opacity scroll wheel interaction [#5111](https://github.com/excalidraw/excalidraw/pull/5111) - -- Propagate keydown events from excalidraw-wysiwyg inputs [#5099](https://github.com/excalidraw/excalidraw/pull/5099) - -- Don't bind text to container if double clicked else instead of center [#5105](https://github.com/excalidraw/excalidraw/pull/5105) - -- ToolIcon height not using rem [#5092](https://github.com/excalidraw/excalidraw/pull/5092) - -- Excalidraw named export type [#5078](https://github.com/excalidraw/excalidraw/pull/5078) - -- BoundElementIds when arrows bound to elements are deleted [#5077](https://github.com/excalidraw/excalidraw/pull/5077) - -- Don't merge libraryItems on updateScene [#5076](https://github.com/excalidraw/excalidraw/pull/5076) - -- SVG metadata extraction regex on multiline elements [#5074](https://github.com/excalidraw/excalidraw/pull/5074) - -- Eraser cursor showing on theme change when not using eraser [#4990](https://github.com/excalidraw/excalidraw/pull/4990) - -- Update `storage.rules` [#5020](https://github.com/excalidraw/excalidraw/pull/5020) - -- Add image button not working on iPad [#5038](https://github.com/excalidraw/excalidraw/pull/5038) - -- Ensure svg image dimensions are always set [#5044](https://github.com/excalidraw/excalidraw/pull/5044) - -- Pinch zoom in view mode [#5001](https://github.com/excalidraw/excalidraw/pull/5001) - -- Select whole group on righclick & few lock-related fixes [#5022](https://github.com/excalidraw/excalidraw/pull/5022) - -- Export serializeLibraryAsJSON from the package [#5017](https://github.com/excalidraw/excalidraw/pull/5017) - -- Support copying PNG to clipboard on Safari [#3746](https://github.com/excalidraw/excalidraw/pull/3746) - -- More copyText fixes [#5016](https://github.com/excalidraw/excalidraw/pull/5016) - -- Copy to clipboard all text nodes as text [#5014](https://github.com/excalidraw/excalidraw/pull/5014) - -- Update cursorButton once freedraw is released [#4996](https://github.com/excalidraw/excalidraw/pull/4996) - -- Decouple actionFinalize and actionErase [#4984](https://github.com/excalidraw/excalidraw/pull/4984) - -- Using stale state when switching tools [#4989](https://github.com/excalidraw/excalidraw/pull/4989) - -- UpdateWysiwygStyle updatedElement is undefined TypeError [#4980](https://github.com/excalidraw/excalidraw/pull/4980) - -- Adding check for link length to prevent early return [#4982](https://github.com/excalidraw/excalidraw/pull/4982) - -- Show link icon for bound text containers [#4960](https://github.com/excalidraw/excalidraw/pull/4960) - -- Cancel erase elements on pointer up if eraser is not active on pointer up [#4956](https://github.com/excalidraw/excalidraw/pull/4956) - -- Restore original opacities when alt pressed while erasing [#4954](https://github.com/excalidraw/excalidraw/pull/4954) - -- Don't bind text to container if already present [#4946](https://github.com/excalidraw/excalidraw/pull/4946) - -- Erase all elements which are hit with single point click [#4934](https://github.com/excalidraw/excalidraw/pull/4934) - -- Add multiElement-edit finalize action to Desktop (currently only visible in Mobile view) [#4764](https://github.com/excalidraw/excalidraw/pull/4764) - -- Hide eraser in view mode in desktop [#4929](https://github.com/excalidraw/excalidraw/pull/4929) - -- Undo when erasing elements by clicking [#4921](https://github.com/excalidraw/excalidraw/pull/4921) - -- Undo when erasing [#4900](https://github.com/excalidraw/excalidraw/pull/4900) - -- Incorrectly erasing on mobile [#4899](https://github.com/excalidraw/excalidraw/pull/4899) - -- Don't crash on drop highlighted text onto canvas [#4890](https://github.com/excalidraw/excalidraw/pull/4890) - -- Paste styles shortcut [#4886](https://github.com/excalidraw/excalidraw/pull/4886) - -- Freedraw element's background fill color missing from SVG when exporting with package API exportToSvg() [#4871](https://github.com/excalidraw/excalidraw/pull/4871) - -- Improve pointer syncing performance [#4883](https://github.com/excalidraw/excalidraw/pull/4883) - -- Collab room initialization [#4882](https://github.com/excalidraw/excalidraw/pull/4882) - -- Ensure verticalAlign properties not shown when no element selected [#4860](https://github.com/excalidraw/excalidraw/pull/4860) - -- Binding text to non-bindable containers and not always preferring selection [#4655](https://github.com/excalidraw/excalidraw/pull/4655) - -- Don't show align icons for single bound container element [#4846](https://github.com/excalidraw/excalidraw/pull/4846) - -- Redraw text bounding box when pasting styles [#4845](https://github.com/excalidraw/excalidraw/pull/4845) - -- Restore cursor position after bound text container value updated [#4836](https://github.com/excalidraw/excalidraw/pull/4836) - -- Support resizing multiple bound text containers [#4824](https://github.com/excalidraw/excalidraw/pull/4824) - -- Also check overflowY: overlay in detectScroll [#4806](https://github.com/excalidraw/excalidraw/pull/4806) - -- Stuck resizing when resizing bound text container very fast beyond threshold [#4804](https://github.com/excalidraw/excalidraw/pull/4804) - -### Refactor - -- Don't pass array to handleBindTextResize [#4826](https://github.com/excalidraw/excalidraw/pull/4826) - -### Build - -- Extract all i18n files into locales folder [#5419](https://github.com/excalidraw/excalidraw/pull/5419) - -- Automate release step fully [#5414](https://github.com/excalidraw/excalidraw/pull/5414) - -- Use next and preview tags instead of separate packages for next and preview release [#5346](https://github.com/excalidraw/excalidraw/pull/5346) - -- Support runtime React Jsx in @excalidraw/utils [#4866](https://github.com/excalidraw/excalidraw/pull/4866) - -- Release @excalidraw/utils 0.1.1 [#4862](https://github.com/excalidraw/excalidraw/pull/4862) - -- Remove build packages workflow [#4835](https://github.com/excalidraw/excalidraw/pull/4835) - ---- - ## 0.11.0 (2022-02-17) -## Excalidraw API - -### Features - -- Add `onLinkOpen` prop which will be triggered when clicked on element hyperlink if present [#4694](https://github.com/excalidraw/excalidraw/pull/4694). -- Support updating library using [`updateScene`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene) API [#4546](https://github.com/excalidraw/excalidraw/pull/4546). - -- Introduced primary colors to the app. The colors can be overridden. Check [readme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#customizing-styles) on how to do so [#4387](https://github.com/excalidraw/excalidraw/pull/4387). - -- [`exportToBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToBlob) now automatically sets `appState.exportBackground` to `true` if exporting to `image/jpeg` MIME type (to ensure that alpha channel is not compressed to black color) [#4342](https://github.com/excalidraw/excalidraw/pull/4342). - - #### BREAKING CHANGE - - Remove `getElementMap` util [#4306](https://github.com/excalidraw/excalidraw/pull/4306). - -- Changes to [`exportToCanvas`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToCanvas) util function [#4321](https://github.com/excalidraw/excalidraw/pull/4321): - - - Add `maxWidthOrHeight?: number` attribute. - - `scale` returned from `getDimensions()` is now optional (default to `1`). - -- Image support added for host [PR](https://github.com/excalidraw/excalidraw/pull/4011) - - General notes: - - - File data are encoded as DataURLs (base64) for portability reasons. - - [ExcalidrawAPI](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onLibraryChange): - - - added `getFiles()` to get current `BinaryFiles` (`Record`). It may contain files that aren't referenced by any element, so if you're persisting the files to a storage, you should compare them against stored elements. - - Excalidraw app props: - - - added `generateIdForFile(file: File)` optional prop so you can generate your own ids for added files. - - `onChange(elements, appState, files)` prop callback is now passed `BinaryFiles` as third argument. - - `onPaste(data, event)` data prop should contain `data.files` (`BinaryFiles`) if the elements pasted are referencing new files. - - `initialData` object now supports additional `files` (`BinaryFiles`) attribute. - - Other notes: - - - `.excalidraw` files may now contain top-level `files` key in format of `Record` when exporting any (image) elements. - - Changes were made to various export utilities exported from the package so that they take `files`, you can refer to the docs for the same. - -- Export [`isLinearElement`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#isLinearElement) and [`getNonDeletedElements`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#getNonDeletedElements) [#4072](https://github.com/excalidraw/excalidraw/pull/4072). - -- Support [`renderTopRightUI`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderTopRightUI) in mobile UI [#4065](https://github.com/excalidraw/excalidraw/pull/4065). - -- Export `THEME` constant from the package so host can use this when passing the theme [#4055](https://github.com/excalidraw/excalidraw/pull/4055). - - #### BREAKING CHANGE - - The `Appearance` type is now removed and renamed to `Theme` so `Theme` type needs to be used. - -### Fixes - -- Reset `unmounted` state on the component once component mounts to fix the mounting/unmounting repeatedly when used with `useEffect` [#4682](https://github.com/excalidraw/excalidraw/pull/4682). -- Panning the canvas using `mousewheel-drag` and `space-drag` now prevents the browser from scrolling the container/page [#4489](https://github.com/excalidraw/excalidraw/pull/4489). -- Scope drag and drop events to Excalidraw container to prevent overriding host application drag and drop events [#4445](https://github.com/excalidraw/excalidraw/pull/4445). - -### Build - -- Release preview package [@excalidraw/excalidraw-preview](https://www.npmjs.com/package/@excalidraw/excalidraw-preview) when triggered via comment - -``` - @excalibot trigger release -``` - -[#4750](https://github.com/excalidraw/excalidraw/pull/4750). - -- Added an example to test and develop the package [locally](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#Development) using `yarn start` [#4488](https://github.com/excalidraw/excalidraw/pull/4488) - -- Remove `file-loader` so font assets are not duplicated by webpack and use webpack asset modules for font generation [#4380](https://github.com/excalidraw/excalidraw/pull/4380). - -- We're now compiling to `es2017` target. Notably, `async/await` is not compiled down to generators. [#4341](https://github.com/excalidraw/excalidraw/pull/4341). - ---- - -## Excalidraw Library - -**_This section lists the updates made to the excalidraw library and will not affect the integration._** - -### Features - -- Show group/group and link action in mobile [#4795](https://github.com/excalidraw/excalidraw/pull/4795) - -- Support background fill for freedraw shapes [#4610](https://github.com/excalidraw/excalidraw/pull/4610) - -- Keep selected tool on canvas reset [#4728](https://github.com/excalidraw/excalidraw/pull/4728) - -- Make whole element clickable in view mode when it has hyperlink [#4735](https://github.com/excalidraw/excalidraw/pull/4735) - -- Allow any precision when zooming [#4730](https://github.com/excalidraw/excalidraw/pull/4730) - -- Throttle `pointermove` events per framerate [#4727](https://github.com/excalidraw/excalidraw/pull/4727) - -- Support hyperlinks 🔥 [#4620](https://github.com/excalidraw/excalidraw/pull/4620) - -- Added penMode for palm rejection [#4657](https://github.com/excalidraw/excalidraw/pull/4657) - -- Support unbinding bound text [#4686](https://github.com/excalidraw/excalidraw/pull/4686) - -- Sync local storage state across tabs when out of sync [#4545](https://github.com/excalidraw/excalidraw/pull/4545) - -- Support contextMenuLabel to be of function type to support dynamic labels [#4654](https://github.com/excalidraw/excalidraw/pull/4654) - -- Support decreasing/increasing `fontSize` via keyboard [#4553](https://github.com/excalidraw/excalidraw/pull/4553) - -- Link to new LP for excalidraw plus [#4549](https://github.com/excalidraw/excalidraw/pull/4549) - -- Update stroke color of bounded text along with container [#4541](https://github.com/excalidraw/excalidraw/pull/4541) - -- Hints and shortcuts help around deep selection [#4502](https://github.com/excalidraw/excalidraw/pull/4502) - -- Support updating text properties by clicking on container [#4499](https://github.com/excalidraw/excalidraw/pull/4499) - -- Bind text to shapes when pressing enter and support sticky notes 🎉 [#4343](https://github.com/excalidraw/excalidraw/pull/4343) - -- Change `boundElementIds` → `boundElements` [#4404](https://github.com/excalidraw/excalidraw/pull/4404) - -- Support selecting multiple points when editing line [#4373](https://github.com/excalidraw/excalidraw/pull/4373) - -- Horizontally center toolbar menu [commit link](https://github.com/excalidraw/excalidraw/commit/9b8ee3cacfec239617c357693cf2a3ca9972d2cb) - -- Add support for rounded corners in diamond [#4369](https://github.com/excalidraw/excalidraw/pull/4369) - -- Allow zooming up to 3000% [#4358](https://github.com/excalidraw/excalidraw/pull/4358) - -- Stop discarding precision when rendering [#4357](https://github.com/excalidraw/excalidraw/pull/4357) - -- Support Image binding [#4347](https://github.com/excalidraw/excalidraw/pull/4347) - -- Add `element.updated` [#4070](https://github.com/excalidraw/excalidraw/pull/4070) - -- Compress shareLink data when uploading to json server [#4225](https://github.com/excalidraw/excalidraw/pull/4225) - -- Supply `version` param when installing libraries [#4305](https://github.com/excalidraw/excalidraw/pull/4305) - -- Log FS abortError to console [#4279](https://github.com/excalidraw/excalidraw/pull/4279) - -- Add validation for website and remove validation for library item name [#4269](https://github.com/excalidraw/excalidraw/pull/4269) - -- Allow publishing libraries from UI [#4115](https://github.com/excalidraw/excalidraw/pull/4115) - -- Create confirm dialog to use instead of window.confirm [#4256](https://github.com/excalidraw/excalidraw/pull/4256) - -- Allow letters in IDs for storing files in backend [#4224](https://github.com/excalidraw/excalidraw/pull/4224) - -- Remove support for V1 unencrypted backend [#4189](https://github.com/excalidraw/excalidraw/pull/4189) - -- Use separate backend for local storage [#4187](https://github.com/excalidraw/excalidraw/pull/4187) - -- Add hint around canvas panning [#4159](https://github.com/excalidraw/excalidraw/pull/4159) - -- Stop using production services for development [#4113](https://github.com/excalidraw/excalidraw/pull/4113) - -- Add triangle arrowhead [#4024](https://github.com/excalidraw/excalidraw/pull/4024) - -- Add rewrite to webex landing page [#4102](https://github.com/excalidraw/excalidraw/pull/4102) - -- Switch collab server [#4092](https://github.com/excalidraw/excalidraw/pull/4092) - -- Use dialog component for clear canvas instead of window confirm [#4075](https://github.com/excalidraw/excalidraw/pull/4075) - -### Fixes - -- Rename --color-primary-chubb to --color-primary-contrast-offset and fallback to primary color if not present [#4803](https://github.com/excalidraw/excalidraw/pull/4803) - -- Add commits directly pushed to master in changelog [#4798](https://github.com/excalidraw/excalidraw/pull/4798) - -- Don't bump element version when adding files data [#4794](https://github.com/excalidraw/excalidraw/pull/4794) - -- Mobile link click [#4742](https://github.com/excalidraw/excalidraw/pull/4742) - -- ContextMenu timer & pointers not correctly reset on iOS [#4765](https://github.com/excalidraw/excalidraw/pull/4765) - -- Use absolute coords when rendering link popover [#4753](https://github.com/excalidraw/excalidraw/pull/4753) - -- Changing font size when text is not selected or edited [#4751](https://github.com/excalidraw/excalidraw/pull/4751) - -- Disable contextmenu on non-secondary `pen` events or `touch` [#4675](https://github.com/excalidraw/excalidraw/pull/4675) - -- Mobile context menu won't show on long press [#4741](https://github.com/excalidraw/excalidraw/pull/4741) - -- Do not open links twice [#4738](https://github.com/excalidraw/excalidraw/pull/4738) - -- Make link icon clickable in mobile [#4736](https://github.com/excalidraw/excalidraw/pull/4736) - -- Apple Pen missing strokes [#4705](https://github.com/excalidraw/excalidraw/pull/4705) - -- Freedraw slow movement jittery lines [#4726](https://github.com/excalidraw/excalidraw/pull/4726) - -- Disable three finger pinch zoom in penMode [#4725](https://github.com/excalidraw/excalidraw/pull/4725) - -- Remove click listener for opening popup [#4700](https://github.com/excalidraw/excalidraw/pull/4700) - -- Link popup position not accounting for offsets [#4695](https://github.com/excalidraw/excalidraw/pull/4695) - -- PenMode darkmode style [#4692](https://github.com/excalidraw/excalidraw/pull/4692) - -- Typing `_+` in wysiwyg not working [#4681](https://github.com/excalidraw/excalidraw/pull/4681) - -- Keyboard-zooming in wysiwyg should zoom canvas [#4676](https://github.com/excalidraw/excalidraw/pull/4676) - -- SceneCoordsToViewportCoords, jumping text when there is an offset [#4413](https://github.com/excalidraw/excalidraw/pull/4413) (#4630) - -- Right-click object menu displays partially off-screen [#4572](https://github.com/excalidraw/excalidraw/pull/4572) (#4631) - -- Support collaboration in bound text [#4573](https://github.com/excalidraw/excalidraw/pull/4573) - -- Cmd/ctrl native browser behavior blocked in inputs [#4589](https://github.com/excalidraw/excalidraw/pull/4589) - -- Use cached width when calculating min width during resize [#4585](https://github.com/excalidraw/excalidraw/pull/4585) - -- Support collaboration in bounded text [#4580](https://github.com/excalidraw/excalidraw/pull/4580) - -- Port for collab server and update docs [#4569](https://github.com/excalidraw/excalidraw/pull/4569) - -- Don't mutate the bounded text if not updated when submitted [#4543](https://github.com/excalidraw/excalidraw/pull/4543) - -- Prevent canvas drag while editing text [#4552](https://github.com/excalidraw/excalidraw/pull/4552) - -- Support shift+P for freedraw [#4550](https://github.com/excalidraw/excalidraw/pull/4550) - -- Prefer spreadsheet data over image [#4533](https://github.com/excalidraw/excalidraw/pull/4533) - -- Show text properties button states correctly for bounded text [#4542](https://github.com/excalidraw/excalidraw/pull/4542) - -- Rotate bounded text when container is rotated before typing [#4535](https://github.com/excalidraw/excalidraw/pull/4535) - -- Undo should work when selecting bounded textr [#4537](https://github.com/excalidraw/excalidraw/pull/4537) - -- Reduce padding to 5px for bounded text [#4530](https://github.com/excalidraw/excalidraw/pull/4530) - -- Bound text doesn't inherit container [#4521](https://github.com/excalidraw/excalidraw/pull/4521) - -- Text wrapping with grid [#4505](https://github.com/excalidraw/excalidraw/pull/4505) (#4506) - -- Check if process is defined before using so it works in browser [#4497](https://github.com/excalidraw/excalidraw/pull/4497) - -- Pending review fixes for sticky notes [#4493](https://github.com/excalidraw/excalidraw/pull/4493) - -- Pasted elements except binded text once paste action is complete [#4472](https://github.com/excalidraw/excalidraw/pull/4472) - -- Don't select binded text when ungrouping [#4470](https://github.com/excalidraw/excalidraw/pull/4470) - -- Set height correctly when text properties updated while editing in container until first submit [#4469](https://github.com/excalidraw/excalidraw/pull/4469) - -- Align and distribute binded text in container and cleanup [#4468](https://github.com/excalidraw/excalidraw/pull/4468) - -- Move binded text when moving container using keyboard [#4466](https://github.com/excalidraw/excalidraw/pull/4466) - -- Support dragging binded text in container selected in a group [#4462](https://github.com/excalidraw/excalidraw/pull/4462) - -- Vertically align single line when deleting text in bounded container [#4460](https://github.com/excalidraw/excalidraw/pull/4460) - -- Update height correctly when updating text properties in binded text [#4459](https://github.com/excalidraw/excalidraw/pull/4459) - -- Align library item previews to center [#4447](https://github.com/excalidraw/excalidraw/pull/4447) - -- Vertically center align text when text deleted [#4457](https://github.com/excalidraw/excalidraw/pull/4457) - -- Vertically center the first line as user starts typing in container [#4454](https://github.com/excalidraw/excalidraw/pull/4454) - -- Switch cursor to center of container when adding text when dimensions are too small [#4452](https://github.com/excalidraw/excalidraw/pull/4452) - -- Vertically center align the bounded text correctly when zoomed [#4444](https://github.com/excalidraw/excalidraw/pull/4444) - -- Support updating stroke color for text by typing in color picker input [#4415](https://github.com/excalidraw/excalidraw/pull/4415) - -- Bound text not atomic with container when changing z-index [#4414](https://github.com/excalidraw/excalidraw/pull/4414) - -- Update viewport coords correctly when editing text [#4416](https://github.com/excalidraw/excalidraw/pull/4416) - -- Use word-break break-word only and update text editor height only when binded to container [#4410](https://github.com/excalidraw/excalidraw/pull/4410) - -- Husky not able to execute pre-commit on windows [#4370](https://github.com/excalidraw/excalidraw/pull/4370) - -- Make firebase config parsing not fail on undefined env [#4381](https://github.com/excalidraw/excalidraw/pull/4381) - -- Adding to library via contextmenu when no image is selected [#4356](https://github.com/excalidraw/excalidraw/pull/4356) - -- Export scale quality regression [#4316](https://github.com/excalidraw/excalidraw/pull/4316) - -- Remove `100%` height from tooltip container to fix layout issues [#3980](https://github.com/excalidraw/excalidraw/pull/3980) - -- Inline ENV variables when building excalidraw package [#4311](https://github.com/excalidraw/excalidraw/pull/4311) - -- SVG export in dark mode with embedded bitmap image [#4285](https://github.com/excalidraw/excalidraw/pull/4285) - -- New FS API not working on Linux [#4280](https://github.com/excalidraw/excalidraw/pull/4280) - -- Url -> URL for consistency [#4277](https://github.com/excalidraw/excalidraw/pull/4277) - -- Prevent adding images to library via contextMenu [#4264](https://github.com/excalidraw/excalidraw/pull/4264) - -- Account for libraries v2 when prompting [#4263](https://github.com/excalidraw/excalidraw/pull/4263) - -- Skia rendering issues [#4200](https://github.com/excalidraw/excalidraw/pull/4200) - -- Ellipse roughness when `0` [#4194](https://github.com/excalidraw/excalidraw/pull/4194) - -- Proper string for invalid SVG [#4191](https://github.com/excalidraw/excalidraw/pull/4191) - -- Images not initialized correctly [#4157](https://github.com/excalidraw/excalidraw/pull/4157) - -- Image-related fixes [#4147](https://github.com/excalidraw/excalidraw/pull/4147) - -- Rewrite collab element reconciliation to fix z-index issues [#4076](https://github.com/excalidraw/excalidraw/pull/4076) - -- Redirect excalidraw.com/about to for-webex.excalidraw.com [#4104](https://github.com/excalidraw/excalidraw/pull/4104) - -- Redirect to webex LP instead of rewrite to fix SW [#4103](https://github.com/excalidraw/excalidraw/pull/4103) - -- Clear image/shape cache of affected elements when adding files [#4089](https://github.com/excalidraw/excalidraw/pull/4089) - -- Clear `LibraryUnit` DOM on unmount [#4084](https://github.com/excalidraw/excalidraw/pull/4084) - -- Pasting images on firefox [#4085](https://github.com/excalidraw/excalidraw/pull/4085) - -### Refactor - -- Simplify zoom by removing `zoom.translation` [#4477](https://github.com/excalidraw/excalidraw/pull/4477) - -- Deduplicate encryption helpers [#4146](https://github.com/excalidraw/excalidraw/pull/4146) - -### Performance - -- Cache approx line height in textwysiwg [#4651](https://github.com/excalidraw/excalidraw/pull/4651) - -### Build - -- Rename release command to 'release package' [#4783](https://github.com/excalidraw/excalidraw/pull/4783) - -- Deploy excalidraw package example [#4762](https://github.com/excalidraw/excalidraw/pull/4762) - -- Allow package.json changes when autoreleasing next [#4068](https://github.com/excalidraw/excalidraw/pull/4068) - ---- +Check out the [release notes](https://github.com/excalidraw/excalidraw/releases/tag/v0.11.0) ## 0.10.0 (2021-10-13) -## Excalidraw API - -### Fixes - -- Don't show save file to disk button in export dialog when `saveFileToDisk` passed as `false` in [`UIOptions.canvasActions.export`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportOpts) [#4073](https://github.com/excalidraw/excalidraw/pull/4073). - -- [`onPaste`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPaste) prop should return false to prevent the native excalidraw paste action [#3974](https://github.com/excalidraw/excalidraw/pull/3974). - - #### BREAKING CHANGE - - - Earlier the paste action was prevented when the prop [`onPaste`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPaste) returned true, but now it should return false to prevent the paste action. This was done to make it semantically more correct and intuitive. - -### Build - -- Enable jsx transform in webpack [#4049](https://github.com/excalidraw/excalidraw/pull/4049) - -### Docs - -- Correct exportToBackend in README to onExportToBackend [#3952](https://github.com/excalidraw/excalidraw/pull/3952) - -## Excalidraw Library - -**_This section lists the updates made to the excalidraw library and will not affect the integration._** - -### Features - -- Improve freedraw shape [#3984](https://github.com/excalidraw/excalidraw/pull/3984) - -- Make color ARIA labels better [#3871](https://github.com/excalidraw/excalidraw/pull/3871) - -- Add origin trial tokens [#3853](https://github.com/excalidraw/excalidraw/pull/3853) - -- Re-order zoom buttons [#3837](https://github.com/excalidraw/excalidraw/pull/3837) - -- Add undo/redo buttons & tweak footer [#3832](https://github.com/excalidraw/excalidraw/pull/3832) - -- Resave to png/svg with metadata if you loaded your scene from a png/svg file [#3645](https://github.com/excalidraw/excalidraw/pull/3645) - -### Fixes - -- Abstract and fix legacy fs [#4032](https://github.com/excalidraw/excalidraw/pull/4032) - -- Context menu positioning [#4025](https://github.com/excalidraw/excalidraw/pull/4025) - -- Added alert for bad encryption key [#3998](https://github.com/excalidraw/excalidraw/pull/3998) - -- OnPaste should return false to prevent paste action [#3974](https://github.com/excalidraw/excalidraw/pull/3974) - -- Help-icon now visible on Safari [#3939](https://github.com/excalidraw/excalidraw/pull/3939) - -- Permanent zoom mode [#3931](https://github.com/excalidraw/excalidraw/pull/3931) - -- Undo/redo buttons gap in Safari [#3836](https://github.com/excalidraw/excalidraw/pull/3836) - -- Prevent gradual canvas misalignment [#3833](https://github.com/excalidraw/excalidraw/pull/3833) - -- Color picker shortcuts not working when elements selected [#3817](https://github.com/excalidraw/excalidraw/pull/3817) - ---- +Check out the [release notes](https://github.com/excalidraw/excalidraw/releases/tag/v0.10.0) ## 0.9.0 (2021-07-10) -## Excalidraw API - -### Features - -- [`restore(data, localAppState, localElements)`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restore) and [`restoreElements(elements, localElements)`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) now take `localElements` argument which will be used to ensure existing elements' versions are used and incremented. This fixes an issue where importing the same file would resolve to elements with older versions, potentially causing issues when reconciling [#3797](https://github.com/excalidraw/excalidraw/pull/3797). - - #### BREAKING CHANGE - - - `localElements` argument is mandatory (can be `null`/`undefined`) if using TypeScript. - -- Support `appState.exportEmbedScene` attribute in [`exportToSvg`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToSvg) which allows to embed the scene data [#3777](https://github.com/excalidraw/excalidraw/pull/3777). - - #### BREAKING CHANGE - - - The attribute `metadata` is now removed as `metadata` was only used to embed scene data which is now supported with the `appState.exportEmbedScene` attribute. - - [`exportToSvg`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToSvg) now resolves to a promise which resolves to `svg` of the exported drawing. - -- Expose [`loadLibraryFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadLibraryFromBlobY), [`loadFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadFromBlob), and [`getFreeDrawSvgPath`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#getFreeDrawSvgPath) [#3764](https://github.com/excalidraw/excalidraw/pull/3764). - -- Expose [`FONT_FAMILY`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#FONT_FAMILY) so that consumer can use when passing `initialData.appState.currentItemFontFamily` [#3710](https://github.com/excalidraw/excalidraw/pull/3710). - -- Added prop [`autoFocus`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#autoFocus) to focus the excalidraw component on page load when enabled, defaults to false [#3691](https://github.com/excalidraw/excalidraw/pull/3691). - - Note: Earlier Excalidraw component was focused by default on page load, you need to enable `autoFocus` prop to retain the same behaviour. - -- Added prop `UIOptions.canvasActions.export.renderCustomUI` to support Custom UI rendering inside export dialog [#3666](https://github.com/excalidraw/excalidraw/pull/3666). -- Added prop `UIOptions.canvasActions.saveAsImage` to show/hide the **Save as image** button in the canvas actions. Defaults to `true` hence the **Save as Image** button is rendered [#3662](https://github.com/excalidraw/excalidraw/pull/3662). - -- Export dialog can be customised with [`UiOptions.canvasActions.export`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportOpts) [#3658](https://github.com/excalidraw/excalidraw/pull/3658). - - Also, [`UIOptions`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#UIOptions) is now memoized to avoid unnecessary rerenders. - - #### BREAKING CHANGE - - - `UIOptions.canvasActions.saveAsScene` is now renamed to `UiOptions.canvasActions.export.saveFileToDisk`. Defaults to `true` hence the **save file to disk** button is rendered inside the export dialog. - - `exportToBackend` is now renamed to `UIOptions.canvasActions.export.exportToBackend`. If this prop is not passed, the **shareable-link** button will not be rendered, same as before. - -### Fixes - -- Use excalidraw Id in elements so every element has unique id [#3696](https://github.com/excalidraw/excalidraw/pull/3696). - -### Refactor - -- #### BREAKING CHANGE - - Rename `UIOptions.canvasActions.saveScene` to `UIOptions.canvasActions.saveToActiveFile`[#3657](https://github.com/excalidraw/excalidraw/pull/3657). - - Removed `shouldAddWatermark: boolean` attribute from options for [export](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#export-utilities) APIs [#3639](https://github.com/excalidraw/excalidraw/pull/3639). - - Removed `appState.shouldAddWatermark` so in case you were passing `shouldAddWatermark` in [initialData.AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L42) it will not work anymore. - -## Excalidraw Library - -**_This section lists the updates made to the excalidraw library and will not affect the integration._** - -### Features - -- Switch to selection tool on library item insert [#3773](https://github.com/excalidraw/excalidraw/pull/3773) - -- Show active file name when saving to current file [#3733](https://github.com/excalidraw/excalidraw/pull/3733) - -- Add hint around text editing [#3708](https://github.com/excalidraw/excalidraw/pull/3708) - -- Change library icon to be more clear [#3583](https://github.com/excalidraw/excalidraw/pull/3583) - -- Pass current `theme` when installing libraries [#3701](https://github.com/excalidraw/excalidraw/pull/3701) - -- Update virgil font [#3692](https://github.com/excalidraw/excalidraw/pull/3692) - -- Support exporting json to excalidraw plus [#3678](https://github.com/excalidraw/excalidraw/pull/3678) - -- Save exportScale in AppState [#3580](https://github.com/excalidraw/excalidraw/pull/3580) - -- Add shortcuts for stroke and background color picker [#3318](https://github.com/excalidraw/excalidraw/pull/3318) - -- Exporting redesign [#3613](https://github.com/excalidraw/excalidraw/pull/3613) - -- Auto-position tooltip and support overflowing container [#3631](https://github.com/excalidraw/excalidraw/pull/3631) - -- Auto release @excalidraw/excalidraw-next on every change [#3614](https://github.com/excalidraw/excalidraw/pull/3614) - -- Allow inner-drag-selecting with cmd/ctrl [#3603](https://github.com/excalidraw/excalidraw/pull/3603) - -### Fixes - -- view mode cursor adjustments [#3809](https://github.com/excalidraw/excalidraw/pull/3809). - -- Pass next release to updatePackageVersion & replace ## unreleased with new version [#3806](https://github.com/excalidraw/excalidraw/pull/3806) - -- Include deleted elements when passing to restore [#3802](https://github.com/excalidraw/excalidraw/pull/3802) - -- Import React before using jsx [#3804](https://github.com/excalidraw/excalidraw/pull/3804) - -- Ensure `s` and `g` shortcuts work on no selection [#3800](https://github.com/excalidraw/excalidraw/pull/3800) - -- Keep binding for attached arrows after changing text [#3754](https://github.com/excalidraw/excalidraw/pull/3754) - -- Deselect elements on viewMode toggle [#3741](https://github.com/excalidraw/excalidraw/pull/3741) - -- Allow pointer events for disable zen mode button [#3743](https://github.com/excalidraw/excalidraw/pull/3743) - -- Use rgba instead of shorthand alpha [#3688](https://github.com/excalidraw/excalidraw/pull/3688) - -- Color pickers not opening on mobile [#3676](https://github.com/excalidraw/excalidraw/pull/3676) - -- On contextMenu, use selected element regardless of z-index [#3668](https://github.com/excalidraw/excalidraw/pull/3668) - -- SelectedGroupIds not being stored in history [#3630](https://github.com/excalidraw/excalidraw/pull/3630) - -- Overscroll on touch devices [#3663](https://github.com/excalidraw/excalidraw/pull/3663) - -- Small UI issues around image export dialog [#3642](https://github.com/excalidraw/excalidraw/pull/3642) - -- Normalize linear element points on restore [#3633](https://github.com/excalidraw/excalidraw/pull/3633) - -- Disable pointer-events on footer-center container [#3629](https://github.com/excalidraw/excalidraw/pull/3629) - -### Refactor - -- Delete React SyntheticEvent persist [#3700](https://github.com/excalidraw/excalidraw/pull/3700) - -- Code clean up [#3681](https://github.com/excalidraw/excalidraw/pull/3681) - -### Performance - -- Improve arrow head sizing [#3480](https://github.com/excalidraw/excalidraw/pull/3480) - -### Build - -- Add release script to update relevant files and commit for next release [#3805](https://github.com/excalidraw/excalidraw/pull/3805) - -- Add script to update changelog before a stable release [#3784](https://github.com/excalidraw/excalidraw/pull/3784) - -- Add script to update readme before stable release [#3781](https://github.com/excalidraw/excalidraw/pull/3781) - ---- +Check out the [release notes](https://github.com/excalidraw/excalidraw/releases/tag/v0.9.0) ## 0.8.0 (2021-05-15) From 6a6b9c90a788b67c0208049abc67172a6637196e Mon Sep 17 00:00:00 2001 From: David Luzar Date: Tue, 13 Sep 2022 21:19:57 +0200 Subject: [PATCH 07/11] fix: revert webpack deduping to fix `@next` runtime (#5695) Revert "chore: Dedupe webpack configs. (#5449)" This reverts commit da4fa91ffc2bf9b1b74c9fec8164348273a9cad2. --- src/packages/common.webpack.dev.config.js | 88 ------------- src/packages/common.webpack.prod.config.js | 119 ------------------ src/packages/excalidraw/webpack.dev.config.js | 89 ++++++++++++- .../excalidraw/webpack.prod.config.js | 112 ++++++++++++++++- src/packages/utils/webpack.prod.config.js | 49 +++++++- 5 files changed, 234 insertions(+), 223 deletions(-) delete mode 100644 src/packages/common.webpack.dev.config.js delete mode 100644 src/packages/common.webpack.prod.config.js diff --git a/src/packages/common.webpack.dev.config.js b/src/packages/common.webpack.dev.config.js deleted file mode 100644 index 0ee82688c..000000000 --- a/src/packages/common.webpack.dev.config.js +++ /dev/null @@ -1,88 +0,0 @@ -const path = require("path"); -const autoprefixer = require("autoprefixer"); -const webpack = require("webpack"); -const { parseEnvVariables } = require(path.resolve(global.__childdir, "./env")); - -module.exports = { - mode: "development", - devtool: false, - output: { - libraryTarget: "umd", - filename: "[name].js", - publicPath: "", - }, - resolve: { - extensions: [".js", ".ts", ".tsx", ".css", ".scss"], - }, - module: { - rules: [ - { - test: /\.(sa|sc|c)ss$/, - exclude: /node_modules/, - use: [ - "style-loader", - { loader: "css-loader" }, - { - loader: "postcss-loader", - options: { - postcssOptions: { - plugins: [autoprefixer()], - }, - }, - }, - "sass-loader", - ], - }, - { - test: /\.(ts|tsx|js|jsx|mjs)$/, - exclude: /node_modules\/(?!browser-fs-access)/, - use: [ - { - loader: "ts-loader", - options: { - transpileOnly: true, - configFile: path.resolve(__dirname, "./tsconfig.dev.json"), - }, - }, - ], - }, - { - test: /\.(woff|woff2|eot|ttf|otf)$/, - type: "asset/resource", - }, - ], - }, - optimization: { - splitChunks: { - chunks: "async", - cacheGroups: { - vendors: { - test: /[\\/]node_modules[\\/]/, - name: "vendor", - }, - }, - }, - }, - plugins: [ - new webpack.EvalSourceMapDevToolPlugin({ exclude: /vendor/ }), - new webpack.DefinePlugin({ - "process.env": parseEnvVariables( - path.resolve(__dirname, "../../.env.development"), - ), - }), - ], - externals: { - react: { - root: "React", - commonjs2: "react", - commonjs: "react", - amd: "react", - }, - "react-dom": { - root: "ReactDOM", - commonjs2: "react-dom", - commonjs: "react-dom", - amd: "react-dom", - }, - }, -}; diff --git a/src/packages/common.webpack.prod.config.js b/src/packages/common.webpack.prod.config.js deleted file mode 100644 index f0cca725d..000000000 --- a/src/packages/common.webpack.prod.config.js +++ /dev/null @@ -1,119 +0,0 @@ -const path = require("path"); -const autoprefixer = require("autoprefixer"); -const webpack = require("webpack"); -const BundleAnalyzerPlugin = require(path.resolve( - path.join(global.__childdir, "node_modules"), - "webpack-bundle-analyzer", -)).BundleAnalyzerPlugin; -const TerserPlugin = require("terser-webpack-plugin"); -const { parseEnvVariables } = - "__noenv" in global ? {} : require(path.resolve(global.__childdir, "./env")); - -module.exports = { - mode: "production", - output: { - libraryTarget: "umd", - filename: "[name].js", - publicPath: "", - }, - resolve: { - extensions: [".js", ".ts", ".tsx", ".css", ".scss"], - }, - module: { - rules: [ - { - test: /\.(sa|sc|c)ss$/, - exclude: /node_modules/, - use: [ - "style-loader", - { - loader: "css-loader", - }, - { - loader: "postcss-loader", - options: { - postcssOptions: { - plugins: [autoprefixer()], - }, - }, - }, - "sass-loader", - ], - }, - { - test: /\.(ts|tsx|js|jsx|mjs)$/, - exclude: /node_modules\/(?!browser-fs-access)/, - use: [ - { - loader: "ts-loader", - options: { - transpileOnly: true, - configFile: path.resolve(__dirname, "./tsconfig.prod.json"), - }, - }, - { - loader: "babel-loader", - options: { - presets: [ - "@babel/preset-env", - ["@babel/preset-react", { runtime: "automatic" }], - "@babel/preset-typescript", - ], - plugins: [ - "transform-class-properties", - "@babel/plugin-transform-runtime", - ], - }, - }, - ], - }, - { - test: /\.(woff|woff2|eot|ttf|otf)$/, - type: "asset/resource", - }, - ], - }, - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - test: /\.js($|\?)/i, - }), - ], - splitChunks: { - chunks: "async", - cacheGroups: { - vendors: { - test: /[\\/]node_modules[\\/]/, - name: "vendor", - }, - }, - }, - }, - plugins: [ - ...(process.env.ANALYZER === "true" ? [new BundleAnalyzerPlugin()] : []), - ...("__noenv" in global - ? [] - : [ - new webpack.DefinePlugin({ - "process.env": parseEnvVariables( - path.resolve(__dirname, "../../.env.production"), - ), - }), - ]), - ], - externals: { - react: { - root: "React", - commonjs2: "react", - commonjs: "react", - amd: "react", - }, - "react-dom": { - root: "ReactDOM", - commonjs2: "react-dom", - commonjs: "react-dom", - amd: "react-dom", - }, - }, -}; diff --git a/src/packages/excalidraw/webpack.dev.config.js b/src/packages/excalidraw/webpack.dev.config.js index 5b535186f..9e8180f5f 100644 --- a/src/packages/excalidraw/webpack.dev.config.js +++ b/src/packages/excalidraw/webpack.dev.config.js @@ -1,18 +1,97 @@ -global.__childdir = __dirname; const path = require("path"); -const { merge } = require("webpack-merge"); -const commonConfig = require("../common.webpack.dev.config"); +const webpack = require("webpack"); +const autoprefixer = require("autoprefixer"); +const { parseEnvVariables } = require("./env"); const outputDir = process.env.EXAMPLE === "true" ? "example/public" : "dist"; -const config = { +module.exports = { + mode: "development", + devtool: false, entry: { "excalidraw.development": "./entry.js", }, output: { path: path.resolve(__dirname, outputDir), library: "ExcalidrawLib", + libraryTarget: "umd", + filename: "[name].js", chunkFilename: "excalidraw-assets-dev/[name]-[contenthash].js", assetModuleFilename: "excalidraw-assets-dev/[name][ext]", + + publicPath: "", + }, + resolve: { + extensions: [".js", ".ts", ".tsx", ".css", ".scss"], + }, + module: { + rules: [ + { + test: /\.(sa|sc|c)ss$/, + exclude: /node_modules/, + use: [ + "style-loader", + { loader: "css-loader" }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: [autoprefixer()], + }, + }, + }, + "sass-loader", + ], + }, + { + test: /\.(ts|tsx|js|jsx|mjs)$/, + exclude: /node_modules\/(?!browser-fs-access)/, + use: [ + { + loader: "ts-loader", + options: { + transpileOnly: true, + configFile: path.resolve(__dirname, "../tsconfig.dev.json"), + }, + }, + ], + }, + { + test: /\.(woff|woff2|eot|ttf|otf)$/, + type: "asset/resource", + }, + ], + }, + optimization: { + splitChunks: { + chunks: "async", + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + }, + }, + }, + }, + plugins: [ + new webpack.EvalSourceMapDevToolPlugin({ exclude: /vendor/ }), + new webpack.DefinePlugin({ + "process.env": parseEnvVariables( + path.resolve(__dirname, "../../../.env.development"), + ), + }), + ], + externals: { + react: { + root: "React", + commonjs2: "react", + commonjs: "react", + amd: "react", + }, + "react-dom": { + root: "ReactDOM", + commonjs2: "react-dom", + commonjs: "react-dom", + amd: "react-dom", + }, }, }; -module.exports = merge(commonConfig, config); diff --git a/src/packages/excalidraw/webpack.prod.config.js b/src/packages/excalidraw/webpack.prod.config.js index 593287076..0450d36fa 100644 --- a/src/packages/excalidraw/webpack.prod.config.js +++ b/src/packages/excalidraw/webpack.prod.config.js @@ -1,17 +1,119 @@ -global.__childdir = __dirname; const path = require("path"); -const { merge } = require("webpack-merge"); -const commonConfig = require("../common.webpack.prod.config"); +const TerserPlugin = require("terser-webpack-plugin"); +const BundleAnalyzerPlugin = + require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const autoprefixer = require("autoprefixer"); +const webpack = require("webpack"); +const { parseEnvVariables } = require("./env"); -const config = { +module.exports = { + mode: "production", entry: { "excalidraw.production.min": "./entry.js", }, output: { path: path.resolve(__dirname, "dist"), library: "ExcalidrawLib", + libraryTarget: "umd", + filename: "[name].js", chunkFilename: "excalidraw-assets/[name]-[contenthash].js", assetModuleFilename: "excalidraw-assets/[name][ext]", + publicPath: "", + }, + resolve: { + extensions: [".js", ".ts", ".tsx", ".css", ".scss"], + }, + module: { + rules: [ + { + test: /\.(sa|sc|c)ss$/, + exclude: /node_modules/, + use: [ + "style-loader", + { + loader: "css-loader", + }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: [autoprefixer()], + }, + }, + }, + "sass-loader", + ], + }, + { + test: /\.(ts|tsx|js|jsx|mjs)$/, + exclude: /node_modules\/(?!browser-fs-access)/, + use: [ + { + loader: "ts-loader", + options: { + transpileOnly: true, + configFile: path.resolve(__dirname, "../tsconfig.prod.json"), + }, + }, + { + loader: "babel-loader", + options: { + presets: [ + "@babel/preset-env", + ["@babel/preset-react", { runtime: "automatic" }], + "@babel/preset-typescript", + ], + plugins: [ + "transform-class-properties", + "@babel/plugin-transform-runtime", + ], + }, + }, + ], + }, + { + test: /\.(woff|woff2|eot|ttf|otf)$/, + type: "asset/resource", + }, + ], + }, + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + test: /\.js($|\?)/i, + }), + ], + splitChunks: { + chunks: "async", + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + }, + }, + }, + }, + plugins: [ + ...(process.env.ANALYZER === "true" ? [new BundleAnalyzerPlugin()] : []), + new webpack.DefinePlugin({ + "process.env": parseEnvVariables( + path.resolve(__dirname, "../../../.env.production"), + ), + }), + ], + externals: { + react: { + root: "React", + commonjs2: "react", + commonjs: "react", + amd: "react", + }, + "react-dom": { + root: "ReactDOM", + commonjs2: "react-dom", + commonjs: "react-dom", + amd: "react-dom", + }, }, }; -module.exports = merge(commonConfig, config); diff --git a/src/packages/utils/webpack.prod.config.js b/src/packages/utils/webpack.prod.config.js index 58a6bbf08..7238ad029 100644 --- a/src/packages/utils/webpack.prod.config.js +++ b/src/packages/utils/webpack.prod.config.js @@ -1,23 +1,60 @@ -global.__childdir = __dirname; -global.__noenv = true; const webpack = require("webpack"); const path = require("path"); -const { merge } = require("webpack-merge"); -const commonConfig = require("../common.webpack.prod.config"); +const BundleAnalyzerPlugin = + require("webpack-bundle-analyzer").BundleAnalyzerPlugin; -const config = { +module.exports = { + mode: "production", entry: { "excalidraw-utils.min": "./index.js" }, output: { path: path.resolve(__dirname, "dist"), + filename: "[name].js", library: "ExcalidrawUtils", + libraryTarget: "umd", + }, + resolve: { + extensions: [".tsx", ".ts", ".js", ".css", ".scss"], }, optimization: { runtimeChunk: false, }, + module: { + rules: [ + { + test: /\.(sa|sc|c)ss$/, + exclude: /node_modules/, + use: ["style-loader", { loader: "css-loader" }, "sass-loader"], + }, + { + test: /\.(ts|tsx|js)$/, + use: [ + { + loader: "ts-loader", + options: { + transpileOnly: true, + configFile: path.resolve(__dirname, "../tsconfig.prod.json"), + }, + }, + { + loader: "babel-loader", + + options: { + presets: [ + "@babel/preset-env", + ["@babel/preset-react", { runtime: "automatic" }], + "@babel/preset-typescript", + ], + plugins: [["@babel/plugin-transform-runtime"]], + }, + }, + ], + }, + ], + }, plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, }), + ...(process.env.ANALYZER === "true" ? [new BundleAnalyzerPlugin()] : []), ], }; -module.exports = merge(commonConfig, config); From c5869979c828bac1d2d3ceb810d4e20de8af7062 Mon Sep 17 00:00:00 2001 From: Seunghyun oh Date: Wed, 14 Sep 2022 15:45:35 +0900 Subject: [PATCH 08/11] chore: fix typo in clipboard alert (#5693) chore: fix typo --- src/packages/excalidraw/example/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx index 272120c1a..5d97bb484 100644 --- a/src/packages/excalidraw/example/App.tsx +++ b/src/packages/excalidraw/example/App.tsx @@ -282,7 +282,7 @@ export default function App() { files: excalidrawAPI?.getFiles(), type, }); - window.alert(`Copied to clipboard as ${type} sucessfully`); + window.alert(`Copied to clipboard as ${type} successfully`); }; const [pointerData, setPointerData] = useState<{ From 0d1058a596145894b703fe3078b574f83bfbb312 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 14 Sep 2022 19:55:54 +0530 Subject: [PATCH 09/11] feat: support segment midpoints in line editor (#5641) * feat: support segment midpoints in line editor * fix tests * midpoints working in bezier curve * midpoint working with non zero roughness * calculate beizer curve control points for points >2 * unnecessary rerender * don't show phantom points inside editor for short segments * don't show phantom points for small curves * improve the algo for plotting midpoints on bezier curve by taking arc lengths and doing binary search * fix tests finally * fix naming * cache editor midpoints * clear midpoint cache when undo * fix caching * calculate index properly when not all segments have midpoints * make sure correct element version is fetched from cache * chore * fix * direct comparison for equal points * create arePointsEqual util * upate name * don't update cache except inside getter * don't compute midpoints outside editor unless 2pointer lines * update cache to object and burst when Zoom updated as well * early return if midpoints not present outside editor * don't early return * cleanup * Add specs * fix --- src/components/App.tsx | 37 ++- src/element/linearElementEditor.ts | 299 ++++++++++++++---- src/math.ts | 166 +++++++++- src/renderer/renderScene.ts | 63 ++-- .../linearElementEditor.test.tsx.snap | 60 ++++ .../regressionTests.test.tsx.snap | 8 +- src/tests/linearElementEditor.test.tsx | 146 +++++++++ 7 files changed, 666 insertions(+), 113 deletions(-) create mode 100644 src/tests/__snapshots__/linearElementEditor.test.tsx.snap create mode 100644 src/tests/linearElementEditor.test.tsx diff --git a/src/components/App.tsx b/src/components/App.tsx index 91a6cbe7f..0ecbe27e4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -2718,18 +2718,23 @@ class App extends React.Component { event, scenePointerX, scenePointerY, - this.state.editingLinearElement, - this.state.gridSize, + this.state, ); - if (editingLinearElement !== this.state.editingLinearElement) { + + if ( + editingLinearElement && + editingLinearElement !== this.state.editingLinearElement + ) { // Since we are reading from previous state which is not possible with // automatic batching in React 18 hence using flush sync to synchronously // update the state. Check https://github.com/excalidraw/excalidraw/pull/5508 for more details. flushSync(() => { - this.setState({ editingLinearElement }); + this.setState({ + editingLinearElement, + }); }); } - if (editingLinearElement.lastUncommittedPoint != null) { + if (editingLinearElement?.lastUncommittedPoint != null) { this.maybeSuggestBindingAtCursor(scenePointer); } else { this.setState({ suggestedBindings: [] }); @@ -3058,7 +3063,7 @@ class App extends React.Component { } if (this.state.selectedLinearElement) { let hoverPointIndex = -1; - let midPointHovered = false; + let segmentMidPointHoveredCoords = null; if ( isHittingElementNotConsideringBoundingBox(element, this.state, [ scenePointerX, @@ -3071,13 +3076,14 @@ class App extends React.Component { scenePointerX, scenePointerY, ); - midPointHovered = LinearElementEditor.isHittingMidPoint( - linearElementEditor, - { x: scenePointerX, y: scenePointerY }, - this.state, - ); + segmentMidPointHoveredCoords = + LinearElementEditor.getSegmentMidpointHitCoords( + linearElementEditor, + { x: scenePointerX, y: scenePointerY }, + this.state, + ); - if (hoverPointIndex >= 0 || midPointHovered) { + if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) { setCursor(this.canvas, CURSOR_TYPE.POINTER); } else { setCursor(this.canvas, CURSOR_TYPE.MOVE); @@ -3106,12 +3112,15 @@ class App extends React.Component { } if ( - this.state.selectedLinearElement.midPointHovered !== midPointHovered + !LinearElementEditor.arePointsEqual( + this.state.selectedLinearElement.segmentMidPointHoveredCoords, + segmentMidPointHoveredCoords, + ) ) { this.setState({ selectedLinearElement: { ...this.state.selectedLinearElement, - midPointHovered, + segmentMidPointHoveredCoords, }, }); } diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index 48972a823..df9157486 100644 --- a/src/element/linearElementEditor.ts +++ b/src/element/linearElementEditor.ts @@ -12,6 +12,11 @@ import { getGridPoint, rotatePoint, centerPoint, + getControlPointsForBezierCurve, + getBezierXY, + getBezierCurveLength, + mapIntervalToBezierT, + arePointsEqual, } from "../math"; import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from "."; import { getElementPointsCoords } from "./bounds"; @@ -29,6 +34,12 @@ import { tupleToCoors } from "../utils"; import { isBindingElement } from "./typeChecks"; import { shouldRotateWithDiscreteAngle } from "../keys"; +const editorMidPointsCache: { + version: number | null; + points: (Point | null)[]; + zoom: number | null; +} = { version: null, points: [], zoom: null }; + export class LinearElementEditor { public readonly elementId: ExcalidrawElement["id"] & { _brand: "excalidrawLinearElementId"; @@ -52,7 +63,7 @@ export class LinearElementEditor { | "keep"; public readonly endBindingElement: ExcalidrawBindableElement | null | "keep"; public readonly hoverPointIndex: number; - public readonly midPointHovered: boolean; + public readonly segmentMidPointHoveredCoords: Point | null; constructor(element: NonDeleted, scene: Scene) { this.elementId = element.id as string & { @@ -72,7 +83,7 @@ export class LinearElementEditor { lastClickedPoint: -1, }; this.hoverPointIndex = -1; - this.midPointHovered = false; + this.segmentMidPointHoveredCoords = null; } // --------------------------------------------------------------------------- @@ -80,7 +91,6 @@ export class LinearElementEditor { // --------------------------------------------------------------------------- static POINT_HANDLE_SIZE = 10; - /** * @param id the `elementId` from the instance of this class (so that we can * statically guarantee this method returns an ExcalidrawLinearElement) @@ -359,7 +369,60 @@ export class LinearElementEditor { }; } - static isHittingMidPoint = ( + static getEditorMidPoints = ( + element: NonDeleted, + appState: AppState, + ): typeof editorMidPointsCache["points"] => { + // Since its not needed outside editor unless 2 pointer lines + if (!appState.editingLinearElement && element.points.length > 2) { + return []; + } + if ( + editorMidPointsCache.version === element.version && + editorMidPointsCache.zoom === appState.zoom.value + ) { + return editorMidPointsCache.points; + } + LinearElementEditor.updateEditorMidPointsCache(element, appState); + return editorMidPointsCache.points!; + }; + + static updateEditorMidPointsCache = ( + element: NonDeleted, + appState: AppState, + ) => { + const points = LinearElementEditor.getPointsGlobalCoordinates(element); + + let index = 0; + const midpoints: (Point | null)[] = []; + while (index < points.length - 1) { + if ( + LinearElementEditor.isSegmentTooShort( + element, + element.points[index], + element.points[index + 1], + appState.zoom, + ) + ) { + midpoints.push(null); + index++; + continue; + } + const segmentMidPoint = LinearElementEditor.getSegmentMidPoint( + element, + points[index], + points[index + 1], + index + 1, + ); + midpoints.push(segmentMidPoint); + index++; + } + editorMidPointsCache.points = midpoints; + editorMidPointsCache.version = element.version; + editorMidPointsCache.zoom = appState.zoom.value; + }; + + static getSegmentMidpointHitCoords = ( linearElementEditor: LinearElementEditor, scenePointer: { x: number; y: number }, appState: AppState, @@ -367,7 +430,7 @@ export class LinearElementEditor { const { elementId } = linearElementEditor; const element = LinearElementEditor.getElement(elementId); if (!element) { - return false; + return null; } const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor( element, @@ -376,37 +439,125 @@ export class LinearElementEditor { scenePointer.y, ); if (clickedPointIndex >= 0) { - return false; - } - const points = LinearElementEditor.getPointsGlobalCoordinates(element); - if (points.length >= 3) { - return false; - } - - const midPoint = LinearElementEditor.getMidPoint(linearElementEditor); - if (midPoint) { - const threshold = - LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value; - const distance = distance2d( - midPoint[0], - midPoint[1], - scenePointer.x, - scenePointer.y, - ); - return distance <= threshold; - } - return false; - }; - - static getMidPoint(linearElementEditor: LinearElementEditor) { - const { elementId } = linearElementEditor; - const element = LinearElementEditor.getElement(elementId); - if (!element) { return null; } const points = LinearElementEditor.getPointsGlobalCoordinates(element); + if (points.length >= 3 && !appState.editingLinearElement) { + return null; + } - return centerPoint(points[0], points.at(-1)!); + const threshold = + LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value; + + const existingSegmentMidpointHitCoords = + linearElementEditor.segmentMidPointHoveredCoords; + if (existingSegmentMidpointHitCoords) { + const distance = distance2d( + existingSegmentMidpointHitCoords[0], + existingSegmentMidpointHitCoords[1], + scenePointer.x, + scenePointer.y, + ); + if (distance <= threshold) { + return existingSegmentMidpointHitCoords; + } + } + let index = 0; + const midPoints: typeof editorMidPointsCache["points"] = + LinearElementEditor.getEditorMidPoints(element, appState); + while (index < midPoints.length) { + if (midPoints[index] !== null) { + const distance = distance2d( + midPoints[index]![0], + midPoints[index]![1], + scenePointer.x, + scenePointer.y, + ); + if (distance <= threshold) { + return midPoints[index]; + } + } + + index++; + } + return null; + }; + + static isSegmentTooShort( + element: NonDeleted, + startPoint: Point, + endPoint: Point, + zoom: AppState["zoom"], + ) { + let distance = distance2d( + startPoint[0], + startPoint[1], + endPoint[0], + endPoint[1], + ); + if (element.points.length > 2 && element.strokeSharpness === "round") { + distance = getBezierCurveLength(element, endPoint); + } + + return distance * zoom.value < LinearElementEditor.POINT_HANDLE_SIZE * 4; + } + + static getSegmentMidPoint( + element: NonDeleted, + startPoint: Point, + endPoint: Point, + endPointIndex: number, + ) { + let segmentMidPoint = centerPoint(startPoint, endPoint); + if (element.points.length > 2 && element.strokeSharpness === "round") { + const controlPoints = getControlPointsForBezierCurve( + element, + element.points[endPointIndex], + ); + if (controlPoints) { + const t = mapIntervalToBezierT( + element, + element.points[endPointIndex], + 0.5, + ); + + const [tx, ty] = getBezierXY( + controlPoints[0], + controlPoints[1], + controlPoints[2], + controlPoints[3], + t, + ); + segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates( + element, + [tx, ty], + ); + } + } + + return segmentMidPoint; + } + + static getSegmentMidPointIndex( + linearElementEditor: LinearElementEditor, + appState: AppState, + midPoint: Point, + ) { + const element = LinearElementEditor.getElement( + linearElementEditor.elementId, + ); + if (!element) { + return -1; + } + const midPoints = LinearElementEditor.getEditorMidPoints(element, appState); + let index = 0; + while (index < midPoints.length - 1) { + if (LinearElementEditor.arePointsEqual(midPoint, midPoints[index])) { + return index + 1; + } + index++; + } + return -1; } static handlePointerDown( @@ -438,33 +589,32 @@ export class LinearElementEditor { if (!element) { return ret; } - const hittingMidPoint = LinearElementEditor.isHittingMidPoint( + const segmentMidPoint = LinearElementEditor.getSegmentMidpointHitCoords( linearElementEditor, scenePointer, appState, ); - if ( - LinearElementEditor.isHittingMidPoint( + if (segmentMidPoint) { + const index = LinearElementEditor.getSegmentMidPointIndex( linearElementEditor, - scenePointer, appState, - ) - ) { - const midPoint = LinearElementEditor.getMidPoint(linearElementEditor); - if (midPoint) { - mutateElement(element, { - points: [ - element.points[0], - LinearElementEditor.createPointAt( - element, - midPoint[0], - midPoint[1], - appState.gridSize, - ), - ...element.points.slice(1), - ], - }); - } + segmentMidPoint, + ); + const newMidPoint = LinearElementEditor.createPointAt( + element, + segmentMidPoint[0], + segmentMidPoint[1], + appState.gridSize, + ); + const points = [ + ...element.points.slice(0, index), + newMidPoint, + ...element.points.slice(index), + ]; + mutateElement(element, { + points, + }); + ret.didAddPoint = true; ret.isMidPoint = true; ret.linearElementEditor = { @@ -520,7 +670,7 @@ export class LinearElementEditor { // if we clicked on a point, set the element as hitElement otherwise // it would get deselected if the point is outside the hitbox area - if (clickedPointIndex >= 0 || hittingMidPoint) { + if (clickedPointIndex >= 0 || segmentMidPoint) { ret.hitElement = element; } else { // You might be wandering why we are storing the binding elements on @@ -579,17 +729,29 @@ export class LinearElementEditor { return ret; } + static arePointsEqual(point1: Point | null, point2: Point | null) { + if (!point1 && !point2) { + return true; + } + if (!point1 || !point2) { + return false; + } + return arePointsEqual(point1, point2); + } + static handlePointerMove( event: React.PointerEvent, scenePointerX: number, scenePointerY: number, - linearElementEditor: LinearElementEditor, - gridSize: number | null, - ): LinearElementEditor { - const { elementId, lastUncommittedPoint } = linearElementEditor; + appState: AppState, + ): LinearElementEditor | null { + if (!appState.editingLinearElement) { + return null; + } + const { elementId, lastUncommittedPoint } = appState.editingLinearElement; const element = LinearElementEditor.getElement(elementId); if (!element) { - return linearElementEditor; + return appState.editingLinearElement; } const { points } = element; @@ -599,7 +761,10 @@ export class LinearElementEditor { if (lastPoint === lastUncommittedPoint) { LinearElementEditor.deletePoints(element, [points.length - 1]); } - return { ...linearElementEditor, lastUncommittedPoint: null }; + return { + ...appState.editingLinearElement, + lastUncommittedPoint: null, + }; } let newPoint: Point; @@ -611,7 +776,7 @@ export class LinearElementEditor { element, lastCommittedPoint, [scenePointerX, scenePointerY], - gridSize, + appState.gridSize, ); newPoint = [ @@ -621,9 +786,9 @@ export class LinearElementEditor { } else { newPoint = LinearElementEditor.createPointAt( element, - scenePointerX - linearElementEditor.pointerOffset.x, - scenePointerY - linearElementEditor.pointerOffset.y, - gridSize, + scenePointerX - appState.editingLinearElement.pointerOffset.x, + scenePointerY - appState.editingLinearElement.pointerOffset.y, + appState.gridSize, ); } @@ -635,11 +800,10 @@ export class LinearElementEditor { }, ]); } else { - LinearElementEditor.addPoints(element, [{ point: newPoint }]); + LinearElementEditor.addPoints(element, appState, [{ point: newPoint }]); } - return { - ...linearElementEditor, + ...appState.editingLinearElement, lastUncommittedPoint: element.points[element.points.length - 1], }; } @@ -884,6 +1048,7 @@ export class LinearElementEditor { static addPoints( element: NonDeleted, + appState: AppState, targetPoints: { point: Point }[], ) { const offsetX = 0; diff --git a/src/math.ts b/src/math.ts index 1f2ffc4f5..dd1a73b56 100644 --- a/src/math.ts +++ b/src/math.ts @@ -1,6 +1,8 @@ import { NormalizedZoomValue, Point, Zoom } from "./types"; import { LINE_CONFIRM_THRESHOLD } from "./constants"; -import { ExcalidrawLinearElement } from "./element/types"; +import { ExcalidrawLinearElement, NonDeleted } from "./element/types"; +import { getShapeForElement } from "./renderer/renderElement"; +import { getCurvePathOps } from "./element/bounds"; export const rotate = ( x1: number, @@ -263,3 +265,165 @@ export const getGridPoint = ( } return [x, y]; }; + +export const getControlPointsForBezierCurve = ( + element: NonDeleted, + endPoint: Point, +) => { + const shape = getShapeForElement(element as ExcalidrawLinearElement); + if (!shape) { + return null; + } + + const ops = getCurvePathOps(shape[0]); + let currentP: Mutable = [0, 0]; + let index = 0; + let minDistance = Infinity; + let controlPoints: Mutable[] | null = null; + + while (index < ops.length) { + const { op, data } = ops[index]; + if (op === "move") { + currentP = data as unknown as Mutable; + } + if (op === "bcurveTo") { + const p0 = currentP; + const p1 = [data[0], data[1]] as Mutable; + const p2 = [data[2], data[3]] as Mutable; + const p3 = [data[4], data[5]] as Mutable; + const distance = distance2d(p3[0], p3[1], endPoint[0], endPoint[1]); + if (distance < minDistance) { + minDistance = distance; + controlPoints = [p0, p1, p2, p3]; + } + currentP = p3; + } + index++; + } + + return controlPoints; +}; + +export const getBezierXY = ( + p0: Point, + p1: Point, + p2: Point, + p3: Point, + t: number, +) => { + const equation = (t: number, idx: number) => + Math.pow(1 - t, 3) * p3[idx] + + 3 * t * Math.pow(1 - t, 2) * p2[idx] + + 3 * Math.pow(t, 2) * (1 - t) * p1[idx] + + p0[idx] * Math.pow(t, 3); + const tx = equation(t, 0); + const ty = equation(t, 1); + return [tx, ty]; +}; + +export const getPointsInBezierCurve = ( + element: NonDeleted, + endPoint: Point, +) => { + const controlPoints: Mutable[] = getControlPointsForBezierCurve( + element, + endPoint, + )!; + if (!controlPoints) { + return []; + } + const pointsOnCurve: Mutable[] = []; + let t = 1; + // Take 20 points on curve for better accuracy + while (t > 0) { + const point = getBezierXY( + controlPoints[0], + controlPoints[1], + controlPoints[2], + controlPoints[3], + t, + ); + pointsOnCurve.push([point[0], point[1]]); + t -= 0.05; + } + if (pointsOnCurve.length) { + if (arePointsEqual(pointsOnCurve.at(-1)!, endPoint)) { + pointsOnCurve.push([endPoint[0], endPoint[1]]); + } + } + return pointsOnCurve; +}; + +export const getBezierCurveArcLengths = ( + element: NonDeleted, + endPoint: Point, +) => { + const arcLengths: number[] = []; + arcLengths[0] = 0; + const points = getPointsInBezierCurve(element, endPoint); + let index = 0; + let distance = 0; + while (index < points.length - 1) { + const segmentDistance = distance2d( + points[index][0], + points[index][1], + points[index + 1][0], + points[index + 1][1], + ); + distance += segmentDistance; + arcLengths.push(distance); + index++; + } + + return arcLengths; +}; + +export const getBezierCurveLength = ( + element: NonDeleted, + endPoint: Point, +) => { + const arcLengths = getBezierCurveArcLengths(element, endPoint); + return arcLengths.at(-1) as number; +}; + +// This maps interval to actual interval t on the curve so that when t = 0.5, its actually the point at 50% of the length +export const mapIntervalToBezierT = ( + element: NonDeleted, + endPoint: Point, + interval: number, // The interval between 0 to 1 for which you want to find the point on the curve, +) => { + const arcLengths = getBezierCurveArcLengths(element, endPoint); + const pointsCount = arcLengths.length - 1; + const curveLength = arcLengths.at(-1) as number; + const targetLength = interval * curveLength; + let low = 0; + let high = pointsCount; + let index = 0; + // Doing a binary search to find the largest length that is less than the target length + while (low < high) { + index = Math.floor(low + (high - low) / 2); + if (arcLengths[index] < targetLength) { + low = index + 1; + } else { + high = index; + } + } + if (arcLengths[index] > targetLength) { + index--; + } + if (arcLengths[index] === targetLength) { + return index / pointsCount; + } + + return ( + 1 - + (index + + (targetLength - arcLengths[index]) / + (arcLengths[index + 1] - arcLengths[index])) / + pointsCount + ); +}; + +export const arePointsEqual = (p1: Point, p2: Point) => { + return p1[0] === p2[0] && p1[1] === p2[1]; +}; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 8ac1e1ab7..d4c6e19de 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -197,12 +197,7 @@ const renderLinearPointHandles = ( context.translate(renderConfig.scrollX, renderConfig.scrollY); context.lineWidth = 1 / renderConfig.zoom.value; const points = LinearElementEditor.getPointsGlobalCoordinates(element); - const centerPoint = LinearElementEditor.getMidPoint( - appState.selectedLinearElement, - ); - if (!centerPoint) { - return; - } + const { POINT_HANDLE_SIZE } = LinearElementEditor; const radius = appState.editingLinearElement ? POINT_HANDLE_SIZE @@ -221,11 +216,20 @@ const renderLinearPointHandles = ( ); }); - if (points.length < 3) { - if (appState.selectedLinearElement.midPointHovered) { - const centerPoint = LinearElementEditor.getMidPoint( - appState.selectedLinearElement, - )!; + //Rendering segment mid points + const midPoints = LinearElementEditor.getEditorMidPoints( + element, + appState, + ).filter((midPoint) => midPoint !== null) as Point[]; + + midPoints.forEach((segmentMidPoint) => { + if ( + appState?.selectedLinearElement?.segmentMidPointHoveredCoords && + LinearElementEditor.arePointsEqual( + segmentMidPoint, + appState.selectedLinearElement.segmentMidPointHoveredCoords, + ) + ) { // The order of renderingSingleLinearPoint and highLight points is different // inside vs outside editor as hover states are different, // in editor when hovered the original point is not visible as hover state fully covers it whereas outside the @@ -235,34 +239,34 @@ const renderLinearPointHandles = ( context, appState, renderConfig, - centerPoint, + segmentMidPoint, radius, false, ); - highlightPoint(centerPoint, context, renderConfig); + highlightPoint(segmentMidPoint, context, renderConfig); } else { - highlightPoint(centerPoint, context, renderConfig); + highlightPoint(segmentMidPoint, context, renderConfig); renderSingleLinearPoint( context, appState, renderConfig, - centerPoint, + segmentMidPoint, radius, false, ); } - } else { + } else if (appState.editingLinearElement || points.length === 2) { renderSingleLinearPoint( context, appState, renderConfig, - centerPoint, + segmentMidPoint, POINT_HANDLE_SIZE / 2, false, true, ); } - } + }); context.restore(); }; @@ -403,6 +407,20 @@ export const _renderScene = ({ visibleElements.forEach((element) => { try { renderElement(element, rc, context, renderConfig); + // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to + // ShapeCache returns empty hence making sure that we get the + // correct element from visible elements + if (appState.editingLinearElement?.elementId === element.id) { + if (element) { + renderLinearPointHandles( + context, + appState, + renderConfig, + element as NonDeleted, + ); + } + } + if (!isExporting) { renderLinkIcon(element, context, appState); } @@ -411,15 +429,6 @@ export const _renderScene = ({ } }); - if (appState.editingLinearElement) { - const element = LinearElementEditor.getElement( - appState.editingLinearElement.elementId, - ); - if (element) { - renderLinearPointHandles(context, appState, renderConfig, element); - } - } - // Paint selection element if (appState.selectionElement) { try { diff --git a/src/tests/__snapshots__/linearElementEditor.test.tsx.snap b/src/tests/__snapshots__/linearElementEditor.test.tsx.snap new file mode 100644 index 000000000..945d8197a --- /dev/null +++ b/src/tests/__snapshots__/linearElementEditor.test.tsx.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Test Linear Elements Inside editor should allow dragging line from midpoint in 2 pointer lines 1`] = ` +Array [ + Array [ + 0, + 0, + ], + Array [ + 70, + 50, + ], + Array [ + 40, + 0, + ], +] +`; + +exports[` Test Linear Elements Inside editor should allow dragging lines from midpoints in between segments 1`] = ` +Array [ + Array [ + 0, + 0, + ], + Array [ + 85, + 75, + ], + Array [ + 70, + 50, + ], + Array [ + 105, + 75, + ], + Array [ + 40, + 0, + ], +] +`; + +exports[` Test Linear Elements should allow dragging line from midpoint in 2 pointer lines outside editor 1`] = ` +Array [ + Array [ + 0, + 0, + ], + Array [ + 70, + 50, + ], + Array [ + 40, + 0, + ], +] +`; diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 3f023df45..1a30d90a4 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -10982,7 +10982,6 @@ Object { "hoverPointIndex": -1, "isDragging": false, "lastUncommittedPoint": null, - "midPointHovered": false, "pointerDownState": Object { "lastClickedPoint": -1, "prevSelectedPointsIndices": null, @@ -10991,6 +10990,7 @@ Object { "x": 0, "y": 0, }, + "segmentMidPointHoveredCoords": null, "selectedPointsIndices": null, "startBindingElement": "keep", }, @@ -11208,7 +11208,6 @@ Object { "hoverPointIndex": -1, "isDragging": false, "lastUncommittedPoint": null, - "midPointHovered": false, "pointerDownState": Object { "lastClickedPoint": -1, "prevSelectedPointsIndices": null, @@ -11217,6 +11216,7 @@ Object { "x": 0, "y": 0, }, + "segmentMidPointHoveredCoords": null, "selectedPointsIndices": null, "startBindingElement": "keep", }, @@ -11661,7 +11661,6 @@ Object { "hoverPointIndex": -1, "isDragging": false, "lastUncommittedPoint": null, - "midPointHovered": false, "pointerDownState": Object { "lastClickedPoint": -1, "prevSelectedPointsIndices": null, @@ -11670,6 +11669,7 @@ Object { "x": 0, "y": 0, }, + "segmentMidPointHoveredCoords": null, "selectedPointsIndices": null, "startBindingElement": "keep", }, @@ -12066,7 +12066,6 @@ Object { "hoverPointIndex": -1, "isDragging": false, "lastUncommittedPoint": null, - "midPointHovered": false, "pointerDownState": Object { "lastClickedPoint": -1, "prevSelectedPointsIndices": null, @@ -12075,6 +12074,7 @@ Object { "x": 0, "y": 0, }, + "segmentMidPointHoveredCoords": null, "selectedPointsIndices": null, "startBindingElement": "keep", }, diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx new file mode 100644 index 000000000..c697fa2cb --- /dev/null +++ b/src/tests/linearElementEditor.test.tsx @@ -0,0 +1,146 @@ +import ReactDOM from "react-dom"; +import { ExcalidrawLinearElement } from "../element/types"; +import ExcalidrawApp from "../excalidraw-app"; +import { centerPoint } from "../math"; +import { reseed } from "../random"; +import * as Renderer from "../renderer/renderScene"; +import { Keyboard } from "./helpers/ui"; +import { screen } from "./test-utils"; + +import { render, fireEvent } from "./test-utils"; +import { Point } from "../types"; +import { KEYS } from "../keys"; +import { LinearElementEditor } from "../element/linearElementEditor"; + +const renderScene = jest.spyOn(Renderer, "renderScene"); + +const { h } = window; + +describe(" Test Linear Elements", () => { + let getByToolName: (...args: string[]) => HTMLElement; + let container: HTMLElement; + let canvas: HTMLCanvasElement; + + beforeEach(async () => { + // Unmount ReactDOM from root + ReactDOM.unmountComponentAtNode(document.getElementById("root")!); + localStorage.clear(); + renderScene.mockClear(); + reseed(7); + const comp = await render(); + getByToolName = comp.getByToolName; + container = comp.container; + canvas = container.querySelector("canvas")!; + }); + + const p1: Point = [20, 20]; + const p2: Point = [60, 20]; + const midpoint = centerPoint(p1, p2); + + const createTwoPointerLinearElement = ( + type: ExcalidrawLinearElement["type"], + edge: "Sharp" | "Round" = "Sharp", + roughness: "Architect" | "Cartoonist" | "Artist" = "Architect", + ) => { + const tool = getByToolName(type); + fireEvent.click(tool); + fireEvent.click(screen.getByTitle(edge)); + fireEvent.click(screen.getByTitle(roughness)); + fireEvent.pointerDown(canvas, { clientX: p1[0], clientY: p1[1] }); + fireEvent.pointerMove(canvas, { clientX: p2[0], clientY: p2[1] }); + fireEvent.pointerUp(canvas, { clientX: p2[0], clientY: p2[1] }); + }; + + const createThreePointerLinearElement = ( + type: ExcalidrawLinearElement["type"], + edge: "Sharp" | "Round" = "Sharp", + ) => { + createTwoPointerLinearElement("line"); + // Extending line via midpoint + fireEvent.pointerDown(canvas, { + clientX: midpoint[0], + clientY: midpoint[1], + }); + fireEvent.pointerMove(canvas, { + clientX: midpoint[0] + 50, + clientY: midpoint[1] + 50, + }); + fireEvent.pointerUp(canvas, { + clientX: midpoint[0] + 50, + clientY: midpoint[1] + 50, + }); + }; + + const dragLinearElementFromPoint = (point: Point) => { + fireEvent.pointerDown(canvas, { + clientX: point[0], + clientY: point[1], + }); + fireEvent.pointerMove(canvas, { + clientX: point[0] + 50, + clientY: point[1] + 50, + }); + fireEvent.pointerUp(canvas, { + clientX: point[0] + 50, + clientY: point[1] + 50, + }); + }; + + it("should allow dragging line from midpoint in 2 pointer lines outside editor", async () => { + createTwoPointerLinearElement("line"); + const line = h.elements[0] as ExcalidrawLinearElement; + + expect(renderScene).toHaveBeenCalledTimes(10); + expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2); + + // drag line from midpoint + dragLinearElementFromPoint(midpoint); + expect(renderScene).toHaveBeenCalledTimes(13); + expect(line.points.length).toEqual(3); + expect(line.points).toMatchSnapshot(); + }); + + describe("Inside editor", () => { + it("should allow dragging line from midpoint in 2 pointer lines", async () => { + createTwoPointerLinearElement("line"); + const line = h.elements[0] as ExcalidrawLinearElement; + + fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] }); + + Keyboard.keyPress(KEYS.ENTER); + expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id); + + // drag line from midpoint + dragLinearElementFromPoint(midpoint); + expect(line.points.length).toEqual(3); + expect(line.points).toMatchSnapshot(); + }); + + it("should allow dragging lines from midpoints in between segments", async () => { + createThreePointerLinearElement("line"); + + const line = h.elements[0] as ExcalidrawLinearElement; + expect(line.points.length).toEqual(3); + fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] }); + + Keyboard.keyPress(KEYS.ENTER); + expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id); + + let points = LinearElementEditor.getPointsGlobalCoordinates(line); + const firstSegmentMidpoint = centerPoint(points[0], points[1]); + // drag line via first segment midpoint + dragLinearElementFromPoint(firstSegmentMidpoint); + expect(line.points.length).toEqual(4); + + // drag line from last segment midpoint + points = LinearElementEditor.getPointsGlobalCoordinates(line); + const lastSegmentMidpoint = centerPoint(points.at(-2)!, points.at(-1)!); + dragLinearElementFromPoint(lastSegmentMidpoint); + expect(line.points.length).toEqual(5); + + expect( + (h.elements[0] as ExcalidrawLinearElement).points, + ).toMatchSnapshot(); + }); + }); +}); From 5390617c0180772c028ac24cdd24c8a72e2937e8 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 15 Sep 2022 19:31:55 +0530 Subject: [PATCH 10/11] test: add more specs for line editor segment midpoints (#5698) * tests: add more specs for line editor segment midpoints * use API to create elements * Add specs for checking midpoint hidden when points too close --- .../linearElementEditor.test.tsx.snap | 60 -- src/tests/linearElementEditor.test.tsx | 543 ++++++++++++++++-- 2 files changed, 484 insertions(+), 119 deletions(-) delete mode 100644 src/tests/__snapshots__/linearElementEditor.test.tsx.snap diff --git a/src/tests/__snapshots__/linearElementEditor.test.tsx.snap b/src/tests/__snapshots__/linearElementEditor.test.tsx.snap deleted file mode 100644 index 945d8197a..000000000 --- a/src/tests/__snapshots__/linearElementEditor.test.tsx.snap +++ /dev/null @@ -1,60 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` Test Linear Elements Inside editor should allow dragging line from midpoint in 2 pointer lines 1`] = ` -Array [ - Array [ - 0, - 0, - ], - Array [ - 70, - 50, - ], - Array [ - 40, - 0, - ], -] -`; - -exports[` Test Linear Elements Inside editor should allow dragging lines from midpoints in between segments 1`] = ` -Array [ - Array [ - 0, - 0, - ], - Array [ - 85, - 75, - ], - Array [ - 70, - 50, - ], - Array [ - 105, - 75, - ], - Array [ - 40, - 0, - ], -] -`; - -exports[` Test Linear Elements should allow dragging line from midpoint in 2 pointer lines outside editor 1`] = ` -Array [ - Array [ - 0, - 0, - ], - Array [ - 70, - 50, - ], - Array [ - 40, - 0, - ], -] -`; diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index c697fa2cb..dccd6f87b 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -4,10 +4,9 @@ import ExcalidrawApp from "../excalidraw-app"; import { centerPoint } from "../math"; import { reseed } from "../random"; import * as Renderer from "../renderer/renderScene"; -import { Keyboard } from "./helpers/ui"; -import { screen } from "./test-utils"; - -import { render, fireEvent } from "./test-utils"; +import { Keyboard, Pointer } from "./helpers/ui"; +import { screen, render, fireEvent } from "./test-utils"; +import { API } from "../tests/helpers/api"; import { Point } from "../types"; import { KEYS } from "../keys"; import { LinearElementEditor } from "../element/linearElementEditor"; @@ -17,7 +16,6 @@ const renderScene = jest.spyOn(Renderer, "renderScene"); const { h } = window; describe(" Test Linear Elements", () => { - let getByToolName: (...args: string[]) => HTMLElement; let container: HTMLElement; let canvas: HTMLCanvasElement; @@ -28,119 +26,546 @@ describe(" Test Linear Elements", () => { renderScene.mockClear(); reseed(7); const comp = await render(); - getByToolName = comp.getByToolName; container = comp.container; canvas = container.querySelector("canvas")!; + canvas.width = 1000; + canvas.height = 1000; }); const p1: Point = [20, 20]; const p2: Point = [60, 20]; const midpoint = centerPoint(p1, p2); + const delta = 50; + const mouse = new Pointer("mouse"); const createTwoPointerLinearElement = ( type: ExcalidrawLinearElement["type"], - edge: "Sharp" | "Round" = "Sharp", - roughness: "Architect" | "Cartoonist" | "Artist" = "Architect", + strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp", + roughness: ExcalidrawLinearElement["roughness"] = 0, ) => { - const tool = getByToolName(type); - fireEvent.click(tool); - fireEvent.click(screen.getByTitle(edge)); - fireEvent.click(screen.getByTitle(roughness)); - fireEvent.pointerDown(canvas, { clientX: p1[0], clientY: p1[1] }); - fireEvent.pointerMove(canvas, { clientX: p2[0], clientY: p2[1] }); - fireEvent.pointerUp(canvas, { clientX: p2[0], clientY: p2[1] }); + h.elements = [ + API.createElement({ + x: p1[0], + y: p1[1], + width: p2[0] - p1[0], + height: 0, + type, + roughness, + points: [ + [0, 0], + [p2[0] - p1[0], p2[1] - p1[1]], + ], + strokeSharpness, + }), + ]; + + mouse.clickAt(p1[0], p1[1]); }; const createThreePointerLinearElement = ( type: ExcalidrawLinearElement["type"], - edge: "Sharp" | "Round" = "Sharp", + strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp", + roughness: ExcalidrawLinearElement["roughness"] = 0, ) => { - createTwoPointerLinearElement("line"); - // Extending line via midpoint + //dragging line from midpoint + const p3 = [midpoint[0] + delta - p1[0], midpoint[1] + delta - p1[1]]; + h.elements = [ + API.createElement({ + x: p1[0], + y: p1[1], + width: p3[0] - p1[0], + height: 0, + type, + roughness, + points: [ + [0, 0], + [p3[0], p3[1]], + [p2[0] - p1[0], p2[1] - p1[1]], + ], + strokeSharpness, + }), + ]; + mouse.clickAt(p1[0], p1[1]); + }; + + const enterLineEditingMode = (line: ExcalidrawLinearElement) => { + mouse.clickAt(p1[0], p1[1]); + Keyboard.keyPress(KEYS.ENTER); + expect(h.state.editingLinearElement?.elementId).toEqual(line.id); + }; + + const drag = (startPoint: Point, endPoint: Point) => { fireEvent.pointerDown(canvas, { - clientX: midpoint[0], - clientY: midpoint[1], + clientX: startPoint[0], + clientY: startPoint[1], }); fireEvent.pointerMove(canvas, { - clientX: midpoint[0] + 50, - clientY: midpoint[1] + 50, + clientX: endPoint[0], + clientY: endPoint[1], }); fireEvent.pointerUp(canvas, { - clientX: midpoint[0] + 50, - clientY: midpoint[1] + 50, + clientX: endPoint[0], + clientY: endPoint[1], }); }; - const dragLinearElementFromPoint = (point: Point) => { + const deletePoint = (point: Point) => { fireEvent.pointerDown(canvas, { clientX: point[0], clientY: point[1], }); - fireEvent.pointerMove(canvas, { - clientX: point[0] + 50, - clientY: point[1] + 50, - }); fireEvent.pointerUp(canvas, { - clientX: point[0] + 50, - clientY: point[1] + 50, + clientX: point[0], + clientY: point[1], }); + Keyboard.keyPress(KEYS.DELETE); }; it("should allow dragging line from midpoint in 2 pointer lines outside editor", async () => { createTwoPointerLinearElement("line"); const line = h.elements[0] as ExcalidrawLinearElement; - expect(renderScene).toHaveBeenCalledTimes(10); + expect(renderScene).toHaveBeenCalledTimes(6); expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2); // drag line from midpoint - dragLinearElementFromPoint(midpoint); - expect(renderScene).toHaveBeenCalledTimes(13); + drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); + expect(renderScene).toHaveBeenCalledTimes(9); expect(line.points.length).toEqual(3); - expect(line.points).toMatchSnapshot(); + expect(line.points).toMatchInlineSnapshot(` + Array [ + Array [ + 0, + 0, + ], + Array [ + 70, + 50, + ], + Array [ + 40, + 0, + ], + ] + `); }); describe("Inside editor", () => { it("should allow dragging line from midpoint in 2 pointer lines", async () => { createTwoPointerLinearElement("line"); + const line = h.elements[0] as ExcalidrawLinearElement; - - fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] }); - - Keyboard.keyPress(KEYS.ENTER); - expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id); + enterLineEditingMode(line); // drag line from midpoint - dragLinearElementFromPoint(midpoint); + drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); + expect(renderScene).toHaveBeenCalledTimes(13); + expect(line.points.length).toEqual(3); - expect(line.points).toMatchSnapshot(); + expect(line.points).toMatchInlineSnapshot(` + Array [ + Array [ + 0, + 0, + ], + Array [ + 70, + 50, + ], + Array [ + 40, + 0, + ], + ] + `); }); - it("should allow dragging lines from midpoints in between segments", async () => { + it("should update the midpoints when element sharpness changed", async () => { createThreePointerLinearElement("line"); const line = h.elements[0] as ExcalidrawLinearElement; expect(line.points.length).toEqual(3); - fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] }); - Keyboard.keyPress(KEYS.ENTER); - expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id); + enterLineEditingMode(line); - let points = LinearElementEditor.getPointsGlobalCoordinates(line); - const firstSegmentMidpoint = centerPoint(points[0], points[1]); - // drag line via first segment midpoint - dragLinearElementFromPoint(firstSegmentMidpoint); - expect(line.points.length).toEqual(4); + const midPointsWithSharpEdge = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); - // drag line from last segment midpoint - points = LinearElementEditor.getPointsGlobalCoordinates(line); - const lastSegmentMidpoint = centerPoint(points.at(-2)!, points.at(-1)!); - dragLinearElementFromPoint(lastSegmentMidpoint); - expect(line.points.length).toEqual(5); + // update sharpness + fireEvent.click(screen.getByTitle("Round")); - expect( - (h.elements[0] as ExcalidrawLinearElement).points, - ).toMatchSnapshot(); + expect(renderScene).toHaveBeenCalledTimes(11); + const midPointsWithRoundEdge = LinearElementEditor.getEditorMidPoints( + h.elements[0] as ExcalidrawLinearElement, + h.state, + ); + expect(midPointsWithRoundEdge[0]).not.toEqual(midPointsWithSharpEdge[0]); + expect(midPointsWithRoundEdge[1]).not.toEqual(midPointsWithSharpEdge[1]); + + expect(midPointsWithRoundEdge).toMatchInlineSnapshot(` + Array [ + Array [ + 55.9697848965255, + 47.442326230998205, + ], + Array [ + 76.08587175006699, + 43.294165939653226, + ], + ] + `); + }); + + it("should update all the midpoints when element position changed", async () => { + createThreePointerLinearElement("line", "round"); + + const line = h.elements[0] as ExcalidrawLinearElement; + expect(line.points.length).toEqual(3); + enterLineEditingMode(line); + + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + expect([line.x, line.y]).toEqual(points[0]); + + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + const startPoint = centerPoint(points[0], midPoints[0] as Point); + const deltaX = 50; + const deltaY = 20; + const endPoint: Point = [startPoint[0] + deltaX, startPoint[1] + deltaY]; + + // Move the element + drag(startPoint, endPoint); + + expect(renderScene).toHaveBeenCalledTimes(14); + expect([line.x, line.y]).toEqual([ + points[0][0] + deltaX, + points[0][1] + deltaY, + ]); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + expect(midPoints[0]).not.toEqual(newMidPoints[0]); + expect(midPoints[1]).not.toEqual(newMidPoints[1]); + expect(newMidPoints).toMatchInlineSnapshot(` + Array [ + Array [ + 105.96978489652551, + 67.4423262309982, + ], + Array [ + 126.08587175006699, + 63.294165939653226, + ], + ] + `); + }); + + describe("When edges are sharp", () => { + // This is the expected midpoint for line with sharp edge + // hence hardcoding it so if later some bug is introduced + // this will fail and we can fix it + const firstSegmentMidpoint: Point = [55, 45]; + const lastSegmentMidpoint: Point = [75, 40]; + + let line: ExcalidrawLinearElement; + + beforeEach(() => { + createThreePointerLinearElement("line"); + line = h.elements[0] as ExcalidrawLinearElement; + expect(line.points.length).toEqual(3); + + enterLineEditingMode(line); + }); + + it("should allow dragging lines from midpoints in between segments", async () => { + // drag line via first segment midpoint + drag(firstSegmentMidpoint, [ + firstSegmentMidpoint[0] + delta, + firstSegmentMidpoint[1] + delta, + ]); + expect(line.points.length).toEqual(4); + + // drag line from last segment midpoint + drag(lastSegmentMidpoint, [ + lastSegmentMidpoint[0] + delta, + lastSegmentMidpoint[1] + delta, + ]); + + expect(renderScene).toHaveBeenCalledTimes(18); + expect(line.points.length).toEqual(5); + + expect((h.elements[0] as ExcalidrawLinearElement).points) + .toMatchInlineSnapshot(` + Array [ + Array [ + 0, + 0, + ], + Array [ + 85, + 75, + ], + Array [ + 70, + 50, + ], + Array [ + 105, + 75, + ], + Array [ + 40, + 0, + ], + ] + `); + }); + + it("should update only the first segment midpoint when its point is dragged", async () => { + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + const hitCoords: Point = [points[0][0], points[0][1]]; + + // Drag from first point + drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]); + + expect(renderScene).toHaveBeenCalledTimes(14); + + const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); + expect([newPoints[0][0], newPoints[0][1]]).toEqual([ + points[0][0] - delta, + points[0][1] - delta, + ]); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + + expect(midPoints[0]).not.toEqual(newMidPoints[0]); + expect(midPoints[1]).toEqual(newMidPoints[1]); + }); + + it("should hide midpoints in the segment when points moved close", async () => { + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + const hitCoords: Point = [points[0][0], points[0][1]]; + + // Drag from first point + drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); + + expect(renderScene).toHaveBeenCalledTimes(14); + + const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); + expect([newPoints[0][0], newPoints[0][1]]).toEqual([ + points[0][0] + delta, + points[0][1] + delta, + ]); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + // This midpoint is hidden since the points are too close + expect(newMidPoints[0]).toBeNull(); + expect(midPoints[1]).toEqual(newMidPoints[1]); + }); + + it("should remove the midpoint when one of the points in the segment is deleted", async () => { + const line = h.elements[0] as ExcalidrawLinearElement; + enterLineEditingMode(line); + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + + // dragging line from last segment midpoint + drag(lastSegmentMidpoint, [ + lastSegmentMidpoint[0] + 50, + lastSegmentMidpoint[1] + 50, + ]); + expect(line.points.length).toEqual(4); + + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + // delete 3rd point + deletePoint(points[2]); + expect(line.points.length).toEqual(3); + expect(renderScene).toHaveBeenCalledTimes(19); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + expect(newMidPoints.length).toEqual(2); + expect(midPoints[0]).toEqual(newMidPoints[0]); + expect(midPoints[1]).toEqual(newMidPoints[1]); + }); + }); + + describe("When edges are round", () => { + // This is the expected midpoint for line with round edge + // hence hardcoding it so if later some bug is introduced + // this will fail and we can fix it + const firstSegmentMidpoint: Point = [ + 55.9697848965255, 47.442326230998205, + ]; + const lastSegmentMidpoint: Point = [ + 76.08587175006699, 43.294165939653226, + ]; + let line: ExcalidrawLinearElement; + + beforeEach(() => { + createThreePointerLinearElement("line", "round"); + line = h.elements[0] as ExcalidrawLinearElement; + expect(line.points.length).toEqual(3); + + enterLineEditingMode(line); + }); + + it("should allow dragging lines from midpoints in between segments", async () => { + // drag line from first segment midpoint + drag(firstSegmentMidpoint, [ + firstSegmentMidpoint[0] + delta, + firstSegmentMidpoint[1] + delta, + ]); + expect(line.points.length).toEqual(4); + + // drag line from last segment midpoint + drag(lastSegmentMidpoint, [ + lastSegmentMidpoint[0] + delta, + lastSegmentMidpoint[1] + delta, + ]); + expect(renderScene).toHaveBeenCalledTimes(18); + + expect(line.points.length).toEqual(5); + + expect((h.elements[0] as ExcalidrawLinearElement).points) + .toMatchInlineSnapshot(` + Array [ + Array [ + 0, + 0, + ], + Array [ + 85.96978489652551, + 77.4423262309982, + ], + Array [ + 70, + 50, + ], + Array [ + 104.58050066266131, + 74.24758482724201, + ], + Array [ + 40, + 0, + ], + ] + `); + }); + + it("should update all the midpoints when its point is dragged", async () => { + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + const hitCoords: Point = [points[0][0], points[0][1]]; + + // Drag from first point + drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]); + + const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); + expect([newPoints[0][0], newPoints[0][1]]).toEqual([ + points[0][0] - delta, + points[0][1] - delta, + ]); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + + expect(midPoints[0]).not.toEqual(newMidPoints[0]); + expect(midPoints[1]).not.toEqual(newMidPoints[1]); + expect(newMidPoints).toMatchInlineSnapshot(` + Array [ + Array [ + 31.884084517616053, + 23.13275505472383, + ], + Array [ + 77.74792546875662, + 44.57840982272327, + ], + ] + `); + }); + + it("should hide midpoints in the segment when points moved close", async () => { + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + + const hitCoords: Point = [points[0][0], points[0][1]]; + + // Drag from first point + drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); + + expect(renderScene).toHaveBeenCalledTimes(14); + + const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); + expect([newPoints[0][0], newPoints[0][1]]).toEqual([ + points[0][0] + delta, + points[0][1] + delta, + ]); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + // This mid point is hidden due to point being too close + expect(newMidPoints[0]).toBeNull(); + expect(newMidPoints[1]).not.toEqual(midPoints[1]); + }); + + it("should update all the midpoints when a point is deleted", async () => { + drag(lastSegmentMidpoint, [ + lastSegmentMidpoint[0] + delta, + lastSegmentMidpoint[1] + delta, + ]); + expect(line.points.length).toEqual(4); + + const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + const points = LinearElementEditor.getPointsGlobalCoordinates(line); + + // delete 3rd point + deletePoint(points[2]); + expect(line.points.length).toEqual(3); + + const newMidPoints = LinearElementEditor.getEditorMidPoints( + line, + h.state, + ); + expect(newMidPoints.length).toEqual(2); + expect(midPoints[0]).not.toEqual(newMidPoints[0]); + expect(midPoints[1]).not.toEqual(newMidPoints[1]); + expect(newMidPoints).toMatchInlineSnapshot(` + Array [ + Array [ + 55.9697848965255, + 47.442326230998205, + ], + Array [ + 76.08587175006699, + 43.294165939653226, + ], + ] + `); + }); }); }); }); From ec4b3d913e27537f9ad39bfcd2785729f5bc8432 Mon Sep 17 00:00:00 2001 From: Seunghyun oh Date: Fri, 16 Sep 2022 04:58:07 +0900 Subject: [PATCH 11/11] fix: remove no longer used code related to collab room loading (#5699) Co-authored-by: dwelle --- src/excalidraw-app/app_constants.ts | 1 - src/excalidraw-app/collab/Collab.tsx | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/src/excalidraw-app/app_constants.ts b/src/excalidraw-app/app_constants.ts index cbead7683..e56f7b24f 100644 --- a/src/excalidraw-app/app_constants.ts +++ b/src/excalidraw-app/app_constants.ts @@ -33,7 +33,6 @@ export const STORAGE_KEYS = { LOCAL_STORAGE_ELEMENTS: "excalidraw", LOCAL_STORAGE_APP_STATE: "excalidraw-state", LOCAL_STORAGE_COLLAB: "excalidraw-collab", - LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG: "collabLinkForceLoadFlag", LOCAL_STORAGE_LIBRARY: "excalidraw-library", VERSION_DATA_STATE: "version-dataState", VERSION_FILES: "version-files", diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx index 7112671c4..363de9498 100644 --- a/src/excalidraw-app/collab/Collab.tsx +++ b/src/excalidraw-app/collab/Collab.tsx @@ -25,7 +25,6 @@ import { INITIAL_SCENE_UPDATE_TIMEOUT, LOAD_IMAGES_TIMEOUT, WS_SCENE_EVENT_TYPES, - STORAGE_KEYS, SYNC_FULL_SCENE_INTERVAL_MS, } from "../app_constants"; import { @@ -225,18 +224,6 @@ class Collab extends PureComponent { preventUnload(event); } - - if (this.isCollaborating || this.portal.roomId) { - try { - localStorage?.setItem( - STORAGE_KEYS.LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG, - JSON.stringify({ - timestamp: Date.now(), - room: this.portal.roomId, - }), - ); - } catch {} - } }); saveCollabRoomToFirebase = async (