From cee00767df5f94759e7911082f049160d65befc7 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Fri, 10 Nov 2023 15:33:43 +0530 Subject: [PATCH 01/13] feat: support excalidrawAPI and remove refs support (#7251) * feat: support excalidrawAPI and remove refs support * update changelog * remove ready and readyPromise * update changelog * update changelog --- excalidraw-app/index.tsx | 2 +- src/components/App.tsx | 18 +++++------------- src/packages/excalidraw/CHANGELOG.md | 8 ++++++++ src/packages/excalidraw/example/App.tsx | 4 +++- src/packages/excalidraw/index.tsx | 22 ++++++---------------- src/tests/packages/events.test.tsx | 4 +++- src/tests/tool.test.tsx | 4 +++- src/types.ts | 17 +++-------------- 8 files changed, 32 insertions(+), 47 deletions(-) diff --git a/excalidraw-app/index.tsx b/excalidraw-app/index.tsx index 5b2e29818c..7708fc1901 100644 --- a/excalidraw-app/index.tsx +++ b/excalidraw-app/index.tsx @@ -691,7 +691,7 @@ const ExcalidrawWrapper = () => { })} > { super(props); const defaultAppState = getDefaultAppState(); const { - excalidrawRef, + excalidrawAPI, viewModeEnabled = false, zenModeEnabled = false, gridModeEnabled = false, @@ -571,14 +570,8 @@ class App extends React.Component { this.rc = rough.canvas(this.canvas); this.renderer = new Renderer(this.scene); - if (excalidrawRef) { - const readyPromise = - ("current" in excalidrawRef && excalidrawRef.current?.readyPromise) || - resolvablePromise(); - + if (excalidrawAPI) { const api: ExcalidrawImperativeAPI = { - ready: true, - readyPromise, updateScene: this.updateScene, updateLibrary: this.library.updateLibrary, addFiles: this.addFiles, @@ -603,12 +596,11 @@ class App extends React.Component { onPointerDown: (cb) => this.onPointerDownEmitter.on(cb), onPointerUp: (cb) => this.onPointerUpEmitter.on(cb), } as const; - if (typeof excalidrawRef === "function") { - excalidrawRef(api); + if (typeof excalidrawAPI === "function") { + excalidrawAPI(api); } else { - excalidrawRef.current = api; + console.error("excalidrawAPI should be a function!"); } - readyPromise.resolve(api); } this.excalidrawContainerValue = { diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 7f21b9cc3b..a001e24269 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -15,6 +15,14 @@ Please add the latest change on the top under the correct section. ### Features +- Support `excalidrawAPI` prop for accessing the Excalidraw API [#7251](https://github.com/excalidraw/excalidraw/pull/7251). + +#### BREAKING CHANGE + +- The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the [`excalidrawAPI`](http://localhost:3003/docs/@excalidraw/excalidraw/api/props/excalidraw-api) + +- Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0. + - Export [`getCommonBounds`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#getcommonbounds) helper from the package [#7247](https://github.com/excalidraw/excalidraw/pull/7247). - Support frames via programmatic API [#7205](https://github.com/excalidraw/excalidraw/pull/7205). diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx index 1574442713..974bbb7efd 100644 --- a/src/packages/excalidraw/example/App.tsx +++ b/src/packages/excalidraw/example/App.tsx @@ -665,7 +665,9 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
setExcalidrawAPI(api)} + excalidrawAPI={(api: ExcalidrawImperativeAPI) => + setExcalidrawAPI(api) + } initialData={initialStatePromiseRef.current.promise} onChange={(elements, state) => { console.info("Elements :", elements, "State : ", state); diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index ac12c65afd..2458c112de 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, forwardRef } from "react"; +import React, { useEffect } from "react"; import { InitializeApp } from "../../components/InitializeApp"; import App from "../../components/App"; import { isShallowEqual } from "../../utils"; @@ -6,7 +6,7 @@ import { isShallowEqual } from "../../utils"; import "../../css/app.scss"; import "../../css/styles.scss"; -import { AppProps, ExcalidrawAPIRefValue, ExcalidrawProps } from "../../types"; +import { AppProps, ExcalidrawProps } from "../../types"; import { defaultLang } from "../../i18n"; import { DEFAULT_UI_OPTIONS } from "../../constants"; import { Provider } from "jotai"; @@ -20,7 +20,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { const { onChange, initialData, - excalidrawRef, + excalidrawAPI, isCollaborating = false, onPointerUpdate, renderTopRightUI, @@ -95,7 +95,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { { ); }; -type PublicExcalidrawProps = Omit; - -const areEqual = ( - prevProps: PublicExcalidrawProps, - nextProps: PublicExcalidrawProps, -) => { +const areEqual = (prevProps: ExcalidrawProps, nextProps: ExcalidrawProps) => { // short-circuit early if (prevProps.children !== nextProps.children) { return false; @@ -189,12 +184,7 @@ const areEqual = ( return isUIOptionsSame && isShallowEqual(prev, next); }; -const forwardedRefComp = forwardRef< - ExcalidrawAPIRefValue, - PublicExcalidrawProps ->((props, ref) => ); - -export const Excalidraw = React.memo(forwardedRefComp, areEqual); +export const Excalidraw = React.memo(ExcalidrawBase, areEqual); Excalidraw.displayName = "Excalidraw"; export { diff --git a/src/tests/packages/events.test.tsx b/src/tests/packages/events.test.tsx index 231af57999..907dc7a8e0 100644 --- a/src/tests/packages/events.test.tsx +++ b/src/tests/packages/events.test.tsx @@ -15,7 +15,9 @@ describe("event callbacks", () => { beforeEach(async () => { const excalidrawAPIPromise = resolvablePromise(); await render( - excalidrawAPIPromise.resolve(api as any)} />, + excalidrawAPIPromise.resolve(api as any)} + />, ); excalidrawAPI = await excalidrawAPIPromise; }); diff --git a/src/tests/tool.test.tsx b/src/tests/tool.test.tsx index 84a43c6b5e..41ffbbf8ed 100644 --- a/src/tests/tool.test.tsx +++ b/src/tests/tool.test.tsx @@ -14,7 +14,9 @@ describe("setActiveTool()", () => { beforeEach(async () => { const excalidrawAPIPromise = resolvablePromise(); await render( - excalidrawAPIPromise.resolve(api as any)} />, + excalidrawAPIPromise.resolve(api as any)} + />, ); excalidrawAPI = await excalidrawAPIPromise; }); diff --git a/src/types.ts b/src/types.ts index 3fe0f2d72e..c1290c8f15 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,7 +23,7 @@ import { LinearElementEditor } from "./element/linearElementEditor"; import { SuggestedBinding } from "./element/binding"; import { ImportedDataState } from "./data/types"; import type App from "./components/App"; -import type { ResolvablePromise, throttleRAF } from "./utils"; +import type { throttleRAF } from "./utils"; import { Spreadsheet } from "./charts"; import { Language } from "./i18n"; import { ClipboardData } from "./clipboard"; @@ -34,7 +34,7 @@ import type { FileSystemHandle } from "./data/filesystem"; import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants"; import { ContextMenuItems } from "./components/ContextMenu"; import { SnapLine } from "./snapping"; -import { Merge, ForwardRef, ValueOf } from "./utility-types"; +import { Merge, ValueOf } from "./utility-types"; export type Point = Readonly; @@ -362,15 +362,6 @@ export type LibraryItemsSource = | Promise; // ----------------------------------------------------------------------------- -// NOTE ready/readyPromise props are optional for host apps' sake (our own -// implem guarantees existence) -export type ExcalidrawAPIRefValue = - | ExcalidrawImperativeAPI - | { - readyPromise?: ResolvablePromise; - ready?: false; - }; - export type ExcalidrawInitialDataState = Merge< ImportedDataState, { @@ -390,7 +381,7 @@ export interface ExcalidrawProps { | ExcalidrawInitialDataState | null | Promise; - excalidrawRef?: ForwardRef; + excalidrawAPI?: (api: ExcalidrawImperativeAPI) => void; isCollaborating?: boolean; onPointerUpdate?: (payload: { pointer: { x: number; y: number; tool: "pointer" | "laser" }; @@ -630,8 +621,6 @@ export type ExcalidrawImperativeAPI = { refresh: InstanceType["refresh"]; setToast: InstanceType["setToast"]; addFiles: (data: BinaryFileData[]) => void; - readyPromise: ResolvablePromise; - ready: true; id: string; setActiveTool: InstanceType["setActiveTool"]; setCursor: InstanceType["setCursor"]; From f66c93633cf9992cbe02eb19b8abf1263c881471 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:32:34 +0100 Subject: [PATCH 02/13] feat: make adaptive-roughness less aggressive (#7250) --- src/scene/Shape.ts | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/scene/Shape.ts b/src/scene/Shape.ts index 16b6518f8e..cf4681542f 100644 --- a/src/scene/Shape.ts +++ b/src/scene/Shape.ts @@ -14,18 +14,34 @@ import { generateFreeDrawShape } from "../renderer/renderElement"; import { isTransparent, assertNever } from "../utils"; import { simplify } from "points-on-curve"; import { ROUGHNESS } from "../constants"; +import { isLinearElement } from "../element/typeChecks"; +import { canChangeRoundness } from "./comparisons"; const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth]; const getDashArrayDotted = (strokeWidth: number) => [1.5, 6 + strokeWidth]; -function adjustRoughness(size: number, roughness: number): number { - if (size >= 50) { +function adjustRoughness(element: ExcalidrawElement): number { + const roughness = element.roughness; + + const maxSize = Math.max(element.width, element.height); + const minSize = Math.min(element.width, element.height); + + // don't reduce roughness if + if ( + // both sides relatively big + (minSize >= 20 && maxSize >= 50) || + // is round & both sides above 15px + (minSize >= 15 && + !!element.roundness && + canChangeRoundness(element.type)) || + // relatively long linear element + (isLinearElement(element) && maxSize >= 50) + ) { return roughness; } - const factor = 2 + (50 - size) / 10; - return roughness / factor; + return Math.min(roughness / (maxSize < 10 ? 3 : 2), 2.5); } export const generateRoughOptions = ( @@ -54,10 +70,7 @@ export const generateRoughOptions = ( // calculate them (and we don't want the fills to be modified) fillWeight: element.strokeWidth / 2, hachureGap: element.strokeWidth * 4, - roughness: adjustRoughness( - Math.min(element.width, element.height), - element.roughness, - ), + roughness: adjustRoughness(element), stroke: element.strokeColor, preserveVertices: continuousPath || element.roughness < ROUGHNESS.cartoonist, From 798e1fd858548905a22eea4bbf5b51a164f3d05d Mon Sep 17 00:00:00 2001 From: zsviczian Date: Fri, 10 Nov 2023 15:21:59 +0100 Subject: [PATCH 03/13] fix: allow pointer events when editing a linear element (#7238) --- src/components/App.tsx | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 9eab45bad3..56dc795f4d 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1173,6 +1173,19 @@ class App extends React.Component { pendingImageElementId: this.state.pendingImageElementId, }); + const shouldBlockPointerEvents = + !( + this.state.editingElement && isLinearElement(this.state.editingElement) + ) && + (this.state.selectionElement || + this.state.draggingElement || + this.state.resizingElement || + (this.state.activeTool.type === "laser" && + // technically we can just test on this once we make it more safe + this.state.cursorButton === "down") || + (this.state.editingElement && + !isTextElement(this.state.editingElement))); + return (
{ "excalidraw--mobile": this.device.editor.isMobile, })} style={{ - ["--ui-pointerEvents" as any]: - this.state.selectionElement || - this.state.draggingElement || - this.state.resizingElement || - (this.state.activeTool.type === "laser" && - // technically we can just test on this once we make it more safe - this.state.cursorButton === "down") || - (this.state.editingElement && - !isTextElement(this.state.editingElement)) - ? POINTER_EVENTS.disabled - : POINTER_EVENTS.enabled, + ["--ui-pointerEvents" as any]: shouldBlockPointerEvents + ? POINTER_EVENTS.disabled + : POINTER_EVENTS.enabled, }} ref={this.excalidrawContainerRef} onDrop={this.handleAppOnDrop} From 3ed15e95da053e6499819efed8cdca2717037be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Peter=20Dhall=C3=A9?= <62998174+janpeterd@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:23:43 +0100 Subject: [PATCH 04/13] Small typo fix frames.mdx (#7216) --- dev-docs/docs/codebase/frames.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-docs/docs/codebase/frames.mdx b/dev-docs/docs/codebase/frames.mdx index 45a551f230..67c84b7921 100644 --- a/dev-docs/docs/codebase/frames.mdx +++ b/dev-docs/docs/codebase/frames.mdx @@ -19,4 +19,4 @@ Frames should be ordered where frame children come first, followed by the frame ] ``` -If not oredered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations. +If not ordered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations. From 68179356e685e5750b1353ceec5c8adabec46237 Mon Sep 17 00:00:00 2001 From: Gabriel Lalonde Date: Fri, 10 Nov 2023 09:33:02 -0500 Subject: [PATCH 05/13] fix: Fixes the shortcut collision between "toggleHandTool" and "distributeHorizontally" (#7189) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- src/actions/actionCanvas.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/actions/actionCanvas.tsx b/src/actions/actionCanvas.tsx index 2ba584bc1d..2194f63b12 100644 --- a/src/actions/actionCanvas.tsx +++ b/src/actions/actionCanvas.tsx @@ -438,5 +438,6 @@ export const actionToggleHandTool = register({ commitToHistory: true, }; }, - keyTest: (event) => event.key === KEYS.H, + keyTest: (event) => + !event.altKey && !event[KEYS.CTRL_OR_CMD] && event.key === KEYS.H, }); From 900b317bf396d9e94b06d00a871bc171ababa4b4 Mon Sep 17 00:00:00 2001 From: Sahil Nagpure <76729141+SahilNagpure07@users.noreply.github.com> Date: Fri, 10 Nov 2023 20:14:02 +0530 Subject: [PATCH 06/13] feat: remove full screen shortcut (#7222) --- src/actions/actionMenu.tsx | 18 ------------------ src/actions/index.ts | 1 - src/components/HelpDialog.tsx | 1 - 3 files changed, 20 deletions(-) diff --git a/src/actions/actionMenu.tsx b/src/actions/actionMenu.tsx index 80a5c2b1fa..b259d72675 100644 --- a/src/actions/actionMenu.tsx +++ b/src/actions/actionMenu.tsx @@ -3,7 +3,6 @@ import { ToolButton } from "../components/ToolButton"; import { t } from "../i18n"; import { showSelectedShapeActions, getNonDeletedElements } from "../element"; import { register } from "./register"; -import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils"; import { KEYS } from "../keys"; export const actionToggleCanvasMenu = register({ @@ -52,23 +51,6 @@ export const actionToggleEditMenu = register({ ), }); -export const actionFullScreen = register({ - name: "toggleFullScreen", - viewMode: true, - trackEvent: { category: "canvas", predicate: (appState) => !isFullScreen() }, - perform: () => { - if (!isFullScreen()) { - allowFullScreen(); - } - if (isFullScreen()) { - exitFullScreen(); - } - return { - commitToHistory: false, - }; - }, -}); - export const actionShortcuts = register({ name: "toggleShortcuts", viewMode: true, diff --git a/src/actions/index.ts b/src/actions/index.ts index 1e72aa48ab..b4551acf58 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -44,7 +44,6 @@ export { actionCopyStyles, actionPasteStyles } from "./actionStyles"; export { actionToggleCanvasMenu, actionToggleEditMenu, - actionFullScreen, actionShortcuts, } from "./actionMenu"; diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx index b27823fc55..961158c0c9 100644 --- a/src/components/HelpDialog.tsx +++ b/src/components/HelpDialog.tsx @@ -254,7 +254,6 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => { label={t("helpDialog.movePageLeftRight")} shortcuts={["Shift+PgUp/PgDn"]} /> - Date: Fri, 10 Nov 2023 16:13:08 +0100 Subject: [PATCH 07/13] fix: perf issue when ungrouping elements within frame (#7265) Co-authored-by: Ryan Di --- src/actions/actionGroup.tsx | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/actions/actionGroup.tsx b/src/actions/actionGroup.tsx index 8ee84ac7bf..219f1444cb 100644 --- a/src/actions/actionGroup.tsx +++ b/src/actions/actionGroup.tsx @@ -17,15 +17,12 @@ import { import { getNonDeletedElements } from "../element"; import { randomId } from "../random"; import { ToolButton } from "../components/ToolButton"; -import { - ExcalidrawElement, - ExcalidrawFrameElement, - ExcalidrawTextElement, -} from "../element/types"; +import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; import { AppClassProperties, AppState } from "../types"; import { isBoundToContainer } from "../element/typeChecks"; import { getElementsInResizingFrame, + getFrameElements, groupByFrames, removeElementsFromFrame, replaceAllElementsInFrame, @@ -190,13 +187,6 @@ export const actionUngroup = register({ let nextElements = [...elements]; - const selectedElements = app.scene.getSelectedElements(appState); - const frames = selectedElements - .filter((element) => element.frameId) - .map((element) => - app.scene.getElement(element.frameId!), - ) as ExcalidrawFrameElement[]; - const boundTextElementIds: ExcalidrawTextElement["id"][] = []; nextElements = nextElements.map((element) => { if (isBoundToContainer(element)) { @@ -221,7 +211,19 @@ export const actionUngroup = register({ null, ); - frames.forEach((frame) => { + const selectedElements = app.scene.getSelectedElements(appState); + + const selectedElementFrameIds = new Set( + selectedElements + .filter((element) => element.frameId) + .map((element) => element.frameId!), + ); + + const targetFrames = getFrameElements(elements).filter((frame) => + selectedElementFrameIds.has(frame.id), + ); + + targetFrames.forEach((frame) => { if (frame) { nextElements = replaceAllElementsInFrame( nextElements, From 02cc8440c4aa5d8f518c0bcd80610d843e6eefbb Mon Sep 17 00:00:00 2001 From: FilBot3 Date: Fri, 10 Nov 2023 09:29:19 -0600 Subject: [PATCH 08/13] feat: allow D&D dice app domain for embeds (#7263) Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com> --- src/element/embeddable.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/element/embeddable.ts b/src/element/embeddable.ts index c0ade4fe27..e03ea7a610 100644 --- a/src/element/embeddable.ts +++ b/src/element/embeddable.ts @@ -64,6 +64,7 @@ const ALLOWED_DOMAINS = new Set([ "stackblitz.com", "val.town", "giphy.com", + "dddice.com", ]); const createSrcDoc = (body: string) => { From af6b81df40d4fe24a74837a1ef1b1ea924cba62b Mon Sep 17 00:00:00 2001 From: zsviczian Date: Sat, 11 Nov 2023 10:04:02 +0100 Subject: [PATCH 09/13] fix: Replace hard coded font family with const value in addFrameLabelsAsTextElements (#7269) --- src/scene/export.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scene/export.ts b/src/scene/export.ts index 694c162da2..38bd09e651 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -15,6 +15,7 @@ import { distance, getFontString } from "../utils"; import { AppState, BinaryFiles } from "../types"; import { DEFAULT_EXPORT_PADDING, + FONT_FAMILY, FRAME_STYLE, SVG_NS, THEME_FILTER, @@ -105,7 +106,7 @@ const addFrameLabelsAsTextElements = ( let textElement: Mutable = newTextElement({ x: element.x, y: element.y - FRAME_STYLE.nameOffsetY, - fontFamily: 4, + fontFamily: FONT_FAMILY.Assistant, fontSize: FRAME_STYLE.nameFontSize, lineHeight: FRAME_STYLE.nameLineHeight as ExcalidrawTextElement["lineHeight"], From 7b000893147cab1b0c3acf6cdeb189da76b9e107 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:23:22 +0100 Subject: [PATCH 10/13] chore: bump @excalidraw/random-username (#7272) --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4311dde604..035409bbf9 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ }, "dependencies": { "@braintree/sanitize-url": "6.0.2", - "@excalidraw/mermaid-to-excalidraw": "0.1.2", "@excalidraw/laser-pointer": "1.2.0", - "@excalidraw/random-username": "1.0.0", + "@excalidraw/mermaid-to-excalidraw": "0.1.2", + "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.0.3", "@radix-ui/react-tabs": "1.0.2", "@sentry/browser": "6.2.5", diff --git a/yarn.lock b/yarn.lock index eacc147c7f..82691f7b2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1602,10 +1602,10 @@ resolved "https://registry.yarnpkg.com/@excalidraw/prettier-config/-/prettier-config-1.0.2.tgz#b7c061c99cee2f78b9ca470ea1fbd602683bba65" integrity sha512-rFIq8+A8WvkEzBsF++Rw6gzxE+hU3ZNkdg8foI+Upz2y/rOC/gUpWJaggPbCkoH3nlREVU59axQjZ1+F6ePRGg== -"@excalidraw/random-username@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@excalidraw/random-username/-/random-username-1.0.0.tgz#6d5293148aee6cd08dcdfcadc0c91276572f4499" - integrity sha512-pd4VapWahQ7PIyThGq32+C+JUS73mf3RSdC7BmQiXzhQsCTU4RHc8y9jBi+pb1CFV0iJXvjJRXnVdLCbTj3+HA== +"@excalidraw/random-username@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@excalidraw/random-username/-/random-username-1.1.0.tgz#6f388d6a9708cf655b8c9c6aa3fa569ee71ecf0f" + integrity sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA== "@firebase/analytics-types@0.4.0": version "0.4.0" From 3d4ff59f40fa802403df3c9df691657148e3814d Mon Sep 17 00:00:00 2001 From: zsviczian Date: Sun, 12 Nov 2023 13:24:13 +0100 Subject: [PATCH 11/13] fix: Can't toggle penMode off due to missing typecheck in togglePenMode (#7273) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- src/components/App.tsx | 2 +- src/components/LayerUI.tsx | 4 ++-- src/components/MobileMenu.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 56dc795f4d..8e4ab63552 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -2716,7 +2716,7 @@ class App extends React.Component { }); }; - togglePenMode = (force?: boolean) => { + togglePenMode = (force: boolean | null) => { this.setState((prevState) => { return { penMode: force ?? !prevState.penMode, diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 8b909da236..5c092fea16 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -66,7 +66,7 @@ interface LayerUIProps { elements: readonly NonDeletedExcalidrawElement[]; onLockToggle: () => void; onHandToolToggle: () => void; - onPenModeToggle: () => void; + onPenModeToggle: AppClassProperties["togglePenMode"]; showExitZenModeBtn: boolean; langCode: Language["code"]; renderTopRightUI?: ExcalidrawProps["renderTopRightUI"]; @@ -258,7 +258,7 @@ const LayerUI = ({ onPenModeToggle(null)} title={t("toolBar.penMode")} penDetected={appState.penDetected} /> diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index bb26fe7136..4299bf8442 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -35,7 +35,7 @@ type MobileMenuProps = { elements: readonly NonDeletedExcalidrawElement[]; onLockToggle: () => void; onHandToolToggle: () => void; - onPenModeToggle: () => void; + onPenModeToggle: AppClassProperties["togglePenMode"]; renderTopRightUI?: ( isMobile: boolean, @@ -94,7 +94,7 @@ export const MobileMenu = ({ )} onPenModeToggle(null)} title={t("toolBar.penMode")} isMobile penDetected={appState.penDetected} From ae5b9a4ffdd34eca9f454a15833463e578289332 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sun, 12 Nov 2023 23:32:12 +0100 Subject: [PATCH 12/13] fix: not cloning elements on export polluting Scene mapping (#7276) --- src/scene/export.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/scene/export.ts b/src/scene/export.ts index 38bd09e651..e515a1da0e 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -11,7 +11,7 @@ import { getElementAbsoluteCoords, } from "../element/bounds"; import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene"; -import { distance, getFontString } from "../utils"; +import { cloneJSON, distance, getFontString } from "../utils"; import { AppState, BinaryFiles } from "../types"; import { DEFAULT_EXPORT_PADDING, @@ -52,8 +52,9 @@ const __createSceneForElementsHack__ = ( // we can't duplicate elements to regenerate ids because we need the // orig ids when embedding. So we do another hack of not mapping element // ids to Scene instances so that we don't override the editor elements - // mapping - scene.replaceAllElements(elements, false); + // mapping. + // We still need to clone the objects themselves to regen references. + scene.replaceAllElements(cloneJSON(elements), false); return scene; }; From ceb255e8ee158e38cbd57460696fb0b09b89a4cb Mon Sep 17 00:00:00 2001 From: zsviczian Date: Sun, 12 Nov 2023 23:34:05 +0100 Subject: [PATCH 13/13] fix: exportToSvg to honor frameRendering also for name not only for frame itself (#7270) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- src/scene/export.ts | 97 +++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/src/scene/export.ts b/src/scene/export.ts index e515a1da0e..26fe7ff436 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -141,6 +141,36 @@ const getFrameRenderingConfig = ( }; }; +const prepareElementsForRender = ({ + elements, + exportingFrame, + frameRendering, + exportWithDarkMode, +}: { + elements: readonly ExcalidrawElement[]; + exportingFrame: ExcalidrawFrameElement | null | undefined; + frameRendering: AppState["frameRendering"]; + exportWithDarkMode: AppState["exportWithDarkMode"]; +}) => { + let nextElements: readonly ExcalidrawElement[]; + + if (exportingFrame) { + nextElements = elementsOverlappingBBox({ + elements, + bounds: exportingFrame, + type: "overlap", + }); + } else if (frameRendering.enabled && frameRendering.name) { + nextElements = addFrameLabelsAsTextElements(elements, { + exportWithDarkMode, + }); + } else { + nextElements = elements; + } + + return nextElements; +}; + export const exportToCanvas = async ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, @@ -169,21 +199,24 @@ export const exportToCanvas = async ( const tempScene = __createSceneForElementsHack__(elements); elements = tempScene.getNonDeletedElements(); - let nextElements: ExcalidrawElement[]; + const frameRendering = getFrameRenderingConfig( + exportingFrame ?? null, + appState.frameRendering ?? null, + ); + + const elementsForRender = prepareElementsForRender({ + elements, + exportingFrame, + exportWithDarkMode: appState.exportWithDarkMode, + frameRendering, + }); if (exportingFrame) { exportPadding = 0; - nextElements = elementsOverlappingBBox({ - elements, - bounds: exportingFrame, - type: "overlap", - }); - } else { - nextElements = addFrameLabelsAsTextElements(elements, appState); } const [minX, minY, width, height] = getCanvasSize( - exportingFrame ? [exportingFrame] : getRootElements(nextElements), + exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportPadding, ); @@ -193,7 +226,7 @@ export const exportToCanvas = async ( const { imageCache } = await updateImageCache({ imageCache: new Map(), - fileIds: getInitializedImageElements(nextElements).map( + fileIds: getInitializedImageElements(elementsForRender).map( (element) => element.fileId, ), files, @@ -202,15 +235,12 @@ export const exportToCanvas = async ( renderStaticScene({ canvas, rc: rough.canvas(canvas), - elements: nextElements, - visibleElements: nextElements, + elements: elementsForRender, + visibleElements: elementsForRender, scale, appState: { ...appState, - frameRendering: getFrameRenderingConfig( - exportingFrame ?? null, - appState.frameRendering ?? null, - ), + frameRendering, viewBackgroundColor: exportBackground ? viewBackgroundColor : null, scrollX: -minX + exportPadding, scrollY: -minY + exportPadding, @@ -250,8 +280,14 @@ export const exportToSvg = async ( const tempScene = __createSceneForElementsHack__(elements); elements = tempScene.getNonDeletedElements(); + const frameRendering = getFrameRenderingConfig( + opts?.exportingFrame ?? null, + appState.frameRendering ?? null, + ); + let { exportPadding = DEFAULT_EXPORT_PADDING, + exportWithDarkMode = false, viewBackgroundColor, exportScale = 1, exportEmbedScene, @@ -259,19 +295,15 @@ export const exportToSvg = async ( const { exportingFrame = null } = opts || {}; - let nextElements: ExcalidrawElement[] = []; + const elementsForRender = prepareElementsForRender({ + elements, + exportingFrame, + exportWithDarkMode, + frameRendering, + }); if (exportingFrame) { exportPadding = 0; - nextElements = elementsOverlappingBBox({ - elements, - bounds: exportingFrame, - type: "overlap", - }); - } else { - nextElements = addFrameLabelsAsTextElements(elements, { - exportWithDarkMode: appState.exportWithDarkMode ?? false, - }); } let metadata = ""; @@ -295,7 +327,7 @@ export const exportToSvg = async ( } const [minX, minY, width, height] = getCanvasSize( - exportingFrame ? [exportingFrame] : getRootElements(nextElements), + exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportPadding, ); @@ -306,7 +338,7 @@ export const exportToSvg = async ( svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`); svgRoot.setAttribute("width", `${width * exportScale}`); svgRoot.setAttribute("height", `${height * exportScale}`); - if (appState.exportWithDarkMode) { + if (exportWithDarkMode) { svgRoot.setAttribute("filter", THEME_FILTER); } @@ -381,15 +413,12 @@ export const exportToSvg = async ( } const rsvg = rough.svg(svgRoot); - renderSceneToSvg(nextElements, rsvg, svgRoot, files || {}, { + renderSceneToSvg(elementsForRender, rsvg, svgRoot, files || {}, { offsetX, offsetY, - exportWithDarkMode: appState.exportWithDarkMode ?? false, + exportWithDarkMode, renderEmbeddables: opts?.renderEmbeddables ?? false, - frameRendering: getFrameRenderingConfig( - exportingFrame ?? null, - appState.frameRendering ?? null, - ), + frameRendering, }); tempScene.destroy();