From f299514e445b4078a55b0856892e6c707043aa44 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 28 Nov 2023 18:11:16 +0530 Subject: [PATCH 01/79] fix: umd build so it can be used in browser (#7349) * fix: umd build so it can be used in browser * fix lint * increase size limit * update changelog * use json.stringify for env preact variable so its accessible as string * update changelog --- dev-docs/docs/@excalidraw/excalidraw/faq.mdx | 2 +- dev-docs/docs/@excalidraw/excalidraw/integration.mdx | 2 +- src/packages/excalidraw/.size-limit.json | 2 +- src/packages/excalidraw/CHANGELOG.md | 10 ++++++++++ src/packages/excalidraw/webpack.preact.config.js | 5 ++--- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx index 5c66a603f..e9c6a81d1 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx @@ -39,7 +39,7 @@ Since Vite removes env variables by default, you can update the vite config to e ``` define: { - "process.env.IS_PREACT": process.env.IS_PREACT, + "process.env.IS_PREACT": JSON.stringify("true"), }, ``` diff --git a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx index aa8e002c5..9fc221bd9 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx @@ -93,7 +93,7 @@ Since Vite removes env variables by default, you can update the vite config to e ``` define: { - "process.env.IS_PREACT": process.env.IS_PREACT, + "process.env.IS_PREACT": JSON.stringify("true"), }, ``` ::: diff --git a/src/packages/excalidraw/.size-limit.json b/src/packages/excalidraw/.size-limit.json index 290de2687..47562968a 100644 --- a/src/packages/excalidraw/.size-limit.json +++ b/src/packages/excalidraw/.size-limit.json @@ -1,7 +1,7 @@ [ { "path": "dist/excalidraw.production.min.js", - "limit": "325 kB" + "limit": "335 kB" }, { "path": "dist/excalidraw-assets/locales", diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 368ff4147..1c23f6f85 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -13,6 +13,16 @@ Please add the latest change on the top under the correct section. ## Unreleased +### Fixes + +- Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean. + +``` +define: { + "process.env.IS_PREACT": JSON.stringify("true"), +} +``` + ### Breaking Changes - `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) diff --git a/src/packages/excalidraw/webpack.preact.config.js b/src/packages/excalidraw/webpack.preact.config.js index c0516a768..0ae969aa6 100644 --- a/src/packages/excalidraw/webpack.preact.config.js +++ b/src/packages/excalidraw/webpack.preact.config.js @@ -1,5 +1,3 @@ -const { merge } = require("webpack-merge"); - const prodConfig = require("./webpack.prod.config"); const devConfig = require("./webpack.dev.config"); @@ -11,6 +9,7 @@ const outputFile = isProd : "excalidraw-with-preact.development"; const preactWebpackConfig = { + ...config, entry: { [outputFile]: "./entry.js", }, @@ -30,4 +29,4 @@ const preactWebpackConfig = { }, }, }; -module.exports = merge(config, preactWebpackConfig); +module.exports = preactWebpackConfig; From 42d8c5a0401c47e8f88680db189205085df4cdb2 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Tue, 28 Nov 2023 19:12:39 +0530 Subject: [PATCH 02/79] chore: update changelog and package.json for v0.17.1 (#7351) --- src/packages/excalidraw/CHANGELOG.md | 14 ++++++++++++-- src/packages/excalidraw/package.json | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 1c23f6f85..de8ce7227 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -13,6 +13,12 @@ Please add the latest change on the top under the correct section. ## Unreleased +### Breaking Changes + +- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) + +## 0.17.1 (2023-11-28) + ### Fixes - Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean. @@ -23,9 +29,13 @@ define: { } ``` -### Breaking Changes +## Excalidraw Library -- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) +### Fixes + +- Disable caching bounds for arrow labels [#7343](https://github.com/excalidraw/excalidraw/pull/7343) + +--- ## 0.17.0 (2023-11-14) diff --git a/src/packages/excalidraw/package.json b/src/packages/excalidraw/package.json index 6f0c38e50..fa1ded557 100644 --- a/src/packages/excalidraw/package.json +++ b/src/packages/excalidraw/package.json @@ -1,6 +1,6 @@ { "name": "@excalidraw/excalidraw", - "version": "0.17.0", + "version": "0.17.1", "main": "main.js", "types": "types/packages/excalidraw/index.d.ts", "files": [ From 4bdeaf999b81be289f121874eb9201b79b9e6f84 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:50:30 +0100 Subject: [PATCH 03/79] feat: TTD dialog UI tweaks (#7384) --- .../TTDDialog/MermaidToExcalidraw.tsx | 70 ++++++++----------- src/components/TTDDialog/TTDDialog.scss | 14 ++++ src/components/TTDDialog/TTDDialog.tsx | 51 +++++++++++++- src/components/TTDDialog/TTDDialogPanel.tsx | 5 ++ .../TTDDialog/TTDDialogSubmitShortcut.tsx | 14 ++++ src/components/TTDDialog/common.ts | 21 +++--- .../dropdownMenu/DropdownMenuItem.tsx | 6 +- src/constants.ts | 1 + src/css/styles.scss | 6 ++ .../MermaidToExcalidraw.test.tsx.snap | 2 +- 10 files changed, 134 insertions(+), 56 deletions(-) create mode 100644 src/components/TTDDialog/TTDDialogSubmitShortcut.tsx diff --git a/src/components/TTDDialog/MermaidToExcalidraw.tsx b/src/components/TTDDialog/MermaidToExcalidraw.tsx index 342bf38b5..df9f4b8b1 100644 --- a/src/components/TTDDialog/MermaidToExcalidraw.tsx +++ b/src/components/TTDDialog/MermaidToExcalidraw.tsx @@ -7,7 +7,6 @@ import "./MermaidToExcalidraw.scss"; import { t } from "../../i18n"; import Trans from "../Trans"; import { - LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, MermaidToExcalidrawLibProps, convertMermaidToExcalidraw, insertToEditor, @@ -17,30 +16,26 @@ import { TTDDialogPanels } from "./TTDDialogPanels"; import { TTDDialogPanel } from "./TTDDialogPanel"; import { TTDDialogInput } from "./TTDDialogInput"; import { TTDDialogOutput } from "./TTDDialogOutput"; +import { EditorLocalStorage } from "../../data/EditorLocalStorage"; +import { EDITOR_LS_KEYS } from "../../constants"; +import { debounce } from "../../utils"; +import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut"; const MERMAID_EXAMPLE = "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]"; -const importMermaidDataFromStorage = () => { - try { - const data = localStorage.getItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW); - if (data) { - return data; - } - } catch (error: any) { - // Unable to access localStorage - console.error(error); - } - - return null; -}; +const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300); const MermaidToExcalidraw = ({ mermaidToExcalidrawLib, }: { mermaidToExcalidrawLib: MermaidToExcalidrawLibProps; }) => { - const [text, setText] = useState(""); + const [text, setText] = useState( + () => + EditorLocalStorage.get(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) || + MERMAID_EXAMPLE, + ); const deferredText = useDeferredValue(text.trim()); const [error, setError] = useState(null); @@ -52,11 +47,6 @@ const MermaidToExcalidraw = ({ const app = useApp(); - useEffect(() => { - const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE; - setText(data); - }, []); - useEffect(() => { convertMermaidToExcalidraw({ canvasRef, @@ -65,22 +55,25 @@ const MermaidToExcalidraw = ({ setError, mermaidDefinition: deferredText, }).catch(() => {}); + + debouncedSaveMermaidDefinition(deferredText); }, [deferredText, mermaidToExcalidrawLib]); - const textRef = useRef(text); + useEffect( + () => () => { + debouncedSaveMermaidDefinition.flush(); + }, + [], + ); - // slightly hacky but really quite simple - // essentially, we want to save the text to LS when the component unmounts - useEffect(() => { - textRef.current = text; - }, [text]); - useEffect(() => { - return () => { - if (textRef.current) { - saveMermaidDataToStorage(textRef.current); - } - }; - }, []); + const onInsertToEditor = () => { + insertToEditor({ + app, + data, + text, + shouldSaveMermaidDataToStorage: true, + }); + }; return ( <> @@ -103,22 +96,21 @@ const MermaidToExcalidraw = ({ input={text} placeholder={"Write Mermaid diagram defintion here..."} onChange={(event) => setText(event.target.value)} + onKeyboardSubmit={() => { + onInsertToEditor(); + }} /> { - insertToEditor({ - app, - data, - text, - shouldSaveMermaidDataToStorage: true, - }); + onInsertToEditor(); }, label: t("mermaid.button"), icon: ArrowRightIcon, }} + renderSubmitShortcut={() => } > (null); +const ttdGenerationAtom = atom<{ + generatedResponse: string | null; + prompt: string | null; +} | null>(null); + type OnTestSubmitRetValue = { rateLimit?: number | null; rateLimitRemaining?: number | null; @@ -80,10 +87,13 @@ export const TTDDialogBase = withInternalFallback( | { __fallback: true } )) => { const app = useApp(); + const setAppState = useExcalidrawSetAppState(); const someRandomDivRef = useRef(null); - const [text, setText] = useState(""); + const [ttdGeneration, setTtdGeneration] = useAtom(ttdGenerationAtom); + + const [text, setText] = useState(ttdGeneration?.prompt ?? ""); const prompt = text.trim(); @@ -91,6 +101,10 @@ export const TTDDialogBase = withInternalFallback( event, ) => { setText(event.target.value); + setTtdGeneration((s) => ({ + generatedResponse: s?.generatedResponse ?? null, + prompt: event.target.value, + })); }; const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false); @@ -131,6 +145,13 @@ export const TTDDialogBase = withInternalFallback( const { generatedResponse, error, rateLimit, rateLimitRemaining } = await rest.onTextSubmit(prompt); + if (typeof generatedResponse === "string") { + setTtdGeneration((s) => ({ + generatedResponse, + prompt: s?.prompt ?? null, + })); + } + if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) { setRateLimits({ rateLimit, rateLimitRemaining }); } @@ -153,7 +174,6 @@ export const TTDDialogBase = withInternalFallback( mermaidDefinition: generatedResponse, }); trackEvent("ai", "mermaid parse success", "ttd"); - saveMermaidDataToStorage(generatedResponse); } catch (error: any) { console.info( `%cTTD mermaid render errror: ${error.message}`, @@ -293,7 +313,32 @@ export const TTDDialogBase = withInternalFallback( ); }} + renderSubmitShortcut={() => } renderBottomRight={() => { + if (typeof ttdGeneration?.generatedResponse === "string") { + return ( +
{ + if ( + typeof ttdGeneration?.generatedResponse === + "string" + ) { + saveMermaidDataToStorage( + ttdGeneration.generatedResponse, + ); + setAppState({ + openDialog: { name: "ttd", tab: "mermaid" }, + }); + } + }} + > + View as Mermaid + +
+ ); + } const ratio = prompt.length / MAX_PROMPT_LENGTH; if (ratio > 0.8) { return ( diff --git a/src/components/TTDDialog/TTDDialogPanel.tsx b/src/components/TTDDialog/TTDDialogPanel.tsx index b74b74890..5c7fba6da 100644 --- a/src/components/TTDDialog/TTDDialogPanel.tsx +++ b/src/components/TTDDialog/TTDDialogPanel.tsx @@ -14,6 +14,7 @@ interface TTDDialogPanelProps { panelActionDisabled?: boolean; onTextSubmitInProgess?: boolean; renderTopRight?: () => ReactNode; + renderSubmitShortcut?: () => ReactNode; renderBottomRight?: () => ReactNode; } @@ -24,6 +25,7 @@ export const TTDDialogPanel = ({ panelActionDisabled = false, onTextSubmitInProgess, renderTopRight, + renderSubmitShortcut, renderBottomRight, }: TTDDialogPanelProps) => { return ( @@ -51,6 +53,9 @@ export const TTDDialogPanel = ({ {onTextSubmitInProgess && } + {!panelActionDisabled && + !onTextSubmitInProgess && + renderSubmitShortcut?.()} {renderBottomRight?.()} diff --git a/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx b/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx new file mode 100644 index 000000000..a8831e3a0 --- /dev/null +++ b/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx @@ -0,0 +1,14 @@ +import { getShortcutKey } from "../../utils"; + +export const TTDDialogSubmitShortcut = () => { + return ( +
+
+ {getShortcutKey("CtrlOrCmd")} +
+
+ {getShortcutKey("Enter")} +
+
+ ); +}; diff --git a/src/components/TTDDialog/common.ts b/src/components/TTDDialog/common.ts index 9d90a0432..5973d9118 100644 --- a/src/components/TTDDialog/common.ts +++ b/src/components/TTDDialog/common.ts @@ -1,6 +1,10 @@ import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw"; import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces"; -import { DEFAULT_EXPORT_PADDING, DEFAULT_FONT_SIZE } from "../../constants"; +import { + DEFAULT_EXPORT_PADDING, + DEFAULT_FONT_SIZE, + EDITOR_LS_KEYS, +} from "../../constants"; import { convertToExcalidrawElements, exportToCanvas, @@ -8,6 +12,7 @@ import { import { NonDeletedExcalidrawElement } from "../../element/types"; import { AppClassProperties, BinaryFiles } from "../../types"; import { canvasToBlob } from "../../data/blob"; +import { EditorLocalStorage } from "../../data/EditorLocalStorage"; const resetPreview = ({ canvasRef, @@ -110,7 +115,6 @@ export const convertMermaidToExcalidraw = async ({ parent.style.background = "var(--default-bg-color)"; canvasNode.replaceChildren(canvas); } catch (err: any) { - console.error(err); parent.style.background = "var(--default-bg-color)"; if (mermaidDefinition) { setError(err); @@ -120,14 +124,11 @@ export const convertMermaidToExcalidraw = async ({ } }; -export const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; -export const saveMermaidDataToStorage = (data: string) => { - try { - localStorage.setItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, data); - } catch (error: any) { - // Unable to access window.localStorage - console.error(error); - } +export const saveMermaidDataToStorage = (mermaidDefinition: string) => { + EditorLocalStorage.set( + EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW, + mermaidDefinition, + ); }; export const insertToEditor = ({ diff --git a/src/components/dropdownMenu/DropdownMenuItem.tsx b/src/components/dropdownMenu/DropdownMenuItem.tsx index 1d92e1f18..c3a165eab 100644 --- a/src/components/dropdownMenu/DropdownMenuItem.tsx +++ b/src/components/dropdownMenu/DropdownMenuItem.tsx @@ -49,12 +49,12 @@ export const DropDownMenuItemBadge = ({ style={{ display: "inline-flex", marginLeft: "auto", - padding: "1px 4px", + padding: "2px 4px", background: "pink", borderRadius: 6, - fontSize: 11, + fontSize: 9, color: "black", - fontFamily: "monospace", + fontFamily: "Cascadia, monospace", }} > {children} diff --git a/src/constants.ts b/src/constants.ts index 902706201..5594f356e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -373,5 +373,6 @@ export const TOOL_TYPE = { export const EDITOR_LS_KEYS = { OAI_API_KEY: "excalidraw-oai-api-key", // legacy naming (non)scheme + MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw", PUBLISH_LIBRARY: "publish-library-data", } as const; diff --git a/src/css/styles.scss b/src/css/styles.scss index d7202c6ac..cafd21fbe 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -53,14 +53,20 @@ // component (e.g. if you select text in a sidebar) user-select: none; + .excalidraw-link, a { font-weight: 500; text-decoration: none; color: var(--link-color); + user-select: none; + cursor: pointer; &:hover { text-decoration: underline; } + &:active { + text-decoration: none; + } } canvas { diff --git a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap index ce35ead1f..c93389663 100644 --- a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap +++ b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap @@ -6,5 +6,5 @@ exports[`Test > should open mermaid popup when active too B --> C{Let me think} C -->|One| D[Laptop] C -->|Two| E[iPhone] - C -->|Three| F[Car]
" + C -->|Three| F[Car]
Ctrl
Enter
" `; From 72ea8022bf23b535aa84675b6b6b5f9b4cbf2bc2 Mon Sep 17 00:00:00 2001 From: Vaibhav Shukla Date: Wed, 6 Dec 2023 11:36:39 +0530 Subject: [PATCH 04/79] docs: changelog instruction removed from docs (#7395) changelog instruction removed from docs --- dev-docs/docs/introduction/contributing.mdx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dev-docs/docs/introduction/contributing.mdx b/dev-docs/docs/introduction/contributing.mdx index a33cb7a03..1602fd76d 100644 --- a/dev-docs/docs/introduction/contributing.mdx +++ b/dev-docs/docs/introduction/contributing.mdx @@ -52,15 +52,6 @@ Make sure the title starts with a semantic prefix: - **chore**: Other changes that don't modify src or test files - **revert**: Reverts a previous commit -### Changelog - -Add a brief description of your pull request to the changelog located here: [changelog](https://github.com/excalidraw/excalidraw/blob/master/CHANGELOG.md) - -Notes: - -- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title -- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request - ### Testing Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise. From a04cc707c323c95edce13a8691d0fa973d4549eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:31:44 +0530 Subject: [PATCH 05/79] build(deps-dev): bump vite from 4.4.2 to 4.4.12 (#7393) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.2 to 4.4.12. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 203 +++++---------------------------------------------- 2 files changed, 20 insertions(+), 185 deletions(-) diff --git a/package.json b/package.json index 4f8e550f3..78ab513b3 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "prettier": "2.6.2", "rewire": "6.0.0", "typescript": "4.9.4", - "vite": "4.4.2", + "vite": "4.4.12", "vite-plugin-checker": "0.6.1", "vite-plugin-ejs": "1.6.4", "vite-plugin-pwa": "0.16.4", diff --git a/yarn.lock b/yarn.lock index 82691f7b2..7689a0733 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1326,221 +1326,111 @@ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== -"@esbuild/android-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" - integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== - "@esbuild/android-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz#fa6f0cc7105367cb79cc0a8bf32bf50cb1673e45" integrity sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw== -"@esbuild/android-arm@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" - integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== - "@esbuild/android-arm@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.11.tgz#ae84a410696c9f549a15be94eaececb860bacacb" integrity sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q== -"@esbuild/android-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" - integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== - "@esbuild/android-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.11.tgz#0e58360bbc789ad0d68174d32ba20e678c2a16b6" integrity sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw== -"@esbuild/darwin-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" - integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== - "@esbuild/darwin-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz#fcdcd2ef76ca656540208afdd84f284072f0d1f9" integrity sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w== -"@esbuild/darwin-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" - integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== - "@esbuild/darwin-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz#c5ac602ec0504a8ff81e876bc8a9811e94d69d37" integrity sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw== -"@esbuild/freebsd-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" - integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== - "@esbuild/freebsd-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz#7012fb06ee3e6e0d5560664a65f3fefbcc46db2e" integrity sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A== -"@esbuild/freebsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" - integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== - "@esbuild/freebsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz#c5de1199f70e1f97d5c8fca51afa9bf9a2af5969" integrity sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q== -"@esbuild/linux-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" - integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== - "@esbuild/linux-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz#2a6d3a74e0b8b5f294e22b4515b29f76ebd42660" integrity sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog== -"@esbuild/linux-arm@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" - integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== - "@esbuild/linux-arm@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz#5175bd61b793b436e4aece6328aa0d9be07751e1" integrity sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg== -"@esbuild/linux-ia32@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" - integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== - "@esbuild/linux-ia32@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz#20ee6cfd65a398875f321a485e7b2278e5f6f67b" integrity sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw== -"@esbuild/linux-loong64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" - integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== - "@esbuild/linux-loong64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz#8e7b251dede75083bf44508dab5edce3f49d052b" integrity sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw== -"@esbuild/linux-mips64el@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" - integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== - "@esbuild/linux-mips64el@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz#a3125eb48538ac4932a9d05089b157f94e443165" integrity sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg== -"@esbuild/linux-ppc64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" - integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== - "@esbuild/linux-ppc64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz#842abadb7a0995bd539adee2be4d681b68279499" integrity sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ== -"@esbuild/linux-riscv64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" - integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== - "@esbuild/linux-riscv64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz#7ce6e6cee1c72d5b4d2f4f8b6fcccf4a9bea0e28" integrity sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w== -"@esbuild/linux-s390x@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" - integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== - "@esbuild/linux-s390x@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz#98fbc794363d02ded07d300df2e535650b297b96" integrity sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg== -"@esbuild/linux-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" - integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== - "@esbuild/linux-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz#f8458ec8cf74c8274e4cacd00744d8446cac52eb" integrity sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA== -"@esbuild/netbsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" - integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== - "@esbuild/netbsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz#a7b2f991b8293748a7be42eac1c4325faf0c7cca" integrity sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q== -"@esbuild/openbsd-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" - integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== - "@esbuild/openbsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz#3e50923de84c54008f834221130fd23646072b2f" integrity sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ== -"@esbuild/sunos-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" - integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== - "@esbuild/sunos-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz#ae47a550b0cd395de03606ecfba03cc96c7c19e2" integrity sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng== -"@esbuild/win32-arm64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" - integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== - "@esbuild/win32-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz#05d364582b7862d7fbf4698ef43644f7346dcfcc" integrity sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg== -"@esbuild/win32-ia32@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" - integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== - "@esbuild/win32-ia32@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz#a3372095a4a1939da672156a3c104f8ce85ee616" integrity sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg== -"@esbuild/win32-x64@0.17.19": - version "0.17.19" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" - integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== - "@esbuild/win32-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz#6526c7e1b40d5b9f0a222c6b767c22f6fb97aa57" @@ -4373,34 +4263,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.17.5: - version "0.17.19" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" - integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== - optionalDependencies: - "@esbuild/android-arm" "0.17.19" - "@esbuild/android-arm64" "0.17.19" - "@esbuild/android-x64" "0.17.19" - "@esbuild/darwin-arm64" "0.17.19" - "@esbuild/darwin-x64" "0.17.19" - "@esbuild/freebsd-arm64" "0.17.19" - "@esbuild/freebsd-x64" "0.17.19" - "@esbuild/linux-arm" "0.17.19" - "@esbuild/linux-arm64" "0.17.19" - "@esbuild/linux-ia32" "0.17.19" - "@esbuild/linux-loong64" "0.17.19" - "@esbuild/linux-mips64el" "0.17.19" - "@esbuild/linux-ppc64" "0.17.19" - "@esbuild/linux-riscv64" "0.17.19" - "@esbuild/linux-s390x" "0.17.19" - "@esbuild/linux-x64" "0.17.19" - "@esbuild/netbsd-x64" "0.17.19" - "@esbuild/openbsd-x64" "0.17.19" - "@esbuild/sunos-x64" "0.17.19" - "@esbuild/win32-arm64" "0.17.19" - "@esbuild/win32-ia32" "0.17.19" - "@esbuild/win32-x64" "0.17.19" - esbuild@^0.18.10: version "0.18.11" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.11.tgz#cbf94dc3359d57f600a0dbf281df9b1d1b4a156e" @@ -6396,10 +6258,10 @@ nanoid@4.0.2: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e" integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw== -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare-lite@^1.4.0: version "1.4.0" @@ -6727,21 +6589,12 @@ portfinder@^1.0.28: debug "^3.2.7" mkdirp "^0.5.6" -postcss@^8.4.23: - version "8.4.24" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" - integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== +postcss@^8.4.27: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.24: - version "8.4.25" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f" - integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw== - dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -7122,17 +6975,10 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" -rollup@^3.21.0: - version "3.26.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.0.tgz#9f2e0316a4ca641911cefd8515c562a9124e6130" - integrity sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg== - optionalDependencies: - fsevents "~2.3.2" - -rollup@^3.25.2: - version "3.26.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.2.tgz#2e76a37606cb523fc9fef43e6f59c93f86d95e7c" - integrity sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA== +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== optionalDependencies: fsevents "~2.3.2" @@ -8033,25 +7879,14 @@ vite-plugin-svgr@2.4.0: "@rollup/pluginutils" "^5.0.2" "@svgr/core" "^6.5.1" -vite@4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.2.tgz#acd47de771c498aec80e4900f30133d9529b278a" - integrity sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA== +vite@4.4.12, "vite@^3.0.0 || ^4.0.0": + version "4.4.12" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.12.tgz#e9c355d5a0d8a47afa46cb4bad10820da333da5c" + integrity sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ== dependencies: esbuild "^0.18.10" - postcss "^8.4.24" - rollup "^3.25.2" - optionalDependencies: - fsevents "~2.3.2" - -"vite@^3.0.0 || ^4.0.0": - version "4.3.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d" - integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg== - dependencies: - esbuild "^0.17.5" - postcss "^8.4.23" - rollup "^3.21.0" + postcss "^8.4.27" + rollup "^3.27.1" optionalDependencies: fsevents "~2.3.2" From b9cfbc2077a89b54a6ab73c262e260da50b59bcc Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:00:00 +0100 Subject: [PATCH 06/79] feat: add support for more UML arrowheads (#7391) --- src/actions/actionProperties.tsx | 143 ++++++++++---------- src/components/App.tsx | 2 + src/components/IconPicker.tsx | 27 +++- src/components/icons.tsx | 70 +++++++++- src/element/bounds.ts | 104 +++++++++++---- src/element/linearElementEditor.ts | 2 +- src/element/types.ts | 11 +- src/locales/en.json | 6 +- src/math.ts | 16 ++- src/renderer/renderElement.ts | 23 ++-- src/renderer/renderScene.ts | 28 +--- src/scene/Shape.ts | 202 +++++++++++++++++++---------- src/scene/ShapeCache.ts | 16 ++- src/scene/export.ts | 3 + src/scene/types.ts | 12 ++ 15 files changed, 449 insertions(+), 216 deletions(-) diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx index d17a87e36..ea4bb0716 100644 --- a/src/actions/actionProperties.tsx +++ b/src/actions/actionProperties.tsx @@ -15,7 +15,7 @@ import { IconPicker } from "../components/IconPicker"; import { ArrowheadArrowIcon, ArrowheadBarIcon, - ArrowheadDotIcon, + ArrowheadCircleIcon, ArrowheadTriangleIcon, ArrowheadNoneIcon, StrokeStyleDashedIcon, @@ -45,6 +45,10 @@ import { TextAlignCenterIcon, TextAlignRightIcon, FillZigZagIcon, + ArrowheadTriangleOutlineIcon, + ArrowheadCircleOutlineIcon, + ArrowheadDiamondIcon, + ArrowheadDiamondOutlineIcon, } from "../components/icons"; import { DEFAULT_FONT_FAMILY, @@ -1013,6 +1017,77 @@ export const actionChangeRoundness = register({ }, }); +const getArrowheadOptions = (flip: boolean) => { + return [ + { + value: null, + text: t("labels.arrowhead_none"), + keyBinding: "q", + icon: ArrowheadNoneIcon, + }, + { + value: "arrow", + text: t("labels.arrowhead_arrow"), + keyBinding: "w", + icon: , + }, + { + value: "bar", + text: t("labels.arrowhead_bar"), + keyBinding: "e", + icon: , + }, + { + value: "dot", + text: t("labels.arrowhead_circle"), + keyBinding: null, + icon: , + showInPicker: false, + }, + { + value: "circle", + text: t("labels.arrowhead_circle"), + keyBinding: "r", + icon: , + showInPicker: false, + }, + { + value: "circle_outline", + text: t("labels.arrowhead_circle_outline"), + keyBinding: null, + icon: , + showInPicker: false, + }, + { + value: "triangle", + text: t("labels.arrowhead_triangle"), + icon: , + keyBinding: "t", + }, + { + value: "triangle_outline", + text: t("labels.arrowhead_triangle_outline"), + icon: , + keyBinding: null, + showInPicker: false, + }, + { + value: "diamond", + text: t("labels.arrowhead_diamond"), + icon: , + keyBinding: null, + showInPicker: false, + }, + { + value: "diamond_outline", + text: t("labels.arrowhead_diamond_outline"), + icon: , + keyBinding: null, + showInPicker: false, + }, + ] as const; +}; + export const actionChangeArrowhead = register({ name: "changeArrowhead", trackEvent: false, @@ -1059,38 +1134,7 @@ export const actionChangeArrowhead = register({
, - keyBinding: "w", - }, - { - value: "bar", - text: t("labels.arrowhead_bar"), - icon: , - keyBinding: "e", - }, - { - value: "dot", - text: t("labels.arrowhead_dot"), - icon: , - keyBinding: "r", - }, - { - value: "triangle", - text: t("labels.arrowhead_triangle"), - icon: , - keyBinding: "t", - }, - ]} + options={getArrowheadOptions(!isRTL)} value={getFormValue( elements, appState, @@ -1106,38 +1150,7 @@ export const actionChangeArrowhead = register({ , - }, - { - value: "bar", - text: t("labels.arrowhead_bar"), - keyBinding: "e", - icon: , - }, - { - value: "dot", - text: t("labels.arrowhead_dot"), - keyBinding: "r", - icon: , - }, - { - value: "triangle", - text: t("labels.arrowhead_triangle"), - icon: , - keyBinding: "t", - }, - ]} + options={getArrowheadOptions(!!isRTL)} value={getFormValue( elements, appState, diff --git a/src/components/App.tsx b/src/components/App.tsx index 2c3f7a6ef..132964728 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1556,6 +1556,8 @@ class App extends React.Component { imageCache: this.imageCache, isExporting: false, renderGrid: true, + canvasBackgroundColor: + this.state.viewBackgroundColor, }} /> ({ }: { label: string; value: T; - options: { value: T; text: string; icon: JSX.Element; keyBinding: string }[]; + options: { + value: T; + text: string; + icon: JSX.Element; + keyBinding: string | null; + }[]; onChange: (value: T) => void; onClose: () => void; }) { @@ -110,9 +115,11 @@ function Picker({ (event.currentTarget as HTMLButtonElement).focus(); onChange(option.value); }} - title={`${option.text} — ${option.keyBinding.toUpperCase()}`} + title={`${option.text} ${ + option.keyBinding && `— ${option.keyBinding.toUpperCase()}` + }`} aria-label={option.text || "none"} - aria-keyshortcuts={option.keyBinding} + aria-keyshortcuts={option.keyBinding || undefined} key={option.text} ref={(el) => { if (el && i === 0) { @@ -127,7 +134,9 @@ function Picker({ }} > {option.icon} - {option.keyBinding} + {option.keyBinding && ( + {option.keyBinding} + )} ))}
@@ -144,7 +153,13 @@ export function IconPicker({ }: { label: string; value: T; - options: { value: T; text: string; icon: JSX.Element; keyBinding: string }[]; + options: readonly { + value: T; + text: string; + icon: JSX.Element; + keyBinding: string | null; + showInPicker?: boolean; + }[]; onChange: (value: T) => void; group?: string; }) { @@ -173,7 +188,7 @@ export function IconPicker({ {...(isRTL ? { right: 5.5 } : { left: -5.5 })} > opt.showInPicker !== false)} value={value} label={label} onChange={onChange} diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 3449ccda0..58664e168 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -1281,7 +1281,7 @@ export const ArrowheadArrowIcon = React.memo( ), ); -export const ArrowheadDotIcon = React.memo( +export const ArrowheadCircleIcon = React.memo( ({ flip = false }: { flip?: boolean }) => createIcon( + createIcon( + + + + , + { width: 40, height: 20 }, + ), +); + export const ArrowheadBarIcon = React.memo( ({ flip = false }: { flip?: boolean }) => createIcon( @@ -1326,6 +1342,58 @@ export const ArrowheadTriangleIcon = React.memo( ), ); +export const ArrowheadTriangleOutlineIcon = React.memo( + ({ flip = false }: { flip?: boolean }) => + createIcon( + + + + , + + { width: 40, height: 20 }, + ), +); + +export const ArrowheadDiamondIcon = React.memo( + ({ flip = false }: { flip?: boolean }) => + createIcon( + + + + , + { width: 40, height: 20 }, + ), +); + +export const ArrowheadDiamondOutlineIcon = React.memo( + ({ flip = false }: { flip?: boolean }) => + createIcon( + + + + , + { width: 40, height: 20 }, + ), +); + export const FontSizeSmallIcon = createIcon( <> diff --git a/src/element/bounds.ts b/src/element/bounds.ts index b0d33cfc9..f8d8223f7 100644 --- a/src/element/bounds.ts +++ b/src/element/bounds.ts @@ -484,6 +484,31 @@ const getFreeDrawElementAbsoluteCoords = ( return [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2]; }; +/** @returns number in pixels */ +export const getArrowheadSize = (arrowhead: Arrowhead): number => { + switch (arrowhead) { + case "arrow": + return 25; + case "diamond": + case "diamond_outline": + return 12; + default: + return 15; + } +}; + +/** @returns number in degrees */ +export const getArrowheadAngle = (arrowhead: Arrowhead): number => { + switch (arrowhead) { + case "bar": + return 90; + case "arrow": + return 20; + default: + return 25; + } +}; + export const getArrowheadPoints = ( element: ExcalidrawLinearElement, shape: Drawable[], @@ -536,53 +561,82 @@ export const getArrowheadPoints = ( const nx = (x2 - x1) / distance; const ny = (y2 - y1) / distance; - const size = { - arrow: 30, - bar: 15, - dot: 15, - triangle: 15, - }[arrowhead]; // pixels (will differ for each arrowhead) + const size = getArrowheadSize(arrowhead); let length = 0; - if (arrowhead === "arrow") { + { // Length for -> arrows is based on the length of the last section - const [cx, cy] = element.points[element.points.length - 1]; + const [cx, cy] = + position === "end" + ? element.points[element.points.length - 1] + : element.points[0]; const [px, py] = element.points.length > 1 - ? element.points[element.points.length - 2] + ? position === "end" + ? element.points[element.points.length - 2] + : element.points[1] : [0, 0]; length = Math.hypot(cx - px, cy - py); - } else { - // Length for other arrowhead types is based on the total length of the line - for (let i = 0; i < element.points.length; i++) { - const [px, py] = element.points[i - 1] || [0, 0]; - const [cx, cy] = element.points[i]; - length += Math.hypot(cx - px, cy - py); - } } // Scale down the arrowhead until we hit a certain size so that it doesn't look weird. // This value is selected by minimizing a minimum size with the last segment of the arrowhead - const minSize = Math.min(size, length / 2); + const lengthMultiplier = + arrowhead === "diamond" || arrowhead === "diamond_outline" ? 0.25 : 0.5; + const minSize = Math.min(size, length * lengthMultiplier); const xs = x2 - nx * minSize; const ys = y2 - ny * minSize; - if (arrowhead === "dot") { - const r = Math.hypot(ys - y2, xs - x2) + element.strokeWidth; - return [x2, y2, r]; + if ( + arrowhead === "dot" || + arrowhead === "circle" || + arrowhead === "circle_outline" + ) { + const diameter = Math.hypot(ys - y2, xs - x2) + element.strokeWidth - 2; + return [x2, y2, diameter]; } - const angle = { - arrow: 20, - bar: 90, - triangle: 25, - }[arrowhead]; // degrees + const angle = getArrowheadAngle(arrowhead); // Return points const [x3, y3] = rotate(xs, ys, x2, y2, (-angle * Math.PI) / 180); const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180); + + if (arrowhead === "diamond" || arrowhead === "diamond_outline") { + // point opposite to the arrowhead point + let ox; + let oy; + + if (position === "start") { + const [px, py] = element.points.length > 1 ? element.points[1] : [0, 0]; + + [ox, oy] = rotate( + x2 + minSize * 2, + y2, + x2, + y2, + Math.atan2(py - y2, px - x2), + ); + } else { + const [px, py] = + element.points.length > 1 + ? element.points[element.points.length - 2] + : [0, 0]; + + [ox, oy] = rotate( + x2 - minSize * 2, + y2, + x2, + y2, + Math.atan2(y2 - py, x2 - px), + ); + } + + return [x2, y2, x3, y3, ox, oy, x4, y4]; + } + return [x2, y2, x3, y3, x4, y4]; }; diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index 9ee490b39..bf64ee732 100644 --- a/src/element/linearElementEditor.ts +++ b/src/element/linearElementEditor.ts @@ -1444,7 +1444,7 @@ export class LinearElementEditor { x2 = maxX + element.x; y2 = maxY + element.y; } else { - const shape = ShapeCache.generateElementShape(element); + const shape = ShapeCache.generateElementShape(element, null); // first element is always the curve const ops = getCurvePathOps(shape[0]); diff --git a/src/element/types.ts b/src/element/types.ts index b863fb429..38be1bda6 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -223,7 +223,16 @@ export type PointBinding = { gap: number; }; -export type Arrowhead = "arrow" | "bar" | "dot" | "triangle"; +export type Arrowhead = + | "arrow" + | "bar" + | "dot" // legacy. Do not use for new elements. + | "circle" + | "circle_outline" + | "triangle" + | "triangle_outline" + | "diamond" + | "diamond_outline"; export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{ diff --git a/src/locales/en.json b/src/locales/en.json index 8b4a1df21..95c6eb2e6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -38,8 +38,12 @@ "arrowhead_none": "None", "arrowhead_arrow": "Arrow", "arrowhead_bar": "Bar", - "arrowhead_dot": "Dot", + "arrowhead_circle": "Circle", + "arrowhead_circle_outline": "Circle (outline)", "arrowhead_triangle": "Triangle", + "arrowhead_triangle_outline": "Triangle (outline)", + "arrowhead_diamond": "Diamond", + "arrowhead_diamond_outline": "Diamond (outline)", "fontSize": "Font size", "fontFamily": "Font family", "addWatermark": "Add \"Made with Excalidraw\"", diff --git a/src/math.ts b/src/math.ts index a56b97a72..8c0fb0ebd 100644 --- a/src/math.ts +++ b/src/math.ts @@ -15,18 +15,20 @@ import { Mutable } from "./utility-types"; import { ShapeCache } from "./scene/ShapeCache"; export const rotate = ( - x1: number, - y1: number, - x2: number, - y2: number, + // target point to rotate + x: number, + y: number, + // point to rotate against + cx: number, + cy: number, angle: number, ): [number, number] => // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥 // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦. // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line [ - (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, - (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2, + (x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx, + (x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy, ]; export const rotatePoint = ( @@ -303,7 +305,7 @@ export const getControlPointsForBezierCurve = ( element: NonDeleted, endPoint: Point, ) => { - const shape = ShapeCache.generateElementShape(element); + const shape = ShapeCache.generateElementShape(element, null); if (!shape) { return null; } diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index 24eaf64b8..2617d4694 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -20,7 +20,7 @@ import type { RoughCanvas } from "roughjs/bin/canvas"; import type { Drawable } from "roughjs/bin/core"; import type { RoughSVG } from "roughjs/bin/svg"; -import { StaticCanvasRenderConfig } from "../scene/types"; +import { SVGRenderConfig, StaticCanvasRenderConfig } from "../scene/types"; import { distance, getFontString, @@ -638,7 +638,7 @@ export const renderElement = ( // TODO investigate if we can do this in situ. Right now we need to call // beforehand because math helpers (such as getElementAbsoluteCoords) // rely on existing shapes - ShapeCache.generateElementShape(element); + ShapeCache.generateElementShape(element, null); if (renderConfig.isExporting) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); @@ -680,7 +680,7 @@ export const renderElement = ( // TODO investigate if we can do this in situ. Right now we need to call // beforehand because math helpers (such as getElementAbsoluteCoords) // rely on existing shapes - ShapeCache.generateElementShape(element, renderConfig.isExporting); + ShapeCache.generateElementShape(element, renderConfig); if (renderConfig.isExporting) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); const cx = (x1 + x2) / 2 + appState.scrollX; @@ -876,11 +876,7 @@ export const renderElementToSvg = ( files: BinaryFiles, offsetX: number, offsetY: number, - renderConfig: { - exportWithDarkMode: boolean; - renderEmbeddables: boolean; - frameRendering: AppState["frameRendering"]; - }, + renderConfig: SVGRenderConfig, ) => { const offset = { x: offsetX, y: offsetY }; const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); @@ -933,7 +929,7 @@ export const renderElementToSvg = ( case "rectangle": case "diamond": case "ellipse": { - const shape = ShapeCache.generateElementShape(element); + const shape = ShapeCache.generateElementShape(element, null); const node = roughSVGDrawWithPrecision( rsvg, shape, @@ -964,7 +960,7 @@ export const renderElementToSvg = ( case "iframe": case "embeddable": { // render placeholder rectangle - const shape = ShapeCache.generateElementShape(element, true); + const shape = ShapeCache.generateElementShape(element, renderConfig); const node = roughSVGDrawWithPrecision( rsvg, shape, @@ -1113,7 +1109,7 @@ export const renderElementToSvg = ( } group.setAttribute("stroke-linecap", "round"); - const shapes = ShapeCache.generateElementShape(element); + const shapes = ShapeCache.generateElementShape(element, renderConfig); shapes.forEach((shape) => { const node = roughSVGDrawWithPrecision( rsvg, @@ -1156,7 +1152,10 @@ export const renderElementToSvg = ( break; } case "freedraw": { - const backgroundFillShape = ShapeCache.generateElementShape(element); + const backgroundFillShape = ShapeCache.generateElementShape( + element, + renderConfig, + ); const node = backgroundFillShape ? roughSVGDrawWithPrecision( rsvg, diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 671e90d06..c41d59bd3 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -30,6 +30,7 @@ import { roundRect } from "./roundRect"; import { InteractiveCanvasRenderConfig, InteractiveSceneRenderConfig, + SVGRenderConfig, StaticCanvasRenderConfig, StaticSceneRenderConfig, } from "../scene/types"; @@ -1448,29 +1449,12 @@ export const renderSceneToSvg = ( rsvg: RoughSVG, svgRoot: SVGElement, files: BinaryFiles, - { - offsetX = 0, - offsetY = 0, - exportWithDarkMode, - renderEmbeddables, - frameRendering, - }: { - offsetX?: number; - offsetY?: number; - exportWithDarkMode: boolean; - renderEmbeddables: boolean; - frameRendering: AppState["frameRendering"]; - }, + renderConfig: SVGRenderConfig, ) => { if (!svgRoot) { return; } - const renderConfig = { - exportWithDarkMode, - renderEmbeddables, - frameRendering, - }; // render elements elements .filter((el) => !isIframeLikeOrItsLabel(el)) @@ -1482,8 +1466,8 @@ export const renderSceneToSvg = ( rsvg, svgRoot, files, - element.x + offsetX, - element.y + offsetY, + element.x + renderConfig.offsetX, + element.y + renderConfig.offsetY, renderConfig, ); } catch (error: any) { @@ -1503,8 +1487,8 @@ export const renderSceneToSvg = ( rsvg, svgRoot, files, - element.x + offsetX, - element.y + offsetY, + element.x + renderConfig.offsetX, + element.y + renderConfig.offsetY, renderConfig, ); } catch (error: any) { diff --git a/src/scene/Shape.ts b/src/scene/Shape.ts index 4d928e949..190f7562f 100644 --- a/src/scene/Shape.ts +++ b/src/scene/Shape.ts @@ -145,6 +145,126 @@ const modifyIframeLikeForRoughOptions = ( return element; }; +const getArrowheadShapes = ( + element: ExcalidrawLinearElement, + shape: Drawable[], + position: "start" | "end", + arrowhead: Arrowhead, + generator: RoughGenerator, + options: Options, + canvasBackgroundColor: string, +) => { + const arrowheadPoints = getArrowheadPoints( + element, + shape, + position, + arrowhead, + ); + + if (arrowheadPoints === null) { + return []; + } + + switch (arrowhead) { + case "dot": + case "circle": + case "circle_outline": { + const [x, y, diameter] = arrowheadPoints; + + // always use solid stroke for arrowhead + delete options.strokeLineDash; + + return [ + generator.circle(x, y, diameter, { + ...options, + fill: + arrowhead === "circle_outline" + ? canvasBackgroundColor + : element.strokeColor, + + fillStyle: "solid", + stroke: element.strokeColor, + roughness: Math.min(0.5, options.roughness || 0), + }), + ]; + } + case "triangle": + case "triangle_outline": { + const [x, y, x2, y2, x3, y3] = arrowheadPoints; + + // always use solid stroke for arrowhead + delete options.strokeLineDash; + + return [ + generator.polygon( + [ + [x, y], + [x2, y2], + [x3, y3], + [x, y], + ], + { + ...options, + fill: + arrowhead === "triangle_outline" + ? canvasBackgroundColor + : element.strokeColor, + fillStyle: "solid", + roughness: Math.min(1, options.roughness || 0), + }, + ), + ]; + } + case "diamond": + case "diamond_outline": { + const [x, y, x2, y2, x3, y3, x4, y4] = arrowheadPoints; + + // always use solid stroke for arrowhead + delete options.strokeLineDash; + + return [ + generator.polygon( + [ + [x, y], + [x2, y2], + [x3, y3], + [x4, y4], + [x, y], + ], + { + ...options, + fill: + arrowhead === "diamond_outline" + ? canvasBackgroundColor + : element.strokeColor, + fillStyle: "solid", + roughness: Math.min(1, options.roughness || 0), + }, + ), + ]; + } + case "bar": + case "arrow": + default: { + const [x2, y2, x3, y3, x4, y4] = arrowheadPoints; + + if (element.strokeStyle === "dotted") { + // for dotted arrows caps, reduce gap to make it more legible + const dash = getDashArrayDotted(element.strokeWidth - 1); + options.strokeLineDash = [dash[0], dash[1] - 1]; + } else { + // for solid/dashed, keep solid arrow cap + delete options.strokeLineDash; + } + options.roughness = Math.min(1, options.roughness || 0); + return [ + generator.line(x3, y3, x2, y2, options), + generator.line(x4, y4, x2, y2, options), + ]; + } + } +}; + /** * Generates the roughjs shape for given element. * @@ -155,7 +275,10 @@ const modifyIframeLikeForRoughOptions = ( export const _generateElementShape = ( element: Exclude, generator: RoughGenerator, - isExporting: boolean = false, + { + isExporting, + canvasBackgroundColor, + }: { isExporting: boolean; canvasBackgroundColor: string }, ): Drawable | Drawable[] | null => { switch (element.type) { case "rectangle": @@ -276,83 +399,15 @@ export const _generateElementShape = ( if (element.type === "arrow") { const { startArrowhead = null, endArrowhead = "arrow" } = element; - const getArrowheadShapes = ( - element: ExcalidrawLinearElement, - shape: Drawable[], - position: "start" | "end", - arrowhead: Arrowhead, - ) => { - const arrowheadPoints = getArrowheadPoints( - element, - shape, - position, - arrowhead, - ); - - if (arrowheadPoints === null) { - return []; - } - - // Other arrowheads here... - if (arrowhead === "dot") { - const [x, y, r] = arrowheadPoints; - - return [ - generator.circle(x, y, r, { - ...options, - fill: element.strokeColor, - fillStyle: "solid", - stroke: "none", - }), - ]; - } - - if (arrowhead === "triangle") { - const [x, y, x2, y2, x3, y3] = arrowheadPoints; - - // always use solid stroke for triangle arrowhead - delete options.strokeLineDash; - - return [ - generator.polygon( - [ - [x, y], - [x2, y2], - [x3, y3], - [x, y], - ], - { - ...options, - fill: element.strokeColor, - fillStyle: "solid", - }, - ), - ]; - } - - // Arrow arrowheads - const [x2, y2, x3, y3, x4, y4] = arrowheadPoints; - - if (element.strokeStyle === "dotted") { - // for dotted arrows caps, reduce gap to make it more legible - const dash = getDashArrayDotted(element.strokeWidth - 1); - options.strokeLineDash = [dash[0], dash[1] - 1]; - } else { - // for solid/dashed, keep solid arrow cap - delete options.strokeLineDash; - } - return [ - generator.line(x3, y3, x2, y2, options), - generator.line(x4, y4, x2, y2, options), - ]; - }; - if (startArrowhead !== null) { const shapes = getArrowheadShapes( element, shape, "start", startArrowhead, + generator, + options, + canvasBackgroundColor, ); shape.push(...shapes); } @@ -367,6 +422,9 @@ export const _generateElementShape = ( shape, "end", endArrowhead, + generator, + options, + canvasBackgroundColor, ); shape.push(...shapes); } diff --git a/src/scene/ShapeCache.ts b/src/scene/ShapeCache.ts index ded1b88fa..e5a08c1f2 100644 --- a/src/scene/ShapeCache.ts +++ b/src/scene/ShapeCache.ts @@ -7,6 +7,8 @@ import { import { elementWithCanvasCache } from "../renderer/renderElement"; import { _generateElementShape } from "./Shape"; import { ElementShape, ElementShapes } from "./types"; +import { COLOR_PALETTE } from "../colors"; +import { AppState } from "../types"; export class ShapeCache { private static rg = new RoughGenerator(); @@ -46,10 +48,15 @@ export class ShapeCache { T extends Exclude, >( element: T, - isExporting = false, + renderConfig: { + isExporting: boolean; + canvasBackgroundColor: AppState["viewBackgroundColor"]; + } | null, ) => { // when exporting, always regenerated to guarantee the latest shape - const cachedShape = isExporting ? undefined : ShapeCache.get(element); + const cachedShape = renderConfig?.isExporting + ? undefined + : ShapeCache.get(element); // `null` indicates no rc shape applicable for this element type, // but it's considered a valid cache value (= do not regenerate) @@ -62,7 +69,10 @@ export class ShapeCache { const shape = _generateElementShape( element, ShapeCache.rg, - isExporting, + renderConfig || { + isExporting: false, + canvasBackgroundColor: COLOR_PALETTE.white, + }, ) as T["type"] extends keyof ElementShapes ? ElementShapes[T["type"]] : Drawable | null; diff --git a/src/scene/export.ts b/src/scene/export.ts index 54ede380c..f20748261 100644 --- a/src/scene/export.ts +++ b/src/scene/export.ts @@ -262,6 +262,7 @@ export const exportToCanvas = async ( theme: appState.exportWithDarkMode ? "dark" : "light", }, renderConfig: { + canvasBackgroundColor: viewBackgroundColor, imageCache, renderGrid: false, isExporting: true, @@ -429,9 +430,11 @@ export const exportToSvg = async ( renderSceneToSvg(elementsForRender, rsvg, svgRoot, files || {}, { offsetX, offsetY, + isExporting: true, exportWithDarkMode, renderEmbeddables: opts?.renderEmbeddables ?? false, frameRendering, + canvasBackgroundColor: viewBackgroundColor, }); tempScene.destroy(); diff --git a/src/scene/types.ts b/src/scene/types.ts index dc709a22a..b4320866c 100644 --- a/src/scene/types.ts +++ b/src/scene/types.ts @@ -6,11 +6,13 @@ import { } from "../element/types"; import { AppClassProperties, + AppState, InteractiveCanvasAppState, StaticCanvasAppState, } from "../types"; export type StaticCanvasRenderConfig = { + canvasBackgroundColor: AppState["viewBackgroundColor"]; // extra options passed to the renderer // --------------------------------------------------------------------------- imageCache: AppClassProperties["imageCache"]; @@ -20,6 +22,16 @@ export type StaticCanvasRenderConfig = { isExporting: boolean; }; +export type SVGRenderConfig = { + offsetX: number; + offsetY: number; + isExporting: boolean; + exportWithDarkMode: boolean; + renderEmbeddables: boolean; + frameRendering: AppState["frameRendering"]; + canvasBackgroundColor: AppState["viewBackgroundColor"]; +}; + export type InteractiveCanvasRenderConfig = { // collab-related state // --------------------------------------------------------------------------- From 557add5bf7dd1d682e5802e5a6289cdb895b1593 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 6 Dec 2023 21:31:54 +0530 Subject: [PATCH 07/79] =?UTF-8?q?feat:=20Support=20Mermaid=20Class=20diagr?= =?UTF-8?q?ams=20=F0=9F=A5=B3=20(#7381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support mermaid class diagrams * upgrade mermaid-to-excalidraw * upgrade mermaid-to-excalidraw * add sequence diagrams in supported chart types * upgrade mermaid-to-excalidraw * update i18n --- package.json | 2 +- src/components/TTDDialog/MermaidToExcalidraw.tsx | 3 +++ src/locales/en.json | 2 +- src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap | 2 +- yarn.lock | 8 ++++---- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 78ab513b3..872a0c51d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "@braintree/sanitize-url": "6.0.2", "@excalidraw/laser-pointer": "1.2.0", - "@excalidraw/mermaid-to-excalidraw": "0.1.2", + "@excalidraw/mermaid-to-excalidraw": "0.2.0", "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.0.3", "@radix-ui/react-tabs": "1.0.2", diff --git a/src/components/TTDDialog/MermaidToExcalidraw.tsx b/src/components/TTDDialog/MermaidToExcalidraw.tsx index df9f4b8b1..237cb2606 100644 --- a/src/components/TTDDialog/MermaidToExcalidraw.tsx +++ b/src/components/TTDDialog/MermaidToExcalidraw.tsx @@ -88,6 +88,9 @@ const MermaidToExcalidraw = ({ {el} )} + classLink={(el) => ( + {el} + )} /> diff --git a/src/locales/en.json b/src/locales/en.json index 95c6eb2e6..2f845bb10 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -518,7 +518,7 @@ "mermaid": { "title": "Mermaid to Excalidraw", "button": "Insert", - "description": "Currently only Flowcharts and Sequence Diagrams are supported. The other types will be rendered as image in Excalidraw.", + "description": "Currently only Flowchart, Sequence, and Class Diagrams are supported. The other types will be rendered as image in Excalidraw.", "syntax": "Mermaid Syntax", "preview": "Preview" } diff --git a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap index c93389663..41388038e 100644 --- a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap +++ b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Test > should open mermaid popup when active tool is mermaid 1`] = ` -"

Mermaid to Excalidraw

Currently only Flowcharts and Sequence Diagrams are supported. The other types will be rendered as image in Excalidraw.
Ctrl
Enter
" + C -->|Three| F[Car]
Ctrl
Enter
" `; diff --git a/src/tests/__snapshots__/export.test.tsx.snap b/src/tests/__snapshots__/export.test.tsx.snap index eff82ae4c..72b379b8a 100644 --- a/src/tests/__snapshots__/export.test.tsx.snap +++ b/src/tests/__snapshots__/export.test.tsx.snap @@ -1,25 +1,25 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`export > exporting svg containing transformed images > svg export output 1`] = ` -" +" - - " + " `; diff --git a/src/tests/scene/__snapshots__/export.test.ts.snap b/src/tests/scene/__snapshots__/export.test.ts.snap index d64ddfb3b..65012ba3e 100644 --- a/src/tests/scene/__snapshots__/export.test.ts.snap +++ b/src/tests/scene/__snapshots__/export.test.ts.snap @@ -85,23 +85,23 @@ exports[`exportToSvg > with elements that have a link 1`] = ` - - " + " `; exports[`exportToSvg > with exportEmbedScene 1`] = ` @@ -109,21 +109,21 @@ exports[`exportToSvg > with exportEmbedScene 1`] = ` eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SPW/CMFx1MDAxMN35XHUwMDE1UbpcIuFAIJSNlqpCqtqBXHUwMDAxqVVcdTAwMDdcdTAwMTNfiFx1MDAxNcdcdTAwMGW2w4dcdTAwMTD/vbaBuETMnerBkt+9d3e+e8dOXHUwMDEwhPpQQThcdELYp5hRXCLxLuxafFx1MDAwYlJRwU2o795K1DJ1zFxc62rS6zFhXHUwMDA0uVB6MkBcYp1FwKBcdTAwMDSulaF9mXdcdTAwMTBcdTAwMWPdbVwilFjpdik3XHUwMDFm06ygnPQ3aZm8zaavn07qSHvDiaO4eVx1MDAxZmz1QdK8d5To3GBcdTAwMTFCXHKWXHUwMDAzXee6XHUwMDA1Yr5mtlePKC1FXHUwMDAxz4JcdGlcdTAwMWJ5QO740iucXHUwMDE2aylqTjwnXHUwMDFhYrzKPCejjC30gZ2ngNO8llx1MDAxMLYqLK8ttvBGp4SZsleZkuucg1I3XHUwMDFhUeGU6kPrV7a/ak7cdL99V1x1MDAxMpcwt+PlNWO/XHUwMDEzc3JJfFx1MDAxM1BcdTAwMDDEJY6j0TB5ROMm4ldcdTAwMWX1UVx1MDAxYn1cdTAwMTfcrT+KxmOE4n4yalx1MDAxOFTNzOK1S5thpsBP1Tbx4k1x00hdXHUwMDExfFx1MDAxNvmPM8qLNs9cdTAwMTituJP7alxcQnEpOFx0XHUwMDFkfur+2+7fdn9hO2CMVlxuLrYzt1x1MDAxYk2Iq2qhTX5DOZsw3FLYPd1Zc+aO1TvT2jWDbfZ46px+XHUwMDAwcU5t0CJ9 - - " + " `; diff --git a/vite.config.ts b/vite.config.mts similarity index 100% rename from vite.config.ts rename to vite.config.mts diff --git a/vitest.config.ts b/vitest.config.mts similarity index 69% rename from vitest.config.ts rename to vitest.config.mts index f1484262f..ec579eb45 100644 --- a/vitest.config.ts +++ b/vitest.config.mts @@ -7,10 +7,12 @@ export default defineConfig({ environment: "jsdom", coverage: { reporter: ["text", "json-summary", "json", "html"], - lines: 70, - branches: 70, - functions: 68, - statements: 70, + thresholds: { + lines: 70, + branches: 70, + functions: 68, + statements: 70, + }, }, }, }); diff --git a/yarn.lock b/yarn.lock index c61cbe701..6ad6530d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1326,115 +1326,115 @@ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== -"@esbuild/android-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz#fa6f0cc7105367cb79cc0a8bf32bf50cb1673e45" - integrity sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw== +"@esbuild/android-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz#fb7130103835b6d43ea499c3f30cfb2b2ed58456" + integrity sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA== -"@esbuild/android-arm@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.11.tgz#ae84a410696c9f549a15be94eaececb860bacacb" - integrity sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q== +"@esbuild/android-arm@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.8.tgz#b46e4d9e984e6d6db6c4224d72c86b7757e35bcb" + integrity sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA== -"@esbuild/android-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.11.tgz#0e58360bbc789ad0d68174d32ba20e678c2a16b6" - integrity sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw== +"@esbuild/android-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.8.tgz#a13db9441b5a4f4e4fec4a6f8ffacfea07888db7" + integrity sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A== -"@esbuild/darwin-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz#fcdcd2ef76ca656540208afdd84f284072f0d1f9" - integrity sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w== +"@esbuild/darwin-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz#49f5718d36541f40dd62bfdf84da9c65168a0fc2" + integrity sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw== -"@esbuild/darwin-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz#c5ac602ec0504a8ff81e876bc8a9811e94d69d37" - integrity sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw== +"@esbuild/darwin-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz#75c5c88371eea4bfc1f9ecfd0e75104c74a481ac" + integrity sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q== -"@esbuild/freebsd-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz#7012fb06ee3e6e0d5560664a65f3fefbcc46db2e" - integrity sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A== +"@esbuild/freebsd-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz#9d7259fea4fd2b5f7437b52b542816e89d7c8575" + integrity sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw== -"@esbuild/freebsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz#c5de1199f70e1f97d5c8fca51afa9bf9a2af5969" - integrity sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q== +"@esbuild/freebsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz#abac03e1c4c7c75ee8add6d76ec592f46dbb39e3" + integrity sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg== -"@esbuild/linux-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz#2a6d3a74e0b8b5f294e22b4515b29f76ebd42660" - integrity sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog== +"@esbuild/linux-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz#c577932cf4feeaa43cb9cec27b89cbe0df7d9098" + integrity sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ== -"@esbuild/linux-arm@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz#5175bd61b793b436e4aece6328aa0d9be07751e1" - integrity sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg== +"@esbuild/linux-arm@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz#d6014d8b98b5cbc96b95dad3d14d75bb364fdc0f" + integrity sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ== -"@esbuild/linux-ia32@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz#20ee6cfd65a398875f321a485e7b2278e5f6f67b" - integrity sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw== +"@esbuild/linux-ia32@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz#2379a0554307d19ac4a6cdc15b08f0ea28e7a40d" + integrity sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ== -"@esbuild/linux-loong64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz#8e7b251dede75083bf44508dab5edce3f49d052b" - integrity sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw== +"@esbuild/linux-loong64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz#e2a5bbffe15748b49356a6cd7b2d5bf60c5a7123" + integrity sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ== -"@esbuild/linux-mips64el@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz#a3125eb48538ac4932a9d05089b157f94e443165" - integrity sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg== +"@esbuild/linux-mips64el@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz#1359331e6f6214f26f4b08db9b9df661c57cfa24" + integrity sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q== -"@esbuild/linux-ppc64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz#842abadb7a0995bd539adee2be4d681b68279499" - integrity sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ== +"@esbuild/linux-ppc64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz#9ba436addc1646dc89dae48c62d3e951ffe70951" + integrity sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg== -"@esbuild/linux-riscv64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz#7ce6e6cee1c72d5b4d2f4f8b6fcccf4a9bea0e28" - integrity sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w== +"@esbuild/linux-riscv64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz#fbcf0c3a0b20f40b5fc31c3b7695f0769f9de66b" + integrity sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg== -"@esbuild/linux-s390x@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz#98fbc794363d02ded07d300df2e535650b297b96" - integrity sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg== +"@esbuild/linux-s390x@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz#989e8a05f7792d139d5564ffa7ff898ac6f20a4a" + integrity sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg== -"@esbuild/linux-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz#f8458ec8cf74c8274e4cacd00744d8446cac52eb" - integrity sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA== +"@esbuild/linux-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz#b187295393a59323397fe5ff51e769ec4e72212b" + integrity sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg== -"@esbuild/netbsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz#a7b2f991b8293748a7be42eac1c4325faf0c7cca" - integrity sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q== +"@esbuild/netbsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz#c1ec0e24ea82313cb1c7bae176bd5acd5bde7137" + integrity sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw== -"@esbuild/openbsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz#3e50923de84c54008f834221130fd23646072b2f" - integrity sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ== +"@esbuild/openbsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz#0c5b696ac66c6d70cf9ee17073a581a28af9e18d" + integrity sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ== -"@esbuild/sunos-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz#ae47a550b0cd395de03606ecfba03cc96c7c19e2" - integrity sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng== +"@esbuild/sunos-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz#2a697e1f77926ff09fcc457d8f29916d6cd48fb1" + integrity sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w== -"@esbuild/win32-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz#05d364582b7862d7fbf4698ef43644f7346dcfcc" - integrity sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg== +"@esbuild/win32-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz#ec029e62a2fca8c071842ecb1bc5c2dd20b066f1" + integrity sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg== -"@esbuild/win32-ia32@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz#a3372095a4a1939da672156a3c104f8ce85ee616" - integrity sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg== +"@esbuild/win32-ia32@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz#cbb9a3146bde64dc15543e48afe418c7a3214851" + integrity sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw== -"@esbuild/win32-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz#6526c7e1b40d5b9f0a222c6b767c22f6fb97aa57" - integrity sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA== +"@esbuild/win32-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz#c8285183dbdb17008578dbacb6e22748709b4822" + integrity sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1801,6 +1801,13 @@ dependencies: "@sinclair/typebox" "^0.25.16" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/types@^29.5.0": version "29.5.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" @@ -2230,6 +2237,66 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rollup/rollup-android-arm-eabi@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz#0ea289f68ff248b50fea5716ca9f65f7d4dba3ae" + integrity sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA== + +"@rollup/rollup-android-arm64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz#27c8c67fc5de574874085a1b480ac65b3e18378e" + integrity sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA== + +"@rollup/rollup-darwin-arm64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz#c5735c042980c85495411af7183dd20294763bd8" + integrity sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw== + +"@rollup/rollup-darwin-x64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz#af844bd54abb73ca3c9cf89a31eec17861d1375d" + integrity sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg== + +"@rollup/rollup-linux-arm-gnueabihf@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz#5e972f63c441eaf859551039b3f18db9b035977d" + integrity sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ== + +"@rollup/rollup-linux-arm64-gnu@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz#f4cfbc71e3b6fdb395b28b1472414e181515c72d" + integrity sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw== + +"@rollup/rollup-linux-arm64-musl@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz#6a94c691830dc29bf708de7c640f494996130893" + integrity sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw== + +"@rollup/rollup-linux-x64-gnu@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz#f07bae3f7dc532d9ea5ab36c9071db329f9a1efb" + integrity sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA== + +"@rollup/rollup-linux-x64-musl@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz#357a34fdbf410af88ce48bd802bea6462bb9a8bc" + integrity sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ== + +"@rollup/rollup-win32-arm64-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz#b6e97fd38281667e35297033393cd1101f4a31be" + integrity sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ== + +"@rollup/rollup-win32-ia32-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz#a95db026c640c8128bfd38546d85342f2329beaf" + integrity sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw== + +"@rollup/rollup-win32-x64-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz#45785b5caf83200a34a9867ba50d69560880c120" + integrity sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A== + "@rushstack/eslint-patch@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" @@ -2302,6 +2369,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" @@ -2448,18 +2520,6 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== -"@types/chai-subset@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" - integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== - dependencies: - "@types/chai" "*" - -"@types/chai@*", "@types/chai@^4.3.5": - version "4.3.5" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" - integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== - "@types/chai@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" @@ -2802,39 +2862,39 @@ test-exclude "^6.0.0" v8-to-istanbul "^9.1.0" -"@vitest/expect@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.1.tgz#2ba6cb96695f4b4388c6d955423a81afc79b8da0" - integrity sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ== +"@vitest/expect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.0.1.tgz#5e63902316a3c65948c6e36f284046962601fb88" + integrity sha512-3cdrb/eKD/0tygDX75YscuHEHMUJ70u3UoLSq2eqhWks57AyzvsDQbyn53IhZ0tBN7gA8Jj2VhXiOV2lef7thw== dependencies: - "@vitest/spy" "0.34.1" - "@vitest/utils" "0.34.1" - chai "^4.3.7" + "@vitest/spy" "1.0.1" + "@vitest/utils" "1.0.1" + chai "^4.3.10" -"@vitest/runner@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.1.tgz#23c21ba1db8bff610988c72744db590d0fb6c4ba" - integrity sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g== +"@vitest/runner@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.0.1.tgz#d94cab9e3008dba52f89e811540184334766ab61" + integrity sha512-/+z0vhJ0MfRPT3AyTvAK6m57rzlew/ct8B2a4LMv7NhpPaiI2QLGyOBMB3lcioWdJHjRuLi9aYppfOv0B5aRQA== dependencies: - "@vitest/utils" "0.34.1" - p-limit "^4.0.0" + "@vitest/utils" "1.0.1" + p-limit "^5.0.0" pathe "^1.1.1" -"@vitest/snapshot@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.1.tgz#814c65f8e714eaf255f47838541004b2a2ba28e6" - integrity sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ== +"@vitest/snapshot@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.0.1.tgz#9d2a01c64726afa62264175554690e5ce148d4a5" + integrity sha512-wIPtPDGSxEZ+DpNMc94AsybX6LV6uN6sosf5TojyP1m2QbKwiRuLV/5RSsjt1oWViHsTj8mlcwrQQ1zHGO0fMw== dependencies: - magic-string "^0.30.1" + magic-string "^0.30.5" pathe "^1.1.1" - pretty-format "^29.5.0" + pretty-format "^29.7.0" -"@vitest/spy@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.1.tgz#2f77234a3d554c5dea664943f2caaab92d304f3c" - integrity sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ== +"@vitest/spy@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.0.1.tgz#d82af1c4d935e08443bf20432ba55afd001ac71f" + integrity sha512-yXwm1uKhBVr/5MhVeSmtNqK+0q2RXIchJt8kokEKdrWLtkPeDgdbZ6SjR1VQGZuNdWL6sSBnLayIyVvcS0qLfA== dependencies: - tinyspy "^2.1.1" + tinyspy "^2.2.0" "@vitest/ui@0.32.2": version "0.32.2" @@ -2858,14 +2918,14 @@ loupe "^2.3.6" pretty-format "^27.5.1" -"@vitest/utils@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.1.tgz#e5545c6618775fb9a2dae2a80d94fc2f35222233" - integrity sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q== +"@vitest/utils@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.0.1.tgz#ab2bf6de50845649b252a9d263765ab7f16bd6a2" + integrity sha512-MGPCHkzXbbAyscrhwGzh8uP1HPrTYLWaj1WTDtWSGrpe2yJWLRN9mF9ooKawr6NMOg9vTBtg2JqWLfuLC7Dknw== dependencies: - diff-sequences "^29.4.3" - loupe "^2.3.6" - pretty-format "^29.5.0" + diff-sequences "^29.6.3" + loupe "^2.3.7" + pretty-format "^29.7.0" abab@^2.0.6: version "2.0.6" @@ -2877,22 +2937,27 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" + integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.10.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + acorn@^8.5.0: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== -acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.9.0: version "8.9.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== @@ -3326,18 +3391,18 @@ chai@4.3.6: pathval "^1.1.1" type-detect "^4.0.5" -chai@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" - integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== +chai@^4.3.10: + version "4.3.10" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" + integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== dependencies: assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^4.1.2" - get-func-name "^2.0.0" - loupe "^2.3.1" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" pathval "^1.1.1" - type-detect "^4.0.5" + type-detect "^4.0.8" chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" @@ -3374,6 +3439,13 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -3964,7 +4036,7 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-eql@^4.1.2: +deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== @@ -4044,6 +4116,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" @@ -4109,7 +4186,7 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ejs@^3.1.6, ejs@^3.1.8: +ejs@^3.1.6, ejs@^3.1.9: version "3.1.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== @@ -4263,33 +4340,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.18.10: - version "0.18.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.11.tgz#cbf94dc3359d57f600a0dbf281df9b1d1b4a156e" - integrity sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA== +esbuild@^0.19.3: + version "0.19.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.8.tgz#ad05b72281d84483fa6b5345bd246c27a207b8f1" + integrity sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w== optionalDependencies: - "@esbuild/android-arm" "0.18.11" - "@esbuild/android-arm64" "0.18.11" - "@esbuild/android-x64" "0.18.11" - "@esbuild/darwin-arm64" "0.18.11" - "@esbuild/darwin-x64" "0.18.11" - "@esbuild/freebsd-arm64" "0.18.11" - "@esbuild/freebsd-x64" "0.18.11" - "@esbuild/linux-arm" "0.18.11" - "@esbuild/linux-arm64" "0.18.11" - "@esbuild/linux-ia32" "0.18.11" - "@esbuild/linux-loong64" "0.18.11" - "@esbuild/linux-mips64el" "0.18.11" - "@esbuild/linux-ppc64" "0.18.11" - "@esbuild/linux-riscv64" "0.18.11" - "@esbuild/linux-s390x" "0.18.11" - "@esbuild/linux-x64" "0.18.11" - "@esbuild/netbsd-x64" "0.18.11" - "@esbuild/openbsd-x64" "0.18.11" - "@esbuild/sunos-x64" "0.18.11" - "@esbuild/win32-arm64" "0.18.11" - "@esbuild/win32-ia32" "0.18.11" - "@esbuild/win32-x64" "0.18.11" + "@esbuild/android-arm" "0.19.8" + "@esbuild/android-arm64" "0.19.8" + "@esbuild/android-x64" "0.19.8" + "@esbuild/darwin-arm64" "0.19.8" + "@esbuild/darwin-x64" "0.19.8" + "@esbuild/freebsd-arm64" "0.19.8" + "@esbuild/freebsd-x64" "0.19.8" + "@esbuild/linux-arm" "0.19.8" + "@esbuild/linux-arm64" "0.19.8" + "@esbuild/linux-ia32" "0.19.8" + "@esbuild/linux-loong64" "0.19.8" + "@esbuild/linux-mips64el" "0.19.8" + "@esbuild/linux-ppc64" "0.19.8" + "@esbuild/linux-riscv64" "0.19.8" + "@esbuild/linux-s390x" "0.19.8" + "@esbuild/linux-x64" "0.19.8" + "@esbuild/netbsd-x64" "0.19.8" + "@esbuild/openbsd-x64" "0.19.8" + "@esbuild/sunos-x64" "0.19.8" + "@esbuild/win32-arm64" "0.19.8" + "@esbuild/win32-ia32" "0.19.8" + "@esbuild/win32-x64" "0.19.8" escalade@^3.1.1: version "3.1.1" @@ -4599,6 +4676,21 @@ execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + expect@^29.0.0: version "29.5.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" @@ -4649,6 +4741,17 @@ fast-glob@^3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -4777,10 +4880,10 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.1: version "1.1.1" @@ -4822,6 +4925,11 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" @@ -4846,6 +4954,11 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -5066,6 +5179,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + husky@7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" @@ -5345,6 +5463,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -5747,10 +5870,13 @@ listr2@^4.0.1: through "^2.3.8" wrap-ansi "^7.0.0" -local-pkg@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" - integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== +local-pkg@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" + integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== + dependencies: + mlly "^1.4.2" + pkg-types "^1.0.3" localforage@^1.8.1: version "1.10.0" @@ -5838,6 +5964,13 @@ loupe@^2.3.1, loupe@^2.3.6: dependencies: get-func-name "^2.0.0" +loupe@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -5878,6 +6011,13 @@ magic-string@^0.30.1: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +magic-string@^0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -6167,6 +6307,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -6198,7 +6343,7 @@ mkdirp@^0.5.6: dependencies: minimist "^1.2.6" -mlly@^1.2.0, mlly@^1.4.0: +mlly@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.0.tgz#830c10d63f1f97bd8785377b24dc2a15d972832b" integrity sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg== @@ -6208,6 +6353,16 @@ mlly@^1.2.0, mlly@^1.4.0: pkg-types "^1.0.3" ufo "^1.1.2" +mlly@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" + integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== + dependencies: + acorn "^8.10.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + ufo "^1.3.0" + moo-color@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.3.tgz#d56435f8359c8284d83ac58016df7427febece74" @@ -6300,6 +6455,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + nwsapi@^2.2.4: version "2.2.5" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.5.tgz#a52744c61b3889dd44b0a158687add39b8d935e2" @@ -6387,6 +6549,13 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + open-color@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/open-color/-/open-color-1.9.1.tgz#a6e6328f60eff7aa60e3e8fcfa50f53ff3eece35" @@ -6409,10 +6578,10 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-limit@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" - integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== dependencies: yocto-queue "^1.0.0" @@ -6477,6 +6646,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -6589,7 +6763,7 @@ portfinder@^1.0.28: debug "^3.2.7" mkdirp "^0.5.6" -postcss@^8.4.27: +postcss@^8.4.32: version "8.4.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== @@ -6620,10 +6794,10 @@ pretty-bytes@^5.3.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-bytes@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.0.tgz#1d1cc9aae1939012c74180b679da6684616bf804" - integrity sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ== +pretty-bytes@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b" + integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" @@ -6643,6 +6817,15 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -6975,11 +7158,23 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" -rollup@^3.27.1: - version "3.29.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" - integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== +rollup@^4.2.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.6.1.tgz#351501c86b5b4f976dde8c5837516452b59921f8" + integrity sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ== optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.6.1" + "@rollup/rollup-android-arm64" "4.6.1" + "@rollup/rollup-darwin-arm64" "4.6.1" + "@rollup/rollup-darwin-x64" "4.6.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.6.1" + "@rollup/rollup-linux-arm64-gnu" "4.6.1" + "@rollup/rollup-linux-arm64-musl" "4.6.1" + "@rollup/rollup-linux-x64-gnu" "4.6.1" + "@rollup/rollup-linux-x64-musl" "4.6.1" + "@rollup/rollup-win32-arm64-msvc" "4.6.1" + "@rollup/rollup-win32-ia32-msvc" "4.6.1" + "@rollup/rollup-win32-x64-msvc" "4.6.1" fsevents "~2.3.2" roughjs@4.6.4: @@ -7137,6 +7332,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sirv@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" @@ -7268,6 +7468,11 @@ std-env@^3.3.3: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg== +std-env@^3.5.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.6.0.tgz#94807562bddc68fa90f2e02c5fd5b6865bb4e98e" + integrity sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg== + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -7382,6 +7587,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -7394,12 +7604,12 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-literal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.0.1.tgz#0115a332710c849b4e46497891fb8d585e404bd2" - integrity sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q== +strip-literal@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07" + integrity sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg== dependencies: - acorn "^8.8.2" + acorn "^8.10.0" stylis@^4.1.3: version "4.3.0" @@ -7500,20 +7710,20 @@ tiny-invariant@^1.1.0: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -tinybench@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5" - integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA== +tinybench@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e" + integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg== -tinypool@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.7.0.tgz#88053cc99b4a594382af23190c609d93fddf8021" - integrity sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww== +tinypool@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.1.tgz#b6c4e4972ede3e3e5cda74a3da1679303d386b03" + integrity sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg== -tinyspy@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.1.1.tgz#9e6371b00c259e5c5b301917ca18c01d40ae558c" - integrity sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w== +tinyspy@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" + integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== to-array@0.1.4: version "0.1.4" @@ -7614,7 +7824,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -7667,6 +7877,11 @@ ufo@^1.1.2: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ== +ufo@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" + integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -7818,17 +8033,16 @@ v8-to-istanbul@^9.1.0: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" -vite-node@0.34.1: - version "0.34.1" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.1.tgz#144900ca4bd54cc419c501d671350bcbc07eb1ee" - integrity sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w== +vite-node@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.0.1.tgz#c16c9df9b5d47b74156a6501c9db5b380d992768" + integrity sha512-Y2Jnz4cr2azsOMMYuVPrQkp3KMnS/0WV8ezZjCy4hU7O5mUHCAVOnFmoEvs1nvix/4mYm74Len8bYRWZJMNP6g== dependencies: cac "^6.7.14" debug "^4.3.4" - mlly "^1.4.0" pathe "^1.1.1" picocolors "^1.0.0" - vite "^3.0.0 || ^4.0.0" + vite "^5.0.0-beta.15 || ^5.0.0" vite-plugin-checker@0.6.1: version "0.6.1" @@ -7853,21 +8067,21 @@ vite-plugin-checker@0.6.1: vscode-languageserver-textdocument "^1.0.1" vscode-uri "^3.0.2" -vite-plugin-ejs@1.6.4: - version "1.6.4" - resolved "https://registry.yarnpkg.com/vite-plugin-ejs/-/vite-plugin-ejs-1.6.4.tgz#aa30820d8235774e717d902754a552480cf7758b" - integrity sha512-23p1RS4PiA0veXY5/gHZ60pl3pPvd8NEqdBsDgxNK8nM1rjFFDcVb0paNmuipzCgNP/Y0f/Id22M7Il4kvZ2jA== +vite-plugin-ejs@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vite-plugin-ejs/-/vite-plugin-ejs-1.7.0.tgz#c0229729d5a26e9eb57b8abadc75f7070d470d23" + integrity sha512-JNP3zQDC4mSbfoJ3G73s5mmZITD8NGjUmLkq4swxyahy/W0xuokK9U9IJGXw7KCggq6UucT6hJ0p+tQrNtqTZw== dependencies: - ejs "^3.1.8" + ejs "^3.1.9" -vite-plugin-pwa@0.16.4: - version "0.16.4" - resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.16.4.tgz#cd2618c8b4f83eac1493f2ed7b05f72552a2b735" - integrity sha512-lmwHFIs9zI2H9bXJld/zVTbCqCQHZ9WrpyDMqosICDV0FVnCJwniX1NMDB79HGTIZzOQkY4gSZaVTJTw6maz/Q== +vite-plugin-pwa@0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.17.4.tgz#be3b3714d4148681bc73e8e0b1e6ce1a71fa79f2" + integrity sha512-j9iiyinFOYyof4Zk3Q+DtmYyDVBDAi6PuMGNGq6uGI0pw7E+LNm9e+nQ2ep9obMP/kjdWwzilqUrlfVRj9OobA== dependencies: debug "^4.3.4" - fast-glob "^3.2.12" - pretty-bytes "^6.0.0" + fast-glob "^3.3.2" + pretty-bytes "^6.1.1" workbox-build "^7.0.0" workbox-window "^7.0.0" @@ -7879,16 +8093,16 @@ vite-plugin-svgr@2.4.0: "@rollup/pluginutils" "^5.0.2" "@svgr/core" "^6.5.1" -vite@4.4.12, "vite@^3.0.0 || ^4.0.0": - version "4.4.12" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.12.tgz#e9c355d5a0d8a47afa46cb4bad10820da333da5c" - integrity sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ== +vite@5.0.6, "vite@^5.0.0-beta.15 || ^5.0.0", "vite@^5.0.0-beta.19 || ^5.0.0": + version "5.0.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c" + integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ== dependencies: - esbuild "^0.18.10" - postcss "^8.4.27" - rollup "^3.27.1" + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" optionalDependencies: - fsevents "~2.3.2" + fsevents "~2.3.3" vitest-canvas-mock@0.3.2: version "0.3.2" @@ -7897,34 +8111,31 @@ vitest-canvas-mock@0.3.2: dependencies: jest-canvas-mock "~2.4.0" -vitest@0.34.1: - version "0.34.1" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.1.tgz#3ad7f845e7a9fb0d72ab703cae832a54b8469e1e" - integrity sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ== +vitest@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.0.1.tgz#3ba1307066842bc801084fa384ce0b23941b91f7" + integrity sha512-MHsOj079S28hDsvdDvyD1pRj4dcS51EC5Vbe0xvOYX+WryP8soiK2dm8oULi+oA/8Xa/h6GoJEMTmcmBy5YM+Q== dependencies: - "@types/chai" "^4.3.5" - "@types/chai-subset" "^1.3.3" - "@types/node" "*" - "@vitest/expect" "0.34.1" - "@vitest/runner" "0.34.1" - "@vitest/snapshot" "0.34.1" - "@vitest/spy" "0.34.1" - "@vitest/utils" "0.34.1" - acorn "^8.9.0" - acorn-walk "^8.2.0" + "@vitest/expect" "1.0.1" + "@vitest/runner" "1.0.1" + "@vitest/snapshot" "1.0.1" + "@vitest/spy" "1.0.1" + "@vitest/utils" "1.0.1" + acorn-walk "^8.3.0" cac "^6.7.14" - chai "^4.3.7" + chai "^4.3.10" debug "^4.3.4" - local-pkg "^0.4.3" - magic-string "^0.30.1" + execa "^8.0.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" pathe "^1.1.1" picocolors "^1.0.0" - std-env "^3.3.3" - strip-literal "^1.0.1" - tinybench "^2.5.0" - tinypool "^0.7.0" - vite "^3.0.0 || ^4.0.0" - vite-node "0.34.1" + std-env "^3.5.0" + strip-literal "^1.3.0" + tinybench "^2.5.1" + tinypool "^0.8.1" + vite "^5.0.0-beta.19 || ^5.0.0" + vite-node "1.0.1" why-is-node-running "^2.2.2" vscode-jsonrpc@6.0.0: From f14ad61bd0108fc6adfa2f06b64d2c3d75cdb6a5 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 7 Dec 2023 16:39:11 +0530 Subject: [PATCH 09/79] build: move build process and excalidraw-app dependencies in its own package.json (#7021) * build: move build process and excalidraw-app dependencies in its own package.json * fix * fix public path * move bug-issue-template to excalidraw-app * make env vars accessible in excalidraw app * update build script * install when building * add ts ignore * fix build-version script * update config in vercel.json * add vercel config for example * fix vercel config * update install script in vercel * update install script in lint.yml * update install script in test workflows * push locales to locales folder pwa * add favicons to manifest * move react to peer deps in editor * fix ts * Enable vite intellisense * add global.d.ts for excalidraw-app * remove console.log * remove react, react-dom and vite from excalidraw-app deps * increase size limit --- .github/workflows/lint.yml | 2 +- .github/workflows/test-coverage-pr.yml | 2 +- .github/workflows/test.yml | 2 +- excalidraw-app/App.tsx | 871 +++++++++++++++++ {src => excalidraw-app}/bug-issue-template.js | 0 excalidraw-app/components/LanguageList.tsx | 2 +- .../components/TopErrorBoundary.tsx | 4 +- excalidraw-app/global.d.ts | 3 + index.html => excalidraw-app/index.html | 2 +- excalidraw-app/index.tsx | 882 +----------------- excalidraw-app/package.json | 51 + excalidraw-app/tests/LanguageList.test.tsx | 2 +- excalidraw-app/tests/MobileMenu.test.tsx | 2 +- excalidraw-app/tests/collab.test.tsx | 2 +- excalidraw-app/vite-env.d.ts | 41 + .../vite.config.mts | 20 +- excalidraw-app/yarn.lock | 857 +++++++++++++++++ package.json | 39 +- src/components/EyeDropper.tsx | 2 +- src/components/Modal.tsx | 1 - src/index.tsx | 16 - src/packages/excalidraw/.size-limit.json | 2 +- src/packages/excalidraw/vercel.json | 3 + vercel.json | 4 +- yarn.lock | 791 +--------------- 25 files changed, 1887 insertions(+), 1716 deletions(-) create mode 100644 excalidraw-app/App.tsx rename {src => excalidraw-app}/bug-issue-template.js (100%) rename {src => excalidraw-app}/components/TopErrorBoundary.tsx (97%) create mode 100644 excalidraw-app/global.d.ts rename index.html => excalidraw-app/index.html (99%) create mode 100644 excalidraw-app/package.json create mode 100644 excalidraw-app/vite-env.d.ts rename vite.config.mts => excalidraw-app/vite.config.mts (90%) create mode 100644 excalidraw-app/yarn.lock delete mode 100644 src/index.tsx create mode 100644 src/packages/excalidraw/vercel.json diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d42f8f632..f922f5e75 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - name: Install and lint run: | - yarn --frozen-lockfile + yarn install:deps yarn test:other yarn test:code yarn test:typecheck diff --git a/.github/workflows/test-coverage-pr.yml b/.github/workflows/test-coverage-pr.yml index 76c818298..7d77d39f5 100644 --- a/.github/workflows/test-coverage-pr.yml +++ b/.github/workflows/test-coverage-pr.yml @@ -16,7 +16,7 @@ jobs: with: node-version: "18.x" - name: "Install Deps" - run: yarn --frozen-lockfile + run: yarn install:deps - name: "Test Coverage" run: yarn test:coverage - name: "Report Coverage" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c4584e82..124cae26e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,5 @@ jobs: node-version: 18.x - name: Install and test run: | - yarn --frozen-lockfile + yarn install:deps yarn test:app diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx new file mode 100644 index 000000000..e2833efbe --- /dev/null +++ b/excalidraw-app/App.tsx @@ -0,0 +1,871 @@ +import polyfill from "../src/polyfill"; +import LanguageDetector from "i18next-browser-languagedetector"; +import { useEffect, useRef, useState } from "react"; +import { trackEvent } from "../src/analytics"; +import { getDefaultAppState } from "../src/appState"; +import { ErrorDialog } from "../src/components/ErrorDialog"; +import { TopErrorBoundary } from "./components/TopErrorBoundary"; +import { + APP_NAME, + EVENT, + THEME, + TITLE_TIMEOUT, + VERSION_TIMEOUT, +} from "../src/constants"; +import { loadFromBlob } from "../src/data/blob"; +import { + ExcalidrawElement, + FileId, + NonDeletedExcalidrawElement, + Theme, +} from "../src/element/types"; +import { useCallbackRefState } from "../src/hooks/useCallbackRefState"; +import { t } from "../src/i18n"; +import { + Excalidraw, + defaultLang, + LiveCollaborationTrigger, + TTDDialog, + TTDDialogTrigger, +} from "../src/packages/excalidraw/index"; +import { + AppState, + LibraryItems, + ExcalidrawImperativeAPI, + BinaryFiles, + ExcalidrawInitialDataState, + UIAppState, +} from "../src/types"; +import { + debounce, + getVersion, + getFrame, + isTestEnv, + preventUnload, + ResolvablePromise, + resolvablePromise, + isRunningInIframe, +} from "../src/utils"; +import { + FIREBASE_STORAGE_PREFIXES, + STORAGE_KEYS, + SYNC_BROWSER_TABS_TIMEOUT, +} from "./app_constants"; +import Collab, { + CollabAPI, + collabAPIAtom, + collabDialogShownAtom, + isCollaboratingAtom, + isOfflineAtom, +} from "./collab/Collab"; +import { + exportToBackend, + getCollaborationLinkData, + isCollaborationLink, + loadScene, +} from "./data"; +import { + getLibraryItemsFromStorage, + importFromLocalStorage, + importUsernameFromLocalStorage, +} from "./data/localStorage"; +import CustomStats from "./CustomStats"; +import { + restore, + restoreAppState, + RestoredDataState, +} from "../src/data/restore"; +import { + ExportToExcalidrawPlus, + exportToExcalidrawPlus, +} from "./components/ExportToExcalidrawPlus"; +import { updateStaleImageStatuses } from "./data/FileManager"; +import { newElementWith } from "../src/element/mutateElement"; +import { isInitializedImageElement } from "../src/element/typeChecks"; +import { loadFilesFromFirebase } from "./data/firebase"; +import { LocalData } from "./data/LocalData"; +import { isBrowserStorageStateNewer } from "./data/tabSync"; +import clsx from "clsx"; +import { reconcileElements } from "./collab/reconciliation"; +import { + parseLibraryTokensFromUrl, + useHandleLibrary, +} from "../src/data/library"; +import { AppMainMenu } from "./components/AppMainMenu"; +import { AppWelcomeScreen } from "./components/AppWelcomeScreen"; +import { AppFooter } from "./components/AppFooter"; +import { atom, Provider, useAtom, useAtomValue } from "jotai"; +import { useAtomWithInitialValue } from "../src/jotai"; +import { appJotaiStore } from "./app-jotai"; + +import "./index.scss"; +import { ResolutionType } from "../src/utility-types"; +import { ShareableLinkDialog } from "../src/components/ShareableLinkDialog"; +import { openConfirmModal } from "../src/components/OverwriteConfirm/OverwriteConfirmState"; +import { OverwriteConfirmDialog } from "../src/components/OverwriteConfirm/OverwriteConfirm"; +import Trans from "../src/components/Trans"; + +polyfill(); + +window.EXCALIDRAW_THROTTLE_RENDER = true; + +let isSelfEmbedding = false; + +if (window.self !== window.top) { + try { + const parentUrl = new URL(document.referrer); + const currentUrl = new URL(window.location.href); + if (parentUrl.origin === currentUrl.origin) { + isSelfEmbedding = true; + } + } catch (error) { + // ignore + } +} + +const languageDetector = new LanguageDetector(); +languageDetector.init({ + languageUtils: {}, +}); + +const shareableLinkConfirmDialog = { + title: t("overwriteConfirm.modal.shareableLink.title"), + description: ( + {text}} + br={() =>
} + /> + ), + actionLabel: t("overwriteConfirm.modal.shareableLink.button"), + color: "danger", +} as const; + +const initializeScene = async (opts: { + collabAPI: CollabAPI | null; + excalidrawAPI: ExcalidrawImperativeAPI; +}): Promise< + { scene: ExcalidrawInitialDataState | null } & ( + | { isExternalScene: true; id: string; key: string } + | { isExternalScene: false; id?: null; key?: null } + ) +> => { + const searchParams = new URLSearchParams(window.location.search); + const id = searchParams.get("id"); + const jsonBackendMatch = window.location.hash.match( + /^#json=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/, + ); + const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/); + + const localDataState = importFromLocalStorage(); + + let scene: RestoredDataState & { + scrollToContent?: boolean; + } = await loadScene(null, null, localDataState); + + let roomLinkData = getCollaborationLinkData(window.location.href); + const isExternalScene = !!(id || jsonBackendMatch || roomLinkData); + if (isExternalScene) { + if ( + // don't prompt if scene is empty + !scene.elements.length || + // don't prompt for collab scenes because we don't override local storage + roomLinkData || + // otherwise, prompt whether user wants to override current scene + (await openConfirmModal(shareableLinkConfirmDialog)) + ) { + if (jsonBackendMatch) { + scene = await loadScene( + jsonBackendMatch[1], + jsonBackendMatch[2], + localDataState, + ); + } + scene.scrollToContent = true; + if (!roomLinkData) { + window.history.replaceState({}, APP_NAME, window.location.origin); + } + } else { + // https://github.com/excalidraw/excalidraw/issues/1919 + if (document.hidden) { + return new Promise((resolve, reject) => { + window.addEventListener( + "focus", + () => initializeScene(opts).then(resolve).catch(reject), + { + once: true, + }, + ); + }); + } + + roomLinkData = null; + window.history.replaceState({}, APP_NAME, window.location.origin); + } + } else if (externalUrlMatch) { + window.history.replaceState({}, APP_NAME, window.location.origin); + + const url = externalUrlMatch[1]; + try { + const request = await fetch(window.decodeURIComponent(url)); + const data = await loadFromBlob(await request.blob(), null, null); + if ( + !scene.elements.length || + (await openConfirmModal(shareableLinkConfirmDialog)) + ) { + return { scene: data, isExternalScene }; + } + } catch (error: any) { + return { + scene: { + appState: { + errorMessage: t("alerts.invalidSceneUrl"), + }, + }, + isExternalScene, + }; + } + } + + if (roomLinkData && opts.collabAPI) { + const { excalidrawAPI } = opts; + + const scene = await opts.collabAPI.startCollaboration(roomLinkData); + + return { + // when collaborating, the state may have already been updated at this + // point (we may have received updates from other clients), so reconcile + // elements and appState with existing state + scene: { + ...scene, + appState: { + ...restoreAppState( + { + ...scene?.appState, + theme: localDataState?.appState?.theme || scene?.appState?.theme, + }, + excalidrawAPI.getAppState(), + ), + // necessary if we're invoking from a hashchange handler which doesn't + // go through App.initializeScene() that resets this flag + isLoading: false, + }, + elements: reconcileElements( + scene?.elements || [], + excalidrawAPI.getSceneElementsIncludingDeleted(), + excalidrawAPI.getAppState(), + ), + }, + isExternalScene: true, + id: roomLinkData.roomId, + key: roomLinkData.roomKey, + }; + } else if (scene) { + return isExternalScene && jsonBackendMatch + ? { + scene, + isExternalScene, + id: jsonBackendMatch[1], + key: jsonBackendMatch[2], + } + : { scene, isExternalScene: false }; + } + return { scene: null, isExternalScene: false }; +}; + +const detectedLangCode = languageDetector.detect() || defaultLang.code; +export const appLangCodeAtom = atom( + Array.isArray(detectedLangCode) ? detectedLangCode[0] : detectedLangCode, +); + +const ExcalidrawWrapper = () => { + const [errorMessage, setErrorMessage] = useState(""); + const [langCode, setLangCode] = useAtom(appLangCodeAtom); + const isCollabDisabled = isRunningInIframe(); + + // initial state + // --------------------------------------------------------------------------- + + const initialStatePromiseRef = useRef<{ + promise: ResolvablePromise; + }>({ promise: null! }); + if (!initialStatePromiseRef.current.promise) { + initialStatePromiseRef.current.promise = + resolvablePromise(); + } + + useEffect(() => { + trackEvent("load", "frame", getFrame()); + // Delayed so that the app has a time to load the latest SW + setTimeout(() => { + trackEvent("load", "version", getVersion()); + }, VERSION_TIMEOUT); + }, []); + + const [excalidrawAPI, excalidrawRefCallback] = + useCallbackRefState(); + + const [collabAPI] = useAtom(collabAPIAtom); + const [, setCollabDialogShown] = useAtom(collabDialogShownAtom); + const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => { + return isCollaborationLink(window.location.href); + }); + + useHandleLibrary({ + excalidrawAPI, + getInitialLibraryItems: getLibraryItemsFromStorage, + }); + + useEffect(() => { + if (!excalidrawAPI || (!isCollabDisabled && !collabAPI)) { + return; + } + + const loadImages = ( + data: ResolutionType, + isInitialLoad = false, + ) => { + if (!data.scene) { + return; + } + if (collabAPI?.isCollaborating()) { + if (data.scene.elements) { + collabAPI + .fetchImageFilesFromFirebase({ + elements: data.scene.elements, + forceFetchFiles: true, + }) + .then(({ loadedFiles, erroredFiles }) => { + excalidrawAPI.addFiles(loadedFiles); + updateStaleImageStatuses({ + excalidrawAPI, + erroredFiles, + elements: excalidrawAPI.getSceneElementsIncludingDeleted(), + }); + }); + } + } else { + const fileIds = + data.scene.elements?.reduce((acc, element) => { + if (isInitializedImageElement(element)) { + return acc.concat(element.fileId); + } + return acc; + }, [] as FileId[]) || []; + + if (data.isExternalScene) { + loadFilesFromFirebase( + `${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`, + data.key, + fileIds, + ).then(({ loadedFiles, erroredFiles }) => { + excalidrawAPI.addFiles(loadedFiles); + updateStaleImageStatuses({ + excalidrawAPI, + erroredFiles, + elements: excalidrawAPI.getSceneElementsIncludingDeleted(), + }); + }); + } else if (isInitialLoad) { + if (fileIds.length) { + LocalData.fileStorage + .getFiles(fileIds) + .then(({ loadedFiles, erroredFiles }) => { + if (loadedFiles.length) { + excalidrawAPI.addFiles(loadedFiles); + } + updateStaleImageStatuses({ + excalidrawAPI, + erroredFiles, + elements: excalidrawAPI.getSceneElementsIncludingDeleted(), + }); + }); + } + // on fresh load, clear unused files from IDB (from previous + // session) + LocalData.fileStorage.clearObsoleteFiles({ currentFileIds: fileIds }); + } + } + }; + + initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => { + loadImages(data, /* isInitialLoad */ true); + initialStatePromiseRef.current.promise.resolve(data.scene); + }); + + const onHashChange = async (event: HashChangeEvent) => { + event.preventDefault(); + const libraryUrlTokens = parseLibraryTokensFromUrl(); + if (!libraryUrlTokens) { + if ( + collabAPI?.isCollaborating() && + !isCollaborationLink(window.location.href) + ) { + collabAPI.stopCollaboration(false); + } + excalidrawAPI.updateScene({ appState: { isLoading: true } }); + + initializeScene({ collabAPI, excalidrawAPI }).then((data) => { + loadImages(data); + if (data.scene) { + excalidrawAPI.updateScene({ + ...data.scene, + ...restore(data.scene, null, null, { repairBindings: true }), + commitToHistory: true, + }); + } + }); + } + }; + + const titleTimeout = setTimeout( + () => (document.title = APP_NAME), + TITLE_TIMEOUT, + ); + + const syncData = debounce(() => { + if (isTestEnv()) { + return; + } + if ( + !document.hidden && + ((collabAPI && !collabAPI.isCollaborating()) || isCollabDisabled) + ) { + // don't sync if local state is newer or identical to browser state + if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_DATA_STATE)) { + const localDataState = importFromLocalStorage(); + const username = importUsernameFromLocalStorage(); + let langCode = languageDetector.detect() || defaultLang.code; + if (Array.isArray(langCode)) { + langCode = langCode[0]; + } + setLangCode(langCode); + excalidrawAPI.updateScene({ + ...localDataState, + }); + excalidrawAPI.updateLibrary({ + libraryItems: getLibraryItemsFromStorage(), + }); + collabAPI?.setUsername(username || ""); + } + + if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_FILES)) { + const elements = excalidrawAPI.getSceneElementsIncludingDeleted(); + const currFiles = excalidrawAPI.getFiles(); + const fileIds = + elements?.reduce((acc, element) => { + if ( + isInitializedImageElement(element) && + // only load and update images that aren't already loaded + !currFiles[element.fileId] + ) { + return acc.concat(element.fileId); + } + return acc; + }, [] as FileId[]) || []; + if (fileIds.length) { + LocalData.fileStorage + .getFiles(fileIds) + .then(({ loadedFiles, erroredFiles }) => { + if (loadedFiles.length) { + excalidrawAPI.addFiles(loadedFiles); + } + updateStaleImageStatuses({ + excalidrawAPI, + erroredFiles, + elements: excalidrawAPI.getSceneElementsIncludingDeleted(), + }); + }); + } + } + } + }, SYNC_BROWSER_TABS_TIMEOUT); + + const onUnload = () => { + LocalData.flushSave(); + }; + + const visibilityChange = (event: FocusEvent | Event) => { + if (event.type === EVENT.BLUR || document.hidden) { + LocalData.flushSave(); + } + if ( + event.type === EVENT.VISIBILITY_CHANGE || + event.type === EVENT.FOCUS + ) { + syncData(); + } + }; + + window.addEventListener(EVENT.HASHCHANGE, onHashChange, false); + window.addEventListener(EVENT.UNLOAD, onUnload, false); + window.addEventListener(EVENT.BLUR, visibilityChange, false); + document.addEventListener(EVENT.VISIBILITY_CHANGE, visibilityChange, false); + window.addEventListener(EVENT.FOCUS, visibilityChange, false); + return () => { + window.removeEventListener(EVENT.HASHCHANGE, onHashChange, false); + window.removeEventListener(EVENT.UNLOAD, onUnload, false); + window.removeEventListener(EVENT.BLUR, visibilityChange, false); + window.removeEventListener(EVENT.FOCUS, visibilityChange, false); + document.removeEventListener( + EVENT.VISIBILITY_CHANGE, + visibilityChange, + false, + ); + clearTimeout(titleTimeout); + }; + }, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]); + + useEffect(() => { + const unloadHandler = (event: BeforeUnloadEvent) => { + LocalData.flushSave(); + + if ( + excalidrawAPI && + LocalData.fileStorage.shouldPreventUnload( + excalidrawAPI.getSceneElements(), + ) + ) { + preventUnload(event); + } + }; + window.addEventListener(EVENT.BEFORE_UNLOAD, unloadHandler); + return () => { + window.removeEventListener(EVENT.BEFORE_UNLOAD, unloadHandler); + }; + }, [excalidrawAPI]); + + useEffect(() => { + languageDetector.cacheUserLanguage(langCode); + }, [langCode]); + + const [theme, setTheme] = useState( + () => + (localStorage.getItem( + STORAGE_KEYS.LOCAL_STORAGE_THEME, + ) as Theme | null) || + // FIXME migration from old LS scheme. Can be removed later. #5660 + importFromLocalStorage().appState?.theme || + THEME.LIGHT, + ); + + useEffect(() => { + localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_THEME, theme); + // currently only used for body styling during init (see public/index.html), + // but may change in the future + document.documentElement.classList.toggle("dark", theme === THEME.DARK); + }, [theme]); + + const onChange = ( + elements: readonly ExcalidrawElement[], + appState: AppState, + files: BinaryFiles, + ) => { + if (collabAPI?.isCollaborating()) { + collabAPI.syncElements(elements); + } + + setTheme(appState.theme); + + // this check is redundant, but since this is a hot path, it's best + // not to evaludate the nested expression every time + if (!LocalData.isSavePaused()) { + LocalData.save(elements, appState, files, () => { + if (excalidrawAPI) { + let didChange = false; + + const elements = excalidrawAPI + .getSceneElementsIncludingDeleted() + .map((element) => { + if ( + LocalData.fileStorage.shouldUpdateImageElementStatus(element) + ) { + const newElement = newElementWith(element, { status: "saved" }); + if (newElement !== element) { + didChange = true; + } + return newElement; + } + return element; + }); + + if (didChange) { + excalidrawAPI.updateScene({ + elements, + }); + } + } + }); + } + }; + + const [latestShareableLink, setLatestShareableLink] = useState( + null, + ); + + const onExportToBackend = async ( + exportedElements: readonly NonDeletedExcalidrawElement[], + appState: Partial, + files: BinaryFiles, + canvas: HTMLCanvasElement, + ) => { + if (exportedElements.length === 0) { + throw new Error(t("alerts.cannotExportEmptyCanvas")); + } + if (canvas) { + try { + const { url, errorMessage } = await exportToBackend( + exportedElements, + { + ...appState, + viewBackgroundColor: appState.exportBackground + ? appState.viewBackgroundColor + : getDefaultAppState().viewBackgroundColor, + }, + files, + ); + + if (errorMessage) { + throw new Error(errorMessage); + } + + if (url) { + setLatestShareableLink(url); + } + } catch (error: any) { + if (error.name !== "AbortError") { + const { width, height } = canvas; + console.error(error, { width, height }); + throw new Error(error.message); + } + } + } + }; + + const renderCustomStats = ( + elements: readonly NonDeletedExcalidrawElement[], + appState: UIAppState, + ) => { + return ( + excalidrawAPI!.setToast({ message })} + appState={appState} + elements={elements} + /> + ); + }; + + const onLibraryChange = async (items: LibraryItems) => { + if (!items.length) { + localStorage.removeItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY); + return; + } + const serializedItems = JSON.stringify(items); + localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems); + }; + + const isOffline = useAtomValue(isOfflineAtom); + + // browsers generally prevent infinite self-embedding, there are + // cases where it still happens, and while we disallow self-embedding + // by not whitelisting our own origin, this serves as an additional guard + if (isSelfEmbedding) { + return ( +
+

I'm not a pretzel!

+
+ ); + } + + return ( +
+ { + return ( + { + excalidrawAPI?.updateScene({ + appState: { + errorMessage: error.message, + }, + }); + }} + onSuccess={() => { + excalidrawAPI?.updateScene({ + appState: { openDialog: null }, + }); + }} + /> + ); + }, + }, + }, + }} + langCode={langCode} + renderCustomStats={renderCustomStats} + detectScroll={false} + handleKeyboardGlobally={true} + onLibraryChange={onLibraryChange} + autoFocus={true} + theme={theme} + renderTopRightUI={(isMobile) => { + if (isMobile || !collabAPI || isCollabDisabled) { + return null; + } + return ( + setCollabDialogShown(true)} + /> + ); + }} + > + + + + + + {excalidrawAPI && ( + { + exportToExcalidrawPlus( + excalidrawAPI.getSceneElements(), + excalidrawAPI.getAppState(), + excalidrawAPI.getFiles(), + ); + }} + > + {t("overwriteConfirm.action.excalidrawPlus.description")} + + )} + + + { + try { + const response = await fetch( + `${ + import.meta.env.VITE_APP_GIT_SHA + }/v1/ai/text-to-diagram/generate`, + { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt: input }), + }, + ); + + const rateLimit = response.headers.has("X-Ratelimit-Limit") + ? parseInt(response.headers.get("X-Ratelimit-Limit") || "0", 10) + : undefined; + + const rateLimitRemaining = response.headers.has( + "X-Ratelimit-Remaining", + ) + ? parseInt( + response.headers.get("X-Ratelimit-Remaining") || "0", + 10, + ) + : undefined; + + const json = await response.json(); + + if (!response.ok) { + if (response.status === 429) { + return { + rateLimit, + rateLimitRemaining, + error: new Error( + "Too many requests today, please try again tomorrow!", + ), + }; + } + + throw new Error(json.message || "Generation failed..."); + } + + const generatedResponse = json.generatedResponse; + if (!generatedResponse) { + throw new Error("Generation failed..."); + } + + return { generatedResponse, rateLimit, rateLimitRemaining }; + } catch (err: any) { + throw new Error("Request failed"); + } + }} + /> + + {isCollaborating && isOffline && ( +
+ {t("alerts.collabOfflineWarning")} +
+ )} + {latestShareableLink && ( + setLatestShareableLink(null)} + setErrorMessage={setErrorMessage} + /> + )} + {excalidrawAPI && !isCollabDisabled && ( + + )} + {errorMessage && ( + setErrorMessage("")}> + {errorMessage} + + )} +
+
+ ); +}; + +const ExcalidrawApp = () => { + return ( + + appJotaiStore}> + + + + ); +}; + +export default ExcalidrawApp; diff --git a/src/bug-issue-template.js b/excalidraw-app/bug-issue-template.js similarity index 100% rename from src/bug-issue-template.js rename to excalidraw-app/bug-issue-template.js diff --git a/excalidraw-app/components/LanguageList.tsx b/excalidraw-app/components/LanguageList.tsx index 11d4b6d00..74c14384b 100644 --- a/excalidraw-app/components/LanguageList.tsx +++ b/excalidraw-app/components/LanguageList.tsx @@ -1,6 +1,6 @@ import { useSetAtom } from "jotai"; import React from "react"; -import { appLangCodeAtom } from ".."; +import { appLangCodeAtom } from "../App"; import { useI18n } from "../../src/i18n"; import { languages } from "../../src/i18n"; diff --git a/src/components/TopErrorBoundary.tsx b/excalidraw-app/components/TopErrorBoundary.tsx similarity index 97% rename from src/components/TopErrorBoundary.tsx rename to excalidraw-app/components/TopErrorBoundary.tsx index d465514f1..25d8c5f2d 100644 --- a/src/components/TopErrorBoundary.tsx +++ b/excalidraw-app/components/TopErrorBoundary.tsx @@ -1,7 +1,7 @@ import React from "react"; import * as Sentry from "@sentry/browser"; -import { t } from "../i18n"; -import Trans from "./Trans"; +import { t } from "../../src/i18n"; +import Trans from "../../src/components/Trans"; interface TopErrorBoundaryState { hasError: boolean; diff --git a/excalidraw-app/global.d.ts b/excalidraw-app/global.d.ts new file mode 100644 index 000000000..1ce684585 --- /dev/null +++ b/excalidraw-app/global.d.ts @@ -0,0 +1,3 @@ +interface Window { + __EXCALIDRAW_SHA__: string | undefined; +} diff --git a/index.html b/excalidraw-app/index.html similarity index 99% rename from index.html rename to excalidraw-app/index.html index b1e0f2abc..c11d9ab68 100644 --- a/index.html +++ b/excalidraw-app/index.html @@ -195,7 +195,7 @@

Excalidraw

- + <% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%" !== 'true') { %> + ``` diff --git a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/new-diagram-type.mdx b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/new-diagram-type.mdx index c59dfaba1..522cf740c 100644 --- a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/new-diagram-type.mdx +++ b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/new-diagram-type.mdx @@ -38,9 +38,9 @@ Add the diagram type in switch case in [`parseMermaid`](https://github.com/excal ## Writing the Excalidraw Skeleton Convertor -With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) format. +With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) format. -Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). +Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133). Thats it, you have added the new diagram type 🥳, now lets test it out! diff --git a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/flowchart.mdx b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/flowchart.mdx index b8d122df3..f85787f4c 100644 --- a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/flowchart.mdx +++ b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/flowchart.mdx @@ -6,7 +6,7 @@ In this section we will be diving into how the [flowchart parser](https://github ![image](https://github.com/excalidraw/excalidraw/assets/11256141/2a097bbb-64bf-49d6-bf7f-21172bdb538d) -We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38). +We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38). For computing `vertices` and `edge`s lets consider the below svg generated by mermaid @@ -42,7 +42,7 @@ Considering the same example this is the response from the API } } ``` -The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response. +The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response. The final output from `parseVertex` looks like :point_down: diff --git a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/parser.mdx b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/parser.mdx index 4f4af4dec..7ecc8d05c 100644 --- a/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/parser.mdx +++ b/dev-docs/docs/@excalidraw/mermaid-to-excalidraw/codebase/parser/parser.mdx @@ -55,11 +55,11 @@ If you want to understand how flowchart parser works, you can navigate to [Flowc ## Converting to ExcalidrawElementSkeleton -Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw. +Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw. For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton. For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton. -For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38). +For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38). ![image](https://github.com/excalidraw/excalidraw/assets/11256141/00226e9d-043d-4a08-989a-3ad9d2a574f1) \ No newline at end of file diff --git a/dev-docs/docusaurus.config.js b/dev-docs/docusaurus.config.js index 1d170ac21..a246522c1 100644 --- a/dev-docs/docusaurus.config.js +++ b/dev-docs/docusaurus.config.js @@ -41,10 +41,7 @@ const config = { showLastUpdateTime: true, }, theme: { - customCss: [ - require.resolve("./src/css/custom.scss"), - require.resolve("../packages/excalidraw/example/App.scss"), - ], + customCss: [require.resolve("./src/css/custom.scss")], }, }), ], diff --git a/dev-docs/vercel.json b/dev-docs/vercel.json new file mode 100644 index 000000000..c997f0d02 --- /dev/null +++ b/dev-docs/vercel.json @@ -0,0 +1,4 @@ +{ + "outputDirectory": "build", + "installCommand": "yarn install" +} From b635b10b5948f6a157d98dbad7ba096c1b8d7b2f Mon Sep 17 00:00:00 2001 From: Excalidraw Bot <77840495+excalibot@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:26:22 +0100 Subject: [PATCH 13/79] chore: Update translations from Crowdin (#7176) * New translations en.json (Azerbaijani) * New translations en.json (Hindi) * New translations en.json (Burmese) * New translations en.json (Chinese Traditional, Hong Kong) * New translations en.json (Sinhala) * New translations en.json (Norwegian Bokmal) * New translations en.json (Occitan) * New translations en.json (Kabyle) * New translations en.json (Karakalpak) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Simplified) * Auto commit: Calculate translation coverage * New translations en.json (Marathi) * New translations en.json (Hindi) * Auto commit: Calculate translation coverage * New translations en.json (Marathi) * New translations en.json (Hindi) * Auto commit: Calculate translation coverage * New translations en.json (German) * New translations en.json (Slovenian) * Auto commit: Calculate translation coverage * New translations en.json (Korean) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Traditional) * Auto commit: Calculate translation coverage * New translations en.json (Russian) * Auto commit: Calculate translation coverage * New translations en.json (Romanian) * New translations en.json (Spanish) * Auto commit: Calculate translation coverage * New translations en.json (Arabic) * New translations en.json (Thai) * New translations en.json (Romanian) * New translations en.json (French) * New translations en.json (Spanish) * New translations en.json (Bulgarian) * New translations en.json (Catalan) * New translations en.json (Czech) * New translations en.json (Danish) * New translations en.json (German) * New translations en.json (Greek) * New translations en.json (Basque) * New translations en.json (Finnish) * New translations en.json (Hebrew) * New translations en.json (Hungarian) * New translations en.json (Italian) * New translations en.json (Japanese) * New translations en.json (Korean) * New translations en.json (Kurdish) * New translations en.json (Lithuanian) * New translations en.json (Dutch) * New translations en.json (Punjabi) * New translations en.json (Polish) * New translations en.json (Portuguese) * New translations en.json (Russian) * New translations en.json (Slovak) * New translations en.json (Slovenian) * New translations en.json (Swedish) * New translations en.json (Turkish) * New translations en.json (Ukrainian) * New translations en.json (Chinese Simplified) * New translations en.json (Chinese Traditional) * New translations en.json (Vietnamese) * New translations en.json (Galician) * New translations en.json (Portuguese, Brazilian) * New translations en.json (Indonesian) * New translations en.json (Persian) * New translations en.json (Khmer) * New translations en.json (Tamil) * New translations en.json (Bengali) * New translations en.json (Marathi) * New translations en.json (Norwegian Nynorsk) * New translations en.json (Kazakh) * New translations en.json (Latvian) * New translations en.json (Azerbaijani) * New translations en.json (Hindi) * New translations en.json (Burmese) * New translations en.json (Chinese Traditional, Hong Kong) * New translations en.json (Sinhala) * New translations en.json (Norwegian Bokmal) * New translations en.json (Occitan) * New translations en.json (Kabyle) * New translations en.json (Karakalpak) * Auto commit: Calculate translation coverage * New translations en.json (Slovenian) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Simplified) * Auto commit: Calculate translation coverage * New translations en.json (German) * Auto commit: Calculate translation coverage * New translations en.json (Russian) * Auto commit: Calculate translation coverage * New translations en.json (Polish) * New translations en.json (Korean) * Auto commit: Calculate translation coverage * New translations en.json (Swedish) * Auto commit: Calculate translation coverage * New translations en.json (Swedish) * Auto commit: Calculate translation coverage * New translations en.json (French) * Auto commit: Calculate translation coverage * New translations en.json (Romanian) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Simplified) * New translations en.json (Catalan) * Auto commit: Calculate translation coverage * New translations en.json (Basque) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Traditional) * Auto commit: Calculate translation coverage * New translations en.json (Spanish) * Auto commit: Calculate translation coverage * New translations en.json (Arabic) * New translations en.json (Thai) * New translations en.json (Romanian) * New translations en.json (French) * New translations en.json (Spanish) * New translations en.json (Bulgarian) * New translations en.json (Catalan) * New translations en.json (Czech) * New translations en.json (Danish) * New translations en.json (German) * New translations en.json (Greek) * New translations en.json (Basque) * New translations en.json (Finnish) * New translations en.json (Hebrew) * New translations en.json (Hungarian) * New translations en.json (Italian) * New translations en.json (Japanese) * New translations en.json (Korean) * New translations en.json (Kurdish) * New translations en.json (Lithuanian) * New translations en.json (Dutch) * New translations en.json (Punjabi) * New translations en.json (Polish) * New translations en.json (Portuguese) * New translations en.json (Russian) * New translations en.json (Slovak) * New translations en.json (Slovenian) * New translations en.json (Swedish) * New translations en.json (Turkish) * New translations en.json (Ukrainian) * New translations en.json (Chinese Simplified) * New translations en.json (Chinese Traditional) * New translations en.json (Vietnamese) * New translations en.json (Galician) * New translations en.json (Portuguese, Brazilian) * New translations en.json (Indonesian) * New translations en.json (Persian) * New translations en.json (Khmer) * New translations en.json (Tamil) * New translations en.json (Bengali) * New translations en.json (Marathi) * New translations en.json (Norwegian Nynorsk) * New translations en.json (Kazakh) * New translations en.json (Latvian) * New translations en.json (Azerbaijani) * New translations en.json (Hindi) * New translations en.json (Burmese) * New translations en.json (Chinese Traditional, Hong Kong) * New translations en.json (Sinhala) * New translations en.json (Norwegian Bokmal) * New translations en.json (Occitan) * New translations en.json (Kabyle) * New translations en.json (Karakalpak) * Auto commit: Calculate translation coverage * New translations en.json (Romanian) * New translations en.json (Spanish) * Auto commit: Calculate translation coverage * New translations en.json (Slovenian) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Traditional) * Auto commit: Calculate translation coverage * New translations en.json (Swedish) * Auto commit: Calculate translation coverage * New translations en.json (French) * Auto commit: Calculate translation coverage * New translations en.json (German) * Auto commit: Calculate translation coverage * New translations en.json (Danish) * Auto commit: Calculate translation coverage * New translations en.json (Italian) * Auto commit: Calculate translation coverage * New translations en.json (Chinese Simplified) * Auto commit: Calculate translation coverage * New translations en.json (Korean) * Auto commit: Calculate translation coverage * New translations en.json (Polish) * New translations en.json (Slovak) * Auto commit: Calculate translation coverage * New translations en.json (Karakalpak) * Auto commit: Calculate translation coverage * New translations en.json (Romanian) * New translations en.json (French) * New translations en.json (Spanish) * New translations en.json (Arabic) * New translations en.json (Bulgarian) * New translations en.json (Catalan) * New translations en.json (Czech) * New translations en.json (Danish) * New translations en.json (German) * New translations en.json (Greek) * New translations en.json (Basque) * New translations en.json (Finnish) * New translations en.json (Hebrew) * New translations en.json (Hungarian) * New translations en.json (Italian) * New translations en.json (Japanese) * New translations en.json (Korean) * New translations en.json (Kurdish) * New translations en.json (Lithuanian) * New translations en.json (Dutch) * New translations en.json (Punjabi) * New translations en.json (Polish) * New translations en.json (Portuguese) * New translations en.json (Russian) * New translations en.json (Slovak) * New translations en.json (Slovenian) * New translations en.json (Swedish) * New translations en.json (Turkish) * New translations en.json (Ukrainian) * New translations en.json (Chinese Simplified) * New translations en.json (Chinese Traditional) * New translations en.json (Vietnamese) * New translations en.json (Galician) * New translations en.json (Portuguese, Brazilian) * New translations en.json (Indonesian) * New translations en.json (Persian) * New translations en.json (Khmer) * New translations en.json (Tamil) * New translations en.json (Bengali) * New translations en.json (Marathi) * New translations en.json (Thai) * New translations en.json (Norwegian Nynorsk) * New translations en.json (Kazakh) * New translations en.json (Latvian) * New translations en.json (Azerbaijani) * New translations en.json (Hindi) * New translations en.json (Burmese) * New translations en.json (Chinese Traditional, Hong Kong) * New translations en.json (Sinhala) * New translations en.json (Norwegian Bokmal) * New translations en.json (Occitan) * New translations en.json (Kabyle) * New translations en.json (Karakalpak) * New translations en.json (Swedish) * New translations en.json (Romanian) * remove packages --------- Co-authored-by: Aakansha Doshi --- packages/excalidraw/locales/ar-SA.json | 31 ++- packages/excalidraw/locales/az-AZ.json | 31 ++- packages/excalidraw/locales/bg-BG.json | 31 ++- packages/excalidraw/locales/bn-BD.json | 31 ++- packages/excalidraw/locales/ca-ES.json | 75 +++-- packages/excalidraw/locales/cs-CZ.json | 31 ++- packages/excalidraw/locales/da-DK.json | 273 ++++++++++--------- packages/excalidraw/locales/de-DE.json | 31 ++- packages/excalidraw/locales/el-GR.json | 31 ++- packages/excalidraw/locales/es-ES.json | 33 ++- packages/excalidraw/locales/eu-ES.json | 33 ++- packages/excalidraw/locales/fa-IR.json | 39 ++- packages/excalidraw/locales/fi-FI.json | 31 ++- packages/excalidraw/locales/fr-FR.json | 39 ++- packages/excalidraw/locales/gl-ES.json | 31 ++- packages/excalidraw/locales/he-IL.json | 31 ++- packages/excalidraw/locales/hi-IN.json | 39 ++- packages/excalidraw/locales/hu-HU.json | 115 ++++---- packages/excalidraw/locales/id-ID.json | 31 ++- packages/excalidraw/locales/it-IT.json | 33 ++- packages/excalidraw/locales/ja-JP.json | 31 ++- packages/excalidraw/locales/kaa.json | 71 +++-- packages/excalidraw/locales/kab-KAB.json | 31 ++- packages/excalidraw/locales/kk-KZ.json | 31 ++- packages/excalidraw/locales/km-KH.json | 31 ++- packages/excalidraw/locales/ko-KR.json | 31 ++- packages/excalidraw/locales/ku-TR.json | 31 ++- packages/excalidraw/locales/lt-LT.json | 31 ++- packages/excalidraw/locales/lv-LV.json | 31 ++- packages/excalidraw/locales/mr-IN.json | 91 ++++--- packages/excalidraw/locales/my-MM.json | 31 ++- packages/excalidraw/locales/nb-NO.json | 31 ++- packages/excalidraw/locales/nl-NL.json | 31 ++- packages/excalidraw/locales/nn-NO.json | 31 ++- packages/excalidraw/locales/oc-FR.json | 151 +++++----- packages/excalidraw/locales/pa-IN.json | 31 ++- packages/excalidraw/locales/percentages.json | 94 +++---- packages/excalidraw/locales/pl-PL.json | 33 ++- packages/excalidraw/locales/pt-BR.json | 31 ++- packages/excalidraw/locales/pt-PT.json | 31 ++- packages/excalidraw/locales/ro-RO.json | 33 ++- packages/excalidraw/locales/ru-RU.json | 31 ++- packages/excalidraw/locales/si-LK.json | 31 ++- packages/excalidraw/locales/sk-SK.json | 35 ++- packages/excalidraw/locales/sl-SI.json | 31 ++- packages/excalidraw/locales/sv-SE.json | 31 ++- packages/excalidraw/locales/ta-IN.json | 31 ++- packages/excalidraw/locales/th-TH.json | 31 ++- packages/excalidraw/locales/tr-TR.json | 31 ++- packages/excalidraw/locales/uk-UA.json | 31 ++- packages/excalidraw/locales/vi-VN.json | 31 ++- packages/excalidraw/locales/zh-CN.json | 39 ++- packages/excalidraw/locales/zh-HK.json | 31 ++- packages/excalidraw/locales/zh-TW.json | 31 ++- 54 files changed, 1796 insertions(+), 577 deletions(-) diff --git a/packages/excalidraw/locales/ar-SA.json b/packages/excalidraw/locales/ar-SA.json index 18742875f..5b0db36b2 100644 --- a/packages/excalidraw/locales/ar-SA.json +++ b/packages/excalidraw/locales/ar-SA.json @@ -11,6 +11,8 @@ "copyAsPng": "نسخ إلى الحافظة بصيغة PNG", "copyAsSvg": "نسخ إلى الحافظة بصيغة SVG", "copyText": "نسخ إلى الحافظة كنص", + "copySource": "", + "convertToCode": "", "bringForward": "جلب للأمام", "sendToBack": "أرسل للخلف", "bringToFront": "أحضر للأمام", @@ -36,8 +38,12 @@ "arrowhead_none": "لا شيء", "arrowhead_arrow": "سهم", "arrowhead_bar": "شريط", - "arrowhead_dot": "نقطة", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "مثلث", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "حجم الخط", "fontFamily": "نوع الخط", "addWatermark": "إضافة \"مصنوعة بواسطة Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "إبقاء الشريط الجانبي مفتوح", "selectAllElementsInFrame": "تحديد جميع العناصر في الإطار", "removeAllElementsFromFrame": "إزالة جميع العناصر من الإطار", - "eyeDropper": "اختيار اللون من القماش" + "eyeDropper": "اختيار اللون من القماش", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "لا توجد عناصر أضيفت بعد...", @@ -209,6 +217,7 @@ "importLibraryError": "تعذر تحميل المكتبة", "collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.", "collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "يبدو أنك تستخدم متصفح Brave مع إعداد حظر صارم لتتبع البصمة.", "line2": "قد يؤدي هذا إلى كسر عناصر النص في الرسومات الخاصة بك.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "لا يمكن إضافة العناصر القابلة للتضمين في المكتبة.", + "iframe": "", "image": "سوف يتم دعم إضافة صور إلى المكتبة قريباً!" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "تحديد", @@ -236,10 +249,13 @@ "link": "إضافة/تحديث الرابط للشكل المحدد", "eraser": "ممحاة", "frame": "أداة الإطار", + "magicframe": "", "embeddable": "تضمين ويب", "laser": "مؤشر ليزر", "hand": "يد (أداة الإزاحة)", - "extraTools": "المزيد من أﻷدوات" + "extraTools": "المزيد من أﻷدوات", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "إجراءات اللوحة", @@ -498,5 +514,12 @@ "description": "سيتسبب تحميل رسمة خارجية باستبدال محتواك الموجود حالياً.

بإمكانك إجراء النسخ الاحتياطي لرسمتك الحالية باستخدام أحد الخيارات أدناه." } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/az-AZ.json b/packages/excalidraw/locales/az-AZ.json index 1ece3cc98..145fc0ac5 100644 --- a/packages/excalidraw/locales/az-AZ.json +++ b/packages/excalidraw/locales/az-AZ.json @@ -11,6 +11,8 @@ "copyAsPng": "PNG olaraq panoya kopyala", "copyAsSvg": "SVG olaraq panoya kopyala", "copyText": "Mətn olaraq panoya kopyala", + "copySource": "", + "convertToCode": "", "bringForward": "Önə daşı", "sendToBack": "Geriyə göndərin", "bringToFront": "Önə gətirin", @@ -36,8 +38,12 @@ "arrowhead_none": "Heç biri", "arrowhead_arrow": "Ox", "arrowhead_bar": "Çubuq", - "arrowhead_dot": "Nöqtə", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Üçbucaq", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Şrift ölçüsü", "fontFamily": "Şrift qrupu", "addWatermark": "\"Made with Excalidraw\" əlavə et", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/bg-BG.json b/packages/excalidraw/locales/bg-BG.json index 716c73d54..d48f47982 100644 --- a/packages/excalidraw/locales/bg-BG.json +++ b/packages/excalidraw/locales/bg-BG.json @@ -11,6 +11,8 @@ "copyAsPng": "Копиране в клипборда", "copyAsSvg": "Копирано в клипборда като SVG", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "Преместване напред", "sendToBack": "Изнасяне назад", "bringToFront": "Изнасяне отпред", @@ -36,8 +38,12 @@ "arrowhead_none": "Без", "arrowhead_arrow": "Стрелка", "arrowhead_bar": "Връх на стрелката", - "arrowhead_dot": "Точка", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Триъгълник", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Размер на шрифта", "fontFamily": "Семейство шрифтове", "addWatermark": "Добави \"Направено с Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "Избери цвят от платното" + "eyeDropper": "Избери цвят от платното", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Няма добавени неща все още...", @@ -209,6 +217,7 @@ "importLibraryError": "Не можем да заредим библиотеката", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Селекция", @@ -236,10 +249,13 @@ "link": "", "eraser": "Гума", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "Още инструменти" + "extraTools": "Още инструменти", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Действия по платното", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/bn-BD.json b/packages/excalidraw/locales/bn-BD.json index a2b926890..9bb910e80 100644 --- a/packages/excalidraw/locales/bn-BD.json +++ b/packages/excalidraw/locales/bn-BD.json @@ -11,6 +11,8 @@ "copyAsPng": "পীএনজী ছবির মতন কপি করুন", "copyAsSvg": "এসভীজী ছবির মতন কপি করুন", "copyText": "লিখিত তথ্যের মতন কপি করুন", + "copySource": "", + "convertToCode": "", "bringForward": "অধিকতর সামনে আনুন", "sendToBack": "অধিকতর পিছনে নিয়ে যান", "bringToFront": "সবার সামনে আনুন", @@ -36,8 +38,12 @@ "arrowhead_none": "কিছু না", "arrowhead_arrow": "তীর", "arrowhead_bar": "রেখাংশ", - "arrowhead_dot": "বিন্দু", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "ত্রিভূজ", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "লেখনীর মাত্রা", "fontFamily": "লেখনীর হরফ", "addWatermark": "এক্সক্যালিড্র দ্বারা প্রস্তুত", @@ -130,7 +136,9 @@ "sidebarLock": "লক", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "সংগ্রহে কিছু যোগ করা হয়নি", @@ -209,6 +217,7 @@ "importLibraryError": "সংগ্রহ লোড করা যায়নি", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "বাছাই", @@ -236,10 +249,13 @@ "link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন", "eraser": "ঝাড়ন", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "ক্যানভাস কার্যকলাপ", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/ca-ES.json b/packages/excalidraw/locales/ca-ES.json index 1205838c4..4f601d93a 100644 --- a/packages/excalidraw/locales/ca-ES.json +++ b/packages/excalidraw/locales/ca-ES.json @@ -11,6 +11,8 @@ "copyAsPng": "Copia al porta-retalls com a PNG", "copyAsSvg": "Copia al porta-retalls com a SVG", "copyText": "Copia al porta-retalls com a text", + "copySource": "Copia l'origen al porta-retalls", + "convertToCode": "", "bringForward": "Porta endavant", "sendToBack": "Envia enrere", "bringToFront": "Porta al davant", @@ -36,8 +38,12 @@ "arrowhead_none": "Cap", "arrowhead_arrow": "Fletxa", "arrowhead_bar": "Barra", - "arrowhead_dot": "Punt", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Triangle", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Mida de lletra", "fontFamily": "Tipus de lletra", "addWatermark": "Afegeix-hi «Fet amb Excalidraw»", @@ -109,12 +115,12 @@ "createContainerFromText": "", "link": { "edit": "Edita l'enllaç", - "editEmbed": "", + "editEmbed": "Edita l'enllaç i incrusta-ho", "create": "Crea un enllaç", "createEmbed": "", "label": "Enllaç", "labelEmbed": "", - "empty": "" + "empty": "No s'ha definit cap enllaç" }, "lineEditor": { "edit": "Editar línia", @@ -128,9 +134,11 @@ }, "statusPublished": "Publicat", "sidebarLock": "Manté la barra lateral oberta", - "selectAllElementsInFrame": "", - "removeAllElementsFromFrame": "", - "eyeDropper": "" + "selectAllElementsInFrame": "Selecciona tots els elements del marc", + "removeAllElementsFromFrame": "Eliminat tots els elements del marc", + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Encara no s'hi han afegit elements...", @@ -173,7 +181,7 @@ "publishLibrary": "Publica", "submit": "Envia", "confirm": "Confirma", - "embeddableInteractionButton": "" + "embeddableInteractionButton": "Feu clic per interactuar" }, "alerts": { "clearReset": "S'esborrarà tot el llenç. N'esteu segur?", @@ -209,6 +217,7 @@ "importLibraryError": "No s'ha pogut carregar la biblioteca", "collabSaveFailed": "No s'ha pogut desar a la base de dades de fons. Si els problemes persisteixen, hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.", "collabSaveFailed_sizeExceeded": "No s'ha pogut desar a la base de dades de fons, sembla que el llenç és massa gran. Hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "No s'ha pogut enganxar.", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Selecció", @@ -236,10 +249,13 @@ "link": "Afegeix / actualitza l'enllaç per a la forma seleccionada", "eraser": "Esborrador", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Mà (eina de desplaçament)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "De Mermaid a Excalidraw", + "magicSettings": "Preferències d'IA" }, "headings": { "canvasActions": "Accions del llenç", @@ -379,8 +395,8 @@ "header": "", "label": { "withBackground": "", - "onlySelected": "", - "darkMode": "", + "onlySelected": "Només els seleccionats", + "darkMode": "Mode fosc", "embedScene": "", "scale": "", "padding": "" @@ -389,12 +405,12 @@ "embedScene": "" }, "title": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "Exporta a PNG", + "exportToSvg": "Exporta a SVG", + "copyPngToClipboard": "Copia el PNG al porta-retalls" }, "button": { - "exportToPng": "", + "exportToPng": "PNG", "exportToSvg": "", "copyPngToClipboard": "" } @@ -443,10 +459,10 @@ "blue": "", "cyan": "", "teal": "", - "green": "", - "yellow": "", - "orange": "", - "bronze": "" + "green": "Verd", + "yellow": "Groc", + "orange": "Taronja", + "bronze": "Bronze" }, "welcomeScreen": { "app": { @@ -471,13 +487,13 @@ "overwriteConfirm": { "action": { "exportToImage": { - "title": "", - "button": "", + "title": "Exporta com a imatge", + "button": "Exporta com a imatge", "description": "" }, "saveToDisk": { - "title": "", - "button": "", + "title": "Desa al disc", + "button": "Desa al disc", "description": "" }, "excalidrawPlus": { @@ -488,15 +504,22 @@ }, "modal": { "loadFromFile": { - "title": "", - "button": "", + "title": "Carrega des d'un fitxer", + "button": "Carrega des d'un fitxer", "description": "" }, "shareableLink": { - "title": "", + "title": "Carrega des d'un enllaç", "button": "", "description": "" } } + }, + "mermaid": { + "title": "De Mermaid a Excalidraw", + "button": "Insereix", + "description": "", + "syntax": "Sintaxi de Mermaid", + "preview": "Previsualització" } } diff --git a/packages/excalidraw/locales/cs-CZ.json b/packages/excalidraw/locales/cs-CZ.json index 8d758e0da..150fecd3d 100644 --- a/packages/excalidraw/locales/cs-CZ.json +++ b/packages/excalidraw/locales/cs-CZ.json @@ -11,6 +11,8 @@ "copyAsPng": "Zkopírovat do schránky jako PNG", "copyAsSvg": "Zkopírovat do schránky jako SVG", "copyText": "Zkopírovat do schránky jako text", + "copySource": "", + "convertToCode": "", "bringForward": "Přenést blíž", "sendToBack": "Přenést do pozadí", "bringToFront": "Přenést do popředí", @@ -36,8 +38,12 @@ "arrowhead_none": "Žádný", "arrowhead_arrow": "Šipka", "arrowhead_bar": "Kóta", - "arrowhead_dot": "Tečka", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trojúhelník", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Velikost písma", "fontFamily": "Písmo", "addWatermark": "Přidat \"Vyrobeno s Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Ponechat postranní panel otevřený", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "Vyberte barvu z plátna" + "eyeDropper": "Vyberte barvu z plátna", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Dosud neexistují žádné položky...", @@ -209,6 +217,7 @@ "importLibraryError": "Nelze načíst knihovnu", "collabSaveFailed": "Nelze uložit do databáze na serveru. Pokud problémy přetrvávají, měli byste uložit soubor lokálně, abyste se ujistili, že neztratíte svou práci.", "collabSaveFailed_sizeExceeded": "Nelze uložit do databáze na serveru, plátno se zdá být příliš velké. Měli byste uložit soubor lokálně, abyste se ujistili, že neztratíte svou práci.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Vypadá to, že používáte Brave prohlížeč s povoleným nastavením Aggressively Block Fingerprinting.", "line2": "To by mohlo vést k narušení Textových elementů ve vašich výkresech.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Výběr", @@ -236,10 +249,13 @@ "link": "Přidat/aktualizovat odkaz pro vybraný tvar", "eraser": "Guma", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Ruka (nástroj pro posouvání)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Akce plátna", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/da-DK.json b/packages/excalidraw/locales/da-DK.json index 0cd9c8ce0..ebefa12ad 100644 --- a/packages/excalidraw/locales/da-DK.json +++ b/packages/excalidraw/locales/da-DK.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopier til klippebord som PNG", "copyAsSvg": "Kopier til klippebord som SVG", "copyText": "Kopiér til udklipsholder som tekst", + "copySource": "Kopiér kilde til udklipsholder", + "convertToCode": "Konvertér til kode", "bringForward": "Flyt fremad", "sendToBack": "Placer bagest", "bringToFront": "Placer forrest", @@ -36,8 +38,12 @@ "arrowhead_none": "Ingen", "arrowhead_arrow": "Pil", "arrowhead_bar": "Bjælke", - "arrowhead_dot": "Prik", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trekant", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Skriftstørrelse", "fontFamily": "Skrifttypefamilie", "addWatermark": "Tilføj \"Lavet med Excalidraw\"", @@ -75,77 +81,79 @@ "name": "Navn", "yourName": "Dit navn", "madeWithExcalidraw": "Fremstillet med Excalidraw", - "group": "", - "ungroup": "", - "collaborators": "", - "showGrid": "", - "addToLibrary": "", - "removeFromLibrary": "", - "libraryLoadingMessage": "", - "libraries": "", - "loadingScene": "", - "align": "", - "alignTop": "", - "alignBottom": "", - "alignLeft": "", - "alignRight": "", - "centerVertically": "", - "centerHorizontally": "", - "distributeHorizontally": "", - "distributeVertically": "", - "flipHorizontal": "", - "flipVertical": "", - "viewMode": "", + "group": "Grupper valgte", + "ungroup": "Opløs gruppe", + "collaborators": "Deltagere", + "showGrid": "Vis gitter", + "addToLibrary": "Føj til Bibliotek", + "removeFromLibrary": "Fjern fra biblioteket", + "libraryLoadingMessage": "Indlæser bibliotek…", + "libraries": "Gennemse biblioteker", + "loadingScene": "Indlæser scene…", + "align": "Justér", + "alignTop": "Juster til top", + "alignBottom": "Juster til bund", + "alignLeft": "Venstrejusteret", + "alignRight": "Juster højre", + "centerVertically": "Center vertikalt", + "centerHorizontally": "Vandret centreret", + "distributeHorizontally": "Distribuer vandret", + "distributeVertically": "Distribuer lodret", + "flipHorizontal": "Spejlvend horisontalt", + "flipVertical": "Vend lodret", + "viewMode": "Visningstilstand", "share": "Del", - "showStroke": "", - "showBackground": "", - "toggleTheme": "", - "personalLib": "", - "excalidrawLib": "", - "decreaseFontSize": "", - "increaseFontSize": "", - "unbindText": "", - "bindText": "", - "createContainerFromText": "", + "showStroke": "Vis stregfarve-vælger", + "showBackground": "Vis baggrundsfarve-vælger", + "toggleTheme": "Skift tema", + "personalLib": "Personligt bibliotek", + "excalidrawLib": "Excalidraw Bibliotek", + "decreaseFontSize": "Gør skriften mindre", + "increaseFontSize": "Gør skriften større", + "unbindText": "Frigør tekst", + "bindText": "Bind tekst til beholderen", + "createContainerFromText": "Ombryd tekst i en beholder", "link": { - "edit": "", - "editEmbed": "", - "create": "", - "createEmbed": "", - "label": "", - "labelEmbed": "", - "empty": "" + "edit": "Redigér link", + "editEmbed": "Redigér link & indlejret", + "create": "Link oprettet", + "createEmbed": "Opret link & indlejret", + "label": "Links", + "labelEmbed": "Link & indlejret", + "empty": "Intet link angivet" }, "lineEditor": { - "edit": "", - "exit": "" + "edit": "Rediger Linje", + "exit": "Afslut linjeeditor" }, "elementLock": { - "lock": "", - "unlock": "", - "lockAll": "", - "unlockAll": "" + "lock": "Lås", + "unlock": "Lås op", + "lockAll": "Lås alle", + "unlockAll": "Lås alle op" }, - "statusPublished": "", - "sidebarLock": "", - "selectAllElementsInFrame": "", - "removeAllElementsFromFrame": "", - "eyeDropper": "" + "statusPublished": "Udgiver", + "sidebarLock": "Hold sidepanel åben", + "selectAllElementsInFrame": "Vælg alle elementer i rammen", + "removeAllElementsFromFrame": "Fjern alle elementer fra ramme", + "eyeDropper": "Vælg farve fra lærred", + "textToDiagram": "Tekst til diagram", + "prompt": "Prompt" }, "library": { - "noItems": "", - "hint_emptyLibrary": "", - "hint_emptyPrivateLibrary": "" + "noItems": "Ingen varer tilføjet endnu...", + "hint_emptyLibrary": "Vælg et element på lærred for at tilføje det her, eller installer et bibliotek fra det offentlige arkiv, nedenfor.", + "hint_emptyPrivateLibrary": "Vælg et element på lærred for at tilføje det her." }, "buttons": { - "clearReset": "", - "exportJSON": "", - "exportImage": "", - "export": "", + "clearReset": "Nulstil lærredet", + "exportJSON": "Eksportér til fil", + "exportImage": "Eksporter billede...", + "export": "Gem til...", "copyToClipboard": "Kopier til klippebord", - "save": "", + "save": "Gem til nuværende fil", "saveAs": "Gem som", - "load": "", + "load": "Åbn", "getShareableLink": "Lav et delbart link", "close": "Luk", "selectLanguage": "Vælg sprog", @@ -164,16 +172,16 @@ "darkMode": "Mørk tilstand", "lightMode": "Lys baggrund", "zenMode": "Zentilstand", - "objectsSnapMode": "", + "objectsSnapMode": "Fastgør til objekter", "exitZenMode": "Stop zentilstand", "cancel": "Annuller", "clear": "Ryd", "remove": "Fjern", - "embed": "", + "embed": "Slå indlejring til/fra", "publishLibrary": "Publicér", "submit": "Gem", "confirm": "Bekræft", - "embeddableInteractionButton": "" + "embeddableInteractionButton": "Klik for at interagere" }, "alerts": { "clearReset": "Dette vil rydde hele lærredet. Er du sikker?", @@ -181,77 +189,85 @@ "couldNotCreateShareableLinkTooBig": "Kunne ikke oprette delbart link: scenen er for stor", "couldNotLoadInvalidFile": "Kunne ikke indlæse ugyldig fil", "importBackendFailed": "Import fra backend mislykkedes.", - "cannotExportEmptyCanvas": "", - "couldNotCopyToClipboard": "", - "decryptFailed": "", - "uploadedSecurly": "", - "loadSceneOverridePrompt": "", - "collabStopOverridePrompt": "", - "errorAddingToLibrary": "", - "errorRemovingFromLibrary": "", - "confirmAddLibrary": "", - "imageDoesNotContainScene": "", - "cannotRestoreFromImage": "", - "invalidSceneUrl": "", - "resetLibrary": "", - "removeItemsFromsLibrary": "", - "invalidEncryptionKey": "", - "collabOfflineWarning": "" + "cannotExportEmptyCanvas": "Kan ikke eksportere tomt lærred.", + "couldNotCopyToClipboard": "Kunne ikke kopiere til udklipsholderen.", + "decryptFailed": "Kunne ikke dekryptere data.", + "uploadedSecurly": "Upload er blevet sikret med ende-til-ende kryptering, hvilket betyder, at Excalidraw server og tredjeparter ikke kan læse indholdet.", + "loadSceneOverridePrompt": "Indlæsning af ekstern tegning erstatter dit eksisterende indhold. Ønsker du at fortsætte?", + "collabStopOverridePrompt": "Stopper sessionen vil overskrive din tidligere, lokalt gemte tegning. Er du sikker?\n\n(Hvis du ønsker at beholde din lokale tegning, skal du blot lukke browserfanen i stedet.)", + "errorAddingToLibrary": "Kunne ikke tilføje element til biblioteket", + "errorRemovingFromLibrary": "Kunne ikke fjerne element fra biblioteket", + "confirmAddLibrary": "Dette vil tilføje {{numShapes}} form(er) til dit bibliotek. Er du sikker?", + "imageDoesNotContainScene": "Dette billede synes ikke at indeholde scene data. Har du aktiveret scene indlejring under eksport?", + "cannotRestoreFromImage": "Scene kunne ikke gendannes fra denne billedfil", + "invalidSceneUrl": "Kunne ikke importere scene fra den angivne URL. Det er enten misdannet eller indeholder ikke gyldige Excalidraw JSON data.", + "resetLibrary": "Dette vil rydde hele lærredet. Er du sikker?", + "removeItemsFromsLibrary": "Slet {{count}} vare(r) fra biblioteket?", + "invalidEncryptionKey": "Krypteringsnøglen skal være på 22 tegn. Live-samarbejde er deaktiveret.", + "collabOfflineWarning": "Ingen internetforbindelse tilgængelig.\nDine ændringer vil ikke blive gemt!" }, "errors": { - "unsupportedFileType": "", - "imageInsertError": "", - "fileTooBig": "", - "svgImageInsertError": "", - "failedToFetchImage": "", - "invalidSVGString": "", - "cannotResolveCollabServer": "", - "importLibraryError": "", - "collabSaveFailed": "", - "collabSaveFailed_sizeExceeded": "", + "unsupportedFileType": "Filtypen er ikke understøttet.", + "imageInsertError": "Billedet kunne ikke indsættes. Prøv igen senere...", + "fileTooBig": "Filen er for stor. Maksimal tilladt størrelse er {{maxSize}}.", + "svgImageInsertError": "Kunne ikke indsætte SVG-billede. SVG-markup'en ser ugyldig ud.", + "failedToFetchImage": "Dataene blev ikke hentet.", + "invalidSVGString": "Ugyldig SVG.", + "cannotResolveCollabServer": "Kunne ikke oprette forbindelse til samarbejdsserveren. Genindlæs siden og prøv igen.", + "importLibraryError": "Biblioteket kunne ikke indlæses", + "collabSaveFailed": "Kunne ikke gemme i databasen. Hvis problemerne fortsætter, bør du gemme din fil lokalt for at sikre, at du ikke mister dit arbejde.", + "collabSaveFailed_sizeExceeded": "Kunne ikke gemme i databasen, lærredet lader til at være for stort. Du bør gemme filen lokalt for at sikre, at du ikke mister dit arbejde.", + "imageToolNotSupported": "Billeder er deaktiveret.", "brave_measure_text_error": { - "line1": "", - "line2": "", - "line3": "", - "line4": "" + "line1": "Det ser ud til, at du bruger Brave browser med indstillingen Aggressively Block Fingerprinting aktiveret.", + "line2": "Dette kan resultere i brud på tekstelementerne i dine tegninger.", + "line3": "Vi anbefaler kraftigt at deaktivere denne indstilling. Du kan følge disse trin om, hvordan du gør det.", + "line4": "Hvis deaktivering af denne indstilling ikke løser visning af tekstelementer, åbn venligst et issue på vores GitHub, eller skriv os på Discord" }, "libraryElementTypeError": { - "embeddable": "", - "image": "" - } + "embeddable": "Indlejringselementer kan ikke tilføjes til biblioteket.", + "iframe": "IFrame elementer kan ikke tilføjes til biblioteket.", + "image": "Understøttelse af at tilføje billeder til biblioteket kommer snart!" + }, + "asyncPasteFailedOnRead": "Kunne ikke indsætte (kan ikke læse fra systemets udklipsholder).", + "asyncPasteFailedOnParse": "Kunne ikke indsætte.", + "copyToSystemClipboardFailed": "Kunne ikke kopiere til udklipsholderen." }, "toolBar": { - "selection": "", - "image": "", - "rectangle": "", - "diamond": "", - "ellipse": "", - "arrow": "", - "line": "", - "freedraw": "", - "text": "", - "library": "", - "lock": "", - "penMode": "", - "link": "", - "eraser": "", - "frame": "", - "embeddable": "", - "laser": "", - "hand": "", - "extraTools": "" + "selection": "&Udvalg", + "image": "Indsæt billeder", + "rectangle": "Rektangler", + "diamond": "Diamanter", + "ellipse": "Ellipser", + "arrow": "Pile", + "line": "Linje", + "freedraw": "Tegn", + "text": "Tekster", + "library": "~Bibliotek", + "lock": "Behold valgte værktøj aktiv efter tegning", + "penMode": "Pen-tilstand - forhindrer berøring", + "link": "Tilføj/ Opdater link for en valgt form", + "eraser": "Slet", + "frame": "Rammeværktøj", + "magicframe": "Wireframe til kode", + "embeddable": "Web-indlejring", + "laser": "Lasermarkør", + "hand": "Hånd (panorering værktøj)", + "extraTools": "Flere værktøjer", + "mermaidToExcalidraw": "Mermaid til Excalidraw", + "magicSettings": "AI indstillinger" }, "headings": { - "canvasActions": "", - "selectedShapeActions": "", - "shapes": "" + "canvasActions": "Lærred handlinger", + "selectedShapeActions": "Valgte figurhandlinger", + "shapes": "Former" }, "hints": { - "canvasPanning": "", - "linearElement": "", + "canvasPanning": "For at flytte lærred, hold musehjulet eller mellemrumstasten mens du trækker, eller brug håndværktøjet", + "linearElement": "Klik for at starte flere punkter, træk for enkelt linje", "freeDraw": "Klik og træk, slip når du er færdig", - "text": "", - "embeddable": "", + "text": "Tip: du kan også tilføje tekst ved at dobbeltklikke hvor som helst med det valgte værktøj", + "embeddable": "Klik på træk for at oprette en hjemmeside indlejret", "text_selected": "", "text_editing": "", "linearElementMulti": "", @@ -271,12 +287,12 @@ "disableSnapping": "" }, "canvasError": { - "cannotShowPreview": "", - "canvasTooBig": "", - "canvasTooBigTip": "" + "cannotShowPreview": "Kan ikke vise forhåndsvisning", + "canvasTooBig": "Lærredet kan være for stort.", + "canvasTooBigTip": "Tip: Prøv at flytte de fjerneste elementer lidt tættere sammen." }, "errorSplash": { - "headingMain": "", + "headingMain": "Der opstod en fejl. Prøv .", "clearCanvasMessage": "", "clearCanvasCaveat": "", "trackedToSentry": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/de-DE.json b/packages/excalidraw/locales/de-DE.json index c41186ef1..5ce97646e 100644 --- a/packages/excalidraw/locales/de-DE.json +++ b/packages/excalidraw/locales/de-DE.json @@ -11,6 +11,8 @@ "copyAsPng": "In Zwischenablage kopieren (PNG)", "copyAsSvg": "In Zwischenablage kopieren (SVG)", "copyText": "In die Zwischenablage als Text kopieren", + "copySource": "Quelle in Zwischenablage kopieren", + "convertToCode": "In Code konvertieren", "bringForward": "Nach vorne", "sendToBack": "In den Hintergrund", "bringToFront": "In den Vordergrund", @@ -36,8 +38,12 @@ "arrowhead_none": "Keine", "arrowhead_arrow": "Pfeil", "arrowhead_bar": "Balken", - "arrowhead_dot": "Punkt", + "arrowhead_circle": "Kreis", + "arrowhead_circle_outline": "Kreis (Umrandung)", "arrowhead_triangle": "Dreieck", + "arrowhead_triangle_outline": "Dreieck (Umrandung)", + "arrowhead_diamond": "Raute", + "arrowhead_diamond_outline": "Raute (Umrandung)", "fontSize": "Schriftgröße", "fontFamily": "Schriftfamilie", "addWatermark": "\"Made with Excalidraw\" hinzufügen", @@ -130,7 +136,9 @@ "sidebarLock": "Seitenleiste offen lassen", "selectAllElementsInFrame": "Alle Elemente im Rahmen auswählen", "removeAllElementsFromFrame": "Alle Elemente aus dem Rahmen entfernen", - "eyeDropper": "Farbe von der Zeichenfläche auswählen" + "eyeDropper": "Farbe von der Zeichenfläche auswählen", + "textToDiagram": "Text zu Diagramm", + "prompt": "Eingabe" }, "library": { "noItems": "Noch keine Elemente hinzugefügt...", @@ -209,6 +217,7 @@ "importLibraryError": "Bibliothek konnte nicht geladen werden", "collabSaveFailed": "Keine Speicherung in der Backend-Datenbank möglich. Wenn die Probleme weiterhin bestehen, solltest Du Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.", "collabSaveFailed_sizeExceeded": "Keine Speicherung in der Backend-Datenbank möglich, die Zeichenfläche scheint zu groß zu sein. Du solltest Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.", + "imageToolNotSupported": "Bilder sind deaktiviert.", "brave_measure_text_error": { "line1": "Sieht so aus, als ob Du den Brave-Browser verwendest und die aggressive Blockierung von Fingerabdrücken aktiviert hast.", "line2": "Dies könnte dazu führen, dass die Textelemente in Ihren Zeichnungen zerstört werden.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Einbettbare Elemente können der Bibliothek nicht hinzugefügt werden.", + "iframe": "IFrame-Elemente können nicht zur Bibliothek hinzugefügt werden.", "image": "Unterstützung für das Hinzufügen von Bildern in die Bibliothek kommt bald!" - } + }, + "asyncPasteFailedOnRead": "Einfügen fehlgeschlagen (konnte aus der Zwischenablage des Systems nicht gelesen werden).", + "asyncPasteFailedOnParse": "Einfügen fehlgeschlagen.", + "copyToSystemClipboardFailed": "Kopieren in die Zwischenablage fehlgeschlagen." }, "toolBar": { "selection": "Auswahl", @@ -236,10 +249,13 @@ "link": "Link für ausgewählte Form hinzufügen / aktualisieren", "eraser": "Radierer", "frame": "Rahmenwerkzeug", + "magicframe": "Wireframe zu Code", "embeddable": "Web-Einbettung", "laser": "Laserpointer", "hand": "Hand (Schwenkwerkzeug)", - "extraTools": "Weitere Werkzeuge" + "extraTools": "Weitere Werkzeuge", + "mermaidToExcalidraw": "Mermaid zu Excalidraw", + "magicSettings": "KI-Einstellungen" }, "headings": { "canvasActions": "Aktionen für Zeichenfläche", @@ -498,5 +514,12 @@ "description": "Das Laden einer externen Zeichnung wird Deinen vorhandenen Inhalt ersetzen.

Du kannst Deine Zeichnung zuerst mit einer der folgenden Optionen sichern." } } + }, + "mermaid": { + "title": "Mermaid zu Excalidraw", + "button": "Einfügen", + "description": "Derzeit werden nur Flussdiagramme, Sequenzdiagramme und Klassendiagramme unterstützt. Die anderen Typen werden als Bild in Excalidraw dargestellt.", + "syntax": "Mermaid-Syntax", + "preview": "Vorschau" } } diff --git a/packages/excalidraw/locales/el-GR.json b/packages/excalidraw/locales/el-GR.json index 5a60fbb8b..f6fa2f0f9 100644 --- a/packages/excalidraw/locales/el-GR.json +++ b/packages/excalidraw/locales/el-GR.json @@ -11,6 +11,8 @@ "copyAsPng": "Αντιγραφή στο πρόχειρο ως PNG", "copyAsSvg": "Αντιγραφή στο πρόχειρο ως SVG", "copyText": "Αντιγραφή στο πρόχειρο ως κείμενο", + "copySource": "", + "convertToCode": "", "bringForward": "Στο προσκήνιο", "sendToBack": "Ένα επίπεδο πίσω", "bringToFront": "Ένα επίπεδο μπροστά", @@ -36,8 +38,12 @@ "arrowhead_none": "Κανένα", "arrowhead_arrow": "Βέλος", "arrowhead_bar": "Μπάρα", - "arrowhead_dot": "Τελεία", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Τρίγωνο", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Μέγεθος γραμματοσειράς", "fontFamily": "Γραμματοσειρά", "addWatermark": "Προσθήκη \"Φτιαγμένο με Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Κρατήστε την πλαϊνή μπάρα ανοιχτή", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Δεν έχουν προστεθεί αντικείμενα ακόμη...", @@ -209,6 +217,7 @@ "importLibraryError": "Αδυναμία φόρτωσης βιβλιοθήκης", "collabSaveFailed": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή. Αν το προβλήματα παραμείνει, θα πρέπει να αποθηκεύσετε το αρχείο σας τοπικά για να βεβαιωθείτε ότι δεν χάνετε την εργασία σας.", "collabSaveFailed_sizeExceeded": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή, ο καμβάς φαίνεται να είναι πολύ μεγάλος. Θα πρέπει να αποθηκεύσετε το αρχείο τοπικά για να βεβαιωθείτε ότι δεν θα χάσετε την εργασία σας.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Επιλογή", @@ -236,10 +249,13 @@ "link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα", "eraser": "Γόμα", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Ενέργειες καμβά", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/es-ES.json b/packages/excalidraw/locales/es-ES.json index d925ab788..ab7152829 100644 --- a/packages/excalidraw/locales/es-ES.json +++ b/packages/excalidraw/locales/es-ES.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiar al portapapeles como PNG", "copyAsSvg": "Copiar al portapapeles como SVG", "copyText": "Copiar al portapapeles como texto", + "copySource": "Copiar fuente al portapapeles", + "convertToCode": "Convertir a código", "bringForward": "Traer hacia delante", "sendToBack": "Enviar al fondo", "bringToFront": "Traer al frente", @@ -36,8 +38,12 @@ "arrowhead_none": "Ninguna", "arrowhead_arrow": "Flecha", "arrowhead_bar": "Barra", - "arrowhead_dot": "Punto", + "arrowhead_circle": "Círculo", + "arrowhead_circle_outline": "Círculo (contorno)", "arrowhead_triangle": "Triángulo", + "arrowhead_triangle_outline": "Triángulo (contorno)", + "arrowhead_diamond": "Diamante", + "arrowhead_diamond_outline": "Diamante (contorno)", "fontSize": "Tamaño de la fuente", "fontFamily": "Tipo de fuente", "addWatermark": "Agregar \"Hecho con Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Mantener barra lateral abierta", "selectAllElementsInFrame": "Seleccionar todos los elementos en el marco", "removeAllElementsFromFrame": "Eliminar todos los elementos del marco", - "eyeDropper": "Seleccionar un color del lienzo" + "eyeDropper": "Seleccionar un color del lienzo", + "textToDiagram": "Texto a diagrama", + "prompt": "Sugerencia" }, "library": { "noItems": "No hay elementos añadidos todavía...", @@ -203,12 +211,13 @@ "imageInsertError": "No se pudo insertar la imagen. Inténtelo de nuevo más tarde...", "fileTooBig": "Archivo demasiado grande. El tamaño máximo permitido es {{maxSize}}.", "svgImageInsertError": "No se pudo insertar la imagen SVG. El código SVG parece inválido.", - "failedToFetchImage": "", + "failedToFetchImage": "Error al obtener la imagen.", "invalidSVGString": "SVG no válido.", "cannotResolveCollabServer": "No se pudo conectar al servidor colaborador. Por favor, vuelva a cargar la página y vuelva a intentarlo.", "importLibraryError": "No se pudo cargar la librería", "collabSaveFailed": "No se pudo guardar en la base de datos del backend. Si los problemas persisten, debería guardar su archivo localmente para asegurarse de que no pierde su trabajo.", "collabSaveFailed_sizeExceeded": "No se pudo guardar en la base de datos del backend, el lienzo parece ser demasiado grande. Debería guardar el archivo localmente para asegurarse de que no pierde su trabajo.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Parece que estás usando el navegador Brave con el ajuste Forzar el bloqueo de huellas digitales habilitado.", "line2": "Esto podría resultar en errores en los Elementos de Texto en tus dibujos.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "Los elementos IFrame no se pueden agregar a la biblioteca.", "image": "" - } + }, + "asyncPasteFailedOnRead": "No se pudo pegar (no se pudo leer desde el portapapeles del sistema).", + "asyncPasteFailedOnParse": "No se pudo pegar.", + "copyToSystemClipboardFailed": "No se pudo copiar al portapapeles." }, "toolBar": { "selection": "Selección", @@ -236,10 +249,13 @@ "link": "Añadir/Actualizar enlace para una forma seleccionada", "eraser": "Borrar", "frame": "", + "magicframe": "Esquema a código", "embeddable": "Incrustar Web", "laser": "Puntero láser", "hand": "Mano (herramienta de panoramización)", - "extraTools": "Más herramientas" + "extraTools": "Más herramientas", + "mermaidToExcalidraw": "Mermaid a Excalidraw", + "magicSettings": "Ajustes AI" }, "headings": { "canvasActions": "Acciones del lienzo", @@ -498,5 +514,12 @@ "description": "Cargar un dibujo externo reemplazará tu contenido existente.

Puedes primero hacer una copia de seguridad de tu dibujo usando una de las opciones de abajo." } } + }, + "mermaid": { + "title": "Mermaid a Excalidraw", + "button": "Insertar", + "description": "Actualmente sólo Flowchart, Secuencia, y Class Diagramas son soportados. Los otros tipos se renderizarán como imagen en Excalidraw.", + "syntax": "Sintaxis Mermaid", + "preview": "Vista previa" } } diff --git a/packages/excalidraw/locales/eu-ES.json b/packages/excalidraw/locales/eu-ES.json index 5a257f05d..19c9adb5f 100644 --- a/packages/excalidraw/locales/eu-ES.json +++ b/packages/excalidraw/locales/eu-ES.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopiatu arbelera PNG gisa", "copyAsSvg": "Kopiatu arbelera SVG gisa", "copyText": "Kopiatu arbelera testu gisa", + "copySource": "Kopiatu iturria arbelean", + "convertToCode": "Bihurtu kodea", "bringForward": "Ekarri aurrerago", "sendToBack": "Eraman atzera", "bringToFront": "Ekarri aurrera", @@ -36,8 +38,12 @@ "arrowhead_none": "Bat ere ez", "arrowhead_arrow": "Gezia", "arrowhead_bar": "Barra", - "arrowhead_dot": "Puntua", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Hirukia", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Letra-tamaina", "fontFamily": "Letra-tipoa", "addWatermark": "Gehitu \"Excalidraw bidez egina\"", @@ -130,7 +136,9 @@ "sidebarLock": "Mantendu alboko barra irekita", "selectAllElementsInFrame": "Hautatu markoko elementu guztiak", "removeAllElementsFromFrame": "Kendu markoko elementu guztiak", - "eyeDropper": "Aukeratu kolorea oihaletik" + "eyeDropper": "Aukeratu kolorea oihaletik", + "textToDiagram": "Testutik diagramara", + "prompt": "" }, "library": { "noItems": "Oraindik ez da elementurik gehitu...", @@ -203,12 +211,13 @@ "imageInsertError": "Ezin izan da irudia txertatu. Saiatu berriro geroago...", "fileTooBig": "Fitxategia handiegia da. Onartutako gehienezko tamaina {{maxSize}} da.", "svgImageInsertError": "Ezin izan da SVG irudia txertatu. SVG markak baliogabea dirudi.", - "failedToFetchImage": "", + "failedToFetchImage": "Ezin izan da irudia eskuratu.", "invalidSVGString": "SVG baliogabea.", "cannotResolveCollabServer": "Ezin izan da elkarlaneko zerbitzarira konektatu. Mesedez, berriro kargatu orria eta saiatu berriro.", "importLibraryError": "Ezin izan da liburutegia kargatu", "collabSaveFailed": "Ezin izan da backend datu-basean gorde. Arazoak jarraitzen badu, zure fitxategia lokalean gorde beharko zenuke zure lana ez duzula galtzen ziurtatzeko.", "collabSaveFailed_sizeExceeded": "Ezin izan da backend datu-basean gorde, ohiala handiegia dela dirudi. Fitxategia lokalean gorde beharko zenuke zure lana galtzen ez duzula ziurtatzeko.", + "imageToolNotSupported": "Irudiak desgaituta daude.", "brave_measure_text_error": { "line1": "Brave arakatzailea erabiltzen ari zarela dirudi Blokeatu hatz-markak erasokorki ezarpena gaituta.", "line2": "Honek zure marrazkietako Testu-elementuak hautsi ditzake.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Kapsulatutako elementuak ezin dira liburutegira gehitu.", + "iframe": "IFrame elementuak ezin dira liburutegira gehitu.", "image": "Laster egongo da irudiak liburutegian gehitzeko laguntza!" - } + }, + "asyncPasteFailedOnRead": "Ezin izan da itsatsi (ezin izan da sistemaren arbeletik irakurri).", + "asyncPasteFailedOnParse": "Ezin izan da itsatsi.", + "copyToSystemClipboardFailed": "Ezin izan da arbelean kopiatu." }, "toolBar": { "selection": "Hautapena", @@ -236,10 +249,13 @@ "link": "Gehitu / Eguneratu esteka hautatutako forma baterako", "eraser": "Borragoma", "frame": "Marko tresna", + "magicframe": "Wireframe kodetzeko", "embeddable": "Web kapsulatzea", "laser": "Laser punteroa", "hand": "Eskua (panoratze tresna)", - "extraTools": "Tresna gehiago" + "extraTools": "Tresna gehiago", + "mermaidToExcalidraw": "", + "magicSettings": "AI ezarpenak" }, "headings": { "canvasActions": "Canvas ekintzak", @@ -498,5 +514,12 @@ "description": "Kanpoko irudi bat kargatzeak lehendik duzun edukia ordezkatuko du.

. Zure marrazkiaren babeskopia egin dezakezu lehenik beheko aukeretako bat erabiliz." } } + }, + "mermaid": { + "title": "", + "button": "Txertatu", + "description": "", + "syntax": "", + "preview": "Aurrebista" } } diff --git a/packages/excalidraw/locales/fa-IR.json b/packages/excalidraw/locales/fa-IR.json index 101803258..838973e52 100644 --- a/packages/excalidraw/locales/fa-IR.json +++ b/packages/excalidraw/locales/fa-IR.json @@ -11,6 +11,8 @@ "copyAsPng": "کپی در حافطه موقت به صورت PNG", "copyAsSvg": "کپی در حافطه موقت به صورت SVG", "copyText": "کپی در حافطه موقت به صورت متن", + "copySource": "", + "convertToCode": "", "bringForward": "جلو آوردن", "sendToBack": "پس فرستادن", "bringToFront": "جلو آوردن", @@ -36,8 +38,12 @@ "arrowhead_none": "هیچ کدام", "arrowhead_arrow": "پیکان", "arrowhead_bar": "میله ای", - "arrowhead_dot": "نقطه", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "مثلث", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "اندازه قلم", "fontFamily": "نوع قلم", "addWatermark": "\"ساخته شده با Excalidraw\" را اضافه کن", @@ -130,7 +136,9 @@ "sidebarLock": "باز نگه داشتن سایدبار", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "انتخاب رنگ از کرباس" + "eyeDropper": "انتخاب رنگ از کرباس", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "آیتمی به اینجا اضافه نشده...", @@ -209,6 +217,7 @@ "importLibraryError": "داده‌ها بارگذاری نشدند", "collabSaveFailed": "در پایگاه داده باطن ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.", "collabSaveFailed_sizeExceeded": "در پایگاه داده بکند ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "به نظر می‌رسد از مرورگر Brave با تنظیم مسدود کردن شدید اثرانگشت استفاده می‌کنید.", "line2": "این می تواند منجر به شکستن عناصر متن در نقاشی های شما شود.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "گزینش", @@ -236,10 +249,13 @@ "link": "افزودن/به‌روزرسانی پیوند برای شکل انتخابی", "eraser": "پاک کن", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "دست (ابزار پانینگ)", - "extraTools": "ابزارهای بیشتر" + "extraTools": "ابزارهای بیشتر", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "عملیات روی بوم", @@ -380,7 +396,7 @@ "label": { "withBackground": "پس زمینه", "onlySelected": "", - "darkMode": "", + "darkMode": "حالت تیره", "embedScene": "", "scale": "", "padding": "" @@ -394,9 +410,9 @@ "copyPngToClipboard": "" }, "button": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "PNG", + "exportToSvg": "SVG", + "copyPngToClipboard": "کپی در کلیپ‌بورد" } }, "encrypted": { @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "پیش‌نمایش" } } diff --git a/packages/excalidraw/locales/fi-FI.json b/packages/excalidraw/locales/fi-FI.json index 0a8963f9a..4193c93ad 100644 --- a/packages/excalidraw/locales/fi-FI.json +++ b/packages/excalidraw/locales/fi-FI.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopioi leikepöydälle PNG-tiedostona", "copyAsSvg": "Kopioi leikepöydälle SVG-tiedostona", "copyText": "Kopioi tekstinä", + "copySource": "", + "convertToCode": "", "bringForward": "Tuo eteenpäin", "sendToBack": "Vie taakse", "bringToFront": "Tuo eteen", @@ -36,8 +38,12 @@ "arrowhead_none": "Ei mitään", "arrowhead_arrow": "Nuoli", "arrowhead_bar": "Tasapää", - "arrowhead_dot": "Piste", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Kolmio", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Kirjasinkoko", "fontFamily": "Kirjasintyyppi", "addWatermark": "Lisää \"Tehty Excalidrawilla\"", @@ -130,7 +136,9 @@ "sidebarLock": "Pidä sivupalkki avoinna", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Kirjastossa ei ole vielä yhtään kohdetta...", @@ -209,6 +217,7 @@ "importLibraryError": "Kokoelman lataaminen epäonnistui", "collabSaveFailed": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.", "collabSaveFailed_sizeExceeded": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Valinta", @@ -236,10 +249,13 @@ "link": "Lisää/päivitä linkki valitulle muodolle", "eraser": "Poistotyökalu", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Käsi (panning-työkalu)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Piirtoalueen toiminnot", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/fr-FR.json b/packages/excalidraw/locales/fr-FR.json index 8dfcc4c05..674204d12 100644 --- a/packages/excalidraw/locales/fr-FR.json +++ b/packages/excalidraw/locales/fr-FR.json @@ -11,6 +11,8 @@ "copyAsPng": "Copier dans le presse-papier en PNG", "copyAsSvg": "Copier dans le presse-papier en SVG", "copyText": "Copier dans le presse-papier en tant que texte", + "copySource": "Copier la source dans le presse-papiers", + "convertToCode": "Convertir en code", "bringForward": "Envoyer vers l'avant", "sendToBack": "Déplacer à l'arrière-plan", "bringToFront": "Mettre au premier plan", @@ -36,8 +38,12 @@ "arrowhead_none": "Sans", "arrowhead_arrow": "Flèche", "arrowhead_bar": "Barre", - "arrowhead_dot": "Point", + "arrowhead_circle": "Cercle", + "arrowhead_circle_outline": "Contour du cercle", "arrowhead_triangle": "Triangle", + "arrowhead_triangle_outline": "Triangle (contour)", + "arrowhead_diamond": "Losange", + "arrowhead_diamond_outline": "", "fontSize": "Taille de la police", "fontFamily": "Police", "addWatermark": "Ajouter \"Réalisé avec Excalidraw\"", @@ -113,7 +119,7 @@ "create": "Ajouter un lien", "createEmbed": "Créer un lien & intégrer", "label": "Lien", - "labelEmbed": "", + "labelEmbed": "Lier & intégrer", "empty": "Aucun lien défini" }, "lineEditor": { @@ -130,7 +136,9 @@ "sidebarLock": "Maintenir la barre latérale ouverte", "selectAllElementsInFrame": "Sélectionner tous les éléments du cadre", "removeAllElementsFromFrame": "Supprimer tous les éléments du cadre", - "eyeDropper": "" + "eyeDropper": "Choisir la couleur depuis la toile", + "textToDiagram": "Texte vers Diagramme", + "prompt": "Consignes" }, "library": { "noItems": "Aucun élément n'a encore été ajouté ...", @@ -203,12 +211,13 @@ "imageInsertError": "Impossible d'insérer l'image. Réessayez plus tard...", "fileTooBig": "Le fichier est trop volumineux. La taille maximale autorisée est de {{maxSize}}.", "svgImageInsertError": "Impossible d'insérer l'image SVG. Le balisage SVG semble invalide.", - "failedToFetchImage": "", + "failedToFetchImage": "Échec de récupération de l'image.", "invalidSVGString": "SVG invalide.", "cannotResolveCollabServer": "Impossible de se connecter au serveur collaboratif. Veuillez recharger la page et réessayer.", "importLibraryError": "Impossible de charger la bibliothèque", "collabSaveFailed": "Impossible d'enregistrer dans la base de données en arrière-plan. Si des problèmes persistent, vous devriez enregistrer votre fichier localement pour vous assurer de ne pas perdre votre travail.", "collabSaveFailed_sizeExceeded": "Impossible d'enregistrer dans la base de données en arrière-plan, le tableau semble trop grand. Vous devriez enregistrer le fichier localement pour vous assurer de ne pas perdre votre travail.", + "imageToolNotSupported": "Les images sont désactivées.", "brave_measure_text_error": { "line1": "On dirait que vous utilisez le navigateur Brave avec l'option Bloquer agressivement le fichage activée.", "line2": "Cela pourrait entraîner des problèmes avec les Éléments Textuels dans vos dessins.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Les éléments intégrés ne peuvent pas être ajoutés à la librairie.", + "iframe": "", "image": "Le support pour l'ajout d'images à la librairie arrive bientôt !" - } + }, + "asyncPasteFailedOnRead": "Impossible de coller (impossible de lire le presse-papiers système).", + "asyncPasteFailedOnParse": "Impossible de coller.", + "copyToSystemClipboardFailed": "Échec de la copie dans le presse-papiers." }, "toolBar": { "selection": "Sélection", @@ -236,10 +249,13 @@ "link": "Ajouter/mettre à jour le lien pour une forme sélectionnée", "eraser": "Gomme", "frame": "Outil de cadre", + "magicframe": "", "embeddable": "Intégration Web", - "laser": "", + "laser": "Pointeur laser", "hand": "Mains (outil de déplacement de la vue)", - "extraTools": "Plus d'outils" + "extraTools": "Plus d'outils", + "mermaidToExcalidraw": "De Mermaid à Excalidraw", + "magicSettings": "Paramètres IA" }, "headings": { "canvasActions": "Actions du canevas", @@ -383,7 +399,7 @@ "darkMode": "Mode sombre", "embedScene": "Intégrer la scène", "scale": "Échelle", - "padding": "" + "padding": "Marge interne" }, "tooltip": { "embedScene": "Les données de la scène seront sauvegardées dans le fichier PNG/SVG exporté afin que la scène puisse être restaurée depuis celui-ci.\nCela augmentera la taille du fichier exporté." @@ -498,5 +514,12 @@ "description": "Charger un dessin externe va remplacer votre contenu existant.

Vous pouvez d'abord sauvegarder votre dessin en utilisant l'une des options ci-dessous." } } + }, + "mermaid": { + "title": "De Mermaid à Excalidraw", + "button": "Insérer", + "description": "", + "syntax": "Syntaxe Mermaid", + "preview": "Prévisualisation" } } diff --git a/packages/excalidraw/locales/gl-ES.json b/packages/excalidraw/locales/gl-ES.json index 658563548..b0e6a7508 100644 --- a/packages/excalidraw/locales/gl-ES.json +++ b/packages/excalidraw/locales/gl-ES.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiar no portapapeis como PNG", "copyAsSvg": "Copiar no portapapeis como SVG", "copyText": "Copia no portapapeis como texto", + "copySource": "", + "convertToCode": "", "bringForward": "Traer cara adiante", "sendToBack": "Enviar cara atrás", "bringToFront": "Traer á fronte", @@ -36,8 +38,12 @@ "arrowhead_none": "Ningunha", "arrowhead_arrow": "Frecha", "arrowhead_bar": "Barra", - "arrowhead_dot": "Punto", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Triángulo", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Tamaño da fonte", "fontFamily": "Tipo de fonte", "addWatermark": "Engadir \"Feito con Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Manter a barra lateral aberta", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Aínda non hai elementos engadidos...", @@ -209,6 +217,7 @@ "importLibraryError": "Non se puido cargar a biblioteca", "collabSaveFailed": "Non se puido gardar na base de datos. Se o problema persiste, deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.", "collabSaveFailed_sizeExceeded": "Non se puido gardar na base de datos, o lenzo semella demasiado grande. Deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Selección", @@ -236,10 +249,13 @@ "link": "Engadir/ Actualizar ligazón para a forma seleccionada", "eraser": "Goma de borrar", "frame": "", + "magicframe": "", "embeddable": "Inserir na web", "laser": "Punteiro láser", "hand": "Man (ferramenta de desprazamento)", - "extraTools": "Máis ferramentas" + "extraTools": "Máis ferramentas", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Accións do lenzo", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/he-IL.json b/packages/excalidraw/locales/he-IL.json index aec18f955..83bf3e718 100644 --- a/packages/excalidraw/locales/he-IL.json +++ b/packages/excalidraw/locales/he-IL.json @@ -11,6 +11,8 @@ "copyAsPng": "העתק ללוח כ PNG", "copyAsSvg": "העתק ללוח כ SVG", "copyText": "העתק ללוח כטקסט", + "copySource": "", + "convertToCode": "", "bringForward": "הבא שכבה קדימה", "sendToBack": "שלח אחורה", "bringToFront": "העבר לחזית", @@ -36,8 +38,12 @@ "arrowhead_none": "ללא", "arrowhead_arrow": "חץ", "arrowhead_bar": "קצה אנכי", - "arrowhead_dot": "נקודה", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "משולש", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "גודל גופן", "fontFamily": "גופן", "addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "שמור את סרגל הצד פתוח", "selectAllElementsInFrame": "בחר את כל האלמנטים במסגרת", "removeAllElementsFromFrame": "הסר את כל האלמנטים שבמסגרת", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "עוד לא הוספת דברים...", @@ -209,6 +217,7 @@ "importLibraryError": "לא ניתן היה לטעון את הספריה", "collabSaveFailed": "לא הצלחתי להתחבר למסד הנתונים האחורי. אם הבעיה ממשיכה, כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.", "collabSaveFailed_sizeExceeded": "לא הצלחתי לשמור למסד הנתונים האחורי, נראה שהקנבס שלך גדול מדי. כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "בחירה", @@ -236,10 +249,13 @@ "link": "הוספה/עדכון קישור של הצורה שנבחרה", "eraser": "מחק", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "יד (כלי הזזה)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "פעולות קנבאס", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/hi-IN.json b/packages/excalidraw/locales/hi-IN.json index 0f3d135cb..4cac567aa 100644 --- a/packages/excalidraw/locales/hi-IN.json +++ b/packages/excalidraw/locales/hi-IN.json @@ -11,6 +11,8 @@ "copyAsPng": "क्लिपबोर्ड पर कॉपी करें ,पीएनजी के रूप में", "copyAsSvg": "क्लिपबोर्ड पर कॉपी करें,एसवीजी के रूप में", "copyText": "लेखन के रूप में पटल पर कॉपी करें", + "copySource": "स्त्रोत को प्रति-फलक पे प्रतिलिपित करे.", + "convertToCode": "सांकेतिक लिपि में परिवर्तित करे", "bringForward": "सामने लाएं", "sendToBack": "पीछे भेजें", "bringToFront": "सामने लाएँ", @@ -36,8 +38,12 @@ "arrowhead_none": "कोई भी नहीं", "arrowhead_arrow": "तीर", "arrowhead_bar": "बार", - "arrowhead_dot": "बिंदु", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "त्रिकोण", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "फ़ॉन्ट का आकार", "fontFamily": "फ़ॉन्ट का परिवार", "addWatermark": "ऐड \"मेड विथ एक्सकैलिडराव\"", @@ -100,15 +106,15 @@ "showStroke": "", "showBackground": "पृष्ठभूमि रंग वरक़ दिखाये", "toggleTheme": "", - "personalLib": "", - "excalidrawLib": "", + "personalLib": "वैयक्तिक समूहकोष", + "excalidrawLib": "एक्सकेलीड्रॉ समूहकोष", "decreaseFontSize": "आकार घटाइऐ", "increaseFontSize": "फ़ॉन्ट आकार बढ़ाएँ", - "unbindText": "", + "unbindText": "लिपि को बंधमुक्त करें", "bindText": "लेखन को कोश से जोड़े", "createContainerFromText": "मूलपाठ कंटेनर में मोड के दिखाए", "link": { - "edit": "", + "edit": "कड़ी संपादित करे", "editEmbed": "", "create": "", "createEmbed": "", @@ -130,7 +136,9 @@ "sidebarLock": "साइडबार खुला रखे.", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "चित्रफलक से रंग चुने" + "eyeDropper": "चित्रफलक से रंग चुने", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "अभी तक कोई आइटम जोडा नहीं गया.", @@ -209,6 +217,7 @@ "importLibraryError": "संग्रह प्रतिष्ठापित नहीं किया जा सका", "collabSaveFailed": "किसी कारण वश अंदरूनी डेटाबेस में सहेजा नहीं जा सका। यदि समस्या बनी रहती है, तो किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।", "collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।", + "imageToolNotSupported": "प्रतिमायें अक्षम की गयी हैं", "brave_measure_text_error": { "line1": "लगता है कि आप Brave ब्राउज़र का उपयोग कर रहे और साथ में आक्रामक उँगलियो के छाप का चयन किया हुवा है", "line2": "यह आपके चित्रों के पाठ तत्वोंको खंडित कर सकता हैं", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "आयफ़्रेम तत्व समूहकोष में जोडा नहीं जा सका.", "image": "" - } + }, + "asyncPasteFailedOnRead": "चिपकाया नहीं जा सका (सिस्टम क्लिपबोर्ड से पढ़ा नहीं जा सका).", + "asyncPasteFailedOnParse": "चिपकाया नहीं जा सका.", + "copyToSystemClipboardFailed": "क्लिपबोर्ड पर प्रतिलिपि नहीं बनाई जा सकी." }, "toolBar": { "selection": "चयन", @@ -236,10 +249,13 @@ "link": "", "eraser": "रबड़", "frame": "", + "magicframe": "तारिक ढाँचें को सांकेतिक लिपि में", "embeddable": "", "laser": "लेसर टॉर्च", "hand": "हाथ ( खिसकाने का औज़ार)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "मर्मेड से एक्सकाली में", + "magicSettings": "कृतिम बुद्धिमत्ता सेटिंग्स" }, "headings": { "canvasActions": "कैनवास क्रिया", @@ -498,5 +514,12 @@ "description": "बाहर का चित्र लोड करने पर यह आपके कार्य की जगह लेलेगा

आप आपकी ड्रॉइंग पहले निम्न दर्शित विकल्पो में से एक चुनके और उपयोग करके सम्हाल सकते हों." } } + }, + "mermaid": { + "title": "मर्मेड से एक्सकाली में", + "button": "अंदर डाले", + "description": "", + "syntax": "मर्मेड संरचना नियम", + "preview": "पूर्वावलोकन" } } diff --git a/packages/excalidraw/locales/hu-HU.json b/packages/excalidraw/locales/hu-HU.json index 130c388c8..e367aa6e9 100644 --- a/packages/excalidraw/locales/hu-HU.json +++ b/packages/excalidraw/locales/hu-HU.json @@ -1,7 +1,7 @@ { "labels": { "paste": "Beillesztés", - "pasteAsPlaintext": "", + "pasteAsPlaintext": "Beillesztés formázatlan szövegként", "pasteCharts": "Grafikon beillesztése", "selectAll": "Összes kijelölése", "multiSelect": "Elem hozzáadása a kijelöléshez", @@ -10,7 +10,9 @@ "copy": "Másolás", "copyAsPng": "Vágólapra másolás mint PNG", "copyAsSvg": "Vágólapra másolás mint SVG", - "copyText": "", + "copyText": "Vágólapra másolás szövegként", + "copySource": "", + "convertToCode": "", "bringForward": "Előrébb hozás", "sendToBack": "Hátraküldés", "bringToFront": "Előrehozás", @@ -36,8 +38,12 @@ "arrowhead_none": "Nincs", "arrowhead_arrow": "Nyíl", "arrowhead_bar": "Oszlop", - "arrowhead_dot": "Pont", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Háromszög", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Betűméret", "fontFamily": "Betűkészlet család", "addWatermark": "Add hozzá, hogy \"Excalidraw-val készült\"", @@ -50,7 +56,7 @@ "veryLarge": "Nagyon nagy", "solid": "Kitöltött", "hachure": "Vonalkázott", - "zigzag": "", + "zigzag": "Cikkcakk", "crossHatch": "Keresztcsíkozott", "thin": "Vékony", "bold": "Félkövér", @@ -69,7 +75,7 @@ "layers": "Rétegek", "actions": "Műveletek", "language": "Nyelv", - "liveCollaboration": "", + "liveCollaboration": "Élő együttműködés...", "duplicateSelection": "Duplikálás", "untitled": "Névtelen", "name": "Név", @@ -106,12 +112,12 @@ "increaseFontSize": "Betűméret növelése", "unbindText": "Szövegkötés feloldása", "bindText": "", - "createContainerFromText": "", + "createContainerFromText": "Szöveg bekeretezése", "link": { "edit": "Hivatkozás szerkesztése", - "editEmbed": "", + "editEmbed": "Link szerkesztése / beágyazása", "create": "Hivatkozás létrehozása", - "createEmbed": "", + "createEmbed": "Link létrehozása / beágyazása", "label": "Hivatkozás", "labelEmbed": "", "empty": "" @@ -121,16 +127,18 @@ "exit": "" }, "elementLock": { - "lock": "", - "unlock": "", - "lockAll": "", - "unlockAll": "" + "lock": "Rögzítés", + "unlock": "Rögzítés feloldása", + "lockAll": "Összes rögzítése", + "unlockAll": "Összes feloldása" }, "statusPublished": "", "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -140,12 +148,12 @@ "buttons": { "clearReset": "Vászon törlése", "exportJSON": "Exportálás fájlba", - "exportImage": "", - "export": "", + "exportImage": "Kép exportálása...", + "export": "Mentés másként...", "copyToClipboard": "Vágólapra másolás", "save": "Mentés az aktuális fájlba", "saveAs": "Mentés másként", - "load": "", + "load": "Megnyitás", "getShareableLink": "Megosztható link létrehozása", "close": "Bezárás", "selectLanguage": "Nyelv kiválasztása", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Kijelölés", @@ -234,12 +247,15 @@ "lock": "Rajzolás után az aktív eszközt tartsa kijelölve", "penMode": "", "link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz", - "eraser": "", + "eraser": "Radír", "frame": "", - "embeddable": "", - "laser": "", + "magicframe": "", + "embeddable": "Weblap beágyazása", + "laser": "Lézermutató", "hand": "", - "extraTools": "" + "extraTools": "További eszközök", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Vászon műveletek", @@ -394,9 +410,9 @@ "copyPngToClipboard": "" }, "button": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "PNG", + "exportToSvg": "SVG", + "copyPngToClipboard": "Vágólapra másolás" } }, "encrypted": { @@ -433,20 +449,20 @@ }, "colors": { "transparent": "Átlátszó", - "black": "", - "white": "", - "red": "", - "pink": "", + "black": "Fekete", + "white": "Fehér", + "red": "Piros", + "pink": "Rózsaszín", "grape": "", - "violet": "", - "gray": "", - "blue": "", - "cyan": "", - "teal": "", - "green": "", - "yellow": "", - "orange": "", - "bronze": "" + "violet": "Ibolya", + "gray": "Szürke", + "blue": "Kék", + "cyan": "Cián", + "teal": "Kékes-zöld", + "green": "Zöld", + "yellow": "Sárga", + "orange": "Narancssárga", + "bronze": "Bronz" }, "welcomeScreen": { "app": { @@ -465,38 +481,45 @@ "mostUsedCustomColors": "", "colors": "", "shades": "", - "hexCode": "", + "hexCode": "Hexadecimális kód", "noShades": "" }, "overwriteConfirm": { "action": { "exportToImage": { - "title": "", - "button": "", + "title": "Exportálás képként", + "button": "Exportálás képként", "description": "" }, "saveToDisk": { - "title": "", - "button": "", + "title": "Mentés a lemezre", + "button": "Mentés a lemezre", "description": "" }, "excalidrawPlus": { - "title": "", + "title": "Excalidraw+", "button": "", "description": "" } }, "modal": { "loadFromFile": { - "title": "", - "button": "", + "title": "Betöltés fájlból", + "button": "Betöltés fájlból", "description": "" }, "shareableLink": { - "title": "", + "title": "Feltöltás linkből", "button": "", "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/id-ID.json b/packages/excalidraw/locales/id-ID.json index bb0274cf2..b35e1bae4 100644 --- a/packages/excalidraw/locales/id-ID.json +++ b/packages/excalidraw/locales/id-ID.json @@ -11,6 +11,8 @@ "copyAsPng": "Salin ke papan klip sebagai PNG", "copyAsSvg": "Salin ke papan klip sebagai SVG", "copyText": "Salin ke papan klip sebagai teks", + "copySource": "", + "convertToCode": "", "bringForward": "Bawa maju", "sendToBack": "Kirim ke belakang", "bringToFront": "Bawa ke depan", @@ -36,8 +38,12 @@ "arrowhead_none": "Tidak ada", "arrowhead_arrow": "Panah", "arrowhead_bar": "Batang", - "arrowhead_dot": "Titik", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Segitiga", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Ukuran font", "fontFamily": "Jenis font", "addWatermark": "Tambahkan \"Dibuat dengan Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Biarkan sidebar tetap terbuka", "selectAllElementsInFrame": "Pilih semua elemen di bingkai", "removeAllElementsFromFrame": "Hapus semua elemen dari bingkai", - "eyeDropper": "Ambil warna dari kanvas" + "eyeDropper": "Ambil warna dari kanvas", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Belum ada item yang ditambahkan...", @@ -209,6 +217,7 @@ "importLibraryError": "Tidak dapat memuat pustaka", "collabSaveFailed": "Tidak dapat menyimpan ke dalam basis data server. Jika masih berlanjut, Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.", "collabSaveFailed_sizeExceeded": "Tidak dapat menyimpan ke dalam basis data server, tampaknya ukuran kanvas terlalu besar. Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Sepertinya Anda menggunkan peramban Brave dengan pengaturan Blokir Fingerprinting yang Agresif diaktifkan.", "line2": "Ini dapat membuat Elemen Teks dalam gambar mu.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Pilihan", @@ -236,10 +249,13 @@ "link": "Tambah/Perbarui tautan untuk bentuk yang dipilih", "eraser": "Penghapus", "frame": "Alat bingkai", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Tangan (alat panning)", - "extraTools": "Alat-alat lain" + "extraTools": "Alat-alat lain", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Opsi Kanvas", @@ -498,5 +514,12 @@ "description": "Memuat dari file yang akan menggantikan konten Anda sekarang.

Anda dapat mencadangkan gambar anda dulu menggunakan opsi-opsi ini." } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/it-IT.json b/packages/excalidraw/locales/it-IT.json index 23490ed5e..6ab03f0ab 100644 --- a/packages/excalidraw/locales/it-IT.json +++ b/packages/excalidraw/locales/it-IT.json @@ -11,6 +11,8 @@ "copyAsPng": "Copia negli appunti come PNG", "copyAsSvg": "Copia negli appunti come SVG", "copyText": "Copia negli appunti come testo", + "copySource": "Copia sorgente negli appunti", + "convertToCode": "Converti in codice", "bringForward": "Porta avanti", "sendToBack": "Manda in fondo", "bringToFront": "Porta in cima", @@ -36,8 +38,12 @@ "arrowhead_none": "Nessuno", "arrowhead_arrow": "Freccia", "arrowhead_bar": "Barra", - "arrowhead_dot": "Punto", + "arrowhead_circle": "Cerchio", + "arrowhead_circle_outline": "Cerchio (contorno)", "arrowhead_triangle": "Triangolo", + "arrowhead_triangle_outline": "Triangolo (contorno)", + "arrowhead_diamond": "Diamante", + "arrowhead_diamond_outline": "Diamante (contorno)", "fontSize": "Dimensione carattere", "fontFamily": "Carattere", "addWatermark": "Aggiungi \"Creato con Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Mantieni aperta la barra laterale", "selectAllElementsInFrame": "Seleziona tutti gli elementi nel riquadro", "removeAllElementsFromFrame": "Rimuovi tutti gli elementi dal riquadro", - "eyeDropper": "Scegli il colore della tela" + "eyeDropper": "Scegli il colore della tela", + "textToDiagram": "Testo a diagramma", + "prompt": "Prompt" }, "library": { "noItems": "Nessun elemento ancora aggiunto...", @@ -203,12 +211,13 @@ "imageInsertError": "Non è stato possibile inserire l'immagine. Riprova più tardi...", "fileTooBig": "Il file è troppo grande. La dimensione massima consentita è {{maxSize}}.", "svgImageInsertError": "Impossibile inserire l'immagine SVG. Il markup SVG non sembra corretto.", - "failedToFetchImage": "", + "failedToFetchImage": "Impossibile recuperare l'immagine.", "invalidSVGString": "SVG non valido.", "cannotResolveCollabServer": "Impossibile connettersi al server di collab. Ricarica la pagina e riprova.", "importLibraryError": "Impossibile caricare la libreria", "collabSaveFailed": "Impossibile salvare nel database di backend. Se i problemi persistono, dovresti salvare il tuo file localmente per assicurarti di non perdere il tuo lavoro.", "collabSaveFailed_sizeExceeded": "Impossibile salvare nel database di backend, la tela sembra essere troppo grande. Dovresti salvare il file localmente per assicurarti di non perdere il tuo lavoro.", + "imageToolNotSupported": "Le immagini sono disabilitate.", "brave_measure_text_error": { "line1": "Sembra che tu stia utilizzando il browser Brave con l'impostazione Blocco aggressivo delle impronte digitali abilitata.", "line2": "Ciò potrebbe causare la rottura degli Elementi di testo nei tuoi disegni.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Gli elementi incorporabili non possono essere aggiunti alla libreria.", + "iframe": "Gli elementi IFrame non possono essere aggiunti alla libreria.", "image": "Il supporto per l'aggiunta d'immagini alla libreria verrà aggiunto a breve!" - } + }, + "asyncPasteFailedOnRead": "Impossibile incollare (non è possibile leggere dagli appunti di sistema).", + "asyncPasteFailedOnParse": "Impossibile incollare.", + "copyToSystemClipboardFailed": "Impossibile copiare negli appunti." }, "toolBar": { "selection": "Selezione", @@ -236,10 +249,13 @@ "link": "Aggiungi/ aggiorna il link per una forma selezionata", "eraser": "Gomma", "frame": "Strumento riquadro", + "magicframe": "", "embeddable": "Incorporamento Web", "laser": "Puntatore laser", "hand": "Mano (strumento di panoramica)", - "extraTools": "Altri strumenti" + "extraTools": "Altri strumenti", + "mermaidToExcalidraw": "", + "magicSettings": "Impostazioni di IA" }, "headings": { "canvasActions": "Azioni sulla Tela", @@ -498,5 +514,12 @@ "description": "Il caricamento da file sostituirà il contenuto esistente.

Puoi salvare il tuo disegno prima usando una delle opzioni qui sotto." } } + }, + "mermaid": { + "title": "", + "button": "Inserisci", + "description": "", + "syntax": "", + "preview": "Anteprima" } } diff --git a/packages/excalidraw/locales/ja-JP.json b/packages/excalidraw/locales/ja-JP.json index 8951739a6..611a8c6a8 100644 --- a/packages/excalidraw/locales/ja-JP.json +++ b/packages/excalidraw/locales/ja-JP.json @@ -11,6 +11,8 @@ "copyAsPng": "PNGとしてクリップボードへコピー", "copyAsSvg": "SVGとしてクリップボードへコピー", "copyText": "テキストとしてクリップボードにコピー", + "copySource": "", + "convertToCode": "", "bringForward": "前面に移動", "sendToBack": "最背面に移動", "bringToFront": "最前面に移動", @@ -36,8 +38,12 @@ "arrowhead_none": "なし", "arrowhead_arrow": "矢印", "arrowhead_bar": "バー", - "arrowhead_dot": "ドット", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "三角", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "フォントの大きさ", "fontFamily": "フォントの種類", "addWatermark": "\"Made with Excalidraw\"と表示", @@ -130,7 +136,9 @@ "sidebarLock": "サイドバーを開いたままにする", "selectAllElementsInFrame": "フレーム内のすべての要素を選択", "removeAllElementsFromFrame": "フレーム内のすべての要素を削除", - "eyeDropper": "キャンバスから色を選択" + "eyeDropper": "キャンバスから色を選択", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "まだアイテムが追加されていません…", @@ -209,6 +217,7 @@ "importLibraryError": "ライブラリを読み込めませんでした。", "collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。", "collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Aggressly Block Fingerprinting の設定が有効なBraveブラウザを使用しているようです。", "line2": "これにより、図面の テキスト要素 が壊れる可能性があります。", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "選択", @@ -236,10 +249,13 @@ "link": "選択した図形のリンクを追加/更新", "eraser": "消しゴム", "frame": "フレームツール", + "magicframe": "", "embeddable": "Web埋め込み", "laser": "", "hand": "手 (パンニングツール)", - "extraTools": "その他のツール" + "extraTools": "その他のツール", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "キャンバス操作", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/kaa.json b/packages/excalidraw/locales/kaa.json index c04589095..cdca8cbe6 100644 --- a/packages/excalidraw/locales/kaa.json +++ b/packages/excalidraw/locales/kaa.json @@ -9,8 +9,10 @@ "cut": "Qıyıw", "copy": "Kóshirip alıw", "copyAsPng": "Almasıw buferine PNG retinde kóshirip alıw", - "copyAsSvg": "", - "copyText": "", + "copyAsSvg": "Almasıw buferine SVG retinde kóshirip alıw", + "copyText": "Almasıw buferine tekst retinde kóshirip alıw", + "copySource": "", + "convertToCode": "", "bringForward": "", "sendToBack": "", "bringToFront": "", @@ -33,19 +35,23 @@ "sharp": "", "round": "", "arrowheads": "", - "arrowhead_none": "", + "arrowhead_none": "Joq", "arrowhead_arrow": "Jebe", "arrowhead_bar": "", - "arrowhead_dot": "Noqat", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Shrift ólshemi", - "fontFamily": "", + "fontFamily": "Shrift toplamı", "addWatermark": "", "handDrawn": "", "normal": "", "code": "Kod", "small": "", - "medium": "", + "medium": "Ortasha", "large": "Úlken", "veryLarge": "Júdá úlken", "solid": "", @@ -59,14 +65,14 @@ "right": "", "extraBold": "", "architect": "", - "artist": "", + "artist": "Súwretshi", "cartoonist": "", "fileTitle": "Fayl ataması", - "colorPicker": "", + "colorPicker": "Reńdi tańlaw", "canvasColors": "", "canvasBackground": "", "drawingCanvas": "", - "layers": "", + "layers": "Qatlamlar", "actions": "Háreketler", "language": "Til", "liveCollaboration": "", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -170,7 +178,7 @@ "clear": "Tazalaw", "remove": "Óshiriw", "embed": "", - "publishLibrary": "", + "publishLibrary": "Jariyalaw", "submit": "Jiberiw", "confirm": "Tastıyıqlaw", "embeddableInteractionButton": "" @@ -209,6 +217,7 @@ "importLibraryError": "Kitapxananı júklew ámelge aspadı", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "Óshirgish", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "", @@ -271,7 +287,7 @@ "disableSnapping": "" }, "canvasError": { - "cannotShowPreview": "", + "cannotShowPreview": "Aldınnan kóriwdi kórsetiw múmkin emes", "canvasTooBig": "", "canvasTooBigTip": "" }, @@ -350,15 +366,15 @@ "website": "Veb-sayt", "placeholder": { "authorName": "Atıńız yamasa paydalanıwshı atı", - "libraryName": "", + "libraryName": "Kitapxanańız ataması", "libraryDesc": "", "githubHandle": "", "twitterHandle": "", - "website": "" + "website": "Jeke veb-saytıńız yamasa basqa saytqa silteme (májbúriy emes)" }, "errors": { - "required": "", - "website": "" + "required": "Májbúriy", + "website": "Jaramlı URL mánzil kirgiziń" }, "noteDescription": "", "noteGuidelines": "", @@ -368,7 +384,7 @@ "republishWarning": "" }, "publishSuccessDialog": { - "title": "", + "title": "Kitapxana jiberildi", "content": "" }, "confirmDialog": { @@ -382,7 +398,7 @@ "onlySelected": "", "darkMode": "Qarańǵı tema", "embedScene": "", - "scale": "", + "scale": "Kólem", "padding": "" }, "tooltip": { @@ -416,7 +432,7 @@ "version": "Versiya", "versionCopy": "Kóshirip alıw ushın basıń", "versionNotAvailable": "", - "width": "" + "width": "Eni" }, "toast": { "addedToLibrary": "Kitapxanaǵa qosıldı", @@ -451,18 +467,18 @@ "welcomeScreen": { "app": { "center_heading": "", - "center_heading_plus": "", + "center_heading_plus": "Excalidraw+ ge ótiwdi qáleysiz be?", "menuHint": "Eksportlaw, sazlawlar, tiller, ..." }, "defaults": { "menuHint": "Eksportlaw, sazlawlar hám basqa...", - "center_heading": "", + "center_heading": "Diagrammalar. Ápiwayı.", "toolbarHint": "", "helpHint": "" } }, "colorPicker": { - "mostUsedCustomColors": "", + "mostUsedCustomColors": "Kóp qollanılatuǵın arnawlı reńler", "colors": "Reńler", "shades": "", "hexCode": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/kab-KAB.json b/packages/excalidraw/locales/kab-KAB.json index ffd3aa079..ed10f51d0 100644 --- a/packages/excalidraw/locales/kab-KAB.json +++ b/packages/excalidraw/locales/kab-KAB.json @@ -11,6 +11,8 @@ "copyAsPng": "Nɣel ɣer tecfawit am PNG", "copyAsSvg": "Nɣel ɣer tecfawit am SVG", "copyText": "Nɣel ɣer tecfawit am uḍris", + "copySource": "", + "convertToCode": "", "bringForward": "Awi ɣer sdat", "sendToBack": "Awi s agilal", "bringToFront": "Err ɣer deffir", @@ -36,8 +38,12 @@ "arrowhead_none": "Ulac", "arrowhead_arrow": "Taneccabt", "arrowhead_bar": "Afeggag", - "arrowhead_dot": "Tanqiḍt", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Akerdis", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Tiddi n tsefsit", "fontFamily": "Tawacult n tsefsiyin", "addWatermark": "Seddu \"Yettwaxdem s Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Eǧǧ afeggag n yidis yeldi", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Ulac iferdisen yettwarnan yakan...", @@ -209,6 +217,7 @@ "importLibraryError": "Ur d-ssalay ara tamkarḍit", "collabSaveFailed": "Ulamek asekles deg uzadur n yisefka deg ugilal. Ma ikemmel wugur, isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.", "collabSaveFailed_sizeExceeded": "Ulamek asekles deg uzadur n yisefka deg ugilal, taɣzut n usuneɣ tettban-d temqer aṭas. Isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "Ayagi yezmer ad d-iglu s truẓi nIferdisen n uḍrisdeg wunuɣen-inek.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Tafrayt", @@ -236,10 +249,13 @@ "link": "Rnu/leqqem aseɣwen i talɣa yettwafernen", "eraser": "Sfeḍ", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Afus (afecku n usmutti n tmuɣli)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Tigawin n teɣzut n usuneɣ", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/kk-KZ.json b/packages/excalidraw/locales/kk-KZ.json index fd86a8ba7..9b11fcaff 100644 --- a/packages/excalidraw/locales/kk-KZ.json +++ b/packages/excalidraw/locales/kk-KZ.json @@ -11,6 +11,8 @@ "copyAsPng": "", "copyAsSvg": "", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "", "sendToBack": "", "bringToFront": "", @@ -36,8 +38,12 @@ "arrowhead_none": "Жоқ", "arrowhead_arrow": "Нұсқар", "arrowhead_bar": "Тосқауыл", - "arrowhead_dot": "Нүкте", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Қаріп өлшемі", "fontFamily": "Қаріп тобы", "addWatermark": "", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/km-KH.json b/packages/excalidraw/locales/km-KH.json index 9ad1ac345..3aca3ce9b 100644 --- a/packages/excalidraw/locales/km-KH.json +++ b/packages/excalidraw/locales/km-KH.json @@ -11,6 +11,8 @@ "copyAsPng": "ចម្លងទៅក្តារតម្បៀតខ្ទាស់ជា​ PNG", "copyAsSvg": "ចម្លងទៅក្តារតម្បៀតខ្ទាស់ជា​ SVG", "copyText": "ចម្លងទៅក្តារតម្បៀតខ្ទាស់ជា​អត្ថបទ", + "copySource": "", + "convertToCode": "", "bringForward": "នាំយកទៅលើ", "sendToBack": "នាំយកទៅក្រោយបង្អស់", "bringToFront": "នាំយកទៅលើបង្អស់", @@ -36,8 +38,12 @@ "arrowhead_none": "គ្មាន", "arrowhead_arrow": "ព្រួញ", "arrowhead_bar": "របារ", - "arrowhead_dot": "ចំណុច", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "ត្រីកោណ", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "ទំហំពុម្ពអក្សរ", "fontFamily": "ក្រុម​ពុម្ពអក្សរ", "addWatermark": "បន្ថែមវ៉ាត់ធើម៉ាក \"Made with Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "ទុករបារចំហៀងបើក", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "មិនទាន់មានធាតុបន្ថែមទេ...", @@ -209,6 +217,7 @@ "importLibraryError": "មិនអាចផ្ទុកបណ្ណាល័យបានទេ។", "collabSaveFailed": "មិនអាចរក្សាទុកទៅម៉ាស៊ីនមេបានទេ។ ប្រសិនបើបញ្ហានៅតែបន្តកើតមាន​ អ្នកគួរតែរក្សាទុកឯកសាររបស់អ្នកនៅលើកុំព្យូទ័ររបស់អ្នកសិន ដើម្បីធានាថាការងាររបស់អ្នកមិនបាត់បង់។", "collabSaveFailed_sizeExceeded": "មិនអាចរក្សាទុកទៅម៉ាស៊ីនមេបានទេ, ផ្ទាំងបាវហាក់ដូចជាធំពេក។ អ្នកគួរតែរក្សាទុកឯកសាររបស់អ្នកនៅលើកុំព្យូទ័ររបស់អ្នកសិន ដើម្បីធានាថាការងាររបស់អ្នកមិនបាត់បង់។", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "អ្នកហាក់ដូចជាកំពុងប្រើប្រាស់កម្មវិធីរុករកតាមអ៊ីនធឺណិត Brave ជាមួយនឹងការកំណត់ ការពារស្នាមម្រាមដៃយ៉ាងធ្ងន់ធ្ងរ ត្រូវបានបើក។", "line2": "វាអាចបណ្តាលឱ្យមានការបំបែក ធាតុអត្ថបទ នៅក្នុងគំនូររបស់អ្នក។", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "ការជ្រើសរើស", @@ -236,10 +249,13 @@ "link": "បន្ថែម/ធ្វើបច្ចុប្បន្នភាពតំណភ្ជាប់សម្រាប់រូបរាងដែលបានជ្រើសរើស", "eraser": "ជ័រលុប", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "ដៃ (panning tool)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "សកម្មភាពបាវ", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/ko-KR.json b/packages/excalidraw/locales/ko-KR.json index fb59bea0b..bb518ea34 100644 --- a/packages/excalidraw/locales/ko-KR.json +++ b/packages/excalidraw/locales/ko-KR.json @@ -11,6 +11,8 @@ "copyAsPng": "클립보드로 PNG 이미지 복사", "copyAsSvg": "클립보드로 SVG 이미지 복사", "copyText": "클립보드로 텍스트 복사", + "copySource": "소스코드를 클립보드로 복사", + "convertToCode": "코드로 변환", "bringForward": "앞으로 가져오기", "sendToBack": "맨 뒤로 보내기", "bringToFront": "맨 앞으로 가져오기", @@ -36,8 +38,12 @@ "arrowhead_none": "없음", "arrowhead_arrow": "화살표", "arrowhead_bar": "막대", - "arrowhead_dot": "점", + "arrowhead_circle": "원", + "arrowhead_circle_outline": "원 (외곽선)", "arrowhead_triangle": "삼각형", + "arrowhead_triangle_outline": "삼각형 (외곽선)", + "arrowhead_diamond": "마름모", + "arrowhead_diamond_outline": "마름모 (외곽선)", "fontSize": "글자 크기", "fontFamily": "글꼴", "addWatermark": "\"Made with Excalidraw\" 추가", @@ -130,7 +136,9 @@ "sidebarLock": "사이드바 유지", "selectAllElementsInFrame": "프레임의 모든 요소 선택", "removeAllElementsFromFrame": "프레임의 모든 요소 삭제", - "eyeDropper": "캔버스에서 색상 고르기" + "eyeDropper": "캔버스에서 색상 고르기", + "textToDiagram": "텍스트를 다이어그램으로", + "prompt": "프롬프트" }, "library": { "noItems": "추가된 아이템 없음", @@ -209,6 +217,7 @@ "importLibraryError": "라이브러리를 불러오지 못했습니다.", "collabSaveFailed": "데이터베이스에 저장하지 못했습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.", "collabSaveFailed_sizeExceeded": "데이터베이스에 저장하지 못했습니다. 캔버스가 너무 큰 거 같습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.", + "imageToolNotSupported": "이미지가 비활성화 되었습니다.", "brave_measure_text_error": { "line1": "귀하께서는 강력한 지문 차단 설정이 활성화된 Brave browser를 사용하고 계신 것 같습니다.", "line2": "이 기능으로 인해 화이트보드의 텍스트 요소들이 손상될 수 있습니다.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "임베드 요소들은 라이브러리에 추가할 수 없습니다.", + "iframe": "IFrame 요소들은 라이브러리에 추가할 수 없습니다.", "image": "라이브러리에 이미지 삽입 기능은 곧 지원될 예정입니다!" - } + }, + "asyncPasteFailedOnRead": "붙여넣는데 실패했습니다. (시스템 클립보드를 읽는데 실패했습니다)", + "asyncPasteFailedOnParse": "붙여넣는데 실패했습니다.", + "copyToSystemClipboardFailed": "클립보드로 복사하는데 실패했습니다." }, "toolBar": { "selection": "선택", @@ -236,10 +249,13 @@ "link": "선택한 도형에 대해서 링크를 추가/업데이트", "eraser": "지우개", "frame": "프레임 도구", + "magicframe": "와이어프레임을 코드로", "embeddable": "웹 임베드", "laser": "레이저 포인터", "hand": "손 (패닝 도구)", - "extraTools": "다른 도구" + "extraTools": "다른 도구", + "mermaidToExcalidraw": "Mermaid에서 불러오기", + "magicSettings": "AI 설정" }, "headings": { "canvasActions": "캔버스 동작", @@ -498,5 +514,12 @@ "description": "외부 작업물을 불러오면 현재 작성된 데이터를 덮어쓰게 됩니다.

다음 옵션 중 하나를 선택하여 작업물을 백업해 둘 수 있습니다." } } + }, + "mermaid": { + "title": "Mermaid에서 불러오기", + "button": "삽입하기", + "description": "지금은 순서도, 시퀀스, 클래스 다이어그램만 지원합니다. 다른 형식들은 Excalidraw에서는 이미지로 표시됩니다.", + "syntax": "Mermaid 구문", + "preview": "미리보기" } } diff --git a/packages/excalidraw/locales/ku-TR.json b/packages/excalidraw/locales/ku-TR.json index b968d0163..c7e60bbaf 100644 --- a/packages/excalidraw/locales/ku-TR.json +++ b/packages/excalidraw/locales/ku-TR.json @@ -11,6 +11,8 @@ "copyAsPng": "PNGلەبەرگرتنەوە بۆ تەختەنووس وەک", "copyAsSvg": "SVGلەبەرگرتنەوە بۆ تەختەنووس وەک", "copyText": "لەبەرگرتنەوە بۆ تەختەنووس وەک نوسین", + "copySource": "", + "convertToCode": "", "bringForward": "بهێنە پێشتر", "sendToBack": "بنێرە دواوە", "bringToFront": "بهێنە پێشەوە", @@ -36,8 +38,12 @@ "arrowhead_none": "هیچیان", "arrowhead_arrow": "تیر", "arrowhead_bar": "هێڵ", - "arrowhead_dot": "خاڵ", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "سێگۆشە", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "قەبارەی فۆنت", "fontFamily": "خێزانی فۆنت", "addWatermark": "زیادبکە \"Made with Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "هێشتنەوەی شریتی لا بە کراوەیی", "selectAllElementsInFrame": "هەموو توخمەکانی ناو چوارچێوەکە دیاری بکە", "removeAllElementsFromFrame": "هەموو توخمەکانی ناو چوارچێوەکە لابەرە", - "eyeDropper": "ڕەنگێک لەسەر تابلۆکە هەڵبژێرە" + "eyeDropper": "ڕەنگێک لەسەر تابلۆکە هەڵبژێرە", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "هێشتا هیچ بڕگەیەک زیاد نەکراوە...", @@ -209,6 +217,7 @@ "importLibraryError": "نەیتوانی کتێبخانە بار بکات", "collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.", "collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "وادیارە وێبگەڕی Brave بەکاردەهێنیت و ڕێکخستنی Aggressively Block Fingerprinting ـت چالاک کردووە.", "line2": "ئەمە ئەکرێ ببێتە هۆی تێکدانی دانە دەقییەکان لە وێنەکێشانەکانتدا.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "دەستنیشانکردن", @@ -236,10 +249,13 @@ "link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو", "eraser": "سڕەر", "frame": "ئامرازی چوارچێوە", + "magicframe": "", "embeddable": "", "laser": "", "hand": "دەست (ئامرازی پانکردن)", - "extraTools": "ئامرازی زیاتر" + "extraTools": "ئامرازی زیاتر", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "کردارەکانی تابلۆ", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/lt-LT.json b/packages/excalidraw/locales/lt-LT.json index 5dc9b3328..2ddffe6e2 100644 --- a/packages/excalidraw/locales/lt-LT.json +++ b/packages/excalidraw/locales/lt-LT.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopijuoti į iškarpinę kaip PNG", "copyAsSvg": "Kopijuoti į iškarpinę kaip SVG", "copyText": "Kopijuoti į iškarpinę kaip tekstą", + "copySource": "", + "convertToCode": "", "bringForward": "Kelti priekio link", "sendToBack": "Nustumti į užnugarį", "bringToFront": "Iškelti į priekį", @@ -36,8 +38,12 @@ "arrowhead_none": "Jokios", "arrowhead_arrow": "Rodyklė", "arrowhead_bar": "Brukšnys", - "arrowhead_dot": "Taškas", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trikampis", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Šrifto dydis", "fontFamily": "Šriftas", "addWatermark": "Sukurta su Excalidraw", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "Nepavyko įkelti bibliotekos", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Žymėjimas", @@ -236,10 +249,13 @@ "link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą", "eraser": "Trintukas", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Veiksmai su drobe", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/lv-LV.json b/packages/excalidraw/locales/lv-LV.json index df142b63b..edfb28e51 100644 --- a/packages/excalidraw/locales/lv-LV.json +++ b/packages/excalidraw/locales/lv-LV.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopēt starpliktuvē kā PNG", "copyAsSvg": "Kopēt starpliktuvē kā SVG", "copyText": "Kopēt starpliktuvē kā tekstu", + "copySource": "", + "convertToCode": "", "bringForward": "Pārvietot vienu slāni augstāk", "sendToBack": "Pārvietot uz zemāko slāni", "bringToFront": "Pārvietot uz virsējo slāni", @@ -36,8 +38,12 @@ "arrowhead_none": "Nekādas", "arrowhead_arrow": "Bulta", "arrowhead_bar": "Svītra", - "arrowhead_dot": "Punkts", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trijstūris", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Teksta lielums", "fontFamily": "Fontu saime", "addWatermark": "Pievienot \"Radīts ar Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Paturēt atvērtu sānjoslu", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Neviena vienība vēl nav pievienota...", @@ -209,6 +217,7 @@ "importLibraryError": "Nevarēja ielādēt bibliotēku", "collabSaveFailed": "Darbs nav saglabāts datubāzē. Ja problēma turpinās, saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.", "collabSaveFailed_sizeExceeded": "Darbs nav saglabāts datubāzē, šķiet, ka tāfele ir pārāk liela. Saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Atlase", @@ -236,10 +249,13 @@ "link": "Pievienot/rediģēt atlasītās figūras saiti", "eraser": "Dzēšgumija", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Roka (panoramēšanas rīks)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Tāfeles darbības", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/mr-IN.json b/packages/excalidraw/locales/mr-IN.json index 114bbe1f2..5d13a2ffe 100644 --- a/packages/excalidraw/locales/mr-IN.json +++ b/packages/excalidraw/locales/mr-IN.json @@ -11,6 +11,8 @@ "copyAsPng": "PNG रूपे फळी वर कॉपी करा", "copyAsSvg": "SVG रूपे फळी वर कॉपी करा", "copyText": "लिखित रूपे फळी वर कॉपी करा", + "copySource": "स्त्रोत फळी वर कॉपी करा", + "convertToCode": "सांकेतिक लिपित रूपांतरित करा", "bringForward": "पुढे पुढे आणा", "sendToBack": "सर्वात मागे करा", "bringToFront": "सर्वात पुढे आणा", @@ -36,8 +38,12 @@ "arrowhead_none": "कुठलाहि नाही", "arrowhead_arrow": "बाण", "arrowhead_bar": "दांडुक", - "arrowhead_dot": "ठिपका", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "त्रिकोण", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "अक्षर आकार", "fontFamily": "अक्षर समूह", "addWatermark": "\"एक्सकेलीड्रॉ ने बनवलेलं\" जोडा", @@ -109,12 +115,12 @@ "createContainerFromText": "मजकूर कंटेनर मधे मोडून दाखवा", "link": { "edit": "दुवा संपादन", - "editEmbed": "", + "editEmbed": "कड़ी सम्पादित करा आणि रुतवा", "create": "दुवा तयार करा", - "createEmbed": "", + "createEmbed": "नवीन कड़ी बनवा आणि रुतवा", "label": "दुवा", - "labelEmbed": "", - "empty": "" + "labelEmbed": "कड़ी आणि रूतवणे", + "empty": "कुठलिही कड़ी दिली नाही" }, "lineEditor": { "edit": "रेघ संपादन", @@ -128,9 +134,11 @@ }, "statusPublished": "प्रकाशित करा", "sidebarLock": "साइडबार उघडं ठेवा", - "selectAllElementsInFrame": "", - "removeAllElementsFromFrame": "", - "eyeDropper": "चित्रफलकातून रंग निवडा" + "selectAllElementsInFrame": "चौकटीतले सर्व तत्वांचे चयन करा", + "removeAllElementsFromFrame": "चौकटीतून सर्व काढून टाका", + "eyeDropper": "चित्रफलकातून रंग निवडा", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "अजून कोणतेही आइटम जोडलेले नाही...", @@ -169,11 +177,11 @@ "cancel": "रद्द", "clear": "स्वछ", "remove": "हटवा", - "embed": "", + "embed": "रुतवणे उलटे करा", "publishLibrary": "प्रकाशित करा", "submit": "जमा करा", "confirm": "पुष्टि करा", - "embeddableInteractionButton": "" + "embeddableInteractionButton": "संवादा साठी क्लिक करा" }, "alerts": { "clearReset": "पटल स्वच्छ होणार, तुम्हाला खात्री आहे का?", @@ -203,12 +211,13 @@ "imageInsertError": "प्रतिमा आत घालता येत नाही. नंतर पुन्हा प्रयत्न करा...", "fileTooBig": "फाइल फार मोठी आहे. आकाराची कमाल परवानगी {{maxSize}} आहे.", "svgImageInsertError": "एस-वी-जी प्रतिमा आत घालवू शकलो नाही. एस-वी-जी-मार्क-अप यंत्र अयोग्य आहे.", - "failedToFetchImage": "", + "failedToFetchImage": "प्रतिमा आणणे नाही जमले.", "invalidSVGString": "अयोग्य एस-वी-जी.", "cannotResolveCollabServer": "कॉलेब-सर्वर हे पोहोचत नाही आहे. पान परत लोड करायचा प्रयत्न करावे.", "importLibraryError": "संग्रह प्रतिस्थापित नाही करता आला", "collabSaveFailed": "काही कारणा निमित्त आतल्या डेटाबेसमध्ये जतन करू शकत नाही। समस्या तशिस राहिल्यास, तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही तुमची फाइल स्थानिक जतन करावी.", "collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।\n\nबॅकएंड डेटाबेसमध्ये जतन करू शकत नाही, कॅनव्हास खूप मोठा असल्याचे दिसते. तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही फाइल स्थानिक पातळीवर जतन करावी.", + "imageToolNotSupported": "प्रतिमां अक्षम केली गेली आहेत.", "brave_measure_text_error": { "line1": "असं वाटते की तुम्हीं Brave ब्राउज़र वापरतात आहात आणि त्या बरोबार आक्रामक पद्धति चें बोटांचे ठसे हां सेटिंग्स चा विकल्प चयन केलेला आहे.", "line2": "हे तुमच्या चित्रांच्या पाठ तत्वांनां खंडित करू शकतात.", @@ -216,9 +225,13 @@ "line4": "ही सेटिंग अक्षम करूनही पृष्ठ योग्यरित्या प्रदर्शित होत नसल्यास, आमच्या GitHub वर समस्या सबमिट करा, किव्हा डिस्कॉर्ड वर आम्हाला लिहा" }, "libraryElementTypeError": { - "embeddable": "", - "image": "" - } + "embeddable": "रुतलेले तत्व समूह कोषात जोडले जाऊ शकत नाही.", + "iframe": "आयफ़्रेम तत्व समूहकोषात जोडला जाऊ शकत नाही.", + "image": "प्रतिमा समूह कोषात जोड़ायचे लवकरच येत आहेत!" + }, + "asyncPasteFailedOnRead": "चिटकवता नाही जमले ( सिस्टम क्लिप्बॉर्ड पासून वाचणे नाही जमले).", + "asyncPasteFailedOnParse": "चिटकवता नाही जमले.", + "copyToSystemClipboardFailed": "क्लिपबोर्ड वर प्रतिलिपि करणे नाही जमले." }, "toolBar": { "selection": "निवड", @@ -235,11 +248,14 @@ "penMode": "पेन चा मोड - स्पर्श टाळा", "link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा", "eraser": "खोड रबर", - "frame": "", - "embeddable": "", + "frame": "चौकट यंत्र", + "magicframe": "वायरफ़्रेम पासून सांकेतिक लिपि", + "embeddable": "वेब रुतवा", "laser": "लेसर टॉर्च", "hand": "हात ( सरकवण्या चे उपकरण)", - "extraTools": "" + "extraTools": "आणिक यंत्रे", + "mermaidToExcalidraw": "मर्मेड पासून एक्सकाली मधे", + "magicSettings": "कृतिम बुद्दिवत्ता सेटिंग्स" }, "headings": { "canvasActions": "पटल क्रिया", @@ -251,7 +267,7 @@ "linearElement": "अनेक बिंदु साठी क्लिक करा, रेघे साठी ड्रैग करा", "freeDraw": "क्लिक आणि ड्रैग करा, झालं तेव्हा सोडा", "text": "टीप: तुम्हीं निवड यंत्रानी कोठेही दुहेरी क्लिक करून टेक्स्ट जोडू शकता", - "embeddable": "", + "embeddable": "वेबसाइट रुतोण्या साठी दाबून-खेचा (क्लिक-ड्रैग करा)", "text_selected": "लेखन संपादन साठी दुहेरी क्लिक करा किव्हा एंटर दाबा", "text_editing": "संपादन संपवायचं असल्यास एस्केप दाबा किव्हा कंट्रोल या कम्मांड बरोबार एंटर दाबा", "linearElementMulti": "शेवटच्या बिंदु वर क्लिक करा किव्हा एस्केप या एंटर दाबा", @@ -376,27 +392,27 @@ "removeItemsFromLib": "निवडलेले आयटम्स संग्रहातून काढा" }, "imageExportDialog": { - "header": "", + "header": "प्रतिमा निर्यात करा", "label": { - "withBackground": "", - "onlySelected": "", - "darkMode": "", - "embedScene": "", - "scale": "", - "padding": "" + "withBackground": "पार्श्वभूमि", + "onlySelected": "फक्त चयन केलेले", + "darkMode": "अंधारमय स्थिति", + "embedScene": "दृश्य रुतवा", + "scale": "क़िती पट", + "padding": "ग़ादी" }, "tooltip": { - "embedScene": "" + "embedScene": "दृश्य डेटा निर्यात केलेल्या पी-एन-जी किव्हा एस-वी-जी फ़ाईल मधे सुरक्षीत केला जाईल, त्याने दृश्य पुनः परत आणता येईल. निर्यात केलेली फ़ाईल चा आकार त्याने वाढेल." }, "title": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "पी-एन-जी स्वरूपात बाहेर ठेवा", + "exportToSvg": "एस-वी-जी स्वरूपात बाहेर ठेवा", + "copyPngToClipboard": "पी-एन-जी रूपे फळी वर कॉपी करा" }, "button": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "पीएनजी", + "exportToSvg": "एसविज़ी", + "copyPngToClipboard": "फळी वर कॉपी करा" } }, "encrypted": { @@ -428,8 +444,8 @@ "canvas": "पटल", "selection": "निवड", "pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा", - "unableToEmbed": "", - "unrecognizedLinkFormat": "" + "unableToEmbed": "युआरेल रूतवणे प्रतिबंधित आहेत. आपली युआरेल श्वेतसूचित आणण्या साठी कृपया एक मुद्दा ग़िटहब वर उठवा", + "unrecognizedLinkFormat": "जी युआरेल तुम्हीं रतवली आहे, ती आपेक्षित प्रकारे नाही आहे. कृपया स्त्रोत साइट नी 'रूतवण्या साठी दिलेलि लिपि' चिपकावण्याचा प्रयास करा" }, "colors": { "transparent": "पारदर्शक", @@ -498,5 +514,12 @@ "description": "बाहरी चित्र लोड केल्या वर ते तुमचा सध्याचा कामा ठिकाणि एईल

तुम्हीं तुमचं चित्र एकाधं खाली दिलेलं विकल्प निवडुन पहले सुरक्षीत करु शकता." } } + }, + "mermaid": { + "title": "मर्मेड पासून एक्सकाली मधे", + "button": "शिरवा", + "description": "", + "syntax": "मर्मेड संरचना नियम", + "preview": "पूर्वावलोकन" } } diff --git a/packages/excalidraw/locales/my-MM.json b/packages/excalidraw/locales/my-MM.json index 2a54c0af2..290fa4ae4 100644 --- a/packages/excalidraw/locales/my-MM.json +++ b/packages/excalidraw/locales/my-MM.json @@ -11,6 +11,8 @@ "copyAsPng": "PNG အနေဖြင့်ကူး", "copyAsSvg": "SVG အနေဖြင့်ကူး", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "ရှေ့ပို့", "sendToBack": "နောက်ဆုံးထား", "bringToFront": "ရှေ့ဆုံးထား", @@ -36,8 +38,12 @@ "arrowhead_none": "ဘာမျှမရှိ", "arrowhead_arrow": "မြှား", "arrowhead_bar": "", - "arrowhead_dot": "အစက်", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "စာလုံးအရွယ်", "fontFamily": "စာလုံးပုံစံ", "addWatermark": "\"Excalidraw ဖြင့်ဖန်တီးသည်။\" စာသားထည့်", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "ရွေးချယ်", @@ -236,10 +249,13 @@ "link": "", "eraser": "", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "ကားချပ်လုပ်ဆောင်ချက်", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/nb-NO.json b/packages/excalidraw/locales/nb-NO.json index 8ebc986e3..13372b59f 100644 --- a/packages/excalidraw/locales/nb-NO.json +++ b/packages/excalidraw/locales/nb-NO.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopier til PNG", "copyAsSvg": "Kopier til utklippstavlen som SVG", "copyText": "Kopier til utklippstavlen som tekst", + "copySource": "", + "convertToCode": "", "bringForward": "Flytt framover", "sendToBack": "Send bakerst", "bringToFront": "Flytt forrest", @@ -36,8 +38,12 @@ "arrowhead_none": "Ingen", "arrowhead_arrow": "Pil", "arrowhead_bar": "Søyle", - "arrowhead_dot": "Prikk", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trekant", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Skriftstørrelse", "fontFamily": "Fontfamilie", "addWatermark": "Legg til \"Laget med Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Holde sidemenyen åpen", "selectAllElementsInFrame": "Velg alle elementene i rammen", "removeAllElementsFromFrame": "Fjern alle elementer fra rammen", - "eyeDropper": "Velg farge fra lerretet" + "eyeDropper": "Velg farge fra lerretet", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Ingen elementer lagt til ennå...", @@ -209,6 +217,7 @@ "importLibraryError": "Kunne ikke laste bibliotek", "collabSaveFailed": "Kan ikke lagre i backend-databasen. Hvis problemer vedvarer, bør du lagre filen lokalt for å sikre at du ikke mister arbeidet.", "collabSaveFailed_sizeExceeded": "Kunne ikke lagre til backend-databasen, lerretet ser ut til å være for stort. Du bør lagre filen lokalt for å sikre at du ikke mister arbeidet ditt.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Ser ut som om du bruker Brave nettleser med Aggressivt Block Finger -innstillingen aktivert.", "line2": "Dette kan resultere i å bryte tekst-elementene i tegningene.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Innebygde elementer kan ikke legges til i biblioteket.", + "iframe": "", "image": "Støtte for å legge til bilder i biblioteket kommer snart!" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Velg", @@ -236,10 +249,13 @@ "link": "Legg til / oppdater link for en valgt figur", "eraser": "Viskelær", "frame": "Rammeverktøy", + "magicframe": "", "embeddable": "Nettinnbygging", "laser": "", "hand": "Hånd (panoreringsverktøy)", - "extraTools": "Flere verktøy" + "extraTools": "Flere verktøy", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Handlinger: lerret", @@ -498,5 +514,12 @@ "description": "Lasting av ekstern tegning vil erstatte ditt eksisterende innhold.

Du kan sikkerhetskopiere tegningen din først ved å bruke en av valgene nedenfor." } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/nl-NL.json b/packages/excalidraw/locales/nl-NL.json index 84471044b..5ef98343b 100644 --- a/packages/excalidraw/locales/nl-NL.json +++ b/packages/excalidraw/locales/nl-NL.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopieer als PNG", "copyAsSvg": "Kopieer naar klembord als SVG", "copyText": "Kopieer naar klembord als tekst", + "copySource": "", + "convertToCode": "", "bringForward": "Breng naar voren", "sendToBack": "Stuur naar achtergrond", "bringToFront": "Breng naar voorgrond", @@ -36,8 +38,12 @@ "arrowhead_none": "Geen", "arrowhead_arrow": "Pijl", "arrowhead_bar": "Balk", - "arrowhead_dot": "Punt", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Driehoek", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Tekstgrootte", "fontFamily": "Lettertype", "addWatermark": "Voeg \"Gemaakt met Excalidraw\" toe", @@ -130,7 +136,9 @@ "sidebarLock": "Zijbalk open houden", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Nog geen items toegevoegd...", @@ -209,6 +217,7 @@ "importLibraryError": "Kon bibliotheek niet laden", "collabSaveFailed": "Kan niet opslaan in de backend database. Als de problemen blijven bestaan, moet u het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest.", "collabSaveFailed_sizeExceeded": "Kan de backend database niet opslaan, het canvas lijkt te groot te zijn. U moet het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Ingesloten elementen kunnen niet worden toegevoegd aan de bibliotheek.", + "iframe": "", "image": "Ondersteuning voor het toevoegen van afbeeldingen aan de bibliotheek komt binnenkort!" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Selectie", @@ -236,10 +249,13 @@ "link": "Link toevoegen / bijwerken voor een geselecteerde vorm", "eraser": "Gum", "frame": "", + "magicframe": "", "embeddable": "Web insluiten", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Canvasacties", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/nn-NO.json b/packages/excalidraw/locales/nn-NO.json index 8d110945b..6658dba2b 100644 --- a/packages/excalidraw/locales/nn-NO.json +++ b/packages/excalidraw/locales/nn-NO.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopier til utklippstavla som PNG", "copyAsSvg": "Kopier til utklippstavla som SVG", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "Flytt framover", "sendToBack": "Send heilt bak", "bringToFront": "Flytt heilt fram", @@ -36,8 +38,12 @@ "arrowhead_none": "Ingen", "arrowhead_arrow": "Pil", "arrowhead_bar": "Stolpe", - "arrowhead_dot": "Prikk", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Trekant", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Skriftstorleik", "fontFamily": "Skrifttype", "addWatermark": "Legg til «Laga med Excalidraw»", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Vel", @@ -236,10 +249,13 @@ "link": "Legg til/ oppdater lenke til valt figur", "eraser": "Viskelêr", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Handlingar: lerret", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/oc-FR.json b/packages/excalidraw/locales/oc-FR.json index 37b9e91d4..3d9594c04 100644 --- a/packages/excalidraw/locales/oc-FR.json +++ b/packages/excalidraw/locales/oc-FR.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiar al quichapapièrs coma PNG", "copyAsSvg": "Copiar al quichapapièrs coma SVG", "copyText": "Copiar al quichapapièrs coma tèxt", + "copySource": "", + "convertToCode": "", "bringForward": "En avant", "sendToBack": "En arrièr", "bringToFront": "A l’endavant", @@ -36,8 +38,12 @@ "arrowhead_none": "Cap", "arrowhead_arrow": "Sageta", "arrowhead_bar": "Barra", - "arrowhead_dot": "Ponch", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Triangle", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Talha polissa", "fontFamily": "Familha de polissa", "addWatermark": "Apondre « Fabricat amb Excalidraw »", @@ -50,7 +56,7 @@ "veryLarge": "Gradassa", "solid": "Solide", "hachure": "Raia", - "zigzag": "", + "zigzag": "Zigzag", "crossHatch": "Raia crosada", "thin": "Fin", "bold": "Espés", @@ -106,15 +112,15 @@ "increaseFontSize": "Aumentar talha polissa", "unbindText": "Dessociar lo tèxte", "bindText": "Ligar lo tèxt al contenidor", - "createContainerFromText": "", + "createContainerFromText": "Envelopar lo tèxte dins un contenedor", "link": { "edit": "Modificar lo ligam", - "editEmbed": "", + "editEmbed": "Modificar lo ligam e l’integracion", "create": "Crear un ligam", - "createEmbed": "", + "createEmbed": "Crear un ligam e son integracion", "label": "Ligam", - "labelEmbed": "", - "empty": "" + "labelEmbed": "Ligam e integracion", + "empty": "Cap de ligam pas definit" }, "lineEditor": { "edit": "Modificar la linha", @@ -128,9 +134,11 @@ }, "statusPublished": "Publicat", "sidebarLock": "Gardar la barra laterala dobèrta", - "selectAllElementsInFrame": "", - "removeAllElementsFromFrame": "", - "eyeDropper": "" + "selectAllElementsInFrame": "Seleccionar totes los elements del quadre", + "removeAllElementsFromFrame": "Tirar totes los elements d’al quadre", + "eyeDropper": "Prendre la color a partir d’un canabàs", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Cap d’element pas encara apondut...", @@ -164,16 +172,16 @@ "darkMode": "Mòde escur", "lightMode": "Mòde clar", "zenMode": "Mòde escur", - "objectsSnapMode": "", + "objectsSnapMode": "Ancorar als objèctes", "exitZenMode": "Sortir del mòde zen", "cancel": "Anullar", "clear": "Escafar", "remove": "Tirar", - "embed": "", + "embed": "Alternar l’integracion", "publishLibrary": "Publicar", "submit": "Enviar", "confirm": "Confirmar", - "embeddableInteractionButton": "" + "embeddableInteractionButton": "Clicar per interagir" }, "alerts": { "clearReset": "Aquò suprimirà lo canabàs complèt. O volètz vertadièrament ?", @@ -203,12 +211,13 @@ "imageInsertError": "Insercion d’imatge impossibla. Tornatz ensajar mai tard...", "fileTooBig": "Fichièr tròp pesuc. La talha maximala autorizada es {{maxSize}}.", "svgImageInsertError": "Insercion d’imatge SVG impossibla. Las balisas SVG semblan invalidas.", - "failedToFetchImage": "", + "failedToFetchImage": "Fracàs de la recuperacion de l’imatge.", "invalidSVGString": "SVG invalid.", "cannotResolveCollabServer": "Connexion impossibla al servidor collab. Mercés de recargar la pagina e tornar ensajar.", "importLibraryError": "Impossible de cargar la bibliotèca", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "Empegatge impossible (lectura impossibla a partir del quichapapièrs).", + "asyncPasteFailedOnParse": "Empegatge impossible.", + "copyToSystemClipboardFailed": "Còpia impossibla al quichapapièrs." }, "toolBar": { "selection": "Seleccion", @@ -235,11 +248,14 @@ "penMode": "Mòde estilo - empachar lo contact", "link": "Apondre/Actualizar lo ligam per una fòrma seleccionada", "eraser": "Goma", - "frame": "", - "embeddable": "", - "laser": "", + "frame": "Esplech quadre", + "magicframe": "", + "embeddable": "Integracion Web", + "laser": "Puntador laser", "hand": "Man (aisina de desplaçament de la vista)", - "extraTools": "" + "extraTools": "Mai d’aisinas", + "mermaidToExcalidraw": "De Mermaid cap a Excalidraw", + "magicSettings": "" }, "headings": { "canvasActions": "Accions del canabàs", @@ -319,7 +335,7 @@ "drag": "lisar", "editor": "Editor", "editLineArrowPoints": "", - "editText": "", + "editText": "Modificar lo tèxte / apondre etiqueta", "github": "Problèma trobat ? Senhalatz-lo", "howto": "Seguissètz nòstras guidas", "or": "o", @@ -376,27 +392,27 @@ "removeItemsFromLib": "Tirar los elements seleccionats de la bibliotèca" }, "imageExportDialog": { - "header": "", + "header": "Exportar imatges", "label": { - "withBackground": "", - "onlySelected": "", - "darkMode": "", - "embedScene": "", - "scale": "", - "padding": "" + "withBackground": "Rèireplan", + "onlySelected": "Seleccion sonque", + "darkMode": "Mòde escur", + "embedScene": "Embarcar la scèna", + "scale": "Escala", + "padding": "Espaçament" }, "tooltip": { "embedScene": "" }, "title": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "Exportar en PNG", + "exportToSvg": "Exportar en SVG", + "copyPngToClipboard": "Copiar PNG al quichapapièrs" }, "button": { - "exportToPng": "", - "exportToSvg": "", - "copyPngToClipboard": "" + "exportToPng": "PNG", + "exportToSvg": "SVG", + "copyPngToClipboard": "Copiar al quichapapièrs" } }, "encrypted": { @@ -433,20 +449,20 @@ }, "colors": { "transparent": "Transparéncia", - "black": "", - "white": "", - "red": "", - "pink": "", - "grape": "", - "violet": "", - "gray": "", - "blue": "", - "cyan": "", - "teal": "", - "green": "", - "yellow": "", - "orange": "", - "bronze": "" + "black": "Negre", + "white": "Blanc", + "red": "Roge", + "pink": "Ròse", + "grape": "Bordèu", + "violet": "Violet", + "gray": "Gris", + "blue": "Blau", + "cyan": "Cian", + "teal": "Sarcèla", + "green": "Verd", + "yellow": "Jaune", + "orange": "Irange", + "bronze": "Bronze" }, "welcomeScreen": { "app": { @@ -462,41 +478,48 @@ } }, "colorPicker": { - "mostUsedCustomColors": "", - "colors": "", - "shades": "", - "hexCode": "", - "noShades": "" + "mostUsedCustomColors": "Colors personalizadas mai utilizadas", + "colors": "Colors", + "shades": "Nuanças", + "hexCode": "Còdi exadecimal", + "noShades": "Cap de nuança pas disponibla per aquesta color" }, "overwriteConfirm": { "action": { "exportToImage": { - "title": "", - "button": "", + "title": "Exportar coma imatge", + "button": "Exportar coma imatge", "description": "" }, "saveToDisk": { - "title": "", - "button": "", - "description": "" + "title": "Salvar al disc", + "button": "Salvar al disc", + "description": "Exportar las donadas de la scèna cap a un fichièr que podètz importar mai tard." }, "excalidrawPlus": { - "title": "", - "button": "", - "description": "" + "title": "Excalidraw+", + "button": "Exportar dins Excalidraw+", + "description": "Enregistrar la scèna dins vòstre espaci de trabalh Excalidraw+." } }, "modal": { "loadFromFile": { - "title": "", - "button": "", + "title": "Cargar d’un fichièr", + "button": "Cargar d’un fichièr", "description": "" }, "shareableLink": { - "title": "", - "button": "", + "title": "Cargar d’un ligam", + "button": "Remplaçar mon contengut", "description": "" } } + }, + "mermaid": { + "title": "De Mermaid cap a Excalidraw", + "button": "Inserir", + "description": "", + "syntax": "Sintaxi Mermaid", + "preview": "Apercebut" } } diff --git a/packages/excalidraw/locales/pa-IN.json b/packages/excalidraw/locales/pa-IN.json index 5f51d1a2c..7bfe56fb1 100644 --- a/packages/excalidraw/locales/pa-IN.json +++ b/packages/excalidraw/locales/pa-IN.json @@ -11,6 +11,8 @@ "copyAsPng": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ PNG ਵਜੋਂ ਕਾਪੀ ਕਰੋ", "copyAsSvg": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ SVG ਵਜੋਂ ਕਾਪੀ ਕਰੋ", "copyText": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਪਾਠ ਵਜੋਂ ਕਾਪੀ ਕਰੋ", + "copySource": "", + "convertToCode": "", "bringForward": "ਅੱਗੇ ਲਿਆਓ", "sendToBack": "ਸਭ ਤੋਂ ਪਿੱਛੇ ਭੇਜੋ", "bringToFront": "ਸਭ ਤੋਂ ਅੱਗੇ ਲਿਆਓ", @@ -36,8 +38,12 @@ "arrowhead_none": "ਕੋਈ ਨਹੀਂ", "arrowhead_arrow": "ਤੀਰ", "arrowhead_bar": "ਡੰਡੀ", - "arrowhead_dot": "ਬਿੰਦੀ", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "ਤਿਕੋਣ", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "ਫੌਂਟ ਅਕਾਰ", "fontFamily": "ਫੌਂਟ ਪਰਿਵਾਰ", "addWatermark": "\"Excalidraw ਨਾਲ ਬਣਾਇਆ\" ਜੋੜੋ", @@ -130,7 +136,9 @@ "sidebarLock": "ਸਾਈਡਬਾਰ ਨੂੰ ਖੁੱਲ੍ਹਾ ਰੱਖੋ", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "ਹਾਲੇ ਤੱਕ ਕੋਈ ਚੀਜ ਜੋੜੀ ਨਹੀਂ ਗਈ...", @@ -209,6 +217,7 @@ "importLibraryError": "ਲਾਇਬ੍ਰੇਰੀ ਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "ਚੋਣਕਾਰ", @@ -236,10 +249,13 @@ "link": "", "eraser": "ਰਬੜ", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "ਕੈਨਵਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/percentages.json b/packages/excalidraw/locales/percentages.json index b73e02126..eb3cd087b 100644 --- a/packages/excalidraw/locales/percentages.json +++ b/packages/excalidraw/locales/percentages.json @@ -1,56 +1,56 @@ { - "ar-SA": 99, - "az-AZ": 18, - "bg-BG": 75, - "bn-BD": 55, - "ca-ES": 81, - "cs-CZ": 91, - "da-DK": 31, + "ar-SA": 94, + "az-AZ": 17, + "bg-BG": 71, + "bn-BD": 52, + "ca-ES": 83, + "cs-CZ": 86, + "da-DK": 61, "de-DE": 100, - "el-GR": 85, + "el-GR": 80, "en": 100, "es-ES": 96, - "eu-ES": 99, - "fa-IR": 87, - "fi-FI": 81, - "fr-FR": 98, - "gl-ES": 90, - "he-IL": 81, - "hi-IN": 75, - "hu-HU": 70, - "id-ID": 96, - "it-IT": 99, - "ja-JP": 95, - "kaa": 34, - "kab-KAB": 80, - "kk-KZ": 20, - "km-KH": 87, + "eu-ES": 97, + "fa-IR": 84, + "fi-FI": 76, + "fr-FR": 99, + "gl-ES": 86, + "he-IL": 77, + "hi-IN": 76, + "hu-HU": 76, + "id-ID": 91, + "it-IT": 98, + "ja-JP": 90, + "kaa": 36, + "kab-KAB": 76, + "kk-KZ": 18, + "km-KH": 83, "ko-KR": 100, - "ku-TR": 92, - "lt-LT": 51, - "lv-LV": 82, - "mr-IN": 92, - "my-MM": 37, - "nb-NO": 99, - "nl-NL": 79, - "nn-NO": 71, - "oc-FR": 79, - "pa-IN": 82, - "pl-PL": 100, - "pt-BR": 96, - "pt-PT": 88, - "ro-RO": 98, - "ru-RU": 94, - "si-LK": 8, - "sk-SK": 99, + "ku-TR": 87, + "lt-LT": 48, + "lv-LV": 77, + "mr-IN": 98, + "my-MM": 35, + "nb-NO": 93, + "nl-NL": 75, + "nn-NO": 67, + "oc-FR": 92, + "pa-IN": 78, + "pl-PL": 99, + "pt-BR": 91, + "pt-PT": 83, + "ro-RO": 99, + "ru-RU": 92, + "si-LK": 7, + "sk-SK": 100, "sl-SI": 100, "sv-SE": 100, - "ta-IN": 85, - "th-TH": 46, - "tr-TR": 91, - "uk-UA": 99, - "vi-VN": 51, - "zh-CN": 99, - "zh-HK": 24, + "ta-IN": 81, + "th-TH": 44, + "tr-TR": 87, + "uk-UA": 93, + "vi-VN": 49, + "zh-CN": 100, + "zh-HK": 22, "zh-TW": 100 } diff --git a/packages/excalidraw/locales/pl-PL.json b/packages/excalidraw/locales/pl-PL.json index ffe5247ea..bc869c19c 100644 --- a/packages/excalidraw/locales/pl-PL.json +++ b/packages/excalidraw/locales/pl-PL.json @@ -11,6 +11,8 @@ "copyAsPng": "Skopiuj do schowka jako plik PNG", "copyAsSvg": "Skopiuj do schowka jako plik SVG", "copyText": "Skopiuj do schowka jako tekst", + "copySource": "Skopiuj źródło do schowka", + "convertToCode": "Skonwertuj do kodu", "bringForward": "Przenieś wyżej", "sendToBack": "Przenieś na spód", "bringToFront": "Przenieś na wierzch", @@ -36,8 +38,12 @@ "arrowhead_none": "Brak", "arrowhead_arrow": "Strzałka", "arrowhead_bar": "Kreska", - "arrowhead_dot": "Kropka", + "arrowhead_circle": "Okrąg", + "arrowhead_circle_outline": "Okrąg (obrys)", "arrowhead_triangle": "Trójkąt", + "arrowhead_triangle_outline": "Trójkąt (obrys)", + "arrowhead_diamond": "Romb", + "arrowhead_diamond_outline": "Romb (obrys)", "fontSize": "Rozmiar tekstu", "fontFamily": "Krój pisma", "addWatermark": "Dodaj \"Zrobione w Excalidraw\"", @@ -114,7 +120,7 @@ "createEmbed": "Stwórz i osadź link", "label": "Łącze", "labelEmbed": "Podlinkuj i osadź", - "empty": "Brakujący link" + "empty": "Nie ustawiono linku" }, "lineEditor": { "edit": "Edytuj linię", @@ -130,7 +136,9 @@ "sidebarLock": "Panel boczny zawsze otwarty", "selectAllElementsInFrame": "Zaznacz wszystkie elementy w ramce", "removeAllElementsFromFrame": "Usuń wszystkie elementy z ramki", - "eyeDropper": "Wybierz kolor z płótna" + "eyeDropper": "Wybierz kolor z płótna", + "textToDiagram": "Tekst do diagramu", + "prompt": "" }, "library": { "noItems": "Nie dodano jeszcze żadnych elementów...", @@ -209,6 +217,7 @@ "importLibraryError": "Wystąpił błąd w trakcie ładowania biblioteki", "collabSaveFailed": "Nie udało się zapisać w bazie danych. Jeśli problemy nie ustąpią, zapisz plik lokalnie, aby nie utracić swojej pracy.", "collabSaveFailed_sizeExceeded": "Nie udało się zapisać w bazie danych — dokument jest za duży. Zapisz plik lokalnie, aby nie utracić swojej pracy.", + "imageToolNotSupported": "Dodawanie obrazów jest wyłączone.", "brave_measure_text_error": { "line1": "Wygląda na to, że używasz przeglądarki Brave z włączonym ustawieniem Agressively Block Fingerprinting.", "line2": "Może to doprowadzić do złamania elementów tekstu na rysunkach.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Elementy osadzone nie mogą zostać dodane do biblioteki.", + "iframe": "Elementy IFrame nie mogą zostać dodane do biblioteki.", "image": "Dodawania obrazów do biblioteki nadejdzie wkrótce!" - } + }, + "asyncPasteFailedOnRead": "Nie udało się wkleić (nie udało się odczytać ze schowka systemowego).", + "asyncPasteFailedOnParse": "Nie udało się wkleić.", + "copyToSystemClipboardFailed": "Nie udało się skopiować do schowka." }, "toolBar": { "selection": "Zaznaczenie", @@ -236,10 +249,13 @@ "link": "Dodaj/aktualizuj link dla wybranego kształtu", "eraser": "Gumka", "frame": "Ramka", + "magicframe": "Wireframe do kodu", "embeddable": "Osadzenie z internetu", "laser": "Wskaźnik laserowy", "hand": "Ręka (narzędzie do przesuwania)", - "extraTools": "Więcej narzędzi" + "extraTools": "Więcej narzędzi", + "mermaidToExcalidraw": "Konwertuj diagram Mermaid do Excalidraw", + "magicSettings": "Ustawienia AI" }, "headings": { "canvasActions": "Narzędzia", @@ -498,5 +514,12 @@ "description": "Wczytanie zewnętrznego pliku nadpisze istniejącą zawartość.

Możesz najpierw utworzyć kopię zapasową swojego rysunku, używając jednej z poniższych opcji." } } + }, + "mermaid": { + "title": "Konwertuj diagram Mermaid do Excalidraw", + "button": "Wstaw", + "description": "Obecnie wspierane są jedynie proste grafy, sekwencje i diagramy klas. Pozostałe typy będą wyświetlane jako obrazy w Excalidraw.", + "syntax": "Składnia diagramów Mermaid", + "preview": "Podgląd" } } diff --git a/packages/excalidraw/locales/pt-BR.json b/packages/excalidraw/locales/pt-BR.json index 68c0059ed..b9701b0e5 100644 --- a/packages/excalidraw/locales/pt-BR.json +++ b/packages/excalidraw/locales/pt-BR.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiar para a área de transferência como PNG", "copyAsSvg": "Copiar para a área de transferência como SVG", "copyText": "Copiar para área de transferência como texto", + "copySource": "", + "convertToCode": "", "bringForward": "Trazer para a frente", "sendToBack": "Enviar para o fundo", "bringToFront": "Trazer para o primeiro plano", @@ -36,8 +38,12 @@ "arrowhead_none": "Nenhuma", "arrowhead_arrow": "Flecha", "arrowhead_bar": "Barra", - "arrowhead_dot": "Ponto", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Triângulo", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Tamanho da fonte", "fontFamily": "Família da fonte", "addWatermark": "Adicionar \"Feito com Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Manter barra lateral aberta", "selectAllElementsInFrame": "Selecionar todos os elementos no quadro", "removeAllElementsFromFrame": "Remover todos os elementos do quadro", - "eyeDropper": "Escolher cor da tela" + "eyeDropper": "Escolher cor da tela", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Nenhum item adicionado ainda...", @@ -209,6 +217,7 @@ "importLibraryError": "Não foi possível carregar a biblioteca", "collabSaveFailed": "Não foi possível salvar no banco de dados do servidor. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.", "collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Parece que você está usando o navegador Brave com a configuração Bloquear Impressões Digitais no modo agressivo.", "line2": "Isso pode acabar quebrando Elementos de Texto em seus desenhos.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Seleção", @@ -236,10 +249,13 @@ "link": "Adicionar/Atualizar link para uma forma selecionada", "eraser": "Borracha", "frame": "Ferramenta de quadro", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Mão (ferramenta de rolagem)", - "extraTools": "Mais ferramentas" + "extraTools": "Mais ferramentas", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Ações da tela", @@ -498,5 +514,12 @@ "description": "Carregar um desenho externo irá substituir seu conteúdo existente.

Você pode salvar seu desenho antes utilizando uma das opções abaixo." } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/pt-PT.json b/packages/excalidraw/locales/pt-PT.json index 401a45ac5..283853b68 100644 --- a/packages/excalidraw/locales/pt-PT.json +++ b/packages/excalidraw/locales/pt-PT.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiar para a área de transferência como PNG", "copyAsSvg": "Copiar para a área de transferência como SVG", "copyText": "Copiar para Área de Transferência como texto", + "copySource": "", + "convertToCode": "", "bringForward": "Trazer para o primeiro plano", "sendToBack": "Enviar para o plano de fundo", "bringToFront": "Trazer para o primeiro plano", @@ -36,8 +38,12 @@ "arrowhead_none": "Nenhuma", "arrowhead_arrow": "Seta", "arrowhead_bar": "Barra", - "arrowhead_dot": "Ponto", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Triângulo", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Tamanho da fonte", "fontFamily": "Família da fontes", "addWatermark": "Adicionar \"Feito com Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Manter a barra lateral aberta", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Ainda não foram adicionados nenhuns itens...", @@ -209,6 +217,7 @@ "importLibraryError": "Não foi possível carregar a biblioteca", "collabSaveFailed": "Não foi possível guardar na base de dados de backend. Se os problemas persistirem, guarde o ficheiro localmente para garantir que não perde o seu trabalho.", "collabSaveFailed_sizeExceeded": "Não foi possível guardar na base de dados de backend, o ecrã parece estar muito grande. Deve guardar o ficheiro localmente para garantir que não perde o seu trabalho.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Seleção", @@ -236,10 +249,13 @@ "link": "Acrescentar/ Adicionar ligação para uma forma seleccionada", "eraser": "Borracha", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Mão (ferramenta de movimento da tela)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Ações da área de desenho", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/ro-RO.json b/packages/excalidraw/locales/ro-RO.json index 125c28663..32bcf9818 100644 --- a/packages/excalidraw/locales/ro-RO.json +++ b/packages/excalidraw/locales/ro-RO.json @@ -11,6 +11,8 @@ "copyAsPng": "Copiere în memoria temporară ca PNG", "copyAsSvg": "Copiere în memoria temporară ca SVG", "copyText": "Copiere în memoria temporară ca text", + "copySource": "Copiere sursă în memoria temporară", + "convertToCode": "Convertire în cod", "bringForward": "Aducere în plan apropiat", "sendToBack": "Trimitere în ultimul plan", "bringToFront": "Aducere în prim plan", @@ -36,8 +38,12 @@ "arrowhead_none": "Niciunul", "arrowhead_arrow": "Săgeată", "arrowhead_bar": "Bară", - "arrowhead_dot": "Bulină", + "arrowhead_circle": "Cerc", + "arrowhead_circle_outline": "Cerc (contur)", "arrowhead_triangle": "Triunghi", + "arrowhead_triangle_outline": "Triunghi (contur)", + "arrowhead_diamond": "Romb", + "arrowhead_diamond_outline": "Romb (contur)", "fontSize": "Dimensiune font", "fontFamily": "Familia de fonturi", "addWatermark": "Adaugă „Realizat cu Excalidraw”", @@ -130,7 +136,9 @@ "sidebarLock": "Păstrează deschisă bara laterală", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "Alegere culoare din pânză" + "eyeDropper": "Alegere culoare din pânză", + "textToDiagram": "Text la diagramă", + "prompt": "Solicitare" }, "library": { "noItems": "Niciun element adăugat încă...", @@ -203,12 +211,13 @@ "imageInsertError": "Imaginea nu a putut fi introdusă. Reîncearcă mai târziu...", "fileTooBig": "Fișierul este prea mare. Dimensiunea maximă permisă este de {{maxSize}}.", "svgImageInsertError": "Imaginea SVG nu a putut fi introdus. Marcajul SVG pare invalid.", - "failedToFetchImage": "", + "failedToFetchImage": "Preluarea imaginii a eșuat.", "invalidSVGString": "SVG invalid.", "cannotResolveCollabServer": "Nu a putut fi realizată conexiunea la serverul de colaborare. Reîncarcă pagina și încearcă din nou.", "importLibraryError": "Biblioteca nu a putut fi încărcată", "collabSaveFailed": "Nu s-a putut salva în baza de date la nivel de server. Dacă problemele persistă, ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca.", "collabSaveFailed_sizeExceeded": "Nu s-a putut salva în baza de date la nivel de server, întrucât se pare că pânza este prea mare. Ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca.", + "imageToolNotSupported": "Imaginile sunt dezactivate.", "brave_measure_text_error": { "line1": "Se pare că folosești navigatorul Brave cu opțiunea strictă pentru blocarea amprentării.", "line2": "Acest lucru poate duce la întreruperea elementelor text din desene.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Elementele încorporabile nu pot fi adăugate la bibliotecă.", + "iframe": "Elementele iFrame nu pot fi adăugate la bibliotecă.", "image": "În curând vor putea fi adăugate imagini în bibliotecă!" - } + }, + "asyncPasteFailedOnRead": "Lipirea nu a putut fi efectuată (nu s-a putut citit din memoria temporară a sistemului).", + "asyncPasteFailedOnParse": "Lipirea nu a putut fi efectuată.", + "copyToSystemClipboardFailed": "Nu s-a putut copia în memoria temporară." }, "toolBar": { "selection": "Selecție", @@ -236,10 +249,13 @@ "link": "Adăugare/actualizare URL pentru forma selectată", "eraser": "Radieră", "frame": "", + "magicframe": "Structură-de-fire la cod", "embeddable": "Încorporare web", "laser": "Indicator laser", "hand": "Mână (instrument de panoramare)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "Mermaid la Excalidraw", + "magicSettings": "Setări IA" }, "headings": { "canvasActions": "Acțiuni pentru pânză", @@ -498,5 +514,12 @@ "description": "Încărcarea unui desen extern va înlocui conținutul existent.

Poți face mai întâi o copie de rezervă a desenului folosind una dintre opțiunile de mai jos." } } + }, + "mermaid": { + "title": "Mermaid la Excalidraw", + "button": "Introducere", + "description": "În prezent, numai Organigramele, Diagramele de secvență și Diagramele de clasă sunt acceptate. Celelalte tipuri vor fi redate ca imagine în Excalidraw.", + "syntax": "Sintaxă Mermaid", + "preview": "Previzualizare" } } diff --git a/packages/excalidraw/locales/ru-RU.json b/packages/excalidraw/locales/ru-RU.json index 7c489022c..41df7d759 100644 --- a/packages/excalidraw/locales/ru-RU.json +++ b/packages/excalidraw/locales/ru-RU.json @@ -11,6 +11,8 @@ "copyAsPng": "Скопировать в буфер обмена как PNG", "copyAsSvg": "Скопировать в буфер обмена как SVG", "copyText": "Скопировать в буфер обмена как текст", + "copySource": "Копировать источник в буфер обмена", + "convertToCode": "Преобразовать в код", "bringForward": "Переместить вперед", "sendToBack": "На задний план", "bringToFront": "На передний план", @@ -36,8 +38,12 @@ "arrowhead_none": "Нет", "arrowhead_arrow": "Cтрелка", "arrowhead_bar": "Черта", - "arrowhead_dot": "Точка", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Треугольник", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Размер шрифта", "fontFamily": "Семейство шрифтов", "addWatermark": "Добавить «Создано в Excalidraw»", @@ -130,7 +136,9 @@ "sidebarLock": "Держать боковую панель открытой", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "Взять образец цвета с холста" + "eyeDropper": "Взять образец цвета с холста", + "textToDiagram": "Текст в диаграмму", + "prompt": "" }, "library": { "noItems": "Пока ничего не добавлено...", @@ -209,6 +217,7 @@ "importLibraryError": "Не удалось загрузить библиотеку", "collabSaveFailed": "Не удалось сохранить в базу данных. Если проблема повторится, нужно будет сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу.", "collabSaveFailed_sizeExceeded": "Не удалось сохранить в базу данных. Похоже, что холст слишком большой. Нужно сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу.", + "imageToolNotSupported": "Изображения отключены.", "brave_measure_text_error": { "line1": "Похоже, вы используете браузер Brave с включенной опцией Агрессивно блокировать отслеживание.", "line2": "Это может привести к поломке Текстовых объектов на рисунке.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "Элементы IFrame не могут быть добавлены в библиотеку.", "image": "" - } + }, + "asyncPasteFailedOnRead": "Не удалось вставить (невозможно прочитать из системного буфера обмена).", + "asyncPasteFailedOnParse": "Не удалось вставить.", + "copyToSystemClipboardFailed": "Не удалось скопировать в буфер обмена." }, "toolBar": { "selection": "Выделение области", @@ -236,10 +249,13 @@ "link": "Добавить/обновить ссылку для выбранной фигуры", "eraser": "Ластик", "frame": "", + "magicframe": "", "embeddable": "", "laser": "Лазерная указка", "hand": "Рука (перемещение холста)", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "Из Mermaid в Excalidraw", + "magicSettings": "Параметры AI" }, "headings": { "canvasActions": "Операции холста", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "Из Mermaid в Excalidraw", + "button": "Вставить", + "description": "", + "syntax": "Синтаксис Mermaid", + "preview": "Предпросмотр" } } diff --git a/packages/excalidraw/locales/si-LK.json b/packages/excalidraw/locales/si-LK.json index 8cc93b03a..95f1914c9 100644 --- a/packages/excalidraw/locales/si-LK.json +++ b/packages/excalidraw/locales/si-LK.json @@ -11,6 +11,8 @@ "copyAsPng": "PNG ලෙස පිටපත් කරන්න", "copyAsSvg": "SVG ලෙස පිටපත් කරන්න", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "ඉදිරියට ගෙන්න", "sendToBack": "පසුපසටම ගෙනියන්න", "bringToFront": "ඉදිරියටම ගෙන්න", @@ -36,8 +38,12 @@ "arrowhead_none": "", "arrowhead_arrow": "", "arrowhead_bar": "", - "arrowhead_dot": "", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "", "fontFamily": "", "addWatermark": "", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/sk-SK.json b/packages/excalidraw/locales/sk-SK.json index b0d9e4b84..9f4135e20 100644 --- a/packages/excalidraw/locales/sk-SK.json +++ b/packages/excalidraw/locales/sk-SK.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopírovať do schránky ako PNG", "copyAsSvg": "Kopírovať do schránky ako SVG", "copyText": "Kopírovať do schránky ako text", + "copySource": "Kopírovať kód do schránky", + "convertToCode": "Konvertovať na kód", "bringForward": "Presunúť o úroveň dopredu", "sendToBack": "Presunúť dozadu", "bringToFront": "Presunúť dopredu", @@ -36,8 +38,12 @@ "arrowhead_none": "Žiadne", "arrowhead_arrow": "Šípka", "arrowhead_bar": "Čiara", - "arrowhead_dot": "Bod", + "arrowhead_circle": "Kruh", + "arrowhead_circle_outline": "Kruh (obrys)", "arrowhead_triangle": "Trojuholník", + "arrowhead_triangle_outline": "Trojuholník (obrys)", + "arrowhead_diamond": "Diamant", + "arrowhead_diamond_outline": "Diamant (obrys)", "fontSize": "Veľkosť písma", "fontFamily": "Písmo", "addWatermark": "Pridať \"Vytvorené s Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Nechať bočný panel otvorený", "selectAllElementsInFrame": "Vybrať všetky prvky v ráme", "removeAllElementsFromFrame": "Odstrániť všetky prvky z rámu", - "eyeDropper": "Vybrať farbu z plátna" + "eyeDropper": "Vybrať farbu z plátna", + "textToDiagram": "Text na diagram", + "prompt": "Inštrukcia" }, "library": { "noItems": "Zatiaľ neboli pridané žiadne položky...", @@ -203,12 +211,13 @@ "imageInsertError": "Nepodarilo sa vložiť obrázok. Skúste to znova neskôr...", "fileTooBig": "Súbor je príliš veľký. Maximálna povolená veľkosť je {{maxSize}}.", "svgImageInsertError": "Nepodarilo sa vložiť SVG obrázok. SVG formát je pravdepodobne nevalidný.", - "failedToFetchImage": "", + "failedToFetchImage": "Načítanie obrázka zlyhalo.", "invalidSVGString": "Nevalidné SVG.", "cannotResolveCollabServer": "Nepodarilo sa pripojiť ku kolaboračnému serveru. Prosím obnovte stránku a skúste to znovu.", "importLibraryError": "Nepodarilo sa načítať knižnicu", "collabSaveFailed": "Uloženie do databázy sa nepodarilo. Ak tento problém pretrváva uložte si váš súbor lokálne aby ste nestratili vašu prácu.", "collabSaveFailed_sizeExceeded": "Uloženie do databázy sa nepodarilo, pretože veľkosť plátna je príliš veľká. Uložte si váš súbor lokálne aby ste nestratili vašu prácu.", + "imageToolNotSupported": "Obrázky sú vypnuté.", "brave_measure_text_error": { "line1": "Vyzerá to, že používate prehliadač Brave so zapnutým nastavením pre agresívne blokovanie.", "line2": "To môže spôsobiť nesprávne zobrazenie textových prvkov vo vašej kresbe.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Zapustené prvky nie je možné pridať do knižnice.", + "iframe": "Vložené rámce IFrame nie je možné pridať do knižnice.", "image": "Podpora pre pridávanie obrázkov do knižnice bude dostupná už čoskoro!" - } + }, + "asyncPasteFailedOnRead": "Vloženie sa nepodarilo (nebolo možné prečítať obsah schránky).", + "asyncPasteFailedOnParse": "Vloženie sa nepodarilo.", + "copyToSystemClipboardFailed": "Kopírovanie do schránky sa nepodarilo." }, "toolBar": { "selection": "Výber", @@ -236,10 +249,13 @@ "link": "Pridať/ Upraviť odkaz pre vybraný tvar", "eraser": "Guma", "frame": "Nástroj rám", + "magicframe": "Drôtený model na kód", "embeddable": "Web Embed", - "laser": "", + "laser": "Laserový ukazovateľ", "hand": "Ruka (nástroj pre pohyb plátna)", - "extraTools": "Ďalšie nástroje" + "extraTools": "Ďalšie nástroje", + "mermaidToExcalidraw": "Mermaid do Excalidraw", + "magicSettings": "AI nastavenia" }, "headings": { "canvasActions": "Akcie plátna", @@ -498,5 +514,12 @@ "description": "Načítanie externej kresby nahradí váš existujúci obsah.

Vašu kresbu môžete zálohovať jednou z nižšie uvedených možností." } } + }, + "mermaid": { + "title": "Mermaid do Excalidraw", + "button": "Vložiť", + "description": "Aktuálne sú podporované iba vývojové diagramy, sekvenčné diagramy a diagramy tried. Ostatné typy budú v Excalidraw vykreslené ako obrázky.", + "syntax": "Mermaid syntax", + "preview": "Ukážka" } } diff --git a/packages/excalidraw/locales/sl-SI.json b/packages/excalidraw/locales/sl-SI.json index 5a6ea8c6f..2cd7e289f 100644 --- a/packages/excalidraw/locales/sl-SI.json +++ b/packages/excalidraw/locales/sl-SI.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopiraj v odložišče kot PNG", "copyAsSvg": "Kopiraj v odložišče kot SVG", "copyText": "Kopiraj v odložišče kot besedilo", + "copySource": "Kopiraj vir v odložišče", + "convertToCode": "Pretvori v kodo", "bringForward": "Postavi naprej", "sendToBack": "Pomakni v ozadje", "bringToFront": "Pomakni v ospredje", @@ -36,8 +38,12 @@ "arrowhead_none": "Brez", "arrowhead_arrow": "Puščica", "arrowhead_bar": "Palica", - "arrowhead_dot": "Pika", + "arrowhead_circle": "Krog", + "arrowhead_circle_outline": "Krog (oris)", "arrowhead_triangle": "Trikotnik", + "arrowhead_triangle_outline": "Trikotnik (oris)", + "arrowhead_diamond": "Diamant", + "arrowhead_diamond_outline": "Diamant (oris)", "fontSize": "Velikost pisave", "fontFamily": "Družina pisave", "addWatermark": "Dodaj \"Izdelano z Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Obdrži stransko vrstico odprto", "selectAllElementsInFrame": "Izberi vse elemente v okvirju", "removeAllElementsFromFrame": "Izbriši vse elemente v okvirju", - "eyeDropper": "Izberi barvo s platna" + "eyeDropper": "Izberi barvo s platna", + "textToDiagram": "Besedilo v diagram", + "prompt": "Poziv" }, "library": { "noItems": "Dodan še ni noben element...", @@ -209,6 +217,7 @@ "importLibraryError": "Nalaganje knjižnice ni uspelo", "collabSaveFailed": "Ni bilo mogoče shraniti v zaledno bazo podatkov. Če se težave nadaljujejo, shranite datoteko lokalno, da ne boste izgubili svojega dela.", "collabSaveFailed_sizeExceeded": "Ni bilo mogoče shraniti v zaledno bazo podatkov, zdi se, da je platno preveliko. Datoteko shranite lokalno, da ne izgubite svojega dela.", + "imageToolNotSupported": "Slike so onemogočene.", "brave_measure_text_error": { "line1": "Videti je, da uporabljate brskalnik Brave z omogočeno nastavitvijo Agresivno blokiranje prstnih odtisov.", "line2": "To bi lahko povzročilo motnje v obnašanju besedilnih elementov v vaših risbah.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Vdelani elementi ne morejo biti dodani v knjižnico.", + "iframe": "Elementov iFrame ni mogoče dodati v knjižnico.", "image": "Podpora za dodajanje slik v knjižnico prihaja kmalu!" - } + }, + "asyncPasteFailedOnRead": "Ni bilo mogoče prilepiti (ni bilo mogoče brati iz sistemskega odložišča).", + "asyncPasteFailedOnParse": "Ni bilo mogoče prilepiti.", + "copyToSystemClipboardFailed": "Ni bilo mogoče kopirati v odložišče." }, "toolBar": { "selection": "Izbor", @@ -236,10 +249,13 @@ "link": "Dodaj/posodobi povezavo za izbrano obliko", "eraser": "Radirka", "frame": "Okvir", + "magicframe": "Žični okvir v kodo", "embeddable": "Spletna vdelava", "laser": "Laserski kazalec", "hand": "Roka (orodje za premikanje)", - "extraTools": "Več orodij" + "extraTools": "Več orodij", + "mermaidToExcalidraw": "Mermaid v Excalidraw", + "magicSettings": "Nastavitve AI" }, "headings": { "canvasActions": "Dejanja za platno", @@ -498,5 +514,12 @@ "description": "Nalaganje zunanje risbe bo prepisalo vašo obstoječo vsebino.

Svojo risbo lahko najprej varnostno kopirate z eno od spodnjih možnosti." } } + }, + "mermaid": { + "title": "Mermaid v Excalidraw", + "button": "Vstavi", + "description": "Trenutno so podprti samo diagrami poteka, diagrami zaporedij in Razredni diagrami. Druge vrste bodo upodobljene kot slike v Excalidraw.", + "syntax": "Sintaksa Mermaid", + "preview": "Predogled" } } diff --git a/packages/excalidraw/locales/sv-SE.json b/packages/excalidraw/locales/sv-SE.json index 94a09610c..d5a788fd4 100644 --- a/packages/excalidraw/locales/sv-SE.json +++ b/packages/excalidraw/locales/sv-SE.json @@ -11,6 +11,8 @@ "copyAsPng": "Kopiera till urklipp som PNG", "copyAsSvg": "Kopiera till urklipp som SVG", "copyText": "Kopiera till urklipp som text", + "copySource": "Kopiera källa till urklipp", + "convertToCode": "Konvertera till kod", "bringForward": "Flytta framåt", "sendToBack": "Flytta underst", "bringToFront": "Flytta främst", @@ -36,8 +38,12 @@ "arrowhead_none": "Inga", "arrowhead_arrow": "Pil", "arrowhead_bar": "Stolpe", - "arrowhead_dot": "Punkt", + "arrowhead_circle": "Cirkel", + "arrowhead_circle_outline": "Cirkel (kontur)", "arrowhead_triangle": "Triangel", + "arrowhead_triangle_outline": "Triangel (kontur)", + "arrowhead_diamond": "Diamant", + "arrowhead_diamond_outline": "Diamant (kontur)", "fontSize": "Teckenstorlek", "fontFamily": "Teckensnitt", "addWatermark": "Lägg till \"Skapad med Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Håll sidofältet öppet", "selectAllElementsInFrame": "Markera alla element i rutan", "removeAllElementsFromFrame": "Ta bort alla element från rutan", - "eyeDropper": "Välj färg från canvas" + "eyeDropper": "Välj färg från canvas", + "textToDiagram": "Text till diagram", + "prompt": "Fråga" }, "library": { "noItems": "Inga objekt tillagda ännu...", @@ -209,6 +217,7 @@ "importLibraryError": "Kunde inte ladda bibliotek", "collabSaveFailed": "Det gick inte att spara i backend-databasen. Om problemen kvarstår bör du spara filen lokalt för att se till att du inte förlorar ditt arbete.", "collabSaveFailed_sizeExceeded": "Det gick inte att spara till backend-databasen, whiteboarden verkar vara för stor. Du bör spara filen lokalt för att du inte ska förlora ditt arbete.", + "imageToolNotSupported": "Bilder är inaktiverade.", "brave_measure_text_error": { "line1": "Det ser ut som om du använder Brave-webbläsaren med Aggressivt Blockera fingeravtryck inställningen aktiverad.", "line2": "Detta kan resultera i trasiga Textelement i dina ritningar.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Inbäddbara element kan inte läggas till i biblioteket.", + "iframe": "IFrame-element kan inte läggas till i biblioteket.", "image": "Stöd för att lägga till bilder till biblioteket kommer snart!" - } + }, + "asyncPasteFailedOnRead": "Kunde inte klistra in (kunde inte läsa från urklipp).", + "asyncPasteFailedOnParse": "Kunde inte klistra in.", + "copyToSystemClipboardFailed": "Kunde inte kopiera till urklipp." }, "toolBar": { "selection": "Markering", @@ -236,10 +249,13 @@ "link": "Lägg till / Uppdatera länk för en vald form", "eraser": "Radergummi", "frame": "Rutverktyg", + "magicframe": "Trådram till kod", "embeddable": "Bädda in (web)", "laser": "Laserpekare", "hand": "Hand (panoreringsverktyg)", - "extraTools": "Fler verktyg" + "extraTools": "Fler verktyg", + "mermaidToExcalidraw": "Mermaid till Excalidraw", + "magicSettings": "AI-inställningar" }, "headings": { "canvasActions": "Canvas-åtgärder", @@ -498,5 +514,12 @@ "description": "Inläsning av en extern ritning kommer ersätta ditt befintliga innehåll.

Du kan säkerhetskopiera din ritning först genom att använda ett av alternativen nedan." } } + }, + "mermaid": { + "title": "Mermaid till Excalidraw", + "button": "Infoga", + "description": "För närvarande stöds endast Flödesdiagram, Sekvensdiagram och Klassdiagram. De andra typerna kommer att återges som bild i Excalidraw.", + "syntax": "Mermaid-syntax", + "preview": "Förhandsgranska" } } diff --git a/packages/excalidraw/locales/ta-IN.json b/packages/excalidraw/locales/ta-IN.json index 05d8c3dae..577d5bb9e 100644 --- a/packages/excalidraw/locales/ta-IN.json +++ b/packages/excalidraw/locales/ta-IN.json @@ -11,6 +11,8 @@ "copyAsPng": "நகலகத்திற்கு PNG ஆக நகலெடு", "copyAsSvg": "நகலகத்திற்கு SVG ஆக நகலெடு", "copyText": "நகலகத்திற்கு உரையாக நகலெடு", + "copySource": "", + "convertToCode": "", "bringForward": "முன்நோக்கி கொண்டுவா", "sendToBack": "பின்னே அனுப்பு", "bringToFront": "முன்னே கொண்டுவா", @@ -36,8 +38,12 @@ "arrowhead_none": "ஏதுமில்லை", "arrowhead_arrow": "அம்பு", "arrowhead_bar": "பட்டை", - "arrowhead_dot": "புள்ளி", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "முக்கோணம்", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "எழுத்துரு அளவு", "fontFamily": "எழுத்துரு குடும்பம்", "addWatermark": "\"எக்ஸ்கேலிட்ரா கொண்டு ஆனது\"-ஐச் சேர்", @@ -130,7 +136,9 @@ "sidebarLock": "பக்கப்பட்டையைத் திறந்தே வை", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "கித்தானிலிருந்து நிறம் தேர்ந்தெடு" + "eyeDropper": "கித்தானிலிருந்து நிறம் தேர்ந்தெடு", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "இதுவரை உருப்படிகள் சேரக்கப்படவில்லை...", @@ -209,6 +217,7 @@ "importLibraryError": "நூலகத்தை ஏற்ற முடியவில்லை", "collabSaveFailed": "பின்முனை தரவுத்தளத்தில் சேமிக்க முடியவில்லை. சிக்கல்கள் நீடித்தால், உமது வேலைகளை இழக்காமலிருப்பதை உறுதிசெய்ய உமது கோப்பை உள்ளகத்தில் சேமிக்க வேண்டும்.", "collabSaveFailed_sizeExceeded": "பின்முனை தரவுத்தளத்தில் சேமிக்க முடியவில்லை, கித்தான் மிகப்பெரிதாகத் தெரிகிறது. உமது வேலைகளை இழக்காமலிருப்பதை உறுதிசெய்ய உமது கோப்பை உள்ளகத்தில் சேமிக்க வேண்டும்.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "தெரிவு", @@ -236,10 +249,13 @@ "link": "தேர்தெடுத்த வடிவத்திற்குத் தொடுப்பைச் சேர்/ புதுப்பி", "eraser": "அழிப்பி", "frame": "சட்டகம் கருவி", + "magicframe": "", "embeddable": "", "laser": "", "hand": "கை (பார்வை நகர்கும் கருவி)", - "extraTools": "மற்ற கருவிகள்" + "extraTools": "மற்ற கருவிகள்", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "கித்தான் செயல்கள்", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/th-TH.json b/packages/excalidraw/locales/th-TH.json index 57258e2fa..4d27fe8b5 100644 --- a/packages/excalidraw/locales/th-TH.json +++ b/packages/excalidraw/locales/th-TH.json @@ -11,6 +11,8 @@ "copyAsPng": "คัดลองไปยังคลิปบอร์ดเป็น PNG", "copyAsSvg": "คัดลองไปยังคลิปบอร์ดเป็น SVG", "copyText": "คัดลองไปยังคลิปบอร์ดเป็นข้อความ", + "copySource": "", + "convertToCode": "", "bringForward": "นำขึ้นข้างบน", "sendToBack": "ย้ายไปข้างล่าง", "bringToFront": "นำขึ้นข้างหน้า", @@ -36,8 +38,12 @@ "arrowhead_none": "ไม่มี", "arrowhead_arrow": "ลูกศร", "arrowhead_bar": "แถบ", - "arrowhead_dot": "จุด", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "สามเหลี่ยม", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "ขนาดตัวอักษร", "fontFamily": "แบบตัวอักษร", "addWatermark": "เพิ่มลายน้ำ \"สร้างด้วย Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "ยังไม่มีรายการที่เพิ่มเข้าไปได้", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "การเพิ่มองค์ประกอบที่ฝังยังไม่สามารถเพิ่มเข้าไปในไลบลารีได้", + "iframe": "", "image": "การสนับสนุนสำหรับเพิ่มรูปภาพลงในไลบลารีจะมาในเร็ว ๆ นี้" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "ยางลบ", "frame": "", + "magicframe": "", "embeddable": "ฝังเว็บ", "laser": "", "hand": "", - "extraTools": "เครื่องมืออื่นๆ" + "extraTools": "เครื่องมืออื่นๆ", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/tr-TR.json b/packages/excalidraw/locales/tr-TR.json index e9cc463e9..de5cf11c7 100644 --- a/packages/excalidraw/locales/tr-TR.json +++ b/packages/excalidraw/locales/tr-TR.json @@ -11,6 +11,8 @@ "copyAsPng": "Panoya PNG olarak kopyala", "copyAsSvg": "Panoya SVG olarak kopyala", "copyText": "Panoya metin olarak kopyala", + "copySource": "", + "convertToCode": "", "bringForward": "Bir öne getir", "sendToBack": "Arkaya gönder", "bringToFront": "En öne getir", @@ -36,8 +38,12 @@ "arrowhead_none": "Yok", "arrowhead_arrow": "Ok", "arrowhead_bar": "Çizgi", - "arrowhead_dot": "Nokta", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Üçgen", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Yazı tipi boyutu", "fontFamily": "Yazı tipi ailesi", "addWatermark": "\"Excalidraw ile yapıldı\" yazısını ekle", @@ -130,7 +136,9 @@ "sidebarLock": "Kenar çubuğu açık kalsın", "selectAllElementsInFrame": "Çerçevedeki tüm bileşenleri seç", "removeAllElementsFromFrame": "Çerçevedeki tüm bileşenleri sil", - "eyeDropper": "Tuvalden renk seç" + "eyeDropper": "Tuvalden renk seç", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Öğe eklenmedi...", @@ -209,6 +217,7 @@ "importLibraryError": "Kütüphane yüklenemedi", "collabSaveFailed": "Backend veritabanına kaydedilemedi. Eğer problem devam ederse, çalışmanızı korumak için dosyayı yerel olarak kaydetmelisiniz.", "collabSaveFailed_sizeExceeded": "Backend veritabanına kaydedilemedi; tuval çok büyük. Çalışmanızı korumak için dosyayı yerel olarak kaydetmelisiniz.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "Resimleri kütüphaneye ekleme desteği yakında geliyor!" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Seçme", @@ -236,10 +249,13 @@ "link": "Seçilen şekil için bağlantı Ekle/Güncelle", "eraser": "Silgi", "frame": "Çerçeve aracı", + "magicframe": "", "embeddable": "Web Yerleştirme", "laser": "Lazer işaretçisi", "hand": "", - "extraTools": "Daha fazla araç" + "extraTools": "Daha fazla araç", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Tuval eylemleri", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/uk-UA.json b/packages/excalidraw/locales/uk-UA.json index 5e3c4add9..8f0b43672 100644 --- a/packages/excalidraw/locales/uk-UA.json +++ b/packages/excalidraw/locales/uk-UA.json @@ -11,6 +11,8 @@ "copyAsPng": "Копіювати як PNG", "copyAsSvg": "Копіювати як SVG", "copyText": "Копіювати в буфер обміну як текст", + "copySource": "", + "convertToCode": "", "bringForward": "Перемістити вперед", "sendToBack": "На задній план", "bringToFront": "На передній план", @@ -36,8 +38,12 @@ "arrowhead_none": "Жоден", "arrowhead_arrow": "Стрілка", "arrowhead_bar": "Колона", - "arrowhead_dot": "Точка", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Трикутник", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Розмір шрифту", "fontFamily": "Шрифт", "addWatermark": "Додати «Накреслене в Excalidraw»", @@ -130,7 +136,9 @@ "sidebarLock": "Не закривати бокове меню", "selectAllElementsInFrame": "Обрати всі елементи у фреймі", "removeAllElementsFromFrame": "Видалити всі елементи з фрейму", - "eyeDropper": "Вибрати колір з полотна" + "eyeDropper": "Вибрати колір з полотна", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Тут поки пусто...", @@ -209,6 +217,7 @@ "importLibraryError": "Не вдалося завантажити бібліотеку", "collabSaveFailed": "Не вдалося зберегти у базу даних сервера. Якщо проблеми не зникнуть, Вам слід зберегти файл локально, щоб не втратити роботу.", "collabSaveFailed_sizeExceeded": "Полотно завелике! Не вдалося зберегти у базу даних сервера. Вам слід зберегти файл локально, щоб не втратити свою роботу.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "Ви використовуєте браузер Brave з увімкненим налаштуванням Агресивного Блокування Розпізнавання Пристрою.", "line2": "Це може нашкодити текстовим елементам у ваших малюнках.", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "Вбудовані елементи не можна додати в бібліотеку.", + "iframe": "", "image": "Підтримка додавання зображень в бібліотеку найближчим часом!" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Виділення", @@ -236,10 +249,13 @@ "link": "Додати/Оновити посилання для вибраної форми", "eraser": "Очищувач", "frame": "Інструмент фрейму", + "magicframe": "", "embeddable": "Веб вкладення", "laser": "", "hand": "Рука (інструмент для панорамування)", - "extraTools": "Інші інструменти" + "extraTools": "Інші інструменти", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Дії з полотном", @@ -498,5 +514,12 @@ "description": "Завантаження зовнішнього малюнка замінить ваш наявний вміст.

Ви можете спочатку створити резервну копію малюнка, скориставшись одним із наведених нижче способів." } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/vi-VN.json b/packages/excalidraw/locales/vi-VN.json index 2f8381b55..5e24dc3e3 100644 --- a/packages/excalidraw/locales/vi-VN.json +++ b/packages/excalidraw/locales/vi-VN.json @@ -11,6 +11,8 @@ "copyAsPng": "Sao chép vào bộ nhớ tạm dưới dạng PNG", "copyAsSvg": "Sao chép vào bộ nhớ tạm dưới dạng SVG", "copyText": "Sao chép vào bộ nhớ tạm dưới dạng chữ", + "copySource": "", + "convertToCode": "", "bringForward": "Đưa ra trước", "sendToBack": "Hạ xuống dưới", "bringToFront": "Đưa ra đầu tiên", @@ -36,8 +38,12 @@ "arrowhead_none": "Không", "arrowhead_arrow": "Mũi tên", "arrowhead_bar": "Thanh", - "arrowhead_dot": "Chấm", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "Tam giác", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "Cỡ chữ", "fontFamily": "Phông chữ", "addWatermark": "Làm với Excalidraw\"", @@ -130,7 +136,9 @@ "sidebarLock": "Giữ thanh bên luôn mở", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "Chưa có món nào...", @@ -209,6 +217,7 @@ "importLibraryError": "Không thể tải thư viện", "collabSaveFailed": "Không thể lưu vào cơ sở dữ liệu. Nếu vấn đề tiếp tục xảy ra, bạn nên lưu tệp vào máy để đảm bảo bạn không bị mất công việc.", "collabSaveFailed_sizeExceeded": "Không thể lưu vào cơ sở dữ liệu, canvas có vẻ quá lớn. Bạn nên lưu tệp cục bộ để đảm bảo bạn không bị mất công việc.", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "Lựa chọn", @@ -236,10 +249,13 @@ "link": "Thêm/ Chỉnh sửa liên kết cho hình được chọn", "eraser": "Xóa", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "Tay kéo", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "Hành động canvas", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/zh-CN.json b/packages/excalidraw/locales/zh-CN.json index 04c5b4d40..19d715750 100644 --- a/packages/excalidraw/locales/zh-CN.json +++ b/packages/excalidraw/locales/zh-CN.json @@ -11,6 +11,8 @@ "copyAsPng": "复制为 PNG 到剪贴板", "copyAsSvg": "复制为 SVG 到剪贴板", "copyText": "复制文本到剪贴板", + "copySource": "复制源码到剪贴板", + "convertToCode": "转换成代码", "bringForward": "上移一层", "sendToBack": "置于底层", "bringToFront": "置于顶层", @@ -36,8 +38,12 @@ "arrowhead_none": "无", "arrowhead_arrow": "箭头", "arrowhead_bar": "条状", - "arrowhead_dot": "圆点", + "arrowhead_circle": "圆点", + "arrowhead_circle_outline": "圆点(空心)", "arrowhead_triangle": "三角箭头", + "arrowhead_triangle_outline": "三角箭头(空心)", + "arrowhead_diamond": "菱形", + "arrowhead_diamond_outline": "菱形(空心)", "fontSize": "字体大小", "fontFamily": "字体", "addWatermark": "添加 “使用 Excalidraw 创建” 水印", @@ -130,7 +136,9 @@ "sidebarLock": "侧边栏常驻", "selectAllElementsInFrame": "选择画框中的所有元素", "removeAllElementsFromFrame": "分离出画框中的所有元素", - "eyeDropper": "从画布上取色" + "eyeDropper": "从画布上取色", + "textToDiagram": "文字至图表", + "prompt": "Prompt" }, "library": { "noItems": "尚未添加任何项目……", @@ -203,12 +211,13 @@ "imageInsertError": "无法插入图像。请稍后再试……", "fileTooBig": "文件过大。最大允许的大小为 {{maxSize}}。", "svgImageInsertError": "无法插入 SVG 图像。该 SVG 标记似乎是无效的。", - "failedToFetchImage": "", + "failedToFetchImage": "无法获取图片。", "invalidSVGString": "无效的 SVG。", "cannotResolveCollabServer": "无法连接到实时协作服务器。请重新加载页面并重试。", "importLibraryError": "无法加载素材库", "collabSaveFailed": "无法保存到后端数据库。如果问题持续存在,您应该保存文件到本地,以确保您的工作不会丢失。", "collabSaveFailed_sizeExceeded": "无法保存到后端数据库,画布似乎过大。您应该保存文件到本地,以确保您的工作不会丢失。", + "imageToolNotSupported": "图片已被禁用。", "brave_measure_text_error": { "line1": "您似乎正在使用 Brave 浏览器并启用了积极阻止指纹识别的设置。", "line2": "这可能会破坏绘图中的 文本元素。", @@ -216,9 +225,13 @@ "line4": "如果禁用此设置无法修复文本元素的显示,请在 GitHub 上提交一个 issue ,或者在 Discord 上反馈" }, "libraryElementTypeError": { - "embeddable": "嵌入元素不能添加到素材库。", + "embeddable": "嵌入的元素不能被添加到素材库。", + "iframe": "不能将 IFrame 元素添加到素材库中。", "image": "我们不久将支持添加图片到素材库" - } + }, + "asyncPasteFailedOnRead": "无法粘贴(无法读取系统剪贴板)。", + "asyncPasteFailedOnParse": "无法粘贴。", + "copyToSystemClipboardFailed": "无法复制到剪贴板。" }, "toolBar": { "selection": "选择", @@ -236,10 +249,13 @@ "link": "为选中的形状添加/更新链接", "eraser": "橡皮", "frame": "画框工具", + "magicframe": "线框图至代码", "embeddable": "嵌入网页", "laser": "激光笔", "hand": "抓手(平移工具)", - "extraTools": "更多工具" + "extraTools": "更多工具", + "mermaidToExcalidraw": "Mermaid 至 Excalidraw", + "magicSettings": "AI 设置" }, "headings": { "canvasActions": "画布动作", @@ -268,7 +284,7 @@ "deepBoxSelect": "按住 CtrlOrCmd 以深度选择,并避免拖拽", "eraserRevert": "按住 Alt 以反选被标记删除的元素", "firefox_clipboard_write": "将高级配置首选项“dom.events.asyncClipboard.clipboardItem”设置为“true”可以启用此功能。要更改 Firefox 的高级配置首选项,请前往“about:config”页面。", - "disableSnapping": "按住 CtrlOrCmd 以禁用吸附" + "disableSnapping": "按住 Ctrl 或 Cmd 以禁用吸附" }, "canvasError": { "cannotShowPreview": "无法显示预览", @@ -320,7 +336,7 @@ "editor": "编辑器", "editLineArrowPoints": "编辑线条或箭头的点", "editText": "添加或编辑文本", - "github": "提交问题", + "github": "发现问题?提交反馈", "howto": "帮助文档", "or": "或", "preventBinding": "禁用箭头吸附", @@ -498,5 +514,12 @@ "description": "加载外部绘图将替换您现有的内容

您可以先使用下列方式备份您的绘图。" } } + }, + "mermaid": { + "title": "Mermaid 至 Excalidraw", + "button": "插入", + "description": "目前仅支持流程图序列图类图。其他类型在 Excalidraw 中将以图像呈现。", + "syntax": "Mermaid 语法", + "preview": "预览" } } diff --git a/packages/excalidraw/locales/zh-HK.json b/packages/excalidraw/locales/zh-HK.json index 1ecdb0ce3..d1760a804 100644 --- a/packages/excalidraw/locales/zh-HK.json +++ b/packages/excalidraw/locales/zh-HK.json @@ -11,6 +11,8 @@ "copyAsPng": "以 PNG 格式複製", "copyAsSvg": "以 SVG 格式複製", "copyText": "", + "copySource": "", + "convertToCode": "", "bringForward": "往上一層移動", "sendToBack": "移到最底層", "bringToFront": "移到最上層", @@ -36,8 +38,12 @@ "arrowhead_none": "無箭嘴", "arrowhead_arrow": "普通箭嘴", "arrowhead_bar": "平頭條狀", - "arrowhead_dot": "圓點", + "arrowhead_circle": "", + "arrowhead_circle_outline": "", "arrowhead_triangle": "三角箭嘴", + "arrowhead_triangle_outline": "", + "arrowhead_diamond": "", + "arrowhead_diamond_outline": "", "fontSize": "字型大小", "fontFamily": "字體", "addWatermark": "加入「使用 Excalidraw 製圖」水印", @@ -130,7 +136,9 @@ "sidebarLock": "", "selectAllElementsInFrame": "", "removeAllElementsFromFrame": "", - "eyeDropper": "" + "eyeDropper": "", + "textToDiagram": "", + "prompt": "" }, "library": { "noItems": "", @@ -209,6 +217,7 @@ "importLibraryError": "", "collabSaveFailed": "", "collabSaveFailed_sizeExceeded": "", + "imageToolNotSupported": "", "brave_measure_text_error": { "line1": "", "line2": "", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "", + "iframe": "", "image": "" - } + }, + "asyncPasteFailedOnRead": "", + "asyncPasteFailedOnParse": "", + "copyToSystemClipboardFailed": "" }, "toolBar": { "selection": "", @@ -236,10 +249,13 @@ "link": "", "eraser": "", "frame": "", + "magicframe": "", "embeddable": "", "laser": "", "hand": "", - "extraTools": "" + "extraTools": "", + "mermaidToExcalidraw": "", + "magicSettings": "" }, "headings": { "canvasActions": "畫布動作", @@ -498,5 +514,12 @@ "description": "" } } + }, + "mermaid": { + "title": "", + "button": "", + "description": "", + "syntax": "", + "preview": "" } } diff --git a/packages/excalidraw/locales/zh-TW.json b/packages/excalidraw/locales/zh-TW.json index 64826dc60..e116d9163 100644 --- a/packages/excalidraw/locales/zh-TW.json +++ b/packages/excalidraw/locales/zh-TW.json @@ -11,6 +11,8 @@ "copyAsPng": "以PNG格式儲存到剪貼板", "copyAsSvg": "以SVG格式複製到剪貼板", "copyText": "以文字格式複製至剪貼簿", + "copySource": "複製來源至剪貼簿", + "convertToCode": "轉換為程式碼", "bringForward": "上移一層", "sendToBack": "移到最底層", "bringToFront": "置於最頂層", @@ -36,8 +38,12 @@ "arrowhead_none": "無", "arrowhead_arrow": "箭頭", "arrowhead_bar": "條狀箭頭", - "arrowhead_dot": "點箭頭", + "arrowhead_circle": "圓形", + "arrowhead_circle_outline": "圓形(外框)", "arrowhead_triangle": "三角形", + "arrowhead_triangle_outline": "三角形(外框)", + "arrowhead_diamond": "菱形", + "arrowhead_diamond_outline": "菱形(外框)", "fontSize": "字型大小", "fontFamily": "字體集", "addWatermark": "加上 \"Made with Excalidraw\" 浮水印", @@ -130,7 +136,9 @@ "sidebarLock": "側欄維持開啟", "selectAllElementsInFrame": "選取框架內的所有元素", "removeAllElementsFromFrame": "從框架內移除所有元素", - "eyeDropper": "從畫布中選取顏色" + "eyeDropper": "從畫布中選取顏色", + "textToDiagram": "文字轉圖表", + "prompt": "提示詞" }, "library": { "noItems": "尚未加入任何物件...", @@ -209,6 +217,7 @@ "importLibraryError": "無法載入資料庫", "collabSaveFailed": "無法儲存至後端資料庫。若此問題持續發生,請將檔案儲存於本機以確保資料不會遺失。", "collabSaveFailed_sizeExceeded": "無法儲存至後端資料庫,可能的原因為畫布尺寸過大。請將檔案儲存於本機以確保資料不會遺失。", + "imageToolNotSupported": "圖片已停用", "brave_measure_text_error": { "line1": "看起來您開啟了 Brave 瀏覽器的 Aggressively Block Fingerprinting 設定。", "line2": "這可能造成您畫布中 文字元素 的異常。", @@ -217,8 +226,12 @@ }, "libraryElementTypeError": { "embeddable": "可嵌入元素無法加入資料庫", + "iframe": "IFrame 元素無法加入資料庫", "image": "即將支援加入圖片至資料庫!" - } + }, + "asyncPasteFailedOnRead": "無法貼上(無法由系統剪貼簿讀入)", + "asyncPasteFailedOnParse": "無法貼上", + "copyToSystemClipboardFailed": "無法複製至剪貼簿" }, "toolBar": { "selection": "選取", @@ -236,10 +249,13 @@ "link": "為所選的形狀增加\b/更新連結", "eraser": "橡皮擦", "frame": "框架工具", + "magicframe": "線框稿轉為程式碼", "embeddable": "嵌入網站", "laser": "雷射筆", "hand": "手形(平移工具)", - "extraTools": "更多工具" + "extraTools": "更多工具", + "mermaidToExcalidraw": "Mermaid 至 Excalidraw", + "magicSettings": "AI 設定" }, "headings": { "canvasActions": "canvas 動作", @@ -498,5 +514,12 @@ "description": "載入外部繪圖將取代您目前的內容

可先使用下方的選項備份您的繪圖。" } } + }, + "mermaid": { + "title": "Mermaid 至 Excalidraw", + "button": "插入", + "description": "目前僅支援 FlowchartSequenceClass 圖表。其餘檔案類型在 Excalidraw 將會以圖像呈現。", + "syntax": "Mermaid 語法", + "preview": "預覽" } } From 88a2b286c7dbc2866621d27ec5b683253c42fe03 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 13 Dec 2023 21:51:27 +0530 Subject: [PATCH 14/79] feat: move utils to utils package and make @excalidraw/utils a workspace (#7432) * feat: move utils to utils package and make @excalidraw/utils a workspace * remove esm and update types path * remove esm script * fix package.json and yarn.lock * update path * fix * fix lint and test --- package.json | 3 +- packages/excalidraw/components/App.tsx | 3 +- .../components/ImageExportDialog.tsx | 2 +- .../excalidraw/components/PublishLibrary.tsx | 2 +- packages/excalidraw/data/index.ts | 2 +- packages/excalidraw/frame.ts | 2 +- .../excalidraw/hooks/useLibraryItemSvg.ts | 2 +- packages/excalidraw/index.tsx | 4 +- packages/excalidraw/package.json | 8 +- packages/excalidraw/scene/export.ts | 2 +- .../utils/__snapshots__/export.test.ts.snap | 100 +++++++++++++ packages/{ => utils}/bbox.ts | 4 +- .../utils/{utils.test.ts => export.test.ts} | 2 +- packages/{utils.ts => utils/export.ts} | 26 ++-- packages/utils/index.js | 6 +- packages/{ => utils}/withinBounds.test.ts | 4 +- packages/{ => utils}/withinBounds.ts | 10 +- yarn.lock | 135 ++++++++++++++++-- 18 files changed, 261 insertions(+), 56 deletions(-) create mode 100644 packages/utils/__snapshots__/export.test.ts.snap rename packages/{ => utils}/bbox.ts (94%) rename packages/utils/{utils.test.ts => export.test.ts} (99%) rename packages/{utils.ts => utils/export.ts} (88%) rename packages/{ => utils}/withinBounds.test.ts (98%) rename packages/{ => utils}/withinBounds.ts (95%) diff --git a/package.json b/package.json index 4b3ebb3e2..24c5a545d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "name": "excalidraw-monorepo", "workspaces": [ "excalidraw-app", - "packages/excalidraw" + "packages/excalidraw", + "packages/utils" ], "dependencies": { "@excalidraw/random-username": "1.0.0", diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 69a60249c..513b8c8e2 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -5,7 +5,6 @@ import { RoughCanvas } from "roughjs/bin/canvas"; import rough from "roughjs/bin/rough"; import clsx from "clsx"; import { nanoid } from "nanoid"; - import { actionAddToLibrary, actionBringForward, @@ -392,7 +391,7 @@ import { import { Emitter } from "../emitter"; import { ElementCanvasButtons } from "../element/ElementCanvasButtons"; import { MagicCacheData, diagramToHTML } from "../data/magic"; -import { elementsOverlappingBBox, exportToBlob } from "../../utils"; +import { elementsOverlappingBBox, exportToBlob } from "../../utils/export"; import { COLOR_PALETTE } from "../colors"; import { ElementCanvasButton } from "./MagicButton"; import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; diff --git a/packages/excalidraw/components/ImageExportDialog.tsx b/packages/excalidraw/components/ImageExportDialog.tsx index 804733eba..d0df35193 100644 --- a/packages/excalidraw/components/ImageExportDialog.tsx +++ b/packages/excalidraw/components/ImageExportDialog.tsx @@ -23,7 +23,7 @@ import { nativeFileSystemSupported } from "../data/filesystem"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; import { isSomeElementSelected } from "../scene"; -import { exportToCanvas } from "../../utils"; +import { exportToCanvas } from "../../utils/export"; import { copyIcon, downloadIcon, helpIcon } from "./icons"; import { Dialog } from "./Dialog"; diff --git a/packages/excalidraw/components/PublishLibrary.tsx b/packages/excalidraw/components/PublishLibrary.tsx index c14d42d50..51e14febc 100644 --- a/packages/excalidraw/components/PublishLibrary.tsx +++ b/packages/excalidraw/components/PublishLibrary.tsx @@ -6,7 +6,7 @@ import { t } from "../i18n"; import Trans from "./Trans"; import { LibraryItems, LibraryItem, UIAppState } from "../types"; -import { exportToCanvas, exportToSvg } from "../../utils"; +import { exportToCanvas, exportToSvg } from "../../utils/export"; import { EDITOR_LS_KEYS, EXPORT_DATA_TYPES, diff --git a/packages/excalidraw/data/index.ts b/packages/excalidraw/data/index.ts index cb7bed208..fdb834764 100644 --- a/packages/excalidraw/data/index.ts +++ b/packages/excalidraw/data/index.ts @@ -11,7 +11,7 @@ import { NonDeletedExcalidrawElement, } from "../element/types"; import { t } from "../i18n"; -import { elementsOverlappingBBox } from "../../withinBounds"; +import { elementsOverlappingBBox } from "../../utils/export"; import { isSomeElementSelected, getSelectedElements } from "../scene"; import { exportToCanvas, exportToSvg } from "../scene/export"; import { ExportType } from "../scene/types"; diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 5b2186533..3818e6684 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -21,7 +21,7 @@ import { getElementsWithinSelection, getSelectedElements } from "./scene"; import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups"; import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene"; import { getElementLineSegments } from "./element/bounds"; -import { doLineSegmentsIntersect } from "../utils"; +import { doLineSegmentsIntersect } from "../utils/export"; import { isFrameElement, isFrameLikeElement } from "./element/typeChecks"; // --------------------------- Frame State ------------------------------------ diff --git a/packages/excalidraw/hooks/useLibraryItemSvg.ts b/packages/excalidraw/hooks/useLibraryItemSvg.ts index 972b7f284..ac40140b4 100644 --- a/packages/excalidraw/hooks/useLibraryItemSvg.ts +++ b/packages/excalidraw/hooks/useLibraryItemSvg.ts @@ -2,7 +2,7 @@ import { atom, useAtom } from "jotai"; import { useEffect, useState } from "react"; import { COLOR_PALETTE } from "../colors"; import { jotaiScope } from "../jotai"; -import { exportToSvg } from "../../utils"; +import { exportToSvg } from "../../utils/export"; import { LibraryItem } from "../types"; export type SvgCache = Map; diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index a4a82a1e0..e8c083415 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -216,7 +216,7 @@ export { getFreeDrawSvgPath, exportToClipboard, mergeLibraryItems, -} from "../utils"; +} from "../utils/export"; export { isLinearElement } from "./element/typeChecks"; export { FONT_FAMILY, THEME, MIME_TYPES } from "./constants"; @@ -254,4 +254,4 @@ export { elementsOverlappingBBox, isElementInsideBBox, elementPartiallyOverlapsWithOrContainsBBox, -} from "../withinBounds"; +} from "../utils/export"; diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index 476630347..18af79be4 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -2,7 +2,7 @@ "name": "@excalidraw/excalidraw", "version": "0.17.1", "main": "main.js", - "types": "types/packages/excalidraw/index.d.ts", + "types": "types/excalidraw/index.d.ts", "files": [ "dist/*", "types/*" @@ -77,9 +77,6 @@ "tunnel-rat": "0.1.2" }, "devDependencies": { - "@types/pako": "1.0.3", - "@types/pica": "5.1.3", - "@types/resize-observer-browser": "0.1.7", "@babel/core": "7.18.9", "@babel/plugin-transform-arrow-functions": "7.18.6", "@babel/plugin-transform-async-to-generator": "7.18.6", @@ -89,6 +86,9 @@ "@babel/preset-react": "7.18.6", "@babel/preset-typescript": "7.18.6", "@size-limit/preset-big-lib": "9.0.0", + "@types/pako": "1.0.3", + "@types/pica": "5.1.3", + "@types/resize-observer-browser": "0.1.7", "autoprefixer": "10.4.7", "babel-loader": "8.2.5", "babel-plugin-transform-class-properties": "6.24.1", diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index b4702af12..bb194e1cb 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -26,7 +26,7 @@ import { getInitializedImageElements, updateImageCache, } from "../element/image"; -import { elementsOverlappingBBox } from "../../withinBounds"; +import { elementsOverlappingBBox } from "../../utils/export"; import { getFrameLikeElements, getFrameLikeTitle, diff --git a/packages/utils/__snapshots__/export.test.ts.snap b/packages/utils/__snapshots__/export.test.ts.snap new file mode 100644 index 000000000..254d4163b --- /dev/null +++ b/packages/utils/__snapshots__/export.test.ts.snap @@ -0,0 +1,100 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`exportToSvg > with default arguments 1`] = ` +{ + "activeEmbeddable": null, + "activeTool": { + "customType": null, + "lastActiveTool": null, + "locked": false, + "type": "selection", + }, + "collaborators": Map {}, + "contextMenu": null, + "currentChartType": "bar", + "currentItemBackgroundColor": "transparent", + "currentItemEndArrowhead": "arrow", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemRoundness": "round", + "currentItemStartArrowhead": null, + "currentItemStrokeColor": "#1e1e1e", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemTextAlign": "left", + "cursorButton": "up", + "defaultSidebarDockedPreference": false, + "draggingElement": null, + "editingElement": null, + "editingFrame": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementsToHighlight": null, + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "exportPadding": undefined, + "exportScale": 1, + "exportWithDarkMode": false, + "fileHandle": null, + "frameRendering": { + "clip": true, + "enabled": true, + "name": true, + "outline": true, + }, + "frameToHighlight": null, + "gridSize": null, + "isBindingEnabled": true, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "name", + "objectsSnapModeEnabled": false, + "openDialog": null, + "openMenu": null, + "openPopup": null, + "openSidebar": null, + "originSnapOffset": { + "x": 0, + "y": 0, + }, + "pasteDialog": { + "data": null, + "shown": false, + }, + "penDetected": false, + "penMode": false, + "pendingImageElementId": null, + "previousSelectedElementIds": {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": {}, + "selectedElementsAreBeingDragged": false, + "selectedGroupIds": {}, + "selectedLinearElement": null, + "selectionElement": null, + "shouldCacheIgnoreZoom": false, + "showHyperlinkPopup": false, + "showStats": false, + "showWelcomeScreen": false, + "snapLines": [], + "startBoundElement": null, + "suggestedBindings": [], + "theme": "light", + "toast": null, + "viewBackgroundColor": "#ffffff", + "viewModeEnabled": false, + "zenModeEnabled": false, + "zoom": { + "value": 1, + }, +} +`; diff --git a/packages/bbox.ts b/packages/utils/bbox.ts similarity index 94% rename from packages/bbox.ts rename to packages/utils/bbox.ts index 05ee3e7ea..5fc6192df 100644 --- a/packages/bbox.ts +++ b/packages/utils/bbox.ts @@ -1,5 +1,5 @@ -import { Bounds } from "./excalidraw/element/bounds"; -import { Point } from "./excalidraw/types"; +import { Bounds } from "../excalidraw/element/bounds"; +import { Point } from "../excalidraw/types"; export type LineSegment = [Point, Point]; diff --git a/packages/utils/utils.test.ts b/packages/utils/export.test.ts similarity index 99% rename from packages/utils/utils.test.ts rename to packages/utils/export.test.ts index a4715ea8b..aa1049cc1 100644 --- a/packages/utils/utils.test.ts +++ b/packages/utils/export.test.ts @@ -1,4 +1,4 @@ -import * as utils from "../utils"; +import * as utils from "."; import { diagramFactory } from "../excalidraw/tests/fixtures/diagramFixture"; import { vi } from "vitest"; import * as mockedSceneExportUtils from "../excalidraw/scene/export"; diff --git a/packages/utils.ts b/packages/utils/export.ts similarity index 88% rename from packages/utils.ts rename to packages/utils/export.ts index 3460bf561..ceb733881 100644 --- a/packages/utils.ts +++ b/packages/utils/export.ts @@ -1,23 +1,23 @@ import { exportToCanvas as _exportToCanvas, exportToSvg as _exportToSvg, -} from "./excalidraw/scene/export"; -import { getDefaultAppState } from "./excalidraw/appState"; -import { AppState, BinaryFiles } from "./excalidraw/types"; +} from "../excalidraw/scene/export"; +import { getDefaultAppState } from "../excalidraw/appState"; +import { AppState, BinaryFiles } from "../excalidraw/types"; import { ExcalidrawElement, ExcalidrawFrameLikeElement, NonDeleted, -} from "./excalidraw/element/types"; -import { restore } from "./excalidraw/data/restore"; -import { MIME_TYPES } from "./excalidraw/constants"; -import { encodePngMetadata } from "./excalidraw/data/image"; -import { serializeAsJSON } from "./excalidraw/data/json"; +} from "../excalidraw/element/types"; +import { restore } from "../excalidraw/data/restore"; +import { MIME_TYPES } from "../excalidraw/constants"; +import { encodePngMetadata } from "../excalidraw/data/image"; +import { serializeAsJSON } from "../excalidraw/data/json"; import { copyBlobToClipboardAsPng, copyTextToSystemClipboard, copyToClipboard, -} from "./excalidraw/clipboard"; +} from "../excalidraw/clipboard"; export { MIME_TYPES }; @@ -215,11 +215,11 @@ export { export { serializeAsJSON, serializeLibraryAsJSON, -} from "./excalidraw/data/json"; +} from "../excalidraw/data/json"; export { loadFromBlob, loadSceneOrLibraryFromBlob, loadLibraryFromBlob, -} from "./excalidraw/data/blob"; -export { getFreeDrawSvgPath } from "./excalidraw/renderer/renderElement"; -export { mergeLibraryItems } from "./excalidraw/data/library"; +} from "../excalidraw/data/blob"; +export { getFreeDrawSvgPath } from "../excalidraw/renderer/renderElement"; +export { mergeLibraryItems } from "../excalidraw/data/library"; diff --git a/packages/utils/index.js b/packages/utils/index.js index 9b59678c5..ffea9c3cf 100644 --- a/packages/utils/index.js +++ b/packages/utils/index.js @@ -1,5 +1 @@ -export { - exportToBlob, - exportToSvg, - exportToCanvas, -} from "../excalidraw/packages/utils.ts"; +export * from "./export"; diff --git a/packages/withinBounds.test.ts b/packages/utils/withinBounds.test.ts similarity index 98% rename from packages/withinBounds.test.ts rename to packages/utils/withinBounds.test.ts index f9d07e9a7..43bf5d6e8 100644 --- a/packages/withinBounds.test.ts +++ b/packages/utils/withinBounds.test.ts @@ -1,5 +1,5 @@ -import { Bounds } from "./excalidraw/element/bounds"; -import { API } from "./excalidraw/tests/helpers/api"; +import { Bounds } from "../excalidraw/element/bounds"; +import { API } from "../excalidraw/tests/helpers/api"; import { elementPartiallyOverlapsWithOrContainsBBox, elementsOverlappingBBox, diff --git a/packages/withinBounds.ts b/packages/utils/withinBounds.ts similarity index 95% rename from packages/withinBounds.ts rename to packages/utils/withinBounds.ts index 4f295342b..637cab3e1 100644 --- a/packages/withinBounds.ts +++ b/packages/utils/withinBounds.ts @@ -3,17 +3,17 @@ import type { ExcalidrawFreeDrawElement, ExcalidrawLinearElement, NonDeletedExcalidrawElement, -} from "./excalidraw/element/types"; +} from "../excalidraw/element/types"; import { isArrowElement, isExcalidrawElement, isFreeDrawElement, isLinearElement, isTextElement, -} from "./excalidraw/element/typeChecks"; -import { isValueInRange, rotatePoint } from "./excalidraw/math"; -import type { Point } from "./excalidraw/types"; -import { Bounds, getElementBounds } from "./excalidraw/element/bounds"; +} from "../excalidraw/element/typeChecks"; +import { isValueInRange, rotatePoint } from "../excalidraw/math"; +import type { Point } from "../excalidraw/types"; +import { Bounds, getElementBounds } from "../excalidraw/element/bounds"; type Element = NonDeletedExcalidrawElement; type Elements = readonly NonDeletedExcalidrawElement[]; diff --git a/yarn.lock b/yarn.lock index f97a7d856..3432c0b71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -61,7 +61,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== -"@babel/compat-data@^7.18.6", "@babel/compat-data@^7.22.9": +"@babel/compat-data@^7.18.6", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.22.9": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== @@ -689,7 +689,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.6": +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.6", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== @@ -769,7 +769,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.18.6", "@babel/plugin-proposal-logical-assignment-operators@^7.20.7": +"@babel/plugin-proposal-logical-assignment-operators@^7.18.6", "@babel/plugin-proposal-logical-assignment-operators@^7.18.9", "@babel/plugin-proposal-logical-assignment-operators@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== @@ -793,7 +793,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.18.6", "@babel/plugin-proposal-object-rest-spread@^7.20.7": +"@babel/plugin-proposal-object-rest-spread@^7.18.6", "@babel/plugin-proposal-object-rest-spread@^7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== @@ -812,7 +812,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.18.6", "@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0": +"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.18.6", "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -1059,7 +1059,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.18.6": +"@babel/plugin-transform-block-scoping@^7.18.6", "@babel/plugin-transform-block-scoping@^7.18.9": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== @@ -1073,7 +1073,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-classes@^7.18.6": +"@babel/plugin-transform-classes@^7.18.6", "@babel/plugin-transform-classes@^7.18.9": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz#e7a75f815e0c534cc4c9a39c56636c84fc0d64f2" integrity sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg== @@ -1103,7 +1103,7 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.6": +"@babel/plugin-transform-computed-properties@^7.18.6", "@babel/plugin-transform-computed-properties@^7.18.9": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== @@ -1119,7 +1119,7 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.18.6": +"@babel/plugin-transform-destructuring@^7.18.6", "@babel/plugin-transform-destructuring@^7.18.9": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== @@ -1178,6 +1178,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-for-of@^7.18.8": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-for-of@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" @@ -1258,7 +1266,7 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-simple-access" "^7.20.2" -"@babel/plugin-transform-modules-systemjs@^7.18.6": +"@babel/plugin-transform-modules-systemjs@^7.18.6", "@babel/plugin-transform-modules-systemjs@^7.18.9": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz#fa7e62248931cb15b9404f8052581c302dd9de81" integrity sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ== @@ -1326,7 +1334,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.18.6": +"@babel/plugin-transform-parameters@^7.18.6", "@babel/plugin-transform-parameters@^7.18.8": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== @@ -1417,6 +1425,18 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-runtime@7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.6.tgz#77b14416015ea93367ca06979710f5000ff34ccb" + integrity sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + babel-plugin-polyfill-corejs2 "^0.3.1" + babel-plugin-polyfill-corejs3 "^0.5.2" + babel-plugin-polyfill-regenerator "^0.3.1" + semver "^6.3.0" + "@babel/plugin-transform-runtime@7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f" @@ -1448,7 +1468,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.18.6": +"@babel/plugin-transform-spread@^7.18.6", "@babel/plugin-transform-spread@^7.18.9": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== @@ -1631,6 +1651,87 @@ core-js-compat "^3.22.1" semver "^6.3.0" +"@babel/preset-env@7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.9.tgz#9b3425140d724fbe590322017466580844c7eaff" + integrity sha512-75pt/q95cMIHWssYtyfjVlvI+QEZQThQbKvR9xH+F/Agtw/s4Wfc2V9Bwd/P39VtixB7oWxGdH4GteTTwYJWMg== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.18.6" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.6" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.18.9" + babel-plugin-polyfill-corejs2 "^0.3.1" + babel-plugin-polyfill-corejs3 "^0.5.2" + babel-plugin-polyfill-regenerator "^0.3.1" + core-js-compat "^3.22.1" + semver "^6.3.0" + "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.16.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.4.tgz#a952482e634a8dd8271a3fe5459a16eb10739c58" @@ -6484,6 +6585,14 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-loader@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + filelist@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -9641,7 +9750,7 @@ schema-utils@^2.6.5: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.2.0: +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== From aad8ab012330dddf09fe8349dc744143fa5e40da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Moln=C3=A1r?= <38168628+barnabasmolnar@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:07:11 +0100 Subject: [PATCH 15/79] feat: follow mode (#6848) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- excalidraw-app/app_constants.ts | 7 +- excalidraw-app/collab/Collab.tsx | 150 ++++++++-- excalidraw-app/collab/Portal.tsx | 49 +++- excalidraw-app/data/index.ts | 8 + packages/excalidraw/actions/actionCanvas.tsx | 57 +++- .../excalidraw/actions/actionNavigate.tsx | 56 +++- packages/excalidraw/appState.ts | 4 + packages/excalidraw/components/App.tsx | 61 ++++- packages/excalidraw/components/Avatar.scss | 30 +- packages/excalidraw/components/Avatar.tsx | 16 +- .../components/FollowMode/FollowMode.scss | 59 ++++ .../components/FollowMode/FollowMode.tsx | 43 +++ .../components/Sidebar/SidebarTrigger.scss | 4 + .../components/Sidebar/SidebarTrigger.tsx | 2 +- packages/excalidraw/components/UserList.scss | 100 ++++++- packages/excalidraw/components/UserList.tsx | 259 +++++++++++++++--- packages/excalidraw/components/icons.tsx | 9 + packages/excalidraw/css/theme.scss | 2 + packages/excalidraw/css/variables.module.scss | 38 +++ packages/excalidraw/index.tsx | 1 + packages/excalidraw/locales/en.json | 9 + .../__snapshots__/contextmenu.test.tsx.snap | 34 +++ .../regressionTests.test.tsx.snap | 104 +++++++ .../packages/__snapshots__/utils.test.ts.snap | 3 + packages/excalidraw/types.ts | 31 ++- packages/excalidraw/utils.ts | 37 ++- .../utils/__snapshots__/export.test.ts.snap | 2 + .../utils/__snapshots__/utils.test.ts.snap | 2 + 28 files changed, 1039 insertions(+), 138 deletions(-) create mode 100644 packages/excalidraw/components/FollowMode/FollowMode.scss create mode 100644 packages/excalidraw/components/FollowMode/FollowMode.tsx diff --git a/excalidraw-app/app_constants.ts b/excalidraw-app/app_constants.ts index 179fe52e7..a20a23506 100644 --- a/excalidraw-app/app_constants.ts +++ b/excalidraw-app/app_constants.ts @@ -15,11 +15,14 @@ export const FILE_CACHE_MAX_AGE_SEC = 31536000; export const WS_EVENTS = { SERVER_VOLATILE: "server-volatile-broadcast", SERVER: "server-broadcast", -}; + USER_FOLLOW_CHANGE: "user-follow", + USER_FOLLOW_ROOM_CHANGE: "user-follow-room-change", +} as const; -export enum WS_SCENE_EVENT_TYPES { +export enum WS_SUBTYPES { INIT = "SCENE_INIT", UPDATE = "SCENE_UPDATE", + USER_VIEWPORT_BOUNDS = "USER_VIEWPORT_BOUNDS", } export const FIREBASE_STORAGE_PREFIXES = { diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 6ecdd1575..99fc4361e 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -1,6 +1,9 @@ import throttle from "lodash.throttle"; import { PureComponent } from "react"; -import { ExcalidrawImperativeAPI } from "../../packages/excalidraw/types"; +import { + ExcalidrawImperativeAPI, + SocketId, +} from "../../packages/excalidraw/types"; import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog"; import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants"; import { ImportedDataState } from "../../packages/excalidraw/data/types"; @@ -11,11 +14,14 @@ import { import { getSceneVersion, restoreElements, + zoomToFitBounds, } from "../../packages/excalidraw/index"; import { Collaborator, Gesture } from "../../packages/excalidraw/types"; import { preventUnload, resolvablePromise, + throttleRAF, + viewportCoordsToSceneCoords, withBatchedUpdates, } from "../../packages/excalidraw/utils"; import { @@ -24,8 +30,9 @@ import { FIREBASE_STORAGE_PREFIXES, INITIAL_SCENE_UPDATE_TIMEOUT, LOAD_IMAGES_TIMEOUT, - WS_SCENE_EVENT_TYPES, + WS_SUBTYPES, SYNC_FULL_SCENE_INTERVAL_MS, + WS_EVENTS, } from "../app_constants"; import { generateCollaborationLinkData, @@ -74,6 +81,7 @@ import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; import { atom, useAtom } from "jotai"; import { appJotaiStore } from "../app-jotai"; +import { Mutable } from "../../packages/excalidraw/utility-types"; export const collabAPIAtom = atom(null); export const collabDialogShownAtom = atom(false); @@ -154,12 +162,28 @@ class Collab extends PureComponent { this.idleTimeoutId = null; } + private onUmmount: (() => void) | null = null; + componentDidMount() { window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload); window.addEventListener("online", this.onOfflineStatusToggle); window.addEventListener("offline", this.onOfflineStatusToggle); window.addEventListener(EVENT.UNLOAD, this.onUnload); + const unsubOnUserFollow = this.excalidrawAPI.onUserFollow((payload) => { + this.portal.socket && this.portal.broadcastUserFollowed(payload); + }); + const throttledRelayUserViewportBounds = throttleRAF( + this.relayUserViewportBounds, + ); + const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() => + throttledRelayUserViewportBounds(), + ); + this.onUmmount = () => { + unsubOnUserFollow(); + unsubOnScrollChange(); + }; + this.onOfflineStatusToggle(); const collabAPI: CollabAPI = { @@ -207,6 +231,7 @@ class Collab extends PureComponent { window.clearTimeout(this.idleTimeoutId); this.idleTimeoutId = null; } + this.onUmmount?.(); } isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!; @@ -489,7 +514,7 @@ class Collab extends PureComponent { switch (decryptedData.type) { case "INVALID_RESPONSE": return; - case WS_SCENE_EVENT_TYPES.INIT: { + case WS_SUBTYPES.INIT: { if (!this.portal.socketInitialized) { this.initializeRoom({ fetchScene: false }); const remoteElements = decryptedData.payload.elements; @@ -505,7 +530,7 @@ class Collab extends PureComponent { } break; } - case WS_SCENE_EVENT_TYPES.UPDATE: + case WS_SUBTYPES.UPDATE: this.handleRemoteSceneUpdate( this.reconcileElements(decryptedData.payload.elements), ); @@ -513,31 +538,61 @@ class Collab extends PureComponent { case "MOUSE_LOCATION": { const { pointer, button, username, selectedElementIds } = decryptedData.payload; + const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] = decryptedData.payload.socketId || // @ts-ignore legacy, see #2094 (#2097) decryptedData.payload.socketID; - const collaborators = new Map(this.collaborators); - const user = collaborators.get(socketId) || {}!; - user.pointer = pointer; - user.button = button; - user.selectedElementIds = selectedElementIds; - user.username = username; - collaborators.set(socketId, user); - this.excalidrawAPI.updateScene({ - collaborators, + this.updateCollaborator(socketId, { + pointer, + button, + selectedElementIds, + username, }); + break; } + + case WS_SUBTYPES.USER_VIEWPORT_BOUNDS: { + const { bounds, socketId } = decryptedData.payload; + + const appState = this.excalidrawAPI.getAppState(); + + // we're not following the user + // (shouldn't happen, but could be late message or bug upstream) + if (appState.userToFollow?.socketId !== socketId) { + console.warn( + `receiving remote client's (from ${socketId}) viewport bounds even though we're not subscribed to it!`, + ); + return; + } + + // cross-follow case, ignore updates in this case + if ( + appState.userToFollow && + appState.followedBy.has(appState.userToFollow.socketId) + ) { + return; + } + + this.excalidrawAPI.updateScene({ + appState: zoomToFitBounds({ + appState, + bounds, + fitToViewport: true, + viewportZoomFactor: 1, + }).appState, + }); + + break; + } + case "IDLE_STATUS": { const { userState, socketId, username } = decryptedData.payload; - const collaborators = new Map(this.collaborators); - const user = collaborators.get(socketId) || {}!; - user.userState = userState; - user.username = username; - this.excalidrawAPI.updateScene({ - collaborators, + this.updateCollaborator(socketId, { + userState, + username, }); break; } @@ -556,6 +611,17 @@ class Collab extends PureComponent { scenePromise.resolve(sceneData); }); + this.portal.socket.on( + WS_EVENTS.USER_FOLLOW_ROOM_CHANGE, + (followedBy: string[]) => { + this.excalidrawAPI.updateScene({ + appState: { followedBy: new Set(followedBy) }, + }); + + this.relayUserViewportBounds({ shouldPerform: true }); + }, + ); + this.initializeIdleDetector(); this.setState({ @@ -738,6 +804,24 @@ class Collab extends PureComponent { this.excalidrawAPI.updateScene({ collaborators }); } + private updateCollaborator = ( + socketId: SocketId, + updates: Partial, + ) => { + const collaborators = new Map(this.collaborators); + const user: Mutable = Object.assign( + {}, + collaborators.get(socketId), + updates, + ); + collaborators.set(socketId, user); + this.collaborators = collaborators; + + this.excalidrawAPI.updateScene({ + collaborators, + }); + }; + public setLastBroadcastedOrReceivedSceneVersion = (version: number) => { this.lastBroadcastedOrReceivedSceneVersion = version; }; @@ -763,6 +847,30 @@ class Collab extends PureComponent { CURSOR_SYNC_TIMEOUT, ); + relayUserViewportBounds = (props?: { shouldPerform: boolean }) => { + const appState = this.excalidrawAPI.getAppState(); + + if ( + this.portal.socket && + (appState.followedBy.size > 0 || props?.shouldPerform) + ) { + const { x: x1, y: y1 } = viewportCoordsToSceneCoords( + { clientX: 0, clientY: 0 }, + appState, + ); + + const { x: x2, y: y2 } = viewportCoordsToSceneCoords( + { clientX: appState.width, clientY: appState.height }, + appState, + ); + + this.portal.broadcastUserViewportBounds( + { bounds: [x1, y1, x2, y2] }, + `follow_${this.portal.socket.id}`, + ); + } + }; + onIdleStateChange = (userState: UserIdleState) => { this.portal.broadcastIdleChange(userState); }; @@ -772,7 +880,7 @@ class Collab extends PureComponent { getSceneVersion(elements) > this.getLastBroadcastedOrReceivedSceneVersion() ) { - this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements, false); + this.portal.broadcastScene(WS_SUBTYPES.UPDATE, elements, false); this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements); this.queueBroadcastAllElements(); } @@ -785,7 +893,7 @@ class Collab extends PureComponent { queueBroadcastAllElements = throttle(() => { this.portal.broadcastScene( - WS_SCENE_EVENT_TYPES.UPDATE, + WS_SUBTYPES.UPDATE, this.excalidrawAPI.getSceneElementsIncludingDeleted(), true, ); diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx index 4e5054329..7486486ce 100644 --- a/excalidraw-app/collab/Portal.tsx +++ b/excalidraw-app/collab/Portal.tsx @@ -7,12 +7,11 @@ import { import { TCollabClass } from "./Collab"; import { ExcalidrawElement } from "../../packages/excalidraw/element/types"; +import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants"; import { - WS_EVENTS, - FILE_UPLOAD_TIMEOUT, - WS_SCENE_EVENT_TYPES, -} from "../app_constants"; -import { UserIdleState } from "../../packages/excalidraw/types"; + OnUserFollowedPayload, + UserIdleState, +} from "../../packages/excalidraw/types"; import { trackEvent } from "../../packages/excalidraw/analytics"; import throttle from "lodash.throttle"; import { newElementWith } from "../../packages/excalidraw/element/mutateElement"; @@ -46,7 +45,7 @@ class Portal { }); this.socket.on("new-user", async (_socketId: string) => { this.broadcastScene( - WS_SCENE_EVENT_TYPES.INIT, + WS_SUBTYPES.INIT, this.collab.getSceneElementsIncludingDeleted(), /* syncAll */ true, ); @@ -83,6 +82,7 @@ class Portal { async _broadcastSocketData( data: SocketUpdateData, volatile: boolean = false, + roomId?: string, ) { if (this.isOpen()) { const json = JSON.stringify(data); @@ -91,7 +91,7 @@ class Portal { this.socket?.emit( volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER, - this.roomId, + roomId ?? this.roomId, encryptedBuffer, iv, ); @@ -130,11 +130,11 @@ class Portal { }, FILE_UPLOAD_TIMEOUT); broadcastScene = async ( - updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE, + updateType: WS_SUBTYPES.INIT | WS_SUBTYPES.UPDATE, allElements: readonly ExcalidrawElement[], syncAll: boolean, ) => { - if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) { + if (updateType === WS_SUBTYPES.INIT && !syncAll) { throw new Error("syncAll must be true when sending SCENE.INIT"); } @@ -213,12 +213,43 @@ class Portal { username: this.collab.state.username, }, }; + return this._broadcastSocketData( data as SocketUpdateData, true, // volatile ); } }; + + broadcastUserViewportBounds = ( + payload: { + bounds: [number, number, number, number]; + }, + roomId: string, + ) => { + if (this.socket?.id) { + const data: SocketUpdateDataSource["USER_VIEWPORT_BOUNDS"] = { + type: WS_SUBTYPES.USER_VIEWPORT_BOUNDS, + payload: { + socketId: this.socket.id, + username: this.collab.state.username, + bounds: payload.bounds, + }, + }; + + return this._broadcastSocketData( + data as SocketUpdateData, + true, // volatile + roomId, + ); + } + }; + + broadcastUserFollowed = (payload: OnUserFollowedPayload) => { + if (this.socket?.id) { + this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload); + } + }; } export default Portal; diff --git a/excalidraw-app/data/index.ts b/excalidraw-app/data/index.ts index 6bab98332..b162da9a4 100644 --- a/excalidraw-app/data/index.ts +++ b/excalidraw-app/data/index.ts @@ -119,6 +119,14 @@ export type SocketUpdateDataSource = { username: string; }; }; + USER_VIEWPORT_BOUNDS: { + type: "USER_VIEWPORT_BOUNDS"; + payload: { + socketId: string; + username: string; + bounds: [number, number, number, number]; + }; + }; IDLE_STATUS: { type: "IDLE_STATUS"; payload: { diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index f61f57dbd..7d57c64a7 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -109,6 +109,7 @@ export const actionZoomIn = register({ }, appState, ), + userToFollow: null, }, commitToHistory: false, }; @@ -146,6 +147,7 @@ export const actionZoomOut = register({ }, appState, ), + userToFollow: null, }, commitToHistory: false, }; @@ -183,6 +185,7 @@ export const actionResetZoom = register({ }, appState, ), + userToFollow: null, }, commitToHistory: false, }; @@ -226,22 +229,20 @@ const zoomValueToFitBoundsOnViewport = ( return clampedZoomValueToFitElements as NormalizedZoomValue; }; -export const zoomToFit = ({ - targetElements, +export const zoomToFitBounds = ({ + bounds, appState, fitToViewport = false, viewportZoomFactor = 0.7, }: { - targetElements: readonly ExcalidrawElement[]; + bounds: readonly [number, number, number, number]; appState: Readonly; /** whether to fit content to viewport (beyond >100%) */ fitToViewport: boolean; /** zoom content to cover X of the viewport, when fitToViewport=true */ viewportZoomFactor?: number; }) => { - const commonBounds = getCommonBounds(getNonDeletedElements(targetElements)); - - const [x1, y1, x2, y2] = commonBounds; + const [x1, y1, x2, y2] = bounds; const centerX = (x1 + x2) / 2; const centerY = (y1 + y2) / 2; @@ -282,7 +283,7 @@ export const zoomToFit = ({ scrollX = (appStateWidth / 2) * (1 / newZoomValue) - centerX; scrollY = (appState.height / 2) * (1 / newZoomValue) - centerY; } else { - newZoomValue = zoomValueToFitBoundsOnViewport(commonBounds, { + newZoomValue = zoomValueToFitBoundsOnViewport(bounds, { width: appState.width, height: appState.height, }); @@ -311,6 +312,29 @@ export const zoomToFit = ({ }; }; +export const zoomToFit = ({ + targetElements, + appState, + fitToViewport, + viewportZoomFactor, +}: { + targetElements: readonly ExcalidrawElement[]; + appState: Readonly; + /** whether to fit content to viewport (beyond >100%) */ + fitToViewport: boolean; + /** zoom content to cover X of the viewport, when fitToViewport=true */ + viewportZoomFactor?: number; +}) => { + const commonBounds = getCommonBounds(getNonDeletedElements(targetElements)); + + return zoomToFitBounds({ + bounds: commonBounds, + appState, + fitToViewport, + viewportZoomFactor, + }); +}; + // Note, this action differs from actionZoomToFitSelection in that it doesn't // zoom beyond 100%. In other words, if the content is smaller than viewport // size, it won't be zoomed in. @@ -321,7 +345,10 @@ export const actionZoomToFitSelectionInViewport = register({ const selectedElements = app.scene.getSelectedElements(appState); return zoomToFit({ targetElements: selectedElements.length ? selectedElements : elements, - appState, + appState: { + ...appState, + userToFollow: null, + }, fitToViewport: false, }); }, @@ -341,7 +368,10 @@ export const actionZoomToFitSelection = register({ const selectedElements = app.scene.getSelectedElements(appState); return zoomToFit({ targetElements: selectedElements.length ? selectedElements : elements, - appState, + appState: { + ...appState, + userToFollow: null, + }, fitToViewport: true, }); }, @@ -358,7 +388,14 @@ export const actionZoomToFit = register({ viewMode: true, trackEvent: { category: "canvas" }, perform: (elements, appState) => - zoomToFit({ targetElements: elements, appState, fitToViewport: false }), + zoomToFit({ + targetElements: elements, + appState: { + ...appState, + userToFollow: null, + }, + fitToViewport: false, + }), keyTest: (event) => event.code === CODES.ONE && event.shiftKey && diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 126e547ae..11dc22128 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -1,6 +1,5 @@ import { getClientColor } from "../clients"; import { Avatar } from "../components/Avatar"; -import { centerScrollOn } from "../scene/scroll"; import { Collaborator } from "../types"; import { register } from "./register"; @@ -9,39 +8,68 @@ export const actionGoToCollaborator = register({ viewMode: true, trackEvent: { category: "collab" }, perform: (_elements, appState, value) => { - const point = value as Collaborator["pointer"]; + const _value = value as Collaborator; + const point = _value.pointer; + if (!point) { return { appState, commitToHistory: false }; } + if (appState.userToFollow?.socketId === _value.socketId) { + return { + appState: { + ...appState, + userToFollow: null, + }, + commitToHistory: false, + }; + } + return { appState: { ...appState, - ...centerScrollOn({ - scenePoint: point, - viewportDimensions: { - width: appState.width, - height: appState.height, - }, - zoom: appState.zoom, - }), + userToFollow: { + socketId: _value.socketId!, + username: _value.username || "", + }, // Close mobile menu openMenu: appState.openMenu === "canvas" ? null : appState.openMenu, }, commitToHistory: false, }; }, - PanelComponent: ({ updateData, data }) => { - const [clientId, collaborator] = data as [string, Collaborator]; + PanelComponent: ({ updateData, data, appState }) => { + const [clientId, collaborator, withName] = data as [ + string, + Collaborator, + boolean, + ]; const background = getClientColor(clientId); - return ( + return withName ? ( +
updateData({ ...collaborator, clientId })} + > + {}} + name={collaborator.username || ""} + src={collaborator.avatarUrl} + isBeingFollowed={appState.userToFollow?.socketId === clientId} + /> + {collaborator.username} +
+ ) : ( updateData(collaborator.pointer)} + onClick={() => { + updateData({ ...collaborator, clientId }); + }} name={collaborator.username || ""} src={collaborator.avatarUrl} + isBeingFollowed={appState.userToFollow?.socketId === clientId} /> ); }, diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index 0089f57e9..4dec9a790 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -105,6 +105,8 @@ export const getDefaultAppState = (): Omit< y: 0, }, objectsSnapModeEnabled: false, + userToFollow: null, + followedBy: new Set(), }; }; @@ -215,6 +217,8 @@ const APP_STATE_STORAGE_CONF = (< snapLines: { browser: false, export: false, server: false }, originSnapOffset: { browser: false, export: false, server: false }, objectsSnapModeEnabled: { browser: true, export: false, server: false }, + userToFollow: { browser: false, export: false, server: false }, + followedBy: { browser: false, export: false, server: false }, }); const _clearAppStateForStorage = < diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 513b8c8e2..ff80ed5b0 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -244,6 +244,7 @@ import { KeyboardModifiersObject, CollaboratorPointer, ToolType, + OnUserFollowedPayload, } from "../types"; import { debounce, @@ -396,6 +397,7 @@ import { COLOR_PALETTE } from "../colors"; import { ElementCanvasButton } from "./MagicButton"; import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; +import FollowMode from "./FollowMode/FollowMode"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); @@ -551,6 +553,10 @@ class App extends React.Component { event: PointerEvent, ] >(); + onUserFollowEmitter = new Emitter<[payload: OnUserFollowedPayload]>(); + onScrollChangeEmitter = new Emitter< + [scrollX: number, scrollY: number, zoom: AppState["zoom"]] + >(); constructor(props: AppProps) { super(props); @@ -620,6 +626,8 @@ class App extends React.Component { onChange: (cb) => this.onChangeEmitter.on(cb), onPointerDown: (cb) => this.onPointerDownEmitter.on(cb), onPointerUp: (cb) => this.onPointerUpEmitter.on(cb), + onScrollChange: (cb) => this.onScrollChangeEmitter.on(cb), + onUserFollow: (cb) => this.onUserFollowEmitter.on(cb), } as const; if (typeof excalidrawAPI === "function") { excalidrawAPI(api); @@ -1582,6 +1590,14 @@ class App extends React.Component { onPointerDown={this.handleCanvasPointerDown} onDoubleClick={this.handleCanvasDoubleClick} /> + {this.state.userToFollow && ( + + )} {this.renderFrameNames()} {this.renderEmbeddables()} @@ -2531,11 +2547,45 @@ class App extends React.Component { this.refreshEditorBreakpoints(); } + const hasFollowedPersonLeft = + prevState.userToFollow && + !this.state.collaborators.has(prevState.userToFollow.socketId); + + if (hasFollowedPersonLeft) { + this.maybeUnfollowRemoteUser(); + } + if ( + prevState.zoom.value !== this.state.zoom.value || prevState.scrollX !== this.state.scrollX || prevState.scrollY !== this.state.scrollY ) { - this.props?.onScrollChange?.(this.state.scrollX, this.state.scrollY); + this.props?.onScrollChange?.( + this.state.scrollX, + this.state.scrollY, + this.state.zoom, + ); + this.onScrollChangeEmitter.trigger( + this.state.scrollX, + this.state.scrollY, + this.state.zoom, + ); + } + + if (prevState.userToFollow !== this.state.userToFollow) { + if (prevState.userToFollow) { + this.onUserFollowEmitter.trigger({ + userToFollow: prevState.userToFollow, + action: "UNFOLLOW", + }); + } + + if (this.state.userToFollow) { + this.onUserFollowEmitter.trigger({ + userToFollow: this.state.userToFollow, + action: "FOLLOW", + }); + } } if ( @@ -3421,11 +3471,18 @@ class App extends React.Component { } }; + private maybeUnfollowRemoteUser = () => { + if (this.state.userToFollow) { + this.setState({ userToFollow: null }); + } + }; + /** use when changing scrollX/scrollY/zoom based on user interaction */ private translateCanvas: React.Component["setState"] = ( state, ) => { this.cancelInProgresAnimation?.(); + this.maybeUnfollowRemoteUser(); this.setState(state); }; @@ -5154,6 +5211,8 @@ class App extends React.Component { private handleCanvasPointerDown = ( event: React.PointerEvent, ) => { + this.maybeUnfollowRemoteUser(); + // since contextMenu options are potentially evaluated on each render, // and an contextMenu action may depend on selection state, we must // close the contextMenu before we update the selection on pointerDown diff --git a/packages/excalidraw/components/Avatar.scss b/packages/excalidraw/components/Avatar.scss index c0c66f0a2..29eece220 100644 --- a/packages/excalidraw/components/Avatar.scss +++ b/packages/excalidraw/components/Avatar.scss @@ -2,34 +2,6 @@ .excalidraw { .Avatar { - width: 1.25rem; - height: 1.25rem; - position: relative; - border-radius: 100%; - outline-offset: 2px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - font-size: 0.75rem; - font-weight: 800; - line-height: 1; - - &-img { - width: 100%; - height: 100%; - border-radius: 100%; - } - - &::before { - content: ""; - position: absolute; - top: -3px; - right: -3px; - bottom: -3px; - left: -3px; - border: 1px solid var(--avatar-border-color); - border-radius: 100%; - } + @include avatarStyles; } } diff --git a/packages/excalidraw/components/Avatar.tsx b/packages/excalidraw/components/Avatar.tsx index 8b4624b7f..82ec88c37 100644 --- a/packages/excalidraw/components/Avatar.tsx +++ b/packages/excalidraw/components/Avatar.tsx @@ -2,21 +2,33 @@ import "./Avatar.scss"; import React, { useState } from "react"; import { getNameInitial } from "../clients"; +import clsx from "clsx"; type AvatarProps = { onClick: (e: React.MouseEvent) => void; color: string; name: string; src?: string; + isBeingFollowed?: boolean; }; -export const Avatar = ({ color, onClick, name, src }: AvatarProps) => { +export const Avatar = ({ + color, + onClick, + name, + src, + isBeingFollowed, +}: AvatarProps) => { const shortName = getNameInitial(name); const [error, setError] = useState(false); const loadImg = !error && src; const style = loadImg ? undefined : { background: color }; return ( -
+
{loadImg ? ( void; +} + +const FollowMode = ({ + height, + width, + userToFollow, + onDisconnect, +}: FollowModeProps) => { + return ( +
+
+
+
+ Following{" "} + + {userToFollow.username} + +
+ +
+
+
+ ); +}; + +export default FollowMode; diff --git a/packages/excalidraw/components/Sidebar/SidebarTrigger.scss b/packages/excalidraw/components/Sidebar/SidebarTrigger.scss index 834df6563..fd8bf814a 100644 --- a/packages/excalidraw/components/Sidebar/SidebarTrigger.scss +++ b/packages/excalidraw/components/Sidebar/SidebarTrigger.scss @@ -21,6 +21,10 @@ width: var(--lg-icon-size); height: var(--lg-icon-size); } + + &__label-element { + align-self: flex-start; + } } .default-sidebar-trigger .sidebar-trigger__label { diff --git a/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx b/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx index 711432818..889156eba 100644 --- a/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx +++ b/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx @@ -19,7 +19,7 @@ export const SidebarTrigger = ({ const appState = useUIAppState(); return ( -
@@ -63,6 +67,7 @@ export const actionGoToCollaborator = register({ name={collaborator.username || ""} src={collaborator.avatarUrl} isBeingFollowed={appState.userToFollow?.socketId === clientId} + isCurrentUser={collaborator.isCurrentUser === true} /> ); }, diff --git a/packages/excalidraw/components/Avatar.tsx b/packages/excalidraw/components/Avatar.tsx index 82ec88c37..b7b1bf962 100644 --- a/packages/excalidraw/components/Avatar.tsx +++ b/packages/excalidraw/components/Avatar.tsx @@ -10,6 +10,7 @@ type AvatarProps = { name: string; src?: string; isBeingFollowed?: boolean; + isCurrentUser: boolean; }; export const Avatar = ({ @@ -18,6 +19,7 @@ export const Avatar = ({ name, src, isBeingFollowed, + isCurrentUser, }: AvatarProps) => { const shortName = getNameInitial(name); const [error, setError] = useState(false); @@ -25,7 +27,10 @@ export const Avatar = ({ const style = loadImg ? undefined : { background: color }; return (
diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index 71ed4f71c..c7866e7a8 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -338,7 +338,9 @@ const LayerUI = ({ }, )} > - + {appState.collaborators.size > 0 && ( + + )} {renderTopRightUI?.(device.editor.isMobile, appState)} {!appState.viewModeEnabled && // hide button when sidebar docked diff --git a/packages/excalidraw/components/UserList.tsx b/packages/excalidraw/components/UserList.tsx index 6e227652a..1fd3ed996 100644 --- a/packages/excalidraw/components/UserList.tsx +++ b/packages/excalidraw/components/UserList.tsx @@ -100,7 +100,7 @@ export const UserList = React.memo( // const uniqueCollaboratorsMap = sampleCollaborators; const uniqueCollaboratorsArray = Array.from(uniqueCollaboratorsMap).filter( - ([_, collaborator]) => Object.keys(collaborator).length !== 1, + ([_, collaborator]) => collaborator.username?.trim(), ); const [searchTerm, setSearchTerm] = React.useState(""); diff --git a/packages/excalidraw/css/variables.module.scss b/packages/excalidraw/css/variables.module.scss index 3499ac4af..e434cff0c 100644 --- a/packages/excalidraw/css/variables.module.scss +++ b/packages/excalidraw/css/variables.module.scss @@ -150,6 +150,9 @@ &--is-followed::before { border-color: var(--color-primary-hover); } + &--is-current-user { + cursor: auto; + } } @mixin filledButtonOnCanvas { diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 7fe7f1363..a4a0b0113 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -59,6 +59,7 @@ export type Collaborator = Readonly<{ // user id. If supplied, we'll filter out duplicates when rendering user avatars. id?: string; socketId?: SocketId; + isCurrentUser?: boolean; }>; export type CollaboratorPointer = { diff --git a/yarn.lock b/yarn.lock index 3432c0b71..2279e1bf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3095,6 +3095,11 @@ nanoid "^3.3.6" webpack "^5.88.2" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" @@ -3577,10 +3582,12 @@ "@types/mime" "*" "@types/node" "*" -"@types/socket.io-client@1.4.36": - version "1.4.36" - resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.36.tgz#e4f1ca065f84c20939e9850e70222202bd76ff3f" - integrity sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag== +"@types/socket.io-client@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-3.0.0.tgz#d0b8ea22121b7c1df68b6a923002f9c8e3cefb42" + integrity sha512-s+IPvFoEIjKA3RdJz/Z2dGR4gLgysKi8owcnrVwNjgvc01Lk68LJDDsG2GRqegFITcxmvCMYM7bhMpwEMlHmDg== + dependencies: + socket.io-client "*" "@types/sockjs@^0.3.33": version "0.3.36" @@ -4138,11 +4145,6 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -4359,11 +4361,6 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" -arraybuffer.slice@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -4379,11 +4376,6 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -4624,11 +4616,6 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -4639,11 +4626,6 @@ base64-arraybuffer-es6@^0.7.0: resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86" integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw== -base64-arraybuffer@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" - integrity sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg== - base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -4680,11 +4662,6 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -5067,21 +5044,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw== - -component-emitter@~1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA== - compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -5591,7 +5553,7 @@ debug@2.6.9, debug@^2.6.8: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -5605,13 +5567,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -5894,33 +5849,21 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -engine.io-client@~3.4.0: - version "3.4.4" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.4.tgz#77d8003f502b0782dd792b073a4d2cf7ca5ab967" - integrity sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ== +engine.io-client@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.3.tgz#4cf6fa24845029b238f83c628916d9149c399bc5" + integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q== dependencies: - component-emitter "~1.3.0" - component-inherit "0.0.3" - debug "~3.1.0" - engine.io-parser "~2.2.0" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.6" - parseuri "0.0.6" - ws "~6.1.0" - xmlhttprequest-ssl "~1.5.4" - yeast "0.1.2" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" -engine.io-parser@~2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7" - integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg== - dependencies: - after "0.8.2" - arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.4" - blob "0.0.5" - has-binary2 "~1.0.2" +engine.io-parser@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb" + integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== enhanced-resolve@^5.0.0, enhanced-resolve@^5.10.0, enhanced-resolve@^5.15.0: version "5.15.0" @@ -6960,18 +6903,6 @@ has-bigints@^1.0.1, has-bigints@^1.0.2: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== -has-binary2@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA== - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -7265,11 +7196,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -7564,11 +7490,6 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ== - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -8878,16 +8799,6 @@ parse5@^7.1.2: dependencies: entities "^4.4.0" -parseqs@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" - integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== - -parseuri@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" - integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== - parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -9986,31 +9897,23 @@ sliced@^1.0.1: resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA== -socket.io-client@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.1.tgz#91a4038ef4d03c19967bb3c646fec6e0eaa78cff" - integrity sha512-YXmXn3pA8abPOY//JtYxou95Ihvzmg8U6kQyolArkIyLd0pgVhrfor/iMsox8cn07WCOOvvuJ6XKegzIucPutQ== +socket.io-client@*, socket.io-client@4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.2.tgz#f2f13f68058bd4e40f94f2a1541f275157ff2c08" + integrity sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w== dependencies: - backo2 "1.0.2" - component-bind "1.0.0" - component-emitter "~1.3.0" - debug "~3.1.0" - engine.io-client "~3.4.0" - has-binary2 "~1.0.2" - indexof "0.0.1" - parseqs "0.0.6" - parseuri "0.0.6" - socket.io-parser "~3.3.0" - to-array "0.1.4" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" -socket.io-parser@~3.3.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.3.tgz#3a8b84823eba87f3f7624e64a8aaab6d6318a72f" - integrity sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: - component-emitter "~1.3.0" - debug "~3.1.0" - isarray "2.0.1" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" sockjs@^0.3.24: version "0.3.24" @@ -10472,11 +10375,6 @@ tinyspy@^2.2.0: resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A== - to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -11555,12 +11453,10 @@ ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== xml-name-validator@^4.0.0: version "4.0.0" @@ -11572,10 +11468,10 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha512-/bFPLUgJrfGUL10AIv4Y7/CUt6so9CLtB/oFxQSHseSDNNCdC6vwwKEqwLN6wNPBg9YWXAiMu8jkf6RPRS/75Q== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== xmlhttprequest@1.8.0: version "1.8.0" @@ -11628,11 +11524,6 @@ yauzl@^2.10.0: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg== - yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" From 561e919a2e086b6837519e7592828e40ea49f92a Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 16 Dec 2023 11:15:04 +0100 Subject: [PATCH 18/79] fix: import `Socket` as type (#7446) --- excalidraw-app/collab/Portal.tsx | 2 +- excalidraw-app/data/firebase.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx index 67542b0f3..74ef27a05 100644 --- a/excalidraw-app/collab/Portal.tsx +++ b/excalidraw-app/collab/Portal.tsx @@ -18,7 +18,7 @@ import { newElementWith } from "../../packages/excalidraw/element/mutateElement" import { BroadcastedExcalidrawElement } from "./reconciliation"; import { encryptData } from "../../packages/excalidraw/data/encryption"; import { PRECEDING_ELEMENT_KEY } from "../../packages/excalidraw/constants"; -import { Socket } from "socket.io-client"; +import type { Socket } from "socket.io-client"; class Portal { collab: TCollabClass; diff --git a/excalidraw-app/data/firebase.ts b/excalidraw-app/data/firebase.ts index 420f457f5..f37fbbd81 100644 --- a/excalidraw-app/data/firebase.ts +++ b/excalidraw-app/data/firebase.ts @@ -21,7 +21,7 @@ import { MIME_TYPES } from "../../packages/excalidraw/constants"; import { reconcileElements } from "../collab/reconciliation"; import { getSyncableElements, SyncableExcalidrawElement } from "."; import { ResolutionType } from "../../packages/excalidraw/utility-types"; -import { Socket } from "socket.io-client"; +import type { Socket } from "socket.io-client"; // private // ----------------------------------------------------------------------------- From 6dfa89e846df4b51319ae8d91b7382c003b3c293 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 16 Dec 2023 17:32:54 +0100 Subject: [PATCH 19/79] fix: emitted visible scene bounds not accounting for offsets (#7450) --- excalidraw-app/app_constants.ts | 5 +- excalidraw-app/collab/Collab.tsx | 52 +++++++++----------- excalidraw-app/collab/Portal.tsx | 14 +++--- excalidraw-app/data/index.ts | 24 ++++----- packages/excalidraw/CHANGELOG.md | 2 + packages/excalidraw/actions/actionCanvas.tsx | 6 +-- packages/excalidraw/element/bounds.ts | 31 +++++++++++- packages/excalidraw/index.tsx | 2 +- 8 files changed, 82 insertions(+), 54 deletions(-) diff --git a/excalidraw-app/app_constants.ts b/excalidraw-app/app_constants.ts index a20a23506..3402bf106 100644 --- a/excalidraw-app/app_constants.ts +++ b/excalidraw-app/app_constants.ts @@ -20,9 +20,12 @@ export const WS_EVENTS = { } as const; export enum WS_SUBTYPES { + INVALID_RESPONSE = "INVALID_RESPONSE", INIT = "SCENE_INIT", UPDATE = "SCENE_UPDATE", - USER_VIEWPORT_BOUNDS = "USER_VIEWPORT_BOUNDS", + MOUSE_LOCATION = "MOUSE_LOCATION", + IDLE_STATUS = "IDLE_STATUS", + USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS", } export const FIREBASE_STORAGE_PREFIXES = { diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index d55ec5f3d..a0cdc2773 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -18,10 +18,10 @@ import { } from "../../packages/excalidraw/index"; import { Collaborator, Gesture } from "../../packages/excalidraw/types"; import { + assertNever, preventUnload, resolvablePromise, throttleRAF, - viewportCoordsToSceneCoords, withBatchedUpdates, } from "../../packages/excalidraw/utils"; import { @@ -81,7 +81,8 @@ import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; import { atom, useAtom } from "jotai"; import { appJotaiStore } from "../app-jotai"; -import { Mutable } from "../../packages/excalidraw/utility-types"; +import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types"; +import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds"; export const collabAPIAtom = atom(null); export const collabDialogShownAtom = atom(false); @@ -174,7 +175,7 @@ class Collab extends PureComponent { this.portal.socket && this.portal.broadcastUserFollowed(payload); }); const throttledRelayUserViewportBounds = throttleRAF( - this.relayUserViewportBounds, + this.relayVisibleSceneBounds, ); const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() => throttledRelayUserViewportBounds(), @@ -384,7 +385,7 @@ class Collab extends PureComponent { iv: Uint8Array, encryptedData: ArrayBuffer, decryptionKey: string, - ) => { + ): Promise> => { try { const decrypted = await decryptData(iv, encryptedData, decryptionKey); @@ -396,7 +397,7 @@ class Collab extends PureComponent { window.alert(t("alerts.decryptFailed")); console.error(error); return { - type: "INVALID_RESPONSE", + type: WS_SUBTYPES.INVALID_RESPONSE, }; } }; @@ -512,7 +513,7 @@ class Collab extends PureComponent { ); switch (decryptedData.type) { - case "INVALID_RESPONSE": + case WS_SUBTYPES.INVALID_RESPONSE: return; case WS_SUBTYPES.INIT: { if (!this.portal.socketInitialized) { @@ -535,7 +536,7 @@ class Collab extends PureComponent { this.reconcileElements(decryptedData.payload.elements), ); break; - case "MOUSE_LOCATION": { + case WS_SUBTYPES.MOUSE_LOCATION: { const { pointer, button, username, selectedElementIds } = decryptedData.payload; @@ -554,8 +555,8 @@ class Collab extends PureComponent { break; } - case WS_SUBTYPES.USER_VIEWPORT_BOUNDS: { - const { bounds, socketId } = decryptedData.payload; + case WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS: { + const { sceneBounds, socketId } = decryptedData.payload; const appState = this.excalidrawAPI.getAppState(); @@ -579,7 +580,7 @@ class Collab extends PureComponent { this.excalidrawAPI.updateScene({ appState: zoomToFitBounds({ appState, - bounds, + bounds: sceneBounds, fitToViewport: true, viewportZoomFactor: 1, }).appState, @@ -588,7 +589,7 @@ class Collab extends PureComponent { break; } - case "IDLE_STATUS": { + case WS_SUBTYPES.IDLE_STATUS: { const { userState, socketId, username } = decryptedData.payload; this.updateCollaborator(socketId, { userState, @@ -596,6 +597,10 @@ class Collab extends PureComponent { }); break; } + + default: { + assertNever(decryptedData, null); + } } }, ); @@ -618,7 +623,7 @@ class Collab extends PureComponent { appState: { followedBy: new Set(followedBy) }, }); - this.relayUserViewportBounds({ shouldPerform: true }); + this.relayVisibleSceneBounds({ force: true }); }, ); @@ -848,25 +853,14 @@ class Collab extends PureComponent { CURSOR_SYNC_TIMEOUT, ); - relayUserViewportBounds = (props?: { shouldPerform: boolean }) => { + relayVisibleSceneBounds = (props?: { force: boolean }) => { const appState = this.excalidrawAPI.getAppState(); - if ( - this.portal.socket && - (appState.followedBy.size > 0 || props?.shouldPerform) - ) { - const { x: x1, y: y1 } = viewportCoordsToSceneCoords( - { clientX: 0, clientY: 0 }, - appState, - ); - - const { x: x2, y: y2 } = viewportCoordsToSceneCoords( - { clientX: appState.width, clientY: appState.height }, - appState, - ); - - this.portal.broadcastUserViewportBounds( - { bounds: [x1, y1, x2, y2] }, + if (this.portal.socket && (appState.followedBy.size > 0 || props?.force)) { + this.portal.broadcastVisibleSceneBounds( + { + sceneBounds: getVisibleSceneBounds(appState), + }, `follow@${this.portal.socket.id}`, ); } diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx index 74ef27a05..66dd8a56b 100644 --- a/excalidraw-app/collab/Portal.tsx +++ b/excalidraw-app/collab/Portal.tsx @@ -184,7 +184,7 @@ class Portal { broadcastIdleChange = (userState: UserIdleState) => { if (this.socket?.id) { const data: SocketUpdateDataSource["IDLE_STATUS"] = { - type: "IDLE_STATUS", + type: WS_SUBTYPES.IDLE_STATUS, payload: { socketId: this.socket.id, userState, @@ -204,7 +204,7 @@ class Portal { }) => { if (this.socket?.id) { const data: SocketUpdateDataSource["MOUSE_LOCATION"] = { - type: "MOUSE_LOCATION", + type: WS_SUBTYPES.MOUSE_LOCATION, payload: { socketId: this.socket.id, pointer: payload.pointer, @@ -222,19 +222,19 @@ class Portal { } }; - broadcastUserViewportBounds = ( + broadcastVisibleSceneBounds = ( payload: { - bounds: [number, number, number, number]; + sceneBounds: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"]["payload"]["sceneBounds"]; }, roomId: string, ) => { if (this.socket?.id) { - const data: SocketUpdateDataSource["USER_VIEWPORT_BOUNDS"] = { - type: WS_SUBTYPES.USER_VIEWPORT_BOUNDS, + const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = { + type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS, payload: { socketId: this.socket.id, username: this.collab.state.username, - bounds: payload.bounds, + sceneBounds: payload.sceneBounds, }, }; diff --git a/excalidraw-app/data/index.ts b/excalidraw-app/data/index.ts index b162da9a4..fe44dc138 100644 --- a/excalidraw-app/data/index.ts +++ b/excalidraw-app/data/index.ts @@ -10,6 +10,7 @@ import { import { serializeAsJSON } from "../../packages/excalidraw/data/json"; import { restore } from "../../packages/excalidraw/data/restore"; import { ImportedDataState } from "../../packages/excalidraw/data/types"; +import { SceneBounds } from "../../packages/excalidraw/element/bounds"; import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers"; import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks"; import { @@ -28,6 +29,7 @@ import { DELETED_ELEMENT_TIMEOUT, FILE_UPLOAD_MAX_BYTES, ROOM_ID_BYTES, + WS_SUBTYPES, } from "../app_constants"; import { encodeFilesForUpload } from "./FileManager"; import { saveFilesToFirebase } from "./firebase"; @@ -97,20 +99,23 @@ export type EncryptedData = { }; export type SocketUpdateDataSource = { + INVALID_RESPONSE: { + type: WS_SUBTYPES.INVALID_RESPONSE; + }; SCENE_INIT: { - type: "SCENE_INIT"; + type: WS_SUBTYPES.INIT; payload: { elements: readonly ExcalidrawElement[]; }; }; SCENE_UPDATE: { - type: "SCENE_UPDATE"; + type: WS_SUBTYPES.UPDATE; payload: { elements: readonly ExcalidrawElement[]; }; }; MOUSE_LOCATION: { - type: "MOUSE_LOCATION"; + type: WS_SUBTYPES.MOUSE_LOCATION; payload: { socketId: string; pointer: { x: number; y: number; tool: "pointer" | "laser" }; @@ -119,16 +124,16 @@ export type SocketUpdateDataSource = { username: string; }; }; - USER_VIEWPORT_BOUNDS: { - type: "USER_VIEWPORT_BOUNDS"; + USER_VISIBLE_SCENE_BOUNDS: { + type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS; payload: { socketId: string; username: string; - bounds: [number, number, number, number]; + sceneBounds: SceneBounds; }; }; IDLE_STATUS: { - type: "IDLE_STATUS"; + type: WS_SUBTYPES.IDLE_STATUS; payload: { socketId: string; userState: UserIdleState; @@ -138,10 +143,7 @@ export type SocketUpdateDataSource = { }; export type SocketUpdateDataIncoming = - | SocketUpdateDataSource[keyof SocketUpdateDataSource] - | { - type: "INVALID_RESPONSE"; - }; + SocketUpdateDataSource[keyof SocketUpdateDataSource]; export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSource] & { diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index de8ce7227..652c77848 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -13,6 +13,8 @@ Please add the latest change on the top under the correct section. ## Unreleased +- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450) + ### Breaking Changes - `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index 7d57c64a7..ab5f8cfd7 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -20,7 +20,7 @@ import { isHandToolActive, } from "../appState"; import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors"; -import { Bounds } from "../element/bounds"; +import { SceneBounds } from "../element/bounds"; import { setCursor } from "../cursor"; export const actionChangeViewBackgroundColor = register({ @@ -211,7 +211,7 @@ export const actionResetZoom = register({ }); const zoomValueToFitBoundsOnViewport = ( - bounds: Bounds, + bounds: SceneBounds, viewportDimensions: { width: number; height: number }, ) => { const [x1, y1, x2, y2] = bounds; @@ -235,7 +235,7 @@ export const zoomToFitBounds = ({ fitToViewport = false, viewportZoomFactor = 0.7, }: { - bounds: readonly [number, number, number, number]; + bounds: SceneBounds; appState: Readonly; /** whether to fit content to viewport (beyond >100%) */ fitToViewport: boolean; diff --git a/packages/excalidraw/element/bounds.ts b/packages/excalidraw/element/bounds.ts index f8d8223f7..292fc995d 100644 --- a/packages/excalidraw/element/bounds.ts +++ b/packages/excalidraw/element/bounds.ts @@ -9,7 +9,7 @@ import { import { distance2d, rotate, rotatePoint } from "../math"; import rough from "roughjs/bin/rough"; import { Drawable, Op } from "roughjs/bin/core"; -import { Point } from "../types"; +import { AppState, Point } from "../types"; import { generateRoughOptions } from "../scene/Shape"; import { isArrowElement, @@ -35,7 +35,9 @@ export type RectangleBox = { type MaybeQuadraticSolution = [number | null, number | null] | false; -// x and y position of top left corner, x and y position of bottom right corner +/** + * x and y position of top left corner, x and y position of bottom right corner + */ export type Bounds = readonly [ minX: number, minY: number, @@ -43,6 +45,13 @@ export type Bounds = readonly [ maxY: number, ]; +export type SceneBounds = readonly [ + sceneX: number, + sceneY: number, + sceneX2: number, + sceneY2: number, +]; + export class ElementBounds { private static boundsCache = new WeakMap< ExcalidrawElement, @@ -879,3 +888,21 @@ export const getCommonBoundingBox = ( midY: (minY + maxY) / 2, }; }; + +/** + * returns scene coords of user's editor viewport (visible canvas area) bounds + */ +export const getVisibleSceneBounds = ({ + scrollX, + scrollY, + width, + height, + zoom, +}: AppState): SceneBounds => { + return [ + -scrollX, + -scrollY, + -scrollX + width / zoom.value, + -scrollY + height / zoom.value, + ]; +}; diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index 74aa6d8d5..915836cb8 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -249,7 +249,7 @@ export { TTDDialogTrigger } from "./components/TTDDialog/TTDDialogTrigger"; export { normalizeLink } from "./data/url"; export { zoomToFitBounds } from "./actions/actionCanvas"; export { convertToExcalidrawElements } from "./data/transform"; -export { getCommonBounds } from "./element/bounds"; +export { getCommonBounds, getVisibleSceneBounds } from "./element/bounds"; export { elementsOverlappingBBox, From 537f6e7f6840b8e7cf95382fcd37becec968b5df Mon Sep 17 00:00:00 2001 From: Jason Praful Date: Sat, 16 Dec 2023 18:18:35 +0000 Subject: [PATCH 20/79] docs: add steps for local development (#7449) * docs: add steps for local development #7434 * docs: minor tweaks --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a8a3f908..e8cd3b06f 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ We'll be adding these features as drop-in plugins for the npm package in the fut ## Quick start -Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw): +**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development). ``` npm install react react-dom @excalidraw/excalidraw @@ -97,7 +97,7 @@ or via yarn yarn add react react-dom @excalidraw/excalidraw ``` -Don't forget to check out our [Documentation](https://docs.excalidraw.com)! +Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details! ## Contributing From 7bd6496854d95ea3b25576308a4b47cdac20b3fa Mon Sep 17 00:00:00 2001 From: Adithyan <123532141+golok727@users.noreply.github.com> Date: Sat, 16 Dec 2023 23:53:11 +0530 Subject: [PATCH 21/79] refactor: Fix Typo (#7445) --- packages/excalidraw/components/App.tsx | 26 ++++++++++++++------------ packages/excalidraw/types.ts | 6 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index ff80ed5b0..cdecbf342 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -439,7 +439,7 @@ ExcalidrawAppStateContext.displayName = "ExcalidrawAppStateContext"; const ExcalidrawSetAppStateContext = React.createContext< React.Component["setState"] >(() => { - console.warn("unitialized ExcalidrawSetAppStateContext context!"); + console.warn("Uninitialized ExcalidrawSetAppStateContext context!"); }); ExcalidrawSetAppStateContext.displayName = "ExcalidrawSetAppStateContext"; @@ -2867,7 +2867,6 @@ class App extends React.Component { // event else some browsers (FF...) will clear the clipboardData // (something something security) let file = event?.clipboardData?.files[0]; - const data = await parseClipboard(event, isPlainPaste); if (!file && !isPlainPaste) { if (data.mixedContent) { @@ -3370,7 +3369,7 @@ class App extends React.Component { }); }; - private cancelInProgresAnimation: (() => void) | null = null; + private cancelInProgressAnimation: (() => void) | null = null; scrollToContent = ( target: @@ -3395,7 +3394,7 @@ class App extends React.Component { duration?: number; }, ) => { - this.cancelInProgresAnimation?.(); + this.cancelInProgressAnimation?.(); // convert provided target into ExcalidrawElement[] if necessary const targetElements = Array.isArray(target) ? target : [target]; @@ -3462,9 +3461,9 @@ class App extends React.Component { duration: opts?.duration ?? 500, }); - this.cancelInProgresAnimation = () => { + this.cancelInProgressAnimation = () => { cancel(); - this.cancelInProgresAnimation = null; + this.cancelInProgressAnimation = null; }; } else { this.setState({ scrollX, scrollY, zoom }); @@ -3481,7 +3480,7 @@ class App extends React.Component { private translateCanvas: React.Component["setState"] = ( state, ) => { - this.cancelInProgresAnimation?.(); + this.cancelInProgressAnimation?.(); this.maybeUnfollowRemoteUser(); this.setState(state); }; @@ -7779,7 +7778,7 @@ class App extends React.Component { ), }; }); - // if not gragging a linear element point (outside editor) + // if not dragging a linear element point (outside editor) } else if (!this.state.selectedLinearElement?.isDragging) { // remove element from selection while // keeping prev elements selected @@ -8104,7 +8103,10 @@ class App extends React.Component { maxWidthOrHeight: DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, }); } catch (error: any) { - console.error("error trying to resing image file on insertion", error); + console.error( + "Error trying to resizing image file on insertion", + error, + ); } if (imageFile.size > MAX_ALLOWED_FILE_BYTES) { @@ -8647,7 +8649,7 @@ class App extends React.Component { } if (file) { - // atetmpt to parse an excalidraw/excalidrawlib file + // Attempt to parse an excalidraw/excalidrawlib file await this.loadFileToCanvas(file, fileHandle); } @@ -8746,13 +8748,13 @@ class App extends React.Component { }); const selectedElements = this.scene.getSelectedElements(this.state); - const isHittignCommonBoundBox = + const isHittingCommonBoundBox = this.isHittingCommonBoundingBoxOfSelectedElements( { x, y }, selectedElements, ); - const type = element || isHittignCommonBoundBox ? "element" : "canvas"; + const type = element || isHittingCommonBoundBox ? "element" : "canvas"; const container = this.excalidrawContainerRef.current!; const { top: offsetTop, left: offsetLeft } = diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index a4a0b0113..249ead30d 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -53,7 +53,7 @@ export type Collaborator = Readonly<{ background: string; stroke: string; }; - // The url of the collaborator's avatar, defaults to username intials + // The url of the collaborator's avatar, defaults to username initials // if not present avatarUrl?: string; // user id. If supplied, we'll filter out duplicates when rendering user avatars. @@ -500,10 +500,10 @@ export type ExportOpts = { ) => JSX.Element; }; -// NOTE at the moment, if action name coressponds to canvasAction prop, its +// NOTE at the moment, if action name corresponds to canvasAction prop, its // truthiness value will determine whether the action is rendered or not // (see manager renderAction). We also override canvasAction values in -// excalidraw package index.tsx. +// Excalidraw package index.tsx. export type CanvasActions = Partial<{ changeViewBackgroundColor: boolean; clearCanvas: boolean; From 2a0fe2584e781b532006c0477aea6d491753a8ba Mon Sep 17 00:00:00 2001 From: Lynda Lin Date: Mon, 18 Dec 2023 20:42:17 +0800 Subject: [PATCH 22/79] fix: empty snapLines arrays would cause re-render (#7454) Co-authored-by: Lynda Lin Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/components/App.tsx | 35 ++++++++--- .../tests/__snapshots__/move.test.tsx.snap | 12 ++-- .../multiPointCreate.test.tsx.snap | 4 +- .../tests/linearElementEditor.test.tsx | 62 ++++++++++++------- packages/excalidraw/tests/move.test.tsx | 30 +++++---- .../tests/multiPointCreate.test.tsx | 20 +++--- packages/excalidraw/utils.ts | 40 ++++++++++-- 7 files changed, 139 insertions(+), 64 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index cdecbf342..71d6ea821 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -268,6 +268,7 @@ import { muteFSAbortError, isTestEnv, easeOut, + updateStable, } from "../utils"; import { createSrcDoc, @@ -4736,13 +4737,31 @@ class App extends React.Component { event, ); - this.setState({ - snapLines, - originSnapOffset: originOffset, + this.setState((prevState) => { + const nextSnapLines = updateStable(prevState.snapLines, snapLines); + const nextOriginOffset = prevState.originSnapOffset + ? updateStable(prevState.originSnapOffset, originOffset) + : originOffset; + + if ( + prevState.snapLines === nextSnapLines && + prevState.originSnapOffset === nextOriginOffset + ) { + return null; + } + return { + snapLines: nextSnapLines, + originSnapOffset: nextOriginOffset, + }; }); } else if (!this.state.draggingElement) { - this.setState({ - snapLines: [], + this.setState((prevState) => { + if (prevState.snapLines.length) { + return { + snapLines: [], + }; + } + return null; }); } @@ -7227,7 +7246,7 @@ class App extends React.Component { isRotating, } = this.state; - this.setState({ + this.setState((prevState) => ({ isResizing: false, isRotating: false, resizingElement: null, @@ -7241,10 +7260,10 @@ class App extends React.Component { multiElement || isTextElement(this.state.editingElement) ? this.state.editingElement : null, - snapLines: [], + snapLines: updateStable(prevState.snapLines, []), originSnapOffset: null, - }); + })); SnapCache.setReferenceSnapPoints(null); SnapCache.setVisibleGaps(null); diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index 015ff7c4c..f348d0501 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`duplicate element on move when ALT is clicked > rectangle 1`] = ` +exports[`duplicate element on move when ALT is clicked > rectangle 5`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -32,7 +32,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 1`] = ` } `; -exports[`duplicate element on move when ALT is clicked > rectangle 2`] = ` +exports[`duplicate element on move when ALT is clicked > rectangle 6`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -64,7 +64,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 2`] = ` } `; -exports[`move element > rectangle 1`] = ` +exports[`move element > rectangle 5`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -96,7 +96,7 @@ exports[`move element > rectangle 1`] = ` } `; -exports[`move element > rectangles with binding arrow 1`] = ` +exports[`move element > rectangles with binding arrow 5`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -133,7 +133,7 @@ exports[`move element > rectangles with binding arrow 1`] = ` } `; -exports[`move element > rectangles with binding arrow 2`] = ` +exports[`move element > rectangles with binding arrow 6`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -170,7 +170,7 @@ exports[`move element > rectangles with binding arrow 2`] = ` } `; -exports[`move element > rectangles with binding arrow 3`] = ` +exports[`move element > rectangles with binding arrow 7`] = ` { "angle": 0, "backgroundColor": "transparent", diff --git a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap index 48ae1f640..12e7e61ed 100644 --- a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`multi point mode in linear elements > arrow 1`] = ` +exports[`multi point mode in linear elements > arrow 3`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -54,7 +54,7 @@ exports[`multi point mode in linear elements > arrow 1`] = ` } `; -exports[`multi point mode in linear elements > line 1`] = ` +exports[`multi point mode in linear elements > line 3`] = ` { "angle": 0, "backgroundColor": "transparent", diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index 6c44c6cd8..f4ddeafd2 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -173,14 +173,14 @@ describe("Test Linear Elements", () => { createTwoPointerLinearElement("line"); const line = h.elements[0] as ExcalidrawLinearElement; - expect(renderInteractiveScene).toHaveBeenCalledTimes(5); - expect(renderStaticScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`5`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`5`); expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2); // drag line from midpoint drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(9); - expect(renderStaticScene).toHaveBeenCalledTimes(7); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`7`); expect(line.points.length).toEqual(3); expect(line.points).toMatchInlineSnapshot(` [ @@ -273,8 +273,10 @@ describe("Test Linear Elements", () => { // drag line from midpoint drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(14); - expect(renderStaticScene).toHaveBeenCalledTimes(6); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `12`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`6`); expect(line.points.length).toEqual(3); expect(line.points).toMatchInlineSnapshot(` @@ -311,8 +313,10 @@ describe("Test Linear Elements", () => { // update roundness fireEvent.click(screen.getByTitle("Round")); - expect(renderInteractiveScene).toHaveBeenCalledTimes(10); - expect(renderStaticScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `10`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`); const midPointsWithRoundEdge = LinearElementEditor.getEditorMidPoints( h.elements[0] as ExcalidrawLinearElement, @@ -357,8 +361,10 @@ describe("Test Linear Elements", () => { // Move the element drag(startPoint, endPoint); - expect(renderInteractiveScene).toHaveBeenCalledTimes(14); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `13`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); expect([line.x, line.y]).toEqual([ points[0][0] + deltaX, @@ -416,8 +422,10 @@ describe("Test Linear Elements", () => { lastSegmentMidpoint[1] + delta, ]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(21); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `17`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); expect(line.points.length).toEqual(5); @@ -457,8 +465,10 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(14); - expect(renderStaticScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `13`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ @@ -484,8 +494,10 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(14); - expect(renderStaticScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `13`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ @@ -519,8 +531,10 @@ describe("Test Linear Elements", () => { // delete 3rd point deletePoint(points[2]); expect(line.points.length).toEqual(3); - expect(renderInteractiveScene).toHaveBeenCalledTimes(21); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `19`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); const newMidPoints = LinearElementEditor.getEditorMidPoints( line, @@ -566,8 +580,10 @@ describe("Test Linear Elements", () => { lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta, ]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(21); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `17`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); expect(line.points.length).toEqual(5); expect((h.elements[0] as ExcalidrawLinearElement).points) @@ -642,8 +658,10 @@ describe("Test Linear Elements", () => { // Drag from first point drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]); - expect(renderInteractiveScene).toHaveBeenCalledTimes(14); - expect(renderStaticScene).toHaveBeenCalledTimes(8); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `13`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`); const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line); expect([newPoints[0][0], newPoints[0][1]]).toEqual([ diff --git a/packages/excalidraw/tests/move.test.tsx b/packages/excalidraw/tests/move.test.tsx index 248f43d72..22d828ee9 100644 --- a/packages/excalidraw/tests/move.test.tsx +++ b/packages/excalidraw/tests/move.test.tsx @@ -42,8 +42,10 @@ describe("move element", () => { fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 }); fireEvent.pointerUp(canvas); - expect(renderInteractiveScene).toHaveBeenCalledTimes(6); - expect(renderStaticScene).toHaveBeenCalledTimes(6); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `6`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`6`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -57,8 +59,8 @@ describe("move element", () => { fireEvent.pointerMove(canvas, { clientX: 20, clientY: 40 }); fireEvent.pointerUp(canvas); - expect(renderInteractiveScene).toHaveBeenCalledTimes(3); - expect(renderStaticScene).toHaveBeenCalledTimes(2); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`3`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`2`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect([h.elements[0].x, h.elements[0].y]).toEqual([0, 40]); @@ -84,8 +86,10 @@ describe("move element", () => { // select the second rectangles new Pointer("mouse").clickOn(rectB); - expect(renderInteractiveScene).toHaveBeenCalledTimes(24); - expect(renderStaticScene).toHaveBeenCalledTimes(19); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `21`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`19`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(3); expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); @@ -103,8 +107,8 @@ describe("move element", () => { Keyboard.keyDown(KEYS.ARROW_DOWN); // Check that the arrow size has been changed according to moving the rectangle - expect(renderInteractiveScene).toHaveBeenCalledTimes(3); - expect(renderStaticScene).toHaveBeenCalledTimes(3); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`3`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`3`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(3); expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); @@ -130,8 +134,10 @@ describe("duplicate element on move when ALT is clicked", () => { fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 }); fireEvent.pointerUp(canvas); - expect(renderInteractiveScene).toHaveBeenCalledTimes(6); - expect(renderStaticScene).toHaveBeenCalledTimes(6); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot( + `6`, + ); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`6`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(1); expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy(); @@ -152,8 +158,8 @@ describe("duplicate element on move when ALT is clicked", () => { // TODO: This used to be 4, but binding made it go up to 5. Do we need // that additional render? - expect(renderInteractiveScene).toHaveBeenCalledTimes(5); - expect(renderStaticScene).toHaveBeenCalledTimes(3); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`4`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`3`); expect(h.state.selectionElement).toBeNull(); expect(h.elements.length).toEqual(2); diff --git a/packages/excalidraw/tests/multiPointCreate.test.tsx b/packages/excalidraw/tests/multiPointCreate.test.tsx index 93256d07c..f462cfacf 100644 --- a/packages/excalidraw/tests/multiPointCreate.test.tsx +++ b/packages/excalidraw/tests/multiPointCreate.test.tsx @@ -47,8 +47,8 @@ describe("remove shape in non linear elements", () => { fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderInteractiveScene).toHaveBeenCalledTimes(5); - expect(renderStaticScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`5`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`5`); expect(h.elements.length).toEqual(0); }); @@ -62,8 +62,8 @@ describe("remove shape in non linear elements", () => { fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderInteractiveScene).toHaveBeenCalledTimes(5); - expect(renderStaticScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`5`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`5`); expect(h.elements.length).toEqual(0); }); @@ -77,8 +77,8 @@ describe("remove shape in non linear elements", () => { fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); - expect(renderInteractiveScene).toHaveBeenCalledTimes(5); - expect(renderStaticScene).toHaveBeenCalledTimes(5); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`5`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`5`); expect(h.elements.length).toEqual(0); }); }); @@ -110,8 +110,8 @@ describe("multi point mode in linear elements", () => { key: KEYS.ENTER, }); - expect(renderInteractiveScene).toHaveBeenCalledTimes(11); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); expect(h.elements.length).toEqual(1); const element = h.elements[0] as ExcalidrawLinearElement; @@ -153,8 +153,8 @@ describe("multi point mode in linear elements", () => { fireEvent.keyDown(document, { key: KEYS.ENTER, }); - expect(renderInteractiveScene).toHaveBeenCalledTimes(11); - expect(renderStaticScene).toHaveBeenCalledTimes(9); + expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`); + expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`9`); expect(h.elements.length).toEqual(1); const element = h.elements[0] as ExcalidrawLinearElement; diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 69b3426e4..4278f36f6 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -769,6 +769,24 @@ export const queryFocusableElements = (container: HTMLElement | null) => { : []; }; +/** use as a fallback after identity check (for perf reasons) */ +const _defaultIsShallowComparatorFallback = (a: any, b: any): boolean => { + // consider two empty arrays equal + if ( + Array.isArray(a) && + Array.isArray(b) && + a.length === 0 && + b.length === 0 + ) { + return true; + } + return a === b; +}; + +/** + * Returns whether object/array is shallow equal. + * Considers empty object/arrays as equal (whether top-level or second-level). + */ export const isShallowEqual = < T extends Record, K extends readonly unknown[], @@ -796,10 +814,12 @@ export const isShallowEqual = < if (comparators && Array.isArray(comparators)) { for (const key of comparators) { - const ret = objA[key] === objB[key]; + const ret = + objA[key] === objB[key] || + _defaultIsShallowComparatorFallback(objA[key], objB[key]); if (!ret) { if (debug) { - console.info( + console.warn( `%cisShallowEqual: ${key} not equal ->`, "color: #8B4000", objA[key], @@ -818,9 +838,11 @@ export const isShallowEqual = < )?.[key as keyof T]; const ret = comparator ? comparator(objA[key], objB[key]) - : objA[key] === objB[key]; + : objA[key] === objB[key] || + _defaultIsShallowComparatorFallback(objA[key], objB[key]); + if (!ret && debug) { - console.info( + console.warn( `%cisShallowEqual: ${key} not equal ->`, "color: #8B4000", objA[key], @@ -960,3 +982,13 @@ export const cloneJSON = (obj: T): T => JSON.parse(JSON.stringify(obj)); export const isFiniteNumber = (value: any): value is number => { return typeof value === "number" && Number.isFinite(value); }; + +export const updateStable = >( + prevValue: T, + nextValue: T, +) => { + if (isShallowEqual(prevValue, nextValue)) { + return prevValue; + } + return nextValue; +}; From 0808532b494cda95815d95a8a9a754a5106a4fc2 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:14:25 +0100 Subject: [PATCH 23/79] fix: follow mode collaborator status indicator (#7459) --- .../excalidraw/actions/actionNavigate.tsx | 30 ++++++---- packages/excalidraw/components/App.tsx | 1 + packages/excalidraw/components/LayerUI.tsx | 5 +- packages/excalidraw/components/Tooltip.tsx | 5 ++ packages/excalidraw/components/UserList.scss | 7 +++ packages/excalidraw/components/UserList.tsx | 57 ++++++++++++------- .../components/main-menu/MainMenu.tsx | 1 + packages/excalidraw/locales/en.json | 3 +- 8 files changed, 75 insertions(+), 34 deletions(-) diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 46492c733..1c55f1046 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -1,5 +1,8 @@ import { getClientColor } from "../clients"; import { Avatar } from "../components/Avatar"; +import { GoToCollaboratorComponentProps } from "../components/UserList"; +import { eyeIcon } from "../components/icons"; +import { t } from "../i18n"; import { Collaborator } from "../types"; import { register } from "./register"; @@ -35,38 +38,43 @@ export const actionGoToCollaborator = register({ }; }, PanelComponent: ({ updateData, data, appState }) => { - const [clientId, collaborator, withName] = data as [ - string, - Collaborator, - boolean, - ]; + const [socketId, collaborator, withName, isBeingFollowed] = + data as GoToCollaboratorComponentProps; - const background = getClientColor(clientId); + const background = getClientColor(socketId); return withName ? (
updateData({ ...collaborator, clientId })} + className="dropdown-menu-item dropdown-menu-item-base UserList__collaborator" + onClick={() => updateData({ ...collaborator, socketId })} > {}} name={collaborator.username || ""} src={collaborator.avatarUrl} - isBeingFollowed={appState.userToFollow?.socketId === clientId} + isBeingFollowed={isBeingFollowed} isCurrentUser={collaborator.isCurrentUser === true} /> {collaborator.username} +
+ {eyeIcon} +
) : ( { - updateData({ ...collaborator, clientId }); + updateData({ ...collaborator, socketId }); }} name={collaborator.username || ""} src={collaborator.avatarUrl} - isBeingFollowed={appState.userToFollow?.socketId === clientId} + isBeingFollowed={isBeingFollowed} isCurrentUser={collaborator.isCurrentUser === true} /> ); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 71d6ea821..5f3f8be28 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3472,6 +3472,7 @@ class App extends React.Component { }; private maybeUnfollowRemoteUser = () => { + console.warn("maybeUnfollowRemoteUser"); if (this.state.userToFollow) { this.setState({ userToFollow: null }); } diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index c7866e7a8..7dd362063 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -339,7 +339,10 @@ const LayerUI = ({ )} > {appState.collaborators.size > 0 && ( - + )} {renderTopRightUI?.(device.editor.isMobile, appState)} {!appState.viewModeEnabled && diff --git a/packages/excalidraw/components/Tooltip.tsx b/packages/excalidraw/components/Tooltip.tsx index 220de2831..38c04ef23 100644 --- a/packages/excalidraw/components/Tooltip.tsx +++ b/packages/excalidraw/components/Tooltip.tsx @@ -80,6 +80,7 @@ type TooltipProps = { label: string; long?: boolean; style?: React.CSSProperties; + disabled?: boolean; }; export const Tooltip = ({ @@ -87,11 +88,15 @@ export const Tooltip = ({ label, long = false, style, + disabled, }: TooltipProps) => { useEffect(() => { return () => getTooltipDiv().classList.remove("excalidraw-tooltip--visible"); }, []); + if (disabled) { + return null; + } return (
shouldWrap ? ( - + {children} ) : ( - {children} + {children} ); const renderCollaborator = ({ actionManager, collaborator, - clientId, + socketId, withName = false, shouldWrapWithTooltip = false, + isBeingFollowed, }: { actionManager: ActionManager; collaborator: Collaborator; - clientId: string; + socketId: string; withName?: boolean; shouldWrapWithTooltip?: boolean; + isBeingFollowed: boolean; }) => { - const avatarJSX = actionManager.renderAction("goToCollaborator", [ - clientId, + const data: GoToCollaboratorComponentProps = [ + socketId, collaborator, withName, - ]); + isBeingFollowed, + ]; + const avatarJSX = actionManager.renderAction("goToCollaborator", data); return ( @@ -75,6 +86,7 @@ type UserListProps = { className?: string; mobile?: boolean; collaborators: Map; + userToFollow: SocketId | null; }; const collaboratorComparatorKeys = [ @@ -85,7 +97,7 @@ const collaboratorComparatorKeys = [ ] as const; export const UserList = React.memo( - ({ className, mobile, collaborators }: UserListProps) => { + ({ className, mobile, collaborators, userToFollow }: UserListProps) => { const actionManager = useExcalidrawActionManager(); const uniqueCollaboratorsMap = new Map(); @@ -98,7 +110,6 @@ export const UserList = React.memo( ); }); - // const uniqueCollaboratorsMap = sampleCollaborators; const uniqueCollaboratorsArray = Array.from(uniqueCollaboratorsMap).filter( ([_, collaborator]) => collaborator.username?.trim(), ); @@ -123,23 +134,25 @@ export const UserList = React.memo( ); const firstNAvatarsJSX = firstNCollaborators.map( - ([clientId, collaborator]) => + ([socketId, collaborator]) => renderCollaborator({ actionManager, collaborator, - clientId, + socketId, shouldWrapWithTooltip: true, + isBeingFollowed: socketId === userToFollow, }), ); return mobile ? (
- {uniqueCollaboratorsArray.map(([clientId, collaborator]) => + {uniqueCollaboratorsArray.map(([socketId, collaborator]) => renderCollaborator({ actionManager, collaborator, - clientId, + socketId, shouldWrapWithTooltip: true, + isBeingFollowed: socketId === userToFollow, }), )}
@@ -161,7 +174,7 @@ export const UserList = React.memo( {t("userList.hint.text")}
- {filteredCollaborators.map(([clientId, collaborator]) => + {filteredCollaborators.map(([socketId, collaborator]) => renderCollaborator({ actionManager, collaborator, - clientId, + socketId, withName: true, + isBeingFollowed: socketId === userToFollow, }), )}
@@ -212,7 +226,8 @@ export const UserList = React.memo( if ( prev.collaborators.size !== next.collaborators.size || prev.mobile !== next.mobile || - prev.className !== next.className + prev.className !== next.className || + prev.userToFollow !== next.userToFollow ) { return false; } diff --git a/packages/excalidraw/components/main-menu/MainMenu.tsx b/packages/excalidraw/components/main-menu/MainMenu.tsx index 37450ca5e..07afd3c1d 100644 --- a/packages/excalidraw/components/main-menu/MainMenu.tsx +++ b/packages/excalidraw/components/main-menu/MainMenu.tsx @@ -60,6 +60,7 @@ const MainMenu = Object.assign( )} diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index dc5e86f4a..a57b823d4 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -528,7 +528,8 @@ "empty": "No users found" }, "hint": { - "text": "Click on user to follow" + "text": "Click on user to follow", + "followStatus": "You're currently following this user" } } } From 57ea4e61d163318edf0f71bec566439821ad7f1b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:21:57 +0100 Subject: [PATCH 24/79] fix: mixing clientId & socketId in UserList (#7461) --- excalidraw-app/collab/Collab.tsx | 6 +-- excalidraw-app/collab/Portal.tsx | 9 ++-- excalidraw-app/data/index.ts | 7 +-- .../excalidraw/actions/actionNavigate.tsx | 3 +- packages/excalidraw/components/App.tsx | 1 - .../components/LaserTool/LaserPathManager.ts | 3 +- packages/excalidraw/components/UserList.tsx | 46 ++++++++++--------- packages/excalidraw/types.ts | 10 ++-- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index a0cdc2773..9b26af054 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -123,7 +123,7 @@ class Collab extends PureComponent { private socketInitializationTimer?: number; private lastBroadcastedOrReceivedSceneVersion: number = -1; - private collaborators = new Map(); + private collaborators = new Map(); constructor(props: Props) { super(props); @@ -618,7 +618,7 @@ class Collab extends PureComponent { this.portal.socket.on( WS_EVENTS.USER_FOLLOW_ROOM_CHANGE, - (followedBy: string[]) => { + (followedBy: SocketId[]) => { this.excalidrawAPI.updateScene({ appState: { followedBy: new Set(followedBy) }, }); @@ -795,7 +795,7 @@ class Collab extends PureComponent { document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange); }; - setCollaborators(sockets: string[]) { + setCollaborators(sockets: SocketId[]) { const collaborators: InstanceType["collaborators"] = new Map(); for (const socketId of sockets) { diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx index 66dd8a56b..bf8ffa5de 100644 --- a/excalidraw-app/collab/Portal.tsx +++ b/excalidraw-app/collab/Portal.tsx @@ -10,6 +10,7 @@ import { ExcalidrawElement } from "../../packages/excalidraw/element/types"; import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants"; import { OnUserFollowedPayload, + SocketId, UserIdleState, } from "../../packages/excalidraw/types"; import { trackEvent } from "../../packages/excalidraw/analytics"; @@ -51,7 +52,7 @@ class Portal { /* syncAll */ true, ); }); - this.socket.on("room-user-change", (clients: string[]) => { + this.socket.on("room-user-change", (clients: SocketId[]) => { this.collab.setCollaborators(clients); }); @@ -186,7 +187,7 @@ class Portal { const data: SocketUpdateDataSource["IDLE_STATUS"] = { type: WS_SUBTYPES.IDLE_STATUS, payload: { - socketId: this.socket.id, + socketId: this.socket.id as SocketId, userState, username: this.collab.state.username, }, @@ -206,7 +207,7 @@ class Portal { const data: SocketUpdateDataSource["MOUSE_LOCATION"] = { type: WS_SUBTYPES.MOUSE_LOCATION, payload: { - socketId: this.socket.id, + socketId: this.socket.id as SocketId, pointer: payload.pointer, button: payload.button || "up", selectedElementIds: @@ -232,7 +233,7 @@ class Portal { const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = { type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS, payload: { - socketId: this.socket.id, + socketId: this.socket.id as SocketId, username: this.collab.state.username, sceneBounds: payload.sceneBounds, }, diff --git a/excalidraw-app/data/index.ts b/excalidraw-app/data/index.ts index fe44dc138..0f54ee880 100644 --- a/excalidraw-app/data/index.ts +++ b/excalidraw-app/data/index.ts @@ -22,6 +22,7 @@ import { AppState, BinaryFileData, BinaryFiles, + SocketId, UserIdleState, } from "../../packages/excalidraw/types"; import { bytesToHexString } from "../../packages/excalidraw/utils"; @@ -117,7 +118,7 @@ export type SocketUpdateDataSource = { MOUSE_LOCATION: { type: WS_SUBTYPES.MOUSE_LOCATION; payload: { - socketId: string; + socketId: SocketId; pointer: { x: number; y: number; tool: "pointer" | "laser" }; button: "down" | "up"; selectedElementIds: AppState["selectedElementIds"]; @@ -127,7 +128,7 @@ export type SocketUpdateDataSource = { USER_VISIBLE_SCENE_BOUNDS: { type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS; payload: { - socketId: string; + socketId: SocketId; username: string; sceneBounds: SceneBounds; }; @@ -135,7 +136,7 @@ export type SocketUpdateDataSource = { IDLE_STATUS: { type: WS_SUBTYPES.IDLE_STATUS; payload: { - socketId: string; + socketId: SocketId; userState: UserIdleState; username: string; }; diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 1c55f1046..8e8d30231 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -12,6 +12,7 @@ export const actionGoToCollaborator = register({ trackEvent: { category: "collab" }, perform: (_elements, appState, collaborator: Collaborator) => { if ( + !collaborator.socketId || appState.userToFollow?.socketId === collaborator.socketId || collaborator.isCurrentUser ) { @@ -28,7 +29,7 @@ export const actionGoToCollaborator = register({ appState: { ...appState, userToFollow: { - socketId: collaborator.socketId!, + socketId: collaborator.socketId, username: collaborator.username || "", }, // Close mobile menu diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 5f3f8be28..71d6ea821 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3472,7 +3472,6 @@ class App extends React.Component { }; private maybeUnfollowRemoteUser = () => { - console.warn("maybeUnfollowRemoteUser"); if (this.state.userToFollow) { this.setState({ userToFollow: null }); } diff --git a/packages/excalidraw/components/LaserTool/LaserPathManager.ts b/packages/excalidraw/components/LaserTool/LaserPathManager.ts index 2f0c63955..b6e462aa3 100644 --- a/packages/excalidraw/components/LaserTool/LaserPathManager.ts +++ b/packages/excalidraw/components/LaserTool/LaserPathManager.ts @@ -3,6 +3,7 @@ import { LaserPointer } from "@excalidraw/laser-pointer"; import { sceneCoordsToViewportCoords } from "../../utils"; import App from "../App"; import { getClientColor } from "../../clients"; +import { SocketId } from "../../types"; // decay time in milliseconds const DECAY_TIME = 1000; @@ -88,7 +89,7 @@ type CollabolatorState = { export class LaserPathManager { private ownState: CollabolatorState; - private collaboratorsState: Map = new Map(); + private collaboratorsState: Map = new Map(); private rafId: number | undefined; private isDrawing = false; diff --git a/packages/excalidraw/components/UserList.tsx b/packages/excalidraw/components/UserList.tsx index 4b6aa8e44..2791d9ef4 100644 --- a/packages/excalidraw/components/UserList.tsx +++ b/packages/excalidraw/components/UserList.tsx @@ -14,51 +14,54 @@ import { t } from "../i18n"; import { isShallowEqual } from "../utils"; export type GoToCollaboratorComponentProps = [ - SocketId, + ClientId, Collaborator, boolean, boolean, ]; +/** collaborator user id or socket id (fallback) */ +type ClientId = string & { _brand: "UserId" }; + const FIRST_N_AVATARS = 3; const SHOW_COLLABORATORS_FILTER_AT = 8; const ConditionalTooltipWrapper = ({ shouldWrap, children, - socketId, + clientId, username, }: { shouldWrap: boolean; children: React.ReactNode; username?: string | null; - socketId: string; + clientId: ClientId; }) => shouldWrap ? ( - + {children} ) : ( - {children} + {children} ); const renderCollaborator = ({ actionManager, collaborator, - socketId, + clientId, withName = false, shouldWrapWithTooltip = false, isBeingFollowed, }: { actionManager: ActionManager; collaborator: Collaborator; - socketId: string; + clientId: ClientId; withName?: boolean; shouldWrapWithTooltip?: boolean; isBeingFollowed: boolean; }) => { const data: GoToCollaboratorComponentProps = [ - socketId, + clientId, collaborator, withName, isBeingFollowed, @@ -67,8 +70,8 @@ const renderCollaborator = ({ return ( @@ -100,12 +103,13 @@ export const UserList = React.memo( ({ className, mobile, collaborators, userToFollow }: UserListProps) => { const actionManager = useExcalidrawActionManager(); - const uniqueCollaboratorsMap = new Map(); + const uniqueCollaboratorsMap = new Map(); collaborators.forEach((collaborator, socketId) => { + const userId = (collaborator.id || socketId) as ClientId; uniqueCollaboratorsMap.set( // filter on user id, else fall back on unique socketId - collaborator.id || socketId, + userId, { ...collaborator, socketId }, ); }); @@ -134,25 +138,25 @@ export const UserList = React.memo( ); const firstNAvatarsJSX = firstNCollaborators.map( - ([socketId, collaborator]) => + ([clientId, collaborator]) => renderCollaborator({ actionManager, collaborator, - socketId, + clientId, shouldWrapWithTooltip: true, - isBeingFollowed: socketId === userToFollow, + isBeingFollowed: collaborator.socketId === userToFollow, }), ); return mobile ? (
- {uniqueCollaboratorsArray.map(([socketId, collaborator]) => + {uniqueCollaboratorsArray.map(([clientId, collaborator]) => renderCollaborator({ actionManager, collaborator, - socketId, + clientId, shouldWrapWithTooltip: true, - isBeingFollowed: socketId === userToFollow, + isBeingFollowed: collaborator.socketId === userToFollow, }), )}
@@ -205,13 +209,13 @@ export const UserList = React.memo(
{t("userList.hint.text")}
- {filteredCollaborators.map(([socketId, collaborator]) => + {filteredCollaborators.map(([clientId, collaborator]) => renderCollaborator({ actionManager, collaborator, - socketId, + clientId, withName: true, - isBeingFollowed: socketId === userToFollow, + isBeingFollowed: collaborator.socketId === userToFollow, }), )}
diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 249ead30d..854a27519 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -41,7 +41,7 @@ import { Merge, ValueOf } from "./utility-types"; export type Point = Readonly; -export type SocketId = string; +export type SocketId = string & { _brand: "SocketId" }; export type Collaborator = Readonly<{ pointer?: CollaboratorPointer; @@ -128,7 +128,7 @@ export type SidebarName = string; export type SidebarTabName = string; export type UserToFollow = { - socketId: string; + socketId: SocketId; username: string; }; @@ -296,7 +296,7 @@ export interface AppState { offsetLeft: number; fileHandle: FileSystemHandle | null; - collaborators: Map; + collaborators: Map; showStats: boolean; currentChartType: ChartType; pasteDialog: @@ -321,7 +321,7 @@ export interface AppState { /** the user's clientId & username who is being followed on the canvas */ userToFollow: UserToFollow | null; /** the clientIds of the users following the current user */ - followedBy: Set; + followedBy: Set; } export type UIAppState = Omit< @@ -474,7 +474,7 @@ export interface ExcalidrawProps { export type SceneData = { elements?: ImportedDataState["elements"]; appState?: ImportedDataState["appState"]; - collaborators?: Map; + collaborators?: Map; commitToHistory?: boolean; }; From d91c98b82ecd8211f071fdb2a356f8796ecc1306 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:14:30 +0100 Subject: [PATCH 25/79] fix: incorrect types in `ActionNavigate` (#7462) --- packages/excalidraw/actions/actionNavigate.tsx | 8 ++++---- packages/excalidraw/actions/types.ts | 2 +- packages/excalidraw/components/UserList.tsx | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 8e8d30231..4ce79b96f 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -39,15 +39,15 @@ export const actionGoToCollaborator = register({ }; }, PanelComponent: ({ updateData, data, appState }) => { - const [socketId, collaborator, withName, isBeingFollowed] = + const { clientId, collaborator, withName, isBeingFollowed } = data as GoToCollaboratorComponentProps; - const background = getClientColor(socketId); + const background = getClientColor(clientId); return withName ? (
updateData({ ...collaborator, socketId })} + onClick={() => updateData(collaborator)} > { - updateData({ ...collaborator, socketId }); + updateData(collaborator); }} name={collaborator.username || ""} src={collaborator.avatarUrl} diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts index c74e19552..118a5b233 100644 --- a/packages/excalidraw/actions/types.ts +++ b/packages/excalidraw/actions/types.ts @@ -129,7 +129,7 @@ export type ActionName = export type PanelComponentProps = { elements: readonly ExcalidrawElement[]; appState: AppState; - updateData: (formData?: any) => void; + updateData: (formData?: T) => void; appProps: ExcalidrawProps; data?: Record; app: AppClassProperties; diff --git a/packages/excalidraw/components/UserList.tsx b/packages/excalidraw/components/UserList.tsx index 2791d9ef4..ba01b52dc 100644 --- a/packages/excalidraw/components/UserList.tsx +++ b/packages/excalidraw/components/UserList.tsx @@ -13,12 +13,12 @@ import { searchIcon } from "./icons"; import { t } from "../i18n"; import { isShallowEqual } from "../utils"; -export type GoToCollaboratorComponentProps = [ - ClientId, - Collaborator, - boolean, - boolean, -]; +export type GoToCollaboratorComponentProps = { + clientId: ClientId; + collaborator: Collaborator; + withName: boolean; + isBeingFollowed: boolean; +}; /** collaborator user id or socket id (fallback) */ type ClientId = string & { _brand: "UserId" }; @@ -60,12 +60,12 @@ const renderCollaborator = ({ shouldWrapWithTooltip?: boolean; isBeingFollowed: boolean; }) => { - const data: GoToCollaboratorComponentProps = [ + const data: GoToCollaboratorComponentProps = { clientId, collaborator, withName, isBeingFollowed, - ]; + }; const avatarJSX = actionManager.renderAction("goToCollaborator", data); return ( From 5f40a4cad4951dca01fa445e050c9bba19d16980 Mon Sep 17 00:00:00 2001 From: zsviczian Date: Tue, 19 Dec 2023 00:02:03 +0100 Subject: [PATCH 26/79] fix: missing cross-env from build:umd in package.json (#7460) --- packages/excalidraw/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index 18af79be4..d11c349f0 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -114,7 +114,7 @@ "homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw", "scripts": { "gen:types": "tsc --project tsconfig-types.json", - "build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && NODE_ENV=development webpack --config webpack.preact.config.js && NODE_ENV=production webpack --config webpack.preact.config.js && yarn gen:types", + "build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && cross-env NODE_ENV=development webpack --config webpack.preact.config.js && cross-env NODE_ENV=production webpack --config webpack.preact.config.js && yarn gen:types", "build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js", "pack": "yarn build:umd && yarn pack", "start": "webpack serve --config webpack.dev-server.config.js", From c72e853c852a1dc3169e16567ba4fa51bd4c2291 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 30 Dec 2023 11:12:38 +0100 Subject: [PATCH 27/79] refactor: editor events sub/unsub refactor (#7483) --- packages/excalidraw/components/App.tsx | 204 +++++++++++-------------- packages/excalidraw/emitter.ts | 15 +- packages/excalidraw/global.d.ts | 13 -- packages/excalidraw/types.ts | 2 +- packages/excalidraw/utils.ts | 81 +++++++++- 5 files changed, 186 insertions(+), 129 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 71d6ea821..3163f7cea 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -269,6 +269,7 @@ import { isTestEnv, easeOut, updateStable, + addEventListener, } from "../utils"; import { createSrcDoc, @@ -559,6 +560,8 @@ class App extends React.Component { [scrollX: number, scrollY: number, zoom: AppState["zoom"]] >(); + onRemoveEventListenersEmitter = new Emitter<[]>(); + constructor(props: AppProps) { super(props); const defaultAppState = getDefaultAppState(); @@ -2390,63 +2393,6 @@ class App extends React.Component { this.setState({}); }); - private removeEventListeners() { - document.removeEventListener(EVENT.POINTER_UP, this.removePointer); - document.removeEventListener(EVENT.COPY, this.onCopy); - document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard); - document.removeEventListener(EVENT.CUT, this.onCut); - this.excalidrawContainerRef.current?.removeEventListener( - EVENT.WHEEL, - this.onWheel, - ); - this.nearestScrollableContainer?.removeEventListener( - EVENT.SCROLL, - this.onScroll, - ); - document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false); - document.removeEventListener( - EVENT.MOUSE_MOVE, - this.updateCurrentCursorPosition, - false, - ); - document.removeEventListener(EVENT.KEYUP, this.onKeyUp); - window.removeEventListener(EVENT.RESIZE, this.onResize, false); - window.removeEventListener(EVENT.UNLOAD, this.onUnload, false); - window.removeEventListener(EVENT.BLUR, this.onBlur, false); - this.excalidrawContainerRef.current?.removeEventListener( - EVENT.DRAG_OVER, - this.disableEvent, - false, - ); - this.excalidrawContainerRef.current?.removeEventListener( - EVENT.DROP, - this.disableEvent, - false, - ); - - document.removeEventListener( - EVENT.GESTURE_START, - this.onGestureStart as any, - false, - ); - document.removeEventListener( - EVENT.GESTURE_CHANGE, - this.onGestureChange as any, - false, - ); - document.removeEventListener( - EVENT.GESTURE_END, - this.onGestureEnd as any, - false, - ); - document.removeEventListener( - EVENT.FULLSCREENCHANGE, - this.onFullscreenChange, - ); - - window.removeEventListener(EVENT.MESSAGE, this.onWindowMessage, false); - } - /** generally invoked only if fullscreen was invoked programmatically */ private onFullscreenChange = () => { if ( @@ -2460,76 +2406,108 @@ class App extends React.Component { } }; + private removeEventListeners() { + this.onRemoveEventListenersEmitter.trigger(); + } + private addEventListeners() { + // remove first as we can add event listeners multiple times this.removeEventListeners(); - window.addEventListener(EVENT.MESSAGE, this.onWindowMessage, false); - document.addEventListener(EVENT.POINTER_UP, this.removePointer); // #3553 - document.addEventListener(EVENT.COPY, this.onCopy); - this.excalidrawContainerRef.current?.addEventListener( - EVENT.WHEEL, - this.onWheel, - { passive: false }, - ); + + // ------------------------------------------------------------------------- + // view+edit mode listeners + // ------------------------------------------------------------------------- if (this.props.handleKeyboardGlobally) { - document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false); + this.onRemoveEventListenersEmitter.once( + addEventListener(document, EVENT.KEYDOWN, this.onKeyDown, false), + ); } - document.addEventListener(EVENT.KEYUP, this.onKeyUp, { passive: true }); - document.addEventListener( - EVENT.MOUSE_MOVE, - this.updateCurrentCursorPosition, - ); - // rerender text elements on font load to fix #637 && #1553 - document.fonts?.addEventListener?.("loadingdone", (event) => { - const loadedFontFaces = (event as FontFaceSetLoadEvent).fontfaces; - this.fonts.onFontsLoaded(loadedFontFaces); - }); - // Safari-only desktop pinch zoom - document.addEventListener( - EVENT.GESTURE_START, - this.onGestureStart as any, - false, - ); - document.addEventListener( - EVENT.GESTURE_CHANGE, - this.onGestureChange as any, - false, - ); - document.addEventListener( - EVENT.GESTURE_END, - this.onGestureEnd as any, - false, + this.onRemoveEventListenersEmitter.once( + addEventListener( + this.excalidrawContainerRef.current, + EVENT.WHEEL, + this.onWheel, + { passive: false }, + ), + addEventListener(window, EVENT.MESSAGE, this.onWindowMessage, false), + addEventListener(document, EVENT.POINTER_UP, this.removePointer), // #3553 + addEventListener(document, EVENT.COPY, this.onCopy), + addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), + addEventListener( + document, + EVENT.MOUSE_MOVE, + this.updateCurrentCursorPosition, + ), + // rerender text elements on font load to fix #637 && #1553 + addEventListener(document.fonts, "loadingdone", (event) => { + const loadedFontFaces = (event as FontFaceSetLoadEvent).fontfaces; + this.fonts.onFontsLoaded(loadedFontFaces); + }), + // Safari-only desktop pinch zoom + addEventListener( + document, + EVENT.GESTURE_START, + this.onGestureStart as any, + false, + ), + addEventListener( + document, + EVENT.GESTURE_CHANGE, + this.onGestureChange as any, + false, + ), + addEventListener( + document, + EVENT.GESTURE_END, + this.onGestureEnd as any, + false, + ), ); + if (this.state.viewModeEnabled) { return; } - document.addEventListener(EVENT.FULLSCREENCHANGE, this.onFullscreenChange); - document.addEventListener(EVENT.PASTE, this.pasteFromClipboard); - document.addEventListener(EVENT.CUT, this.onCut); + // ------------------------------------------------------------------------- + // edit-mode listeners only + // ------------------------------------------------------------------------- + + this.onRemoveEventListenersEmitter.once( + addEventListener( + document, + EVENT.FULLSCREENCHANGE, + this.onFullscreenChange, + ), + addEventListener(document, EVENT.PASTE, this.pasteFromClipboard), + addEventListener(document, EVENT.CUT, this.onCut), + addEventListener(window, EVENT.RESIZE, this.onResize, false), + addEventListener(window, EVENT.UNLOAD, this.onUnload, false), + addEventListener(window, EVENT.BLUR, this.onBlur, false), + addEventListener( + this.excalidrawContainerRef.current, + EVENT.DRAG_OVER, + this.disableEvent, + false, + ), + addEventListener( + this.excalidrawContainerRef.current, + EVENT.DROP, + this.disableEvent, + false, + ), + ); + if (this.props.detectScroll) { - this.nearestScrollableContainer = getNearestScrollableContainer( - this.excalidrawContainerRef.current!, - ); - this.nearestScrollableContainer.addEventListener( - EVENT.SCROLL, - this.onScroll, + this.onRemoveEventListenersEmitter.once( + addEventListener( + getNearestScrollableContainer(this.excalidrawContainerRef.current!), + EVENT.SCROLL, + this.onScroll, + ), ); } - window.addEventListener(EVENT.RESIZE, this.onResize, false); - window.addEventListener(EVENT.UNLOAD, this.onUnload, false); - window.addEventListener(EVENT.BLUR, this.onBlur, false); - this.excalidrawContainerRef.current?.addEventListener( - EVENT.DRAG_OVER, - this.disableEvent, - false, - ); - this.excalidrawContainerRef.current?.addEventListener( - EVENT.DROP, - this.disableEvent, - false, - ); } componentDidUpdate(prevProps: AppProps, prevState: AppState) { diff --git a/packages/excalidraw/emitter.ts b/packages/excalidraw/emitter.ts index 5b1cdd0a7..cb86670be 100644 --- a/packages/excalidraw/emitter.ts +++ b/packages/excalidraw/emitter.ts @@ -1,3 +1,5 @@ +import { UnsubscribeCallback } from "./types"; + type Subscriber = (...payload: T) => void; export class Emitter { @@ -15,7 +17,7 @@ export class Emitter { * * @returns unsubscribe function */ - on(...handlers: Subscriber[] | Subscriber[][]) { + on(...handlers: Subscriber[] | Subscriber[][]): UnsubscribeCallback { const _handlers = handlers .flat() .filter((item) => typeof item === "function"); @@ -25,6 +27,17 @@ export class Emitter { return () => this.off(_handlers); } + once(...handlers: Subscriber[] | Subscriber[][]): UnsubscribeCallback { + const _handlers = handlers + .flat() + .filter((item) => typeof item === "function"); + + _handlers.push(() => detach()); + + const detach = this.on(..._handlers); + return detach; + } + off(...handlers: Subscriber[] | Subscriber[][]) { const _handlers = handlers.flat(); this.subscribers = this.subscribers.filter( diff --git a/packages/excalidraw/global.d.ts b/packages/excalidraw/global.d.ts index 76730c8de..49e5eac1c 100644 --- a/packages/excalidraw/global.d.ts +++ b/packages/excalidraw/global.d.ts @@ -1,16 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -interface Document { - fonts?: { - ready?: Promise; - check?: (font: string, text?: string) => boolean; - load?: (font: string, text?: string) => Promise; - addEventListener?( - type: "loading" | "loadingdone" | "loadingerror", - listener: (this: Document, ev: Event) => any, - ): void; - }; -} - interface Window { ClipboardItem: any; __EXCALIDRAW_SHA__: string | undefined; diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 854a27519..2ba9bd68d 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -641,7 +641,7 @@ export type PointerDownState = Readonly<{ }; }>; -type UnsubscribeCallback = () => void; +export type UnsubscribeCallback = () => void; export type ExcalidrawImperativeAPI = { updateScene: InstanceType["updateScene"]; diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 4278f36f6..8b39ba6bd 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -7,7 +7,13 @@ import { WINDOWS_EMOJI_FALLBACK_FONT, } from "./constants"; import { FontFamilyValues, FontString } from "./element/types"; -import { ActiveTool, AppState, ToolType, Zoom } from "./types"; +import { + ActiveTool, + AppState, + ToolType, + UnsubscribeCallback, + Zoom, +} from "./types"; import { unstable_batchedUpdates } from "react-dom"; import { ResolutionType } from "./utility-types"; import React from "react"; @@ -992,3 +998,76 @@ export const updateStable = >( } return nextValue; }; + +// Window +export function addEventListener( + target: Window & typeof globalThis, + type: K, + listener: (this: Window, ev: WindowEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +export function addEventListener( + target: Window & typeof globalThis, + type: string, + listener: (this: Window, ev: Event) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +// Document +export function addEventListener( + target: Document, + type: K, + listener: (this: Document, ev: DocumentEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +export function addEventListener( + target: Document, + type: string, + listener: (this: Document, ev: Event) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +// FontFaceSet (document.fonts) +export function addEventListener( + target: FontFaceSet, + type: K, + listener: (this: FontFaceSet, ev: FontFaceSetEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +// HTMLElement / mix +export function addEventListener( + target: + | Document + | (Window & typeof globalThis) + | HTMLElement + | undefined + | null + | false, + type: K, + listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback; +// implem +export function addEventListener( + /** + * allows for falsy values so you don't have to type check when adding + * event listeners to optional elements + */ + target: + | Document + | (Window & typeof globalThis) + | FontFaceSet + | HTMLElement + | undefined + | null + | false, + type: keyof WindowEventMap | keyof DocumentEventMap | string, + listener: (ev: Event) => any, + options?: boolean | AddEventListenerOptions, +): UnsubscribeCallback { + if (!target) { + return () => {}; + } + target?.addEventListener?.(type, listener, options); + return () => { + target?.removeEventListener?.(type, listener, options); + }; +} From d19b51d4f8eee8a661b2f9df95f3620f23fe4b93 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 30 Dec 2023 15:00:12 +0100 Subject: [PATCH 28/79] fix: drawing-tablet stylus touch events being prevented (#7494) --- packages/excalidraw/components/App.tsx | 10 +++------- packages/excalidraw/constants.ts | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 3163f7cea..9ff151675 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -67,7 +67,6 @@ import { GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, - isAndroid, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, @@ -90,6 +89,7 @@ import { POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, + isIOS, } from "../constants"; import { ExportedElements, exportCanvas, loadFromBlob } from "../data"; import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library"; @@ -2756,9 +2756,8 @@ class App extends React.Component { } private onTouchStart = (event: TouchEvent) => { - // fix for Apple Pencil Scribble - // On Android, preventing the event would disable contextMenu on tap-hold - if (!isAndroid) { + // fix for Apple Pencil Scribble (do not prevent for other devices) + if (isIOS) { event.preventDefault(); } @@ -2783,9 +2782,6 @@ class App extends React.Component { didTapTwice = false; clearTimeout(tappedTwiceTimer); } - if (isAndroid) { - event.preventDefault(); - } if (event.touches.length === 2) { this.setState({ diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts index 5594f356e..ff1fdabb7 100644 --- a/packages/excalidraw/constants.ts +++ b/packages/excalidraw/constants.ts @@ -13,6 +13,10 @@ export const isFirefox = export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1; export const isSafari = !isChrome && navigator.userAgent.indexOf("Safari") !== -1; +export const isIOS = + /iPad|iPhone/.test(navigator.platform) || + // iPadOS 13+ + (navigator.userAgent.includes("Mac") && "ontouchend" in document); // keeping function so it can be mocked in test export const isBrave = () => (navigator as any).brave?.isBrave?.name === "isBrave"; From e6c3c06c2eb552aed9241aa1e1fb72052e2caf35 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:27:03 +0100 Subject: [PATCH 29/79] feat: support pen erasing (#7496) --- packages/excalidraw/components/App.tsx | 114 ++++++-- packages/excalidraw/constants.ts | 1 + packages/excalidraw/emitter.ts | 19 +- .../__snapshots__/contextmenu.test.tsx.snap | 264 +++++++++--------- .../regressionTests.test.tsx.snap | 26 +- 5 files changed, 240 insertions(+), 184 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 9ff151675..e1f480fa8 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -245,6 +245,7 @@ import { CollaboratorPointer, ToolType, OnUserFollowedPayload, + UnsubscribeCallback, } from "../types"; import { debounce, @@ -488,7 +489,7 @@ let IS_PLAIN_PASTE = false; let IS_PLAIN_PASTE_TIMER = 0; let PLAIN_PASTE_TOAST_SHOWN = false; -let lastPointerUp: ((event: any) => void) | null = null; +let lastPointerUp: (() => void) | null = null; const gesture: Gesture = { pointers: new Map(), lastCenter: null, @@ -528,6 +529,7 @@ class App extends React.Component { lastPointerDownEvent: React.PointerEvent | null = null; lastPointerUpEvent: React.PointerEvent | PointerEvent | null = null; + lastPointerMoveEvent: PointerEvent | null = null; lastViewportPosition = { x: 0, y: 0 }; laserPathManager: LaserPathManager = new LaserPathManager(this); @@ -560,6 +562,9 @@ class App extends React.Component { [scrollX: number, scrollY: number, zoom: AppState["zoom"]] >(); + missingPointerEventCleanupEmitter = new Emitter< + [event: PointerEvent | null] + >(); onRemoveEventListenersEmitter = new Emitter<[]>(); constructor(props: AppProps) { @@ -2372,7 +2377,7 @@ class App extends React.Component { this.scene.destroy(); this.library.destroy(); this.laserPathManager.destroy(); - this.onChangeEmitter.destroy(); + this.onChangeEmitter.clear(); ShapeCache.destroy(); SnapCache.destroy(); clearTimeout(touchTimeout); @@ -2464,6 +2469,9 @@ class App extends React.Component { this.onGestureEnd as any, false, ), + addEventListener(window, EVENT.FOCUS, () => { + this.maybeCleanupAfterMissingPointerUp(null); + }), ); if (this.state.viewModeEnabled) { @@ -4616,6 +4624,7 @@ class App extends React.Component { event: React.PointerEvent, ) => { this.savePointer(event.clientX, event.clientY, this.state.cursorButton); + this.lastPointerMoveEvent = event.nativeEvent; if (gesture.pointers.has(event.pointerId)) { gesture.pointers.set(event.pointerId, { @@ -5203,6 +5212,7 @@ class App extends React.Component { private handleCanvasPointerDown = ( event: React.PointerEvent, ) => { + this.maybeCleanupAfterMissingPointerUp(event.nativeEvent); this.maybeUnfollowRemoteUser(); // since contextMenu options are potentially evaluated on each render, @@ -5265,7 +5275,6 @@ class App extends React.Component { selection.removeAllRanges(); } this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event); - this.maybeCleanupAfterMissingPointerUp(event); //fires only once, if pen is detected, penMode is enabled //the user can disable this by toggling the penMode button @@ -5304,10 +5313,60 @@ class App extends React.Component { }); this.savePointer(event.clientX, event.clientY, "down"); + if ( + event.button === POINTER_BUTTON.ERASER && + this.state.activeTool.type !== TOOL_TYPE.eraser + ) { + this.setState( + { + activeTool: updateActiveTool(this.state, { + type: TOOL_TYPE.eraser, + lastActiveToolBeforeEraser: this.state.activeTool, + }), + }, + () => { + this.handleCanvasPointerDown(event); + const onPointerUp = () => { + unsubPointerUp(); + unsubCleanup?.(); + if (isEraserActive(this.state)) { + this.setState({ + activeTool: updateActiveTool(this.state, { + ...(this.state.activeTool.lastActiveTool || { + type: TOOL_TYPE.selection, + }), + lastActiveToolBeforeEraser: null, + }), + }); + } + }; + + const unsubPointerUp = addEventListener( + window, + EVENT.POINTER_UP, + onPointerUp, + { + once: true, + }, + ); + let unsubCleanup: UnsubscribeCallback | undefined; + // subscribe inside rAF lest it'd be triggered on the same pointerdown + // if we start erasing while coming from blurred document since + // we cleanup pointer events on focus + requestAnimationFrame(() => { + unsubCleanup = + this.missingPointerEventCleanupEmitter.once(onPointerUp); + }); + }, + ); + return; + } + // only handle left mouse button or touch if ( event.button !== POINTER_BUTTON.MAIN && - event.button !== POINTER_BUTTON.TOUCH + event.button !== POINTER_BUTTON.TOUCH && + event.button !== POINTER_BUTTON.ERASER ) { return; } @@ -5435,7 +5494,9 @@ class App extends React.Component { const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState); const onKeyUp = this.onKeyUpFromPointerDownHandler(pointerDownState); - lastPointerUp = onPointerUp; + this.missingPointerEventCleanupEmitter.once((_event) => + onPointerUp(_event || event.nativeEvent), + ); if (!this.state.viewModeEnabled || this.state.activeTool.type === "laser") { window.addEventListener(EVENT.POINTER_MOVE, onPointerMove); @@ -5546,16 +5607,15 @@ class App extends React.Component { invalidateContextMenu = false; }; - private maybeCleanupAfterMissingPointerUp( - event: React.PointerEvent, - ): void { - if (lastPointerUp !== null) { - // Unfortunately, sometimes we don't get a pointerup after a pointerdown, - // this can happen when a contextual menu or alert is triggered. In order to avoid - // being in a weird state, we clean up on the next pointerdown - lastPointerUp(event); - } - } + /** + * pointerup may not fire in certian cases (user tabs away...), so in order + * to properly cleanup pointerdown state, we need to fire any hanging + * pointerup handlers manually + */ + private maybeCleanupAfterMissingPointerUp = (event: PointerEvent | null) => { + lastPointerUp?.(); + this.missingPointerEventCleanupEmitter.trigger(event).clear(); + }; // Returns whether the event is a panning private handleCanvasPanUsingWheelOrSpaceDrag = ( @@ -5758,11 +5818,10 @@ class App extends React.Component { this.handlePointerMoveOverScrollbars(event, pointerDownState); }); - const onPointerUp = withBatchedUpdates(() => { + lastPointerUp = null; isDraggingScrollBar = false; setCursorForShape(this.interactiveCanvas, this.state); - lastPointerUp = null; this.setState({ cursorButton: "up", }); @@ -7208,6 +7267,7 @@ class App extends React.Component { pointerDownState: PointerDownState, ): (event: PointerEvent) => void { return withBatchedUpdates((childEvent: PointerEvent) => { + this.removePointer(childEvent); if (pointerDownState.eventListeners.onMove) { pointerDownState.eventListeners.onMove.flush(); } @@ -7310,7 +7370,7 @@ class App extends React.Component { } } - lastPointerUp = null; + this.missingPointerEventCleanupEmitter.clear(); window.removeEventListener( EVENT.POINTER_MOVE, @@ -7693,19 +7753,23 @@ class App extends React.Component { }); } } - if (isEraserActive(this.state)) { + + const pointerStart = this.lastPointerDownEvent; + const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent; + + if (isEraserActive(this.state) && pointerStart && pointerEnd) { const draggedDistance = distance2d( - this.lastPointerDownEvent!.clientX, - this.lastPointerDownEvent!.clientY, - this.lastPointerUpEvent!.clientX, - this.lastPointerUpEvent!.clientY, + pointerStart.clientX, + pointerStart.clientY, + pointerEnd.clientX, + pointerEnd.clientY, ); if (draggedDistance === 0) { const scenePointer = viewportCoordsToSceneCoords( { - clientX: this.lastPointerUpEvent!.clientX, - clientY: this.lastPointerUpEvent!.clientY, + clientX: pointerEnd.clientX, + clientY: pointerEnd.clientY, }, this.state, ); diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts index ff1fdabb7..72286e698 100644 --- a/packages/excalidraw/constants.ts +++ b/packages/excalidraw/constants.ts @@ -43,6 +43,7 @@ export const POINTER_BUTTON = { WHEEL: 1, SECONDARY: 2, TOUCH: -1, + ERASER: 5, } as const; export const POINTER_EVENTS = { diff --git a/packages/excalidraw/emitter.ts b/packages/excalidraw/emitter.ts index cb86670be..98e97ad46 100644 --- a/packages/excalidraw/emitter.ts +++ b/packages/excalidraw/emitter.ts @@ -4,13 +4,6 @@ type Subscriber = (...payload: T) => void; export class Emitter { public subscribers: Subscriber[] = []; - public value: T | undefined; - private updateOnChangeOnly: boolean; - - constructor(opts?: { initialState?: T; updateOnChangeOnly?: boolean }) { - this.updateOnChangeOnly = opts?.updateOnChangeOnly ?? false; - this.value = opts?.initialState; - } /** * Attaches subscriber @@ -45,16 +38,14 @@ export class Emitter { ); } - trigger(...payload: T): any[] { - if (this.updateOnChangeOnly && this.value === payload) { - return []; + trigger(...payload: T) { + for (const handler of this.subscribers) { + handler(...payload); } - this.value = payload; - return this.subscribers.map((handler) => handler(...payload)); + return this; } - destroy() { + clear() { this.subscribers = []; - this.value = undefined; } } diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index d672a2542..b14000c2c 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -604,7 +604,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -663,7 +663,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -799,14 +799,14 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -838,7 +838,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -897,7 +897,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -940,7 +940,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -962,14 +962,14 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -1005,14 +1005,14 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -1041,7 +1041,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -1177,14 +1177,14 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -1216,7 +1216,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -1275,7 +1275,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -1318,7 +1318,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -1340,14 +1340,14 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -1383,14 +1383,14 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -1419,7 +1419,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": -10, "y": 0, @@ -1557,7 +1557,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1616,7 +1616,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1764,7 +1764,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1116226695, "width": 20, "x": -10, "y": 0, @@ -1823,7 +1823,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -1864,7 +1864,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1116226695, "width": 20, "x": -10, "y": 0, @@ -2007,7 +2007,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2032,14 +2032,14 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": 0, "y": 10, @@ -2098,7 +2098,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2141,7 +2141,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2163,14 +2163,14 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": 0, "y": 10, @@ -2320,7 +2320,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 1505387817, "width": 20, "x": -10, "y": 0, @@ -2347,14 +2347,14 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 20, "x": 20, "y": 30, @@ -2413,7 +2413,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2456,7 +2456,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2478,14 +2478,14 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -2533,7 +2533,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1604849351, + "versionNonce": 1505387817, "width": 20, "x": -10, "y": 0, @@ -2557,14 +2557,14 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 20, "x": 20, "y": 30, @@ -2709,7 +2709,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1898319239, + "versionNonce": 640725609, "width": 20, "x": -10, "y": 0, @@ -2734,14 +2734,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1723083209, + "seed": 760410951, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 8, - "versionNonce": 289600103, + "versionNonce": 1315507081, "width": 20, "x": 20, "y": 30, @@ -2800,7 +2800,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2843,7 +2843,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2865,14 +2865,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -2915,7 +2915,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -2937,14 +2937,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#e03131", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -2987,7 +2987,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3009,14 +3009,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#e03131", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 20, "x": 20, "y": 30, @@ -3059,7 +3059,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3081,14 +3081,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#e03131", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 5, - "versionNonce": 493213705, + "versionNonce": 915032327, "width": 20, "x": 20, "y": 30, @@ -3131,7 +3131,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3153,14 +3153,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 6, - "versionNonce": 81784553, + "versionNonce": 747212839, "width": 20, "x": 20, "y": 30, @@ -3203,7 +3203,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3225,14 +3225,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1723083209, + "seed": 760410951, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 7, - "versionNonce": 760410951, + "versionNonce": 1006504105, "width": 20, "x": 20, "y": 30, @@ -3275,7 +3275,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3297,14 +3297,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1723083209, + "seed": 760410951, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 8, - "versionNonce": 289600103, + "versionNonce": 1315507081, "width": 20, "x": 20, "y": 30, @@ -3347,7 +3347,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1898319239, + "versionNonce": 640725609, "width": 20, "x": -10, "y": 0, @@ -3369,14 +3369,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "roundness": { "type": 3, }, - "seed": 1723083209, + "seed": 760410951, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 8, - "versionNonce": 289600103, + "versionNonce": 1315507081, "width": 20, "x": 20, "y": 30, @@ -3512,14 +3512,14 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -3551,7 +3551,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3610,7 +3610,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3653,7 +3653,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3675,14 +3675,14 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -3718,14 +3718,14 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -3754,7 +3754,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3890,14 +3890,14 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -3929,7 +3929,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -3988,7 +3988,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -4031,7 +4031,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -4053,14 +4053,14 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 238820263, "width": 20, "x": 20, "y": 30, @@ -4096,14 +4096,14 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 1116226695, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 400692809, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -4132,7 +4132,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -4271,14 +4271,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 81784553, "width": 20, "x": -10, "y": 0, @@ -4303,14 +4303,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 81784553, + "versionNonce": 747212839, "width": 20, "x": 20, "y": 30, @@ -4362,7 +4362,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4405,7 +4405,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4434,14 +4434,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 238820263, + "versionNonce": 400692809, "width": 20, "x": 20, "y": 30, @@ -4482,14 +4482,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 20, "x": -10, "y": 0, @@ -4513,14 +4513,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 20, "x": 20, "y": 30, @@ -4557,14 +4557,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 915032327, + "versionNonce": 81784553, "width": 20, "x": -10, "y": 0, @@ -4586,14 +4586,14 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 81784553, + "versionNonce": 747212839, "width": 20, "x": 20, "y": 30, @@ -5005,14 +5005,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "roundness": { "type": 3, }, - "seed": 449462985, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1116226695, "width": 10, "x": -10, "y": 0, @@ -5037,14 +5037,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -5096,14 +5096,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "roundness": { "type": 3, }, - "seed": 449462985, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1116226695, "width": 10, "x": -10, "y": 0, @@ -5139,14 +5139,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "roundness": { "type": 3, }, - "seed": 449462985, + "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 1116226695, "width": 10, "x": -10, "y": 0, @@ -5168,14 +5168,14 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "roundness": { "type": 3, }, - "seed": 1150084233, + "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 1014066025, + "versionNonce": 1604849351, "width": 10, "x": 10, "y": 0, @@ -5591,14 +5591,14 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 10, "x": -10, "y": 0, @@ -5625,14 +5625,14 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 493213705, + "versionNonce": 915032327, "width": 10, "x": 10, "y": 0, @@ -5684,7 +5684,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5727,7 +5727,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5756,14 +5756,14 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 238820263, + "versionNonce": 400692809, "width": 10, "x": 10, "y": 0, @@ -5804,14 +5804,14 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 453191, + "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 10, "x": -10, "y": 0, @@ -5835,14 +5835,14 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "roundness": { "type": 3, }, - "seed": 1116226695, + "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 493213705, + "versionNonce": 915032327, "width": 10, "x": 10, "y": 0, @@ -6892,7 +6892,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, @@ -7015,7 +7015,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 2019559783, "width": 20, "x": -10, "y": 0, diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index 452f9242c..65fa16899 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -1908,7 +1908,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 401146281, "width": 10, "x": 0, "y": 0, @@ -1951,7 +1951,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "type": "ellipse", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 1116226695, "width": 10, "x": 25, "y": 25, @@ -16160,7 +16160,7 @@ exports[`regression tests > switches from group of selected elements to another "roundness": { "type": 2, }, - "seed": 493213705, + "seed": 915032327, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16246,7 +16246,7 @@ exports[`regression tests > switches from group of selected elements to another "roundness": { "type": 2, }, - "seed": 493213705, + "seed": 915032327, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16330,7 +16330,7 @@ exports[`regression tests > switches from group of selected elements to another "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 401146281, "width": 10, "x": 0, "y": 0, @@ -16373,7 +16373,7 @@ exports[`regression tests > switches from group of selected elements to another "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 401146281, "width": 10, "x": 0, "y": 0, @@ -16395,14 +16395,14 @@ exports[`regression tests > switches from group of selected elements to another "roundness": { "type": 2, }, - "seed": 2019559783, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1116226695, + "versionNonce": 1014066025, "width": 100, "x": 110, "y": 110, @@ -16445,7 +16445,7 @@ exports[`regression tests > switches from group of selected elements to another "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 401146281, "width": 10, "x": 0, "y": 0, @@ -16467,14 +16467,14 @@ exports[`regression tests > switches from group of selected elements to another "roundness": { "type": 2, }, - "seed": 2019559783, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "ellipse", "updated": 1, "version": 2, - "versionNonce": 1116226695, + "versionNonce": 1014066025, "width": 100, "x": 110, "y": 110, @@ -16496,14 +16496,14 @@ exports[`regression tests > switches from group of selected elements to another "roundness": { "type": 2, }, - "seed": 238820263, + "seed": 400692809, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "diamond", "updated": 1, "version": 2, - "versionNonce": 1604849351, + "versionNonce": 1505387817, "width": 100, "x": 310, "y": 310, From a8064ba3eef10fa50bf5d9f402b779d54558ae76 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 1 Jan 2024 20:18:44 +0530 Subject: [PATCH 30/79] build: Welcome ESM and Bye Bye UMD (#7441) * build: Welcome ESM and Bye Bye UMD * remove package * create unbundled esm build * update script for example * fix typo * dummy commit * update autorelease script to build esm * revert dummy commit * move react, react-dom and testing library to dev dependencies * remove entry.js, publicPath and yarn install:deps script * fix * upgrade esbuild to fix glob import error for locales * remove webpack chunk names as thats not needed anymore * marking the code sideeffects free * make the library tree-shakeable and move fonts to fonts directory * allow side effects for css, scss files * remove tree-shaking * comment code for tree shaking * move to vite for example * bye bye webpack * ignore ts * separate build and output dir * use esbuild for creating bundle for example * update output dir * lint * create browser dev build with source maps and prod with minification * add dev and prod builds for bundler * lint * update script * remove await * load prod build * create minified build in dist * prod and dev builds using export field * remove import.meta * dummy * define import.meta prod and dev * fix * export types * add types field * typo * lint * Update scripts/buildPackage.js * move types inside export * newline --- .github/workflows/lint.yml | 2 +- .github/workflows/size-limit.yml | 2 +- .github/workflows/test-coverage-pr.yml | 2 +- .github/workflows/test.yml | 2 +- .gitignore | 3 - excalidraw-app/collab/RoomDialog.scss | 2 +- excalidraw-app/index.html | 2 +- excalidraw-app/package.json | 4 +- package.json | 1 - packages/excalidraw/.gitignore | 4 +- packages/excalidraw/components/App.tsx | 1 - packages/excalidraw/components/Avatar.scss | 2 +- packages/excalidraw/components/Card.scss | 2 +- .../excalidraw/components/CheckboxItem.scss | 2 +- .../components/ColorPicker/ColorPicker.scss | 2 +- .../excalidraw/components/ConfirmDialog.scss | 2 +- .../excalidraw/components/ContextMenu.scss | 2 +- packages/excalidraw/components/Dialog.scss | 2 +- .../excalidraw/components/ExportDialog.scss | 2 +- .../excalidraw/components/FilledButton.scss | 2 +- .../components/FixedSideContainer.scss | 2 +- .../excalidraw/components/HelpDialog.scss | 2 +- .../excalidraw/components/HintViewer.scss | 2 +- .../excalidraw/components/IconPicker.scss | 2 +- .../components/ImageExportDialog.scss | 2 +- packages/excalidraw/components/LayerUI.scss | 2 +- .../excalidraw/components/LibraryUnit.scss | 2 +- packages/excalidraw/components/Modal.scss | 2 +- .../OverwriteConfirm/OverwriteConfirm.scss | 2 +- .../components/PasteChartDialog.scss | 2 +- .../excalidraw/components/PublishLibrary.scss | 2 +- .../excalidraw/components/RadioGroup.scss | 2 +- .../components/ShareableLinkDialog.scss | 2 +- .../components/Sidebar/Sidebar.scss | 2 +- .../components/Sidebar/SidebarTrigger.scss | 2 +- packages/excalidraw/components/Stats.scss | 2 +- packages/excalidraw/components/Switch.scss | 2 +- .../components/TTDDialog/TTDDialog.scss | 2 +- .../components/TTDDialog/TTDDialog.tsx | 4 +- packages/excalidraw/components/TextField.scss | 2 +- packages/excalidraw/components/TextInput.scss | 2 +- packages/excalidraw/components/Toast.scss | 2 +- packages/excalidraw/components/ToolIcon.scss | 2 +- packages/excalidraw/components/Toolbar.scss | 2 +- packages/excalidraw/components/Tooltip.scss | 2 +- .../components/dropdownMenu/DropdownMenu.scss | 2 +- .../LiveCollaborationTrigger.scss | 2 +- packages/excalidraw/css/styles.scss | 2 +- packages/excalidraw/data/blob.ts | 6 +- packages/excalidraw/data/index.ts | 2 +- packages/excalidraw/element/Hyperlink.scss | 2 +- packages/excalidraw/entry.js | 7 - packages/excalidraw/{env.js => env.cjs} | 0 packages/excalidraw/example/App.tsx | 9 +- packages/excalidraw/example/CustomFooter.tsx | 5 +- packages/excalidraw/example/MobileFooter.tsx | 2 +- packages/excalidraw/example/index.tsx | 7 +- packages/excalidraw/example/initialData.tsx | 4 +- packages/excalidraw/example/public/index.html | 11 +- .../example/sidebar/ExampleSidebar.tsx | 6 +- packages/excalidraw/i18n.ts | 4 +- packages/excalidraw/index.tsx | 4 + packages/excalidraw/main.js | 12 +- packages/excalidraw/package.json | 50 +- packages/excalidraw/publicPath.js | 8 - packages/excalidraw/scene/export.ts | 2 +- packages/excalidraw/tests/flip.test.tsx | 1 - packages/excalidraw/tests/shortcuts.test.tsx | 2 +- packages/excalidraw/tsconfig.json | 15 + packages/excalidraw/vite.config.mts | 15 + .../excalidraw/webpack.dev-server.config.js | 28 - packages/excalidraw/webpack.dev.config.js | 108 -- packages/excalidraw/webpack.preact.config.js | 32 - packages/excalidraw/webpack.prod.config.js | 131 -- public/{ => fonts}/Assistant-Bold.woff2 | Bin public/{ => fonts}/Assistant-Medium.woff2 | Bin public/{ => fonts}/Assistant-Regular.woff2 | Bin public/{ => fonts}/Assistant-SemiBold.woff2 | Bin public/{ => fonts}/Cascadia.ttf | Bin public/{ => fonts}/Cascadia.woff2 | Bin public/{ => fonts}/FG_Virgil.ttf | Bin public/{ => fonts}/FG_Virgil.woff2 | Bin public/{ => fonts}/Virgil.woff2 | Bin public/{ => fonts}/fonts.css | 0 scripts/autorelease.js | 8 +- scripts/buildExample.mjs | 35 + scripts/buildPackage.js | 135 ++ yarn.lock | 1094 +++-------------- 88 files changed, 511 insertions(+), 1335 deletions(-) delete mode 100644 packages/excalidraw/entry.js rename packages/excalidraw/{env.js => env.cjs} (100%) delete mode 100644 packages/excalidraw/publicPath.js create mode 100644 packages/excalidraw/tsconfig.json create mode 100644 packages/excalidraw/vite.config.mts delete mode 100644 packages/excalidraw/webpack.dev-server.config.js delete mode 100644 packages/excalidraw/webpack.dev.config.js delete mode 100644 packages/excalidraw/webpack.preact.config.js delete mode 100644 packages/excalidraw/webpack.prod.config.js rename public/{ => fonts}/Assistant-Bold.woff2 (100%) rename public/{ => fonts}/Assistant-Medium.woff2 (100%) rename public/{ => fonts}/Assistant-Regular.woff2 (100%) rename public/{ => fonts}/Assistant-SemiBold.woff2 (100%) rename public/{ => fonts}/Cascadia.ttf (100%) rename public/{ => fonts}/Cascadia.woff2 (100%) rename public/{ => fonts}/FG_Virgil.ttf (100%) rename public/{ => fonts}/FG_Virgil.woff2 (100%) rename public/{ => fonts}/Virgil.woff2 (100%) rename public/{ => fonts}/fonts.css (100%) create mode 100644 scripts/buildExample.mjs create mode 100644 scripts/buildPackage.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f922f5e75..82f826361 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - name: Install and lint run: | - yarn install:deps + yarn install yarn test:other yarn test:code yarn test:typecheck diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 02aade54e..5bd3c0d92 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -23,6 +23,6 @@ jobs: - uses: andresz1/size-limit-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - build_script: build:umd + build_script: build:esm skip_step: install directory: packages/excalidraw diff --git a/.github/workflows/test-coverage-pr.yml b/.github/workflows/test-coverage-pr.yml index 7d77d39f5..7ff40ad5d 100644 --- a/.github/workflows/test-coverage-pr.yml +++ b/.github/workflows/test-coverage-pr.yml @@ -16,7 +16,7 @@ jobs: with: node-version: "18.x" - name: "Install Deps" - run: yarn install:deps + run: yarn install - name: "Test Coverage" run: yarn test:coverage - name: "Report Coverage" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 124cae26e..2c458a810 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,5 @@ jobs: node-version: 18.x - name: Install and test run: | - yarn install:deps + yarn install yarn test:app diff --git a/.gitignore b/.gitignore index d670c78ab..17e3e7dcf 100644 --- a/.gitignore +++ b/.gitignore @@ -22,9 +22,6 @@ package-lock.json yarn-debug.log* yarn-error.log* packages/excalidraw/types -packages/excalidraw/example/public/bundle.js -packages/excalidraw/example/public/excalidraw-assets-dev -packages/excalidraw/example/public/excalidraw.development.js coverage dev-dist html diff --git a/excalidraw-app/collab/RoomDialog.scss b/excalidraw-app/collab/RoomDialog.scss index 93885e647..61624664b 100644 --- a/excalidraw-app/collab/RoomDialog.scss +++ b/excalidraw-app/collab/RoomDialog.scss @@ -1,4 +1,4 @@ -@import "../../packages/excalidraw/css/variables.module"; +@import "../../packages/excalidraw/css/variables.module.scss"; .excalidraw { .RoomDialog { diff --git a/excalidraw-app/index.html b/excalidraw-app/index.html index c11d9ab68..66f3afdab 100644 --- a/excalidraw-app/index.html +++ b/excalidraw-app/index.html @@ -121,7 +121,7 @@ crossorigin="anonymous" /> - + <% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%"==="true" ) { %> + + @@ -19,11 +21,12 @@
- - - - + + diff --git a/packages/excalidraw/example/sidebar/ExampleSidebar.tsx b/packages/excalidraw/example/sidebar/ExampleSidebar.tsx index 4c51ecdc2..a6e1b6475 100644 --- a/packages/excalidraw/example/sidebar/ExampleSidebar.tsx +++ b/packages/excalidraw/example/sidebar/ExampleSidebar.tsx @@ -1,7 +1,9 @@ -import React, { useState } from "react"; import "./ExampleSidebar.scss"; + +const React = window.React; + export default function Sidebar({ children }: { children: React.ReactNode }) { - const [open, setOpen] = useState(false); + const [open, setOpen] = React.useState(false); return ( <> diff --git a/packages/excalidraw/i18n.ts b/packages/excalidraw/i18n.ts index 6536b2c6d..a014b33b8 100644 --- a/packages/excalidraw/i18n.ts +++ b/packages/excalidraw/i18n.ts @@ -96,9 +96,7 @@ export const setLanguage = async (lang: Language) => { currentLangData = {}; } else { try { - currentLangData = await import( - /* webpackChunkName: "locales/[request]" */ `./locales/${currentLang.code}.json` - ); + currentLangData = await import(`./locales/${currentLang.code}.json`); } catch (error: any) { console.error(`Failed to load language ${lang.code}:`, error.message); currentLangData = fallbackLangData; diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index 915836cb8..6524873a2 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -5,6 +5,8 @@ import { isShallowEqual } from "./utils"; import "./css/app.scss"; import "./css/styles.scss"; +import "../../public/fonts/fonts.css"; +import polyfill from "./polyfill"; import { AppProps, ExcalidrawProps } from "./types"; import { defaultLang } from "./i18n"; @@ -16,6 +18,8 @@ import MainMenu from "./components/main-menu/MainMenu"; import WelcomeScreen from "./components/welcome-screen/WelcomeScreen"; import LiveCollaborationTrigger from "./components/live-collaboration/LiveCollaborationTrigger"; +polyfill(); + const ExcalidrawBase = (props: ExcalidrawProps) => { const { onChange, diff --git a/packages/excalidraw/main.js b/packages/excalidraw/main.js index 853bb70f8..56e511b25 100644 --- a/packages/excalidraw/main.js +++ b/packages/excalidraw/main.js @@ -1,11 +1,5 @@ -if (process.env.IS_PREACT === "true") { - if (process.env.NODE_ENV === "production") { - module.exports = require("./dist/excalidraw-with-preact.production.min.js"); - } else { - module.exports = require("./dist/excalidraw-with-preact.development.js"); - } -} else if (process.env.NODE_ENV === "production") { - module.exports = require("./dist/excalidraw.production.min.js"); +if (process.env.NODE_ENV !== "development") { + import("./dist/dev/index.js"); } else { - module.exports = require("./dist/excalidraw.development.js"); + import("./dist/prod/index.js"); } diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index d11c349f0..1cd837fdd 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -1,11 +1,23 @@ { "name": "@excalidraw/excalidraw", "version": "0.17.1", - "main": "main.js", - "types": "types/excalidraw/index.d.ts", + "main": "./dist/prod/index.js", + "type": "module", + "module": "./dist/prod/index.js", + "exports": { + ".": { + "development": "./dist/dev/index.js", + "default": "./dist/prod/index.js", + "types": "./dist/excalidraw/index.d.ts" + }, + "./index.css": { + "development": "./dist/dev/index.css", + "default": "./dist/prod/index.css" + } + }, + "types": "./dist/excalidraw/index.d.ts", "files": [ - "dist/*", - "types/*" + "dist/*" ], "publishConfig": { "access": "public" @@ -50,15 +62,11 @@ "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.0.3", "@radix-ui/react-tabs": "1.0.2", - "@testing-library/jest-dom": "5.16.2", - "@testing-library/react": "12.1.5", "@tldraw/vec": "1.7.1", "browser-fs-access": "0.29.1", "canvas-roundrect-polyfill": "0.0.1", "clsx": "1.1.1", "cross-env": "7.0.3", - "eslint-plugin-react": "7.32.2", - "fake-indexeddb": "3.1.7", "image-blob-reduce": "3.0.1", "jotai": "1.13.1", "lodash.throttle": "4.1.1", @@ -95,30 +103,32 @@ "cross-env": "7.0.3", "css-loader": "6.7.1", "dotenv": "16.0.1", + "esbuild": "0.19.10", + "esbuild-plugin-external-global": "1.0.1", + "esbuild-sass-plugin": "2.16.0", + "eslint-plugin-react": "7.32.2", + "fake-indexeddb": "3.1.7", "import-meta-loader": "1.1.0", "mini-css-extract-plugin": "2.6.1", "postcss-loader": "7.0.1", + "react": "18.2.0", + "react-dom": "18.2.0", "sass-loader": "13.0.2", "size-limit": "9.0.0", "style-loader": "3.3.3", - "terser-webpack-plugin": "5.3.3", + "@testing-library/jest-dom": "5.16.2", + "@testing-library/react": "12.1.5", "ts-loader": "9.3.1", - "typescript": "4.9.4", - "webpack": "5.76.0", - "webpack-bundle-analyzer": "4.5.0", - "webpack-cli": "4.10.0", - "webpack-dev-server": "4.9.3", - "webpack-merge": "5.8.0" + "typescript": "4.9.4" }, "bugs": "https://github.com/excalidraw/excalidraw/issues", "homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw", "scripts": { - "gen:types": "tsc --project tsconfig-types.json", - "build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && cross-env NODE_ENV=development webpack --config webpack.preact.config.js && cross-env NODE_ENV=production webpack --config webpack.preact.config.js && yarn gen:types", - "build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js", + "gen:types": "rm -rf types && tsc", + "build:esm": "rm -rf dist && node ../../scripts/buildPackage.js && yarn gen:types", "pack": "yarn build:umd && yarn pack", - "start": "webpack serve --config webpack.dev-server.config.js", - "build:example": "EXAMPLE=true webpack --config webpack.dev-server.config.js && yarn gen:types", + "start": "node ../../scripts/buildExample.mjs && vite", + "build:example": "node ../../scripts/buildExample.mjs", "size": "yarn build:umd && size-limit" } } diff --git a/packages/excalidraw/publicPath.js b/packages/excalidraw/publicPath.js deleted file mode 100644 index 3eb6bd272..000000000 --- a/packages/excalidraw/publicPath.js +++ /dev/null @@ -1,8 +0,0 @@ -import { ENV } from "./constants"; -if (process.env.NODE_ENV !== ENV.TEST) { - /* eslint-disable */ - /* global __webpack_public_path__:writable */ - __webpack_public_path__ = - window.EXCALIDRAW_ASSET_PATH || - `https://unpkg.com/${process.env.VITE_PKG_NAME}@${process.env.VITE_PKG_VERSION}/dist/`; -} diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index bb194e1cb..9bfab7e77 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -327,7 +327,7 @@ export const exportToSvg = async ( if (exportEmbedScene) { try { metadata = await ( - await import(/* webpackChunkName: "image" */ "../data/image") + await import("../data/image") ).encodeSvgMetadata({ // when embedding scene, we want to embed the origionally supplied // elements which don't contain the temp frame labels. diff --git a/packages/excalidraw/tests/flip.test.tsx b/packages/excalidraw/tests/flip.test.tsx index 68dce2c4a..875e87752 100644 --- a/packages/excalidraw/tests/flip.test.tsx +++ b/packages/excalidraw/tests/flip.test.tsx @@ -198,7 +198,6 @@ const checkElementsBoundingBox = async ( const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2); - debugger; await waitFor(() => { // Check if width and height did not change expect(x2 - x1).toBeCloseTo(x22 - x12, -1); diff --git a/packages/excalidraw/tests/shortcuts.test.tsx b/packages/excalidraw/tests/shortcuts.test.tsx index 52fa6c2bf..4da160fee 100644 --- a/packages/excalidraw/tests/shortcuts.test.tsx +++ b/packages/excalidraw/tests/shortcuts.test.tsx @@ -1,5 +1,5 @@ import { KEYS } from "../keys"; -import { Excalidraw } from "../entry"; +import { Excalidraw } from "../index"; import { API } from "./helpers/api"; import { Keyboard } from "./helpers/ui"; import { fireEvent, render, waitFor } from "./test-utils"; diff --git a/packages/excalidraw/tsconfig.json b/packages/excalidraw/tsconfig.json new file mode 100644 index 000000000..28e276c35 --- /dev/null +++ b/packages/excalidraw/tsconfig.json @@ -0,0 +1,15 @@ +{ + "exclude": ["**/*.test.*", "tests", "types", "example", "dist"], + "compilerOptions": { + "target": "ESNext", + "strict": true, + "outDir": "dist", + "skipLibCheck": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "jsx": "react-jsx" + } +} diff --git a/packages/excalidraw/vite.config.mts b/packages/excalidraw/vite.config.mts new file mode 100644 index 000000000..9639966b2 --- /dev/null +++ b/packages/excalidraw/vite.config.mts @@ -0,0 +1,15 @@ +import { defineConfig, loadEnv } from "vite"; +import react from "@vitejs/plugin-react"; + +// To load .env.local variables +const envVars = loadEnv("", `../../`); +// https://vitejs.dev/config/ +export default defineConfig({ + root: "example/public", + server: { + port: 3001, + // open the browser + open: true, + }, + publicDir: "public", +}); diff --git a/packages/excalidraw/webpack.dev-server.config.js b/packages/excalidraw/webpack.dev-server.config.js deleted file mode 100644 index 4e8df8992..000000000 --- a/packages/excalidraw/webpack.dev-server.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const path = require("path"); -const { merge } = require("webpack-merge"); - -const devConfig = require("./webpack.dev.config"); - -const devServerConfig = { - entry: { - bundle: "./example/index.tsx", - }, - // Server Configuration options - devServer: { - port: 3001, - host: "localhost", - hot: true, - compress: true, - static: { - directory: path.join(__dirname, "./example/public"), - }, - client: { - progress: true, - logging: "info", - overlay: true, //Shows a full-screen overlay in the browser when there are compiler errors or warnings. - }, - open: ["./"], - }, -}; - -module.exports = merge(devServerConfig, devConfig); diff --git a/packages/excalidraw/webpack.dev.config.js b/packages/excalidraw/webpack.dev.config.js deleted file mode 100644 index 2b06e9a11..000000000 --- a/packages/excalidraw/webpack.dev.config.js +++ /dev/null @@ -1,108 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); -const autoprefixer = require("autoprefixer"); -const { parseEnvVariables } = require("./env"); -const outputDir = process.env.EXAMPLE === "true" ? "example/public" : "dist"; - -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", - ], - }, - // So that type module works with webpack - // https://github.com/webpack/webpack/issues/11467#issuecomment-691873586 - { - test: /\.m?js/, - resolve: { - fullySpecified: false, - }, - }, - { - test: /\.(ts|tsx|js|jsx|mjs)$/, - exclude: - /node_modules[\\/](?!(browser-fs-access|canvas-roundrect-polyfill))/, - use: [ - { - loader: "import-meta-loader", - }, - { - 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/packages/excalidraw/webpack.preact.config.js b/packages/excalidraw/webpack.preact.config.js deleted file mode 100644 index 0ae969aa6..000000000 --- a/packages/excalidraw/webpack.preact.config.js +++ /dev/null @@ -1,32 +0,0 @@ -const prodConfig = require("./webpack.prod.config"); -const devConfig = require("./webpack.dev.config"); - -const isProd = process.env.NODE_ENV === "production"; - -const config = isProd ? prodConfig : devConfig; -const outputFile = isProd - ? "excalidraw-with-preact.production.min" - : "excalidraw-with-preact.development"; - -const preactWebpackConfig = { - ...config, - entry: { - [outputFile]: "./entry.js", - }, - externals: { - ...config.externals, - "react-dom/client": { - root: "ReactDOMClient", - commonjs2: "react-dom/client", - commonjs: "react-dom/client", - amd: "react-dom/client", - }, - "react/jsx-runtime": { - root: "ReactJSXRuntime", - commonjs2: "react/jsx-runtime", - commonjs: "react/jsx-runtime", - amd: "react/jsx-runtime", - }, - }, -}; -module.exports = preactWebpackConfig; diff --git a/packages/excalidraw/webpack.prod.config.js b/packages/excalidraw/webpack.prod.config.js deleted file mode 100644 index e1d38509b..000000000 --- a/packages/excalidraw/webpack.prod.config.js +++ /dev/null @@ -1,131 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); -const autoprefixer = require("autoprefixer"); -const { parseEnvVariables } = require("./env"); -const TerserPlugin = require("terser-webpack-plugin"); -const BundleAnalyzerPlugin = - require("webpack-bundle-analyzer").BundleAnalyzerPlugin; - -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", - ], - }, - // So that type module works with webpack - // https://github.com/webpack/webpack/issues/11467#issuecomment-691873586 - { - test: /\.m?js/, - resolve: { - fullySpecified: false, - }, - }, - { - test: /\.(ts|tsx|js|jsx|mjs)$/, - exclude: - /node_modules[\\/](?!(browser-fs-access|canvas-roundrect-polyfill))/, - use: [ - { - loader: "import-meta-loader", - }, - { - 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", - }, - }, -}; diff --git a/public/Assistant-Bold.woff2 b/public/fonts/Assistant-Bold.woff2 similarity index 100% rename from public/Assistant-Bold.woff2 rename to public/fonts/Assistant-Bold.woff2 diff --git a/public/Assistant-Medium.woff2 b/public/fonts/Assistant-Medium.woff2 similarity index 100% rename from public/Assistant-Medium.woff2 rename to public/fonts/Assistant-Medium.woff2 diff --git a/public/Assistant-Regular.woff2 b/public/fonts/Assistant-Regular.woff2 similarity index 100% rename from public/Assistant-Regular.woff2 rename to public/fonts/Assistant-Regular.woff2 diff --git a/public/Assistant-SemiBold.woff2 b/public/fonts/Assistant-SemiBold.woff2 similarity index 100% rename from public/Assistant-SemiBold.woff2 rename to public/fonts/Assistant-SemiBold.woff2 diff --git a/public/Cascadia.ttf b/public/fonts/Cascadia.ttf similarity index 100% rename from public/Cascadia.ttf rename to public/fonts/Cascadia.ttf diff --git a/public/Cascadia.woff2 b/public/fonts/Cascadia.woff2 similarity index 100% rename from public/Cascadia.woff2 rename to public/fonts/Cascadia.woff2 diff --git a/public/FG_Virgil.ttf b/public/fonts/FG_Virgil.ttf similarity index 100% rename from public/FG_Virgil.ttf rename to public/fonts/FG_Virgil.ttf diff --git a/public/FG_Virgil.woff2 b/public/fonts/FG_Virgil.woff2 similarity index 100% rename from public/FG_Virgil.woff2 rename to public/fonts/FG_Virgil.woff2 diff --git a/public/Virgil.woff2 b/public/fonts/Virgil.woff2 similarity index 100% rename from public/Virgil.woff2 rename to public/fonts/Virgil.woff2 diff --git a/public/fonts.css b/public/fonts/fonts.css similarity index 100% rename from public/fonts.css rename to public/fonts/fonts.css diff --git a/scripts/autorelease.js b/scripts/autorelease.js index ab5d26e27..f506cf13c 100644 --- a/scripts/autorelease.js +++ b/scripts/autorelease.js @@ -16,8 +16,7 @@ const publish = () => { try { execSync(`yarn --frozen-lockfile`); - execSync(`yarn --frozen-lockfile`, { cwd: excalidrawDir }); - execSync(`yarn run build:umd`, { cwd: excalidrawDir }); + execSync(`yarn run build:esm`, { cwd: excalidrawDir }); execSync(`yarn --cwd ${excalidrawDir} publish --tag ${tag}`); console.info(`Published ${pkg.name}@${tag}🎉`); core.setOutput( @@ -41,7 +40,10 @@ exec(`git diff --name-only HEAD^ HEAD`, async (error, stdout, stderr) => { const changedFiles = stdout.trim().split("\n"); const excalidrawPackageFiles = changedFiles.filter((file) => { - return file.indexOf("packages/excalidraw") >= 0; + return ( + file.indexOf("packages/excalidraw") >= 0 || + file.indexOf("buildPackage.js") > 0 + ); }); if (!excalidrawPackageFiles.length) { console.info("Skipping release as no valid diff found"); diff --git a/scripts/buildExample.mjs b/scripts/buildExample.mjs new file mode 100644 index 000000000..cfcbe8420 --- /dev/null +++ b/scripts/buildExample.mjs @@ -0,0 +1,35 @@ +import * as esbuild from "esbuild"; +import { sassPlugin } from "esbuild-sass-plugin"; +import { execSync } from "child_process"; + +const createDevBuild = async () => { + return await esbuild.build({ + entryPoints: ["example/index.tsx"], + outfile: "example/public/bundle.js", + define: { + "import.meta.env": "{}", + }, + bundle: true, + format: "esm", + plugins: [sassPlugin()], + loader: { + ".woff2": "dataurl", + ".html": "copy", + }, + }); +}; + +const startServer = async (ctx) => { + await ctx.serve({ + servedir: "example/public", + port: 5001, + }); +}; +execSync( + `rm -rf example/public/dist && yarn build:esm && cp -r dist example/public`, +); + +const ctx = await createDevBuild(); + +// await startServer(ctx); +// console.info("Hosted at port http://localhost:5001!!"); diff --git a/scripts/buildPackage.js b/scripts/buildPackage.js new file mode 100644 index 000000000..f564466d5 --- /dev/null +++ b/scripts/buildPackage.js @@ -0,0 +1,135 @@ +const { build } = require("esbuild"); +const { sassPlugin } = require("esbuild-sass-plugin"); +const { externalGlobalPlugin } = require("esbuild-plugin-external-global"); +// Will be used later for treeshaking +//const fs = require("fs"); +// const path = require("path"); + +// function getFiles(dir, files = []) { +// const fileList = fs.readdirSync(dir); +// for (const file of fileList) { +// const name = `${dir}/${file}`; +// if ( +// name.includes("node_modules") || +// name.includes("config") || +// name.includes("package.json") || +// name.includes("main.js") || +// name.includes("index-node.ts") || +// name.endsWith(".d.ts") +// ) { +// continue; +// } + +// if (fs.statSync(name).isDirectory()) { +// getFiles(name, files); +// } else if ( +// !( +// name.match(/\.(sa|sc|c)ss$/) || +// name.match(/\.(woff|woff2|eot|ttf|otf)$/) || +// name.match(/locales\/[^/]+\.json$/) +// ) +// ) { +// continue; +// } else { +// files.push(name); +// } +// } +// return files; +// } + +const browserConfig = { + entryPoints: ["index.tsx"], + bundle: true, + format: "esm", + plugins: [ + sassPlugin(), + externalGlobalPlugin({ + react: "React", + "react-dom": "ReactDOM", + }), + ], + splitting: true, + loader: { + ".woff2": "copy", + ".ttf": "copy", + }, +}; +const createESMBrowserBuild = async () => { + // Development unminified build with source maps + await build({ + ...browserConfig, + outdir: "dist/browser/dev", + sourcemap: true, + chunkNames: "excalidraw-assets-dev/[name]-[hash]", + define: { + "import.meta.env": JSON.stringify({ DEV: true }), + }, + }); + + // production minified build without sourcemaps + await build({ + ...browserConfig, + outdir: "dist/browser/prod", + minify: true, + chunkNames: "excalidraw-assets/[name]-[hash]", + define: { + "import.meta.env": JSON.stringify({ PROD: true }), + }, + }); +}; + +// const BASE_PATH = `${path.resolve(`${__dirname}/..`)}`; +// const filesinExcalidrawPackage = [ +// ...getFiles(`${BASE_PATH}/packages/excalidraw`), +// `${BASE_PATH}/packages/utils/export.ts`, +// `${BASE_PATH}/packages/utils/bbox.ts`, +// ...getFiles(`${BASE_PATH}/public/fonts`), +// ]; + +// const filesToTransform = filesinExcalidrawPackage.filter((file) => { +// return !( +// file.includes("/__tests__/") || +// file.includes(".test.") || +// file.includes("/tests/") || +// file.includes("example") +// ); +// }); + +const rawConfig = { + entryPoints: ["index.tsx"], + bundle: true, + format: "esm", + plugins: [sassPlugin()], + + loader: { + ".woff2": "copy", + ".ttf": "copy", + ".json": "copy", + }, + packages: "external", +}; + +const createESMRawBuild = async () => { + // Development unminified build with source maps + await build({ + ...rawConfig, + sourcemap: true, + outdir: "dist/dev", + define: { + "import.meta.env": JSON.stringify({ DEV: true }), + }, + }); + + // production minified build without sourcemaps + await build({ + ...rawConfig, + minify: true, + outdir: "dist/prod", + define: { + "import.meta.env": JSON.stringify({ PROD: true }), + }, + }); +}; + +createESMRawBuild(); +createESMBrowserBuild(); diff --git a/yarn.lock b/yarn.lock index 2279e1bf3..87493423e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1990,111 +1990,226 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@esbuild/aix-ppc64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz#fb3922a0183d27446de00cf60d4f7baaadf98d84" + integrity sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q== + +"@esbuild/android-arm64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz#ef31015416dd79398082409b77aaaa2ade4d531a" + integrity sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q== + "@esbuild/android-arm64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz#fb7130103835b6d43ea499c3f30cfb2b2ed58456" integrity sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA== +"@esbuild/android-arm@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.10.tgz#1c23c7e75473aae9fb323be5d9db225142f47f52" + integrity sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w== + "@esbuild/android-arm@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.8.tgz#b46e4d9e984e6d6db6c4224d72c86b7757e35bcb" integrity sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA== +"@esbuild/android-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.10.tgz#df6a4e6d6eb8da5595cfce16d4e3f6bc24464707" + integrity sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw== + "@esbuild/android-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.8.tgz#a13db9441b5a4f4e4fec4a6f8ffacfea07888db7" integrity sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A== +"@esbuild/darwin-arm64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz#8462a55db07c1b2fad61c8244ce04469ef1043be" + integrity sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA== + "@esbuild/darwin-arm64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz#49f5718d36541f40dd62bfdf84da9c65168a0fc2" integrity sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw== +"@esbuild/darwin-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz#d1de20bfd41bb75b955ba86a6b1004539e8218c1" + integrity sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA== + "@esbuild/darwin-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz#75c5c88371eea4bfc1f9ecfd0e75104c74a481ac" integrity sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q== +"@esbuild/freebsd-arm64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz#16904879e34c53a2e039d1284695d2db3e664d57" + integrity sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg== + "@esbuild/freebsd-arm64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz#9d7259fea4fd2b5f7437b52b542816e89d7c8575" integrity sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw== +"@esbuild/freebsd-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz#8ad9e5ca9786ca3f1ef1411bfd10b08dcd9d4cef" + integrity sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag== + "@esbuild/freebsd-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz#abac03e1c4c7c75ee8add6d76ec592f46dbb39e3" integrity sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg== +"@esbuild/linux-arm64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz#d82cf2c590faece82d28bbf1cfbe36f22ae25bd2" + integrity sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ== + "@esbuild/linux-arm64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz#c577932cf4feeaa43cb9cec27b89cbe0df7d9098" integrity sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ== +"@esbuild/linux-arm@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz#477b8e7c7bcd34369717b04dd9ee6972c84f4029" + integrity sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg== + "@esbuild/linux-arm@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz#d6014d8b98b5cbc96b95dad3d14d75bb364fdc0f" integrity sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ== +"@esbuild/linux-ia32@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz#d55ff822cf5b0252a57112f86857ff23be6cab0e" + integrity sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg== + "@esbuild/linux-ia32@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz#2379a0554307d19ac4a6cdc15b08f0ea28e7a40d" integrity sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ== +"@esbuild/linux-loong64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz#a9ad057d7e48d6c9f62ff50f6f208e331c4543c7" + integrity sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA== + "@esbuild/linux-loong64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz#e2a5bbffe15748b49356a6cd7b2d5bf60c5a7123" integrity sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ== +"@esbuild/linux-mips64el@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz#b011a96924773d60ebab396fbd7a08de66668179" + integrity sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A== + "@esbuild/linux-mips64el@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz#1359331e6f6214f26f4b08db9b9df661c57cfa24" integrity sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q== +"@esbuild/linux-ppc64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz#5d8b59929c029811e473f2544790ea11d588d4dd" + integrity sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ== + "@esbuild/linux-ppc64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz#9ba436addc1646dc89dae48c62d3e951ffe70951" integrity sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg== +"@esbuild/linux-riscv64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz#292b06978375b271bd8bc0a554e0822957508d22" + integrity sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA== + "@esbuild/linux-riscv64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz#fbcf0c3a0b20f40b5fc31c3b7695f0769f9de66b" integrity sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg== +"@esbuild/linux-s390x@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz#d30af63530f8d4fa96930374c9dd0d62bf59e069" + integrity sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA== + "@esbuild/linux-s390x@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz#989e8a05f7792d139d5564ffa7ff898ac6f20a4a" integrity sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg== +"@esbuild/linux-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz#898c72eeb74d9f2fb43acf316125b475548b75ce" + integrity sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA== + "@esbuild/linux-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz#b187295393a59323397fe5ff51e769ec4e72212b" integrity sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg== +"@esbuild/netbsd-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz#fd473a5ae261b43eab6dad4dbd5a3155906e6c91" + integrity sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q== + "@esbuild/netbsd-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz#c1ec0e24ea82313cb1c7bae176bd5acd5bde7137" integrity sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw== +"@esbuild/openbsd-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz#96eb8992e526717b5272321eaad3e21f3a608e46" + integrity sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg== + "@esbuild/openbsd-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz#0c5b696ac66c6d70cf9ee17073a581a28af9e18d" integrity sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ== +"@esbuild/sunos-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz#c16ee1c167f903eaaa6acf7372bee42d5a89c9bc" + integrity sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA== + "@esbuild/sunos-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz#2a697e1f77926ff09fcc457d8f29916d6cd48fb1" integrity sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w== +"@esbuild/win32-arm64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz#7e417d1971dbc7e469b4eceb6a5d1d667b5e3dcc" + integrity sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw== + "@esbuild/win32-arm64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz#ec029e62a2fca8c071842ecb1bc5c2dd20b066f1" integrity sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg== +"@esbuild/win32-ia32@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz#2b52dfec6cd061ecb36171c13bae554888b439e5" + integrity sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ== + "@esbuild/win32-ia32@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz#cbb9a3146bde64dc15543e48afe418c7a3214851" integrity sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw== +"@esbuild/win32-x64@0.19.10": + version "0.19.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz#bd123a74f243d2f3a1f046447bb9b363ee25d072" + integrity sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA== + "@esbuild/win32-x64@0.19.8": version "0.19.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz#c8285183dbdb17008578dbacb6e22748709b4822" @@ -2495,11 +2610,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" @@ -2526,7 +2636,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -2539,19 +2649,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@jridgewell/trace-mapping@^0.3.7": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -3246,41 +3343,11 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - "@types/chai@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw== -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -3324,38 +3391,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.41" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" - integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/http-proxy@^1.17.8": - version "1.17.14" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== - dependencies: - "@types/node" "*" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -3435,28 +3470,11 @@ dependencies: "@types/unist" "^2" -"@types/mime@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" - integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - "@types/ms@*": version "0.7.34" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== -"@types/node-forge@^1.3.0": - version "1.3.10" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.10.tgz#62a19d4f75a8b03290578c2b04f294b1a5a71b07" - integrity sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw== - dependencies: - "@types/node" "*" - "@types/node@*": version "18.15.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" @@ -3489,16 +3507,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== -"@types/qs@*": - version "6.9.10" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" - integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - "@types/react-dom@18.0.6": version "18.0.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1" @@ -3543,11 +3551,6 @@ dependencies: "@types/node" "*" -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - "@types/scheduler@*": version "0.16.3" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -3558,30 +3561,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== -"@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" - integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - "@types/socket.io-client@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-3.0.0.tgz#d0b8ea22121b7c1df68b6a923002f9c8e3cefb42" @@ -3589,13 +3568,6 @@ dependencies: socket.io-client "*" -"@types/sockjs@^0.3.33": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -3618,13 +3590,6 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== -"@types/ws@^8.5.1": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -4102,14 +4067,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-import-assertions@^1.7.6, acorn-import-assertions@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" @@ -4211,11 +4168,6 @@ ansi-escapes@^4.3.0: dependencies: type-fest "^0.21.3" -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -4304,16 +4256,6 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - array-includes@^3.1.5, array-includes@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" @@ -4638,11 +4580,6 @@ basic-auth@^2.0.1: dependencies: safe-buffer "5.1.2" -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -4662,34 +4599,6 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" - integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4765,16 +4674,6 @@ bytes-iec@^3.1.1: resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083" integrity sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA== -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - cac@^6.7.14: version "6.7.14" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" @@ -4997,7 +4896,7 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^2.0.10, colorette@^2.0.14: +colorette@^2.0.14: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== @@ -5044,26 +4943,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -5074,38 +4953,11 @@ confusing-browser-globals@^1.0.11: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - core-js-compat@^3.21.0, core-js-compat@^3.22.1: version "3.34.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.34.0.tgz#61a4931a13c52f8f08d924522bba65f8c94a5f17" @@ -5135,11 +4987,6 @@ core-js@^3.4: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5" integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - corser@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" @@ -5546,13 +5393,6 @@ dayjs@^1.11.7: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== -debug@2.6.9, debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -5560,6 +5400,13 @@ debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, de dependencies: ms "2.1.2" +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -5631,18 +5478,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3, define-properties@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" @@ -5663,36 +5498,16 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - devtools-protocol@0.0.981744: version "0.0.981744" resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.981744.tgz#9960da0370284577d46c28979a0b32651022bacf" @@ -5725,18 +5540,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -5795,11 +5598,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - ejs@^3.1.6, ejs@^3.1.9: version "3.1.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" @@ -5837,11 +5635,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -5987,6 +5780,48 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-plugin-external-global@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esbuild-plugin-external-global/-/esbuild-plugin-external-global-1.0.1.tgz#e3bba0e3a561f61b395bec0984a90ed0de06c4ce" + integrity sha512-NDzYHRoShpvLqNcrgV8ZQh61sMIFAry5KLTQV83BPG5iTXCCu7h72SCfJ97bW0GqtuqDD/1aqLbKinI/rNgUsg== + +esbuild-sass-plugin@2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz#2908ab5e104cfc980118c46d0b409cbab8aa32dd" + integrity sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw== + dependencies: + resolve "^1.22.6" + sass "^1.7.3" + +esbuild@0.19.10: + version "0.19.10" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.10.tgz#55e83e4a6b702e3498b9f872d84bfb4ebcb6d16e" + integrity sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.10" + "@esbuild/android-arm" "0.19.10" + "@esbuild/android-arm64" "0.19.10" + "@esbuild/android-x64" "0.19.10" + "@esbuild/darwin-arm64" "0.19.10" + "@esbuild/darwin-x64" "0.19.10" + "@esbuild/freebsd-arm64" "0.19.10" + "@esbuild/freebsd-x64" "0.19.10" + "@esbuild/linux-arm" "0.19.10" + "@esbuild/linux-arm64" "0.19.10" + "@esbuild/linux-ia32" "0.19.10" + "@esbuild/linux-loong64" "0.19.10" + "@esbuild/linux-mips64el" "0.19.10" + "@esbuild/linux-ppc64" "0.19.10" + "@esbuild/linux-riscv64" "0.19.10" + "@esbuild/linux-s390x" "0.19.10" + "@esbuild/linux-x64" "0.19.10" + "@esbuild/netbsd-x64" "0.19.10" + "@esbuild/openbsd-x64" "0.19.10" + "@esbuild/sunos-x64" "0.19.10" + "@esbuild/win32-arm64" "0.19.10" + "@esbuild/win32-ia32" "0.19.10" + "@esbuild/win32-x64" "0.19.10" + esbuild@^0.19.3: version "0.19.8" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.8.tgz#ad05b72281d84483fa6b5345bd246c27a207b8f1" @@ -6020,11 +5855,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -6319,11 +6149,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -6334,7 +6159,7 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^5.0.0, execa@^5.1.1: +execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -6375,43 +6200,6 @@ expect@^29.0.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" @@ -6502,13 +6290,6 @@ faye-websocket@0.11.3: dependencies: websocket-driver ">=0.5.1" -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -6550,19 +6331,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - find-cache-dir@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" @@ -6644,21 +6412,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - fraction.js@^4.2.0: version "4.3.7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -6683,11 +6441,6 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^2.0.0" -fs-monkey@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" - integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -6864,7 +6617,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6886,11 +6639,6 @@ hachure-fill@^0.5.2: resolved "https://registry.yarnpkg.com/hachure-fill/-/hachure-fill-0.5.2.tgz#d19bc4cc8750a5962b47fb1300557a85fcf934cc" integrity sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg== -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -6961,16 +6709,6 @@ heap@^0.2.6: resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc" integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - html-encoding-sniffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" @@ -6978,42 +6716,11 @@ html-encoding-sniffer@^3.0.0: dependencies: whatwg-encoding "^2.0.0" -html-entities@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-parser-js@>=0.5.1: version "0.5.8" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" @@ -7028,17 +6735,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -7097,13 +6793,6 @@ i18next-browser-languagedetector@6.1.4: dependencies: "@babel/runtime" "^7.14.6" -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6, iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -7204,16 +6893,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" @@ -7240,16 +6924,6 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== - is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -7327,11 +7001,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -7386,11 +7055,6 @@ is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -7483,23 +7147,11 @@ is-weakset@^2.0.1: call-bind "^1.0.2" get-intrinsic "^1.1.1" -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -8089,23 +7741,6 @@ mdast-util-to-string@^3.1.0: dependencies: "@types/mdast" "^3.0.0" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -8139,11 +7774,6 @@ mermaid@10.2.3: uuid "^9.0.0" web-worker "^1.2.0" -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - micromark-core-commonmark@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8" @@ -8338,7 +7968,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -8346,19 +7976,19 @@ micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.6.0: +mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -8385,11 +8015,6 @@ mini-css-extract-plugin@2.6.1: dependencies: schema-utils "^4.0.0" -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -8468,19 +8093,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - multimath@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/multimath/-/multimath-2.0.0.tgz#0d37acf67c328f30e3d8c6b0d3209e6082710302" @@ -8521,11 +8138,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -8543,11 +8155,6 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -8660,23 +8267,6 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -8703,15 +8293,6 @@ open-color@1.9.1: resolved "https://registry.yarnpkg.com/open-color/-/open-color-1.9.1.tgz#a6e6328f60eff7aa60e3e8fcfa50f53ff3eece35" integrity sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw== -open@^8.0.9: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - opener@^1.5.1, opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -8757,14 +8338,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -8799,11 +8372,6 @@ parse5@^7.1.2: dependencies: entities "^4.4.0" -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-data-parser@0.1.0, path-data-parser@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c" @@ -8834,11 +8402,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -9062,11 +8625,6 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - progress@2.0.3, progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -9123,14 +8681,6 @@ protobufjs@^7.2.4: "@types/node" ">=13.7.0" long "^5.0.0" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - proxy-from-env@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -9177,13 +8727,6 @@ pwacompat@2.0.17: resolved "https://registry.yarnpkg.com/pwacompat/-/pwacompat-2.0.17.tgz#707959ff97f239bf1fe7b260b63aeea416a15eab" integrity sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w== -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - qs@^6.4.0: version "6.11.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" @@ -9208,21 +8751,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - react-dom@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -9286,20 +8814,7 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -9455,7 +8970,7 @@ resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.9.0: +resolve@^1.22.6, resolve@^1.9.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -9481,11 +8996,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -9597,12 +9107,12 @@ safari-14-idb-fix@^3.0.0: resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -9616,7 +9126,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -9638,6 +9148,15 @@ sass@1.51.0: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +sass@^1.7.3: + version "1.69.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.5.tgz#23e18d1c757a35f2e52cc81871060b9ad653dfde" + integrity sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + saxes@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" @@ -9685,19 +9204,6 @@ secure-compare@3.0.1: resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - semver@7.5.4, semver@^7.3.4, semver@^7.3.5, semver@^7.5.0, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -9722,25 +9228,6 @@ semver@^7.2.1, semver@^7.3.7: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -9748,46 +9235,13 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: +serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== dependencies: randombytes "^2.1.0" -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -9915,15 +9369,6 @@ socket.io-parser@~4.2.4: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -9962,29 +9407,6 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -10002,16 +9424,6 @@ stackback@0.0.2: resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - std-env@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" @@ -10105,13 +9517,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" @@ -10289,17 +9694,6 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -terser-webpack-plugin@5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90" - integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.7" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.7.2" - terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.7: version "5.3.9" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" @@ -10321,7 +9715,7 @@ terser@^5.0.0: commander "^2.20.0" source-map-support "~0.5.20" -terser@^5.16.8, terser@^5.7.2: +terser@^5.16.8: version "5.26.0" resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== @@ -10350,11 +9744,6 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - tiny-invariant@^1.1.0: version "1.3.1" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" @@ -10392,11 +9781,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - totalist@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" @@ -10524,14 +9908,6 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -10647,11 +10023,6 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -10718,21 +10089,11 @@ use-sync-external-store@1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - uuid@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" @@ -10762,11 +10123,6 @@ v8-to-istanbul@^9.1.0: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - vite-node@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.0.1.tgz#c16c9df9b5d47b74156a6501c9db5b380d992768" @@ -10931,13 +10287,6 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - web-worker@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da" @@ -10996,60 +10345,6 @@ webpack-cli@4.10.0: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@4.9.3: - version "4.9.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9" - integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.0.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.4.2" - -webpack-merge@5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - webpack-merge@^5.7.3: version "5.10.0" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" @@ -11124,7 +10419,7 @@ webpack@^5.88.2: watchpack "^2.4.0" webpack-sources "^3.2.3" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: +websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -11448,11 +10743,6 @@ ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== -ws@^8.4.2: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - ws@~8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" From 49f15c736b3b8f07c86b04c8654133714bd70574 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 3 Jan 2024 16:25:36 +0530 Subject: [PATCH 31/79] chore: remove unused files (#7509) chore remove unused files --- packages/excalidraw/main.js | 5 ----- packages/excalidraw/tsconfig-types.json | 20 -------------------- 2 files changed, 25 deletions(-) delete mode 100644 packages/excalidraw/main.js delete mode 100644 packages/excalidraw/tsconfig-types.json diff --git a/packages/excalidraw/main.js b/packages/excalidraw/main.js deleted file mode 100644 index 56e511b25..000000000 --- a/packages/excalidraw/main.js +++ /dev/null @@ -1,5 +0,0 @@ -if (process.env.NODE_ENV !== "development") { - import("./dist/dev/index.js"); -} else { - import("./dist/prod/index.js"); -} diff --git a/packages/excalidraw/tsconfig-types.json b/packages/excalidraw/tsconfig-types.json deleted file mode 100644 index 3f9b8c6ce..000000000 --- a/packages/excalidraw/tsconfig-types.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "include": ["./**/*"], - "exclude": ["**/*.test.*", "tests", "types"], - "compilerOptions": { - "types": ["vite/client", "vite-plugin-svgr/client"], - "allowJs": true, - "declaration": true, - "emitDeclarationOnly": true, - "outDir": "types", - "jsx": "react-jsx", - "target": "es6", - "lib": ["dom", "dom.iterable", "esnext"], - "module": "ESNext", - "moduleResolution": "node", - "resolveJsonModule": true, - "skipLibCheck": true, - "allowSyntheticDefaultImports": true, - "strict": true - } -} From 4249b7dec888711796fac1b043b3e982b35fa647 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 4 Jan 2024 13:53:19 +0530 Subject: [PATCH 32/79] chore: add version for excalidraw-app workspace (#7514) --- excalidraw-app/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/excalidraw-app/package.json b/excalidraw-app/package.json index f9a59a40c..7d602d03a 100644 --- a/excalidraw-app/package.json +++ b/excalidraw-app/package.json @@ -1,4 +1,8 @@ { + "name": "excalidraw-app", + "version": "1.0.0", + "private": true, + "homepage": ".", "browserslist": { "production": [ ">0.2%", @@ -22,17 +26,13 @@ "node": ">=18.0.0" }, "dependencies": {}, - "homepage": ".", - "name": "excalidraw-app", "prettier": "@excalidraw/prettier-config", - "private": true, "scripts": { "build-node": "node ./scripts/build-node.js", "build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build", "build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build", "build:version": "node ../scripts/build-version.js", "build": "yarn build:app && yarn build:version", - "install:deps": "yarn install --frozen-lockfile && yarn --cwd ../", "start": "yarn && vite", "start:production": "npm run build && npx http-server build -a localhost -p 5001 -o", "build:preview": "yarn build && vite preview --port 5000" From 43ccc875fb1658383cdb29a9ac65517f4e0584e0 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:27:52 +0100 Subject: [PATCH 33/79] feat: support multi-embed pasting & x.com domain (#7516) --- packages/excalidraw/components/App.tsx | 56 +++++++++++++++++----- packages/excalidraw/element/embeddable.ts | 22 +++++---- packages/excalidraw/element/textElement.ts | 8 ++-- packages/excalidraw/utils.ts | 4 ++ 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 069392a2d..b439907e9 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -182,6 +182,7 @@ import { ExcalidrawIframeLikeElement, IframeData, ExcalidrawIframeElement, + ExcalidrawEmbeddableElement, } from "../element/types"; import { getCenter, getDistance } from "../gesture"; import { @@ -271,11 +272,12 @@ import { easeOut, updateStable, addEventListener, + normalizeEOL, } from "../utils"; import { createSrcDoc, embeddableURLValidator, - extractSrc, + maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable"; import { @@ -2924,21 +2926,49 @@ class App extends React.Component { retainSeed: isPlainPaste, }); } else if (data.text) { - const maybeUrl = extractSrc(data.text); + const nonEmptyLines = normalizeEOL(data.text) + .split(/\n+/) + .map((s) => s.trim()) + .filter(Boolean); + + const embbeddableUrls = nonEmptyLines + .map((str) => maybeParseEmbedSrc(str)) + .filter((string) => { + return ( + embeddableURLValidator(string, this.props.validateEmbeddable) && + (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(string) || + getEmbedLink(string)?.type === "video") + ); + }); if ( - !isPlainPaste && - embeddableURLValidator(maybeUrl, this.props.validateEmbeddable) && - (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(maybeUrl) || - getEmbedLink(maybeUrl)?.type === "video") + !IS_PLAIN_PASTE && + embbeddableUrls.length > 0 && + // if there were non-embeddable text (lines) mixed in with embeddable + // urls, ignore and paste as text + embbeddableUrls.length === nonEmptyLines.length ) { - const embeddable = this.insertEmbeddableElement({ - sceneX, - sceneY, - link: normalizeLink(maybeUrl), - }); - if (embeddable) { - this.setState({ selectedElementIds: { [embeddable.id]: true } }); + const embeddables: NonDeleted[] = []; + for (const url of embbeddableUrls) { + const prevEmbeddable: ExcalidrawEmbeddableElement | undefined = + embeddables[embeddables.length - 1]; + const embeddable = this.insertEmbeddableElement({ + sceneX: prevEmbeddable + ? prevEmbeddable.x + prevEmbeddable.width + 20 + : sceneX, + sceneY, + link: normalizeLink(url), + }); + if (embeddable) { + embeddables.push(embeddable); + } + } + if (embeddables.length) { + this.setState({ + selectedElementIds: Object.fromEntries( + embeddables.map((embeddable) => [embeddable.id, true]), + ), + }); } return; } diff --git a/packages/excalidraw/element/embeddable.ts b/packages/excalidraw/element/embeddable.ts index c129d3927..025ed4901 100644 --- a/packages/excalidraw/element/embeddable.ts +++ b/packages/excalidraw/element/embeddable.ts @@ -32,9 +32,9 @@ const RE_GH_GIST_EMBED = /^ twitter embeds -const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?twitter.com/; +const RE_TWITTER = /(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:twitter|x).com/; const RE_TWITTER_EMBED = - /^ { - const twitterMatch = htmlString.match(RE_TWITTER_EMBED); +export const maybeParseEmbedSrc = (str: string): string => { + const twitterMatch = str.match(RE_TWITTER_EMBED); if (twitterMatch && twitterMatch.length === 2) { return twitterMatch[1]; } - const gistMatch = htmlString.match(RE_GH_GIST_EMBED); + const gistMatch = str.match(RE_GH_GIST_EMBED); if (gistMatch && gistMatch.length === 2) { return gistMatch[1]; } - if (RE_GIPHY.test(htmlString)) { - return `https://giphy.com/embed/${RE_GIPHY.exec(htmlString)![1]}`; + if (RE_GIPHY.test(str)) { + return `https://giphy.com/embed/${RE_GIPHY.exec(str)![1]}`; } - const match = htmlString.match(RE_GENERIC_EMBED); + const match = str.match(RE_GENERIC_EMBED); if (match && match.length === 2) { return match[1]; } - return htmlString; + return str; }; export const embeddableURLValidator = ( diff --git a/packages/excalidraw/element/textElement.ts b/packages/excalidraw/element/textElement.ts index f812b8577..e084dfba3 100644 --- a/packages/excalidraw/element/textElement.ts +++ b/packages/excalidraw/element/textElement.ts @@ -1,4 +1,4 @@ -import { getFontString, arrayToMap, isTestEnv } from "../utils"; +import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils"; import { ExcalidrawElement, ExcalidrawElementType, @@ -39,15 +39,13 @@ import { ExtractSetType } from "../utility-types"; export const normalizeText = (text: string) => { return ( - text + normalizeEOL(text) // replace tabs with spaces so they render and measure correctly .replace(/\t/g, " ") - // normalize newlines - .replace(/\r?\n|\r/g, "\n") ); }; -export const splitIntoLines = (text: string) => { +const splitIntoLines = (text: string) => { return normalizeText(text).split("\n"); }; diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 8b39ba6bd..1f0e31760 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -1071,3 +1071,7 @@ export function addEventListener( target?.removeEventListener?.(type, listener, options); }; } + +export const normalizeEOL = (str: string) => { + return str.replace(/\r?\n|\r/g, "\n"); +}; From 1cb350b2aa90c37cd86c8e9e40738babbae4e0bc Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:57:31 +0100 Subject: [PATCH 34/79] feat: update X brand logo & tweak labels (#7518) --- packages/excalidraw/components/icons.tsx | 7 +- .../components/main-menu/DefaultItems.tsx | 56 ++++++------ packages/excalidraw/locales/en.json | 4 +- .../__snapshots__/excalidraw.test.tsx.snap | 91 ++++++++++--------- 4 files changed, 84 insertions(+), 74 deletions(-) diff --git a/packages/excalidraw/components/icons.tsx b/packages/excalidraw/components/icons.tsx index 62fc39574..fcf8df4a6 100644 --- a/packages/excalidraw/components/icons.tsx +++ b/packages/excalidraw/components/icons.tsx @@ -486,10 +486,11 @@ export const DiscordIcon = createIcon( modifiedTablerIconProps, ); -export const TwitterIcon = createIcon( +export const XBrandIcon = createIcon( - - + + + , tablerIconProps, ); diff --git a/packages/excalidraw/components/main-menu/DefaultItems.tsx b/packages/excalidraw/components/main-menu/DefaultItems.tsx index 9191bbe83..cc74e4c74 100644 --- a/packages/excalidraw/components/main-menu/DefaultItems.tsx +++ b/packages/excalidraw/components/main-menu/DefaultItems.tsx @@ -17,7 +17,7 @@ import { TrashIcon, usersIcon, } from "../icons"; -import { GithubIcon, DiscordIcon, TwitterIcon } from "../icons"; +import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons"; import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem"; import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink"; import { @@ -241,31 +241,35 @@ export const Export = () => { }; Export.displayName = "Export"; -export const Socials = () => ( - <> - - GitHub - - - Discord - - - Twitter - - -); +export const Socials = () => { + const { t } = useI18n(); + + return ( + <> + + GitHub + + + {t("labels.followUs")} + + + {t("labels.discordChat")} + + + ); +}; Socials.displayName = "Socials"; export const LiveCollaborationTrigger = ({ diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index a57b823d4..52305535a 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -138,7 +138,9 @@ "removeAllElementsFromFrame": "Remove all elements from frame", "eyeDropper": "Pick color from canvas", "textToDiagram": "Text to diagram", - "prompt": "Prompt" + "prompt": "Prompt", + "followUs": "Follow us", + "discordChat": "Discord chat" }, "library": { "noItems": "No items added yet...", diff --git a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap index 39aed3745..c06fff7e4 100644 --- a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap @@ -386,6 +386,52 @@ exports[` > Test UIOptions prop > Test canvasActions > should rende GitHub
+ + + + > Test UIOptions prop > Test canvasActions > should rende - - - - From 8b993d409ee444cc531a744e1d89ae0dac67e88a Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 4 Jan 2024 19:03:04 +0100 Subject: [PATCH 35/79] feat: render embeds lazily (#7519) --- packages/excalidraw/components/App.tsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index b439907e9..5b0a3b593 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -526,6 +526,7 @@ class App extends React.Component { public files: BinaryFiles = {}; public imageCache: AppClassProperties["imageCache"] = new Map(); private iFrameRefs = new Map(); + private initializedEmbeds = new Set(); hitLinkElement?: NonDeletedExcalidrawElement; lastPointerDownEvent: React.PointerEvent | null = null; @@ -897,6 +898,23 @@ class App extends React.Component { this.state, ); + const isVisible = isElementInViewport( + el, + normalizedWidth, + normalizedHeight, + this.state, + ); + const hasBeenInitialized = this.initializedEmbeds.has(el.id); + + if (isVisible && !hasBeenInitialized) { + this.initializedEmbeds.add(el.id); + } + const shouldRender = isVisible || hasBeenInitialized; + + if (!shouldRender) { + return null; + } + let src: IframeData | null; if (isIframeElement(el)) { @@ -1038,14 +1056,6 @@ class App extends React.Component { src = getEmbedLink(toValidURL(el.link || "")); } - // console.log({ src }); - - const isVisible = isElementInViewport( - el, - normalizedWidth, - normalizedHeight, - this.state, - ); const isActive = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "active"; From 65047cc2cb59843a4305f9088d886edd9b933585 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 8 Jan 2024 21:01:47 +0530 Subject: [PATCH 36/79] fix: decouple react and react-dom imports from utils and make it treeshakeable (#7527) fix: decouple react and react-dom imports from utils and make it tree-shakeable --- excalidraw-app/collab/Collab.tsx | 2 +- packages/excalidraw/components/App.tsx | 3 +- .../components/canvases/InteractiveCanvas.tsx | 7 +-- .../components/canvases/StaticCanvas.tsx | 3 +- packages/excalidraw/example/App.tsx | 8 +-- packages/excalidraw/reactUtils.ts | 61 +++++++++++++++++++ packages/excalidraw/utils.ts | 59 ------------------ 7 files changed, 69 insertions(+), 74 deletions(-) create mode 100644 packages/excalidraw/reactUtils.ts diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 9b26af054..92d94dbc9 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -22,7 +22,6 @@ import { preventUnload, resolvablePromise, throttleRAF, - withBatchedUpdates, } from "../../packages/excalidraw/utils"; import { CURSOR_SYNC_TIMEOUT, @@ -83,6 +82,7 @@ import { atom, useAtom } from "jotai"; import { appJotaiStore } from "../app-jotai"; import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types"; import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds"; +import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils"; export const collabAPIAtom = atom(null); export const collabDialogShownAtom = atom(false); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 5b0a3b593..792db29f7 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -259,9 +259,7 @@ import { sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, - withBatchedUpdates, wrapEvent, - withBatchedUpdatesThrottled, updateObject, updateActiveTool, getShortcutKey, @@ -403,6 +401,7 @@ import { ElementCanvasButton } from "./MagicButton"; import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; import FollowMode from "./FollowMode/FollowMode"; +import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); diff --git a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx index 5a524921a..0aaa52c7c 100644 --- a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx +++ b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx @@ -1,10 +1,6 @@ import React, { useEffect, useRef } from "react"; import { renderInteractiveScene } from "../../renderer/renderScene"; -import { - isRenderThrottlingEnabled, - isShallowEqual, - sceneCoordsToViewportCoords, -} from "../../utils"; +import { isShallowEqual, sceneCoordsToViewportCoords } from "../../utils"; import { CURSOR_TYPE } from "../../constants"; import { t } from "../../i18n"; import type { DOMAttributes } from "react"; @@ -14,6 +10,7 @@ import type { RenderInteractiveSceneCallback, } from "../../scene/types"; import type { NonDeletedExcalidrawElement } from "../../element/types"; +import { isRenderThrottlingEnabled } from "../../reactUtils"; type InteractiveCanvasProps = { containerRef: React.RefObject; diff --git a/packages/excalidraw/components/canvases/StaticCanvas.tsx b/packages/excalidraw/components/canvases/StaticCanvas.tsx index 38b9baade..c8174566b 100644 --- a/packages/excalidraw/components/canvases/StaticCanvas.tsx +++ b/packages/excalidraw/components/canvases/StaticCanvas.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useRef } from "react"; import { RoughCanvas } from "roughjs/bin/canvas"; import { renderStaticScene } from "../../renderer/renderScene"; -import { isRenderThrottlingEnabled, isShallowEqual } from "../../utils"; +import { isShallowEqual } from "../../utils"; import type { AppState, StaticCanvasAppState } from "../../types"; import type { StaticCanvasRenderConfig } from "../../scene/types"; import type { NonDeletedExcalidrawElement } from "../../element/types"; +import { isRenderThrottlingEnabled } from "../../reactUtils"; type StaticCanvasProps = { canvas: HTMLCanvasElement; diff --git a/packages/excalidraw/example/App.tsx b/packages/excalidraw/example/App.tsx index 4a5160788..50dc5b9a3 100644 --- a/packages/excalidraw/example/App.tsx +++ b/packages/excalidraw/example/App.tsx @@ -5,12 +5,7 @@ import type * as TExcalidraw from "../index"; import "./App.scss"; import initialData from "./initialData"; import { nanoid } from "nanoid"; -import { - resolvablePromise, - ResolvablePromise, - withBatchedUpdates, - withBatchedUpdatesThrottled, -} from "../utils"; +import { resolvablePromise, ResolvablePromise } from "../utils"; import { EVENT, ROUNDNESS } from "../constants"; import { distance2d } from "../math"; import { fileOpen } from "../data/filesystem"; @@ -29,6 +24,7 @@ import { ImportedLibraryData } from "../data/types"; import CustomFooter from "./CustomFooter"; import MobileFooter from "./MobileFooter"; import { KEYS } from "../keys"; +import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; declare global { interface Window { diff --git a/packages/excalidraw/reactUtils.ts b/packages/excalidraw/reactUtils.ts new file mode 100644 index 000000000..535302d42 --- /dev/null +++ b/packages/excalidraw/reactUtils.ts @@ -0,0 +1,61 @@ +/** + * @param func handler taking at most single parameter (event). + */ + +import { unstable_batchedUpdates } from "react-dom"; +import { version as ReactVersion } from "react"; +import { throttleRAF } from "./utils"; + +export const withBatchedUpdates = < + TFunction extends ((event: any) => void) | (() => void), +>( + func: Parameters["length"] extends 0 | 1 ? TFunction : never, +) => + ((event) => { + unstable_batchedUpdates(func as TFunction, event); + }) as TFunction; + +/** + * barches React state updates and throttles the calls to a single call per + * animation frame + */ +export const withBatchedUpdatesThrottled = < + TFunction extends ((event: any) => void) | (() => void), +>( + func: Parameters["length"] extends 0 | 1 ? TFunction : never, +) => { + // @ts-ignore + return throttleRAF>(((event) => { + unstable_batchedUpdates(func, event); + }) as TFunction); +}; + +export const isRenderThrottlingEnabled = (() => { + // we don't want to throttle in react < 18 because of #5439 and it was + // getting more complex to maintain the fix + let IS_REACT_18_AND_UP: boolean; + try { + const version = ReactVersion.split("."); + IS_REACT_18_AND_UP = Number(version[0]) > 17; + } catch { + IS_REACT_18_AND_UP = false; + } + + let hasWarned = false; + + return () => { + if (window.EXCALIDRAW_THROTTLE_RENDER === true) { + if (!IS_REACT_18_AND_UP) { + if (!hasWarned) { + hasWarned = true; + console.warn( + "Excalidraw: render throttling is disabled on React versions < 18.", + ); + } + return false; + } + return true; + } + return false; + }; +})(); diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 1f0e31760..c2afedb32 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -14,9 +14,7 @@ import { UnsubscribeCallback, Zoom, } from "./types"; -import { unstable_batchedUpdates } from "react-dom"; import { ResolutionType } from "./utility-types"; -import React from "react"; let mockDateTime: string | null = null; @@ -555,33 +553,6 @@ export const resolvablePromise = () => { return promise as ResolvablePromise; }; -/** - * @param func handler taking at most single parameter (event). - */ -export const withBatchedUpdates = < - TFunction extends ((event: any) => void) | (() => void), ->( - func: Parameters["length"] extends 0 | 1 ? TFunction : never, -) => - ((event) => { - unstable_batchedUpdates(func as TFunction, event); - }) as TFunction; - -/** - * barches React state updates and throttles the calls to a single call per - * animation frame - */ -export const withBatchedUpdatesThrottled = < - TFunction extends ((event: any) => void) | (() => void), ->( - func: Parameters["length"] extends 0 | 1 ? TFunction : never, -) => { - // @ts-ignore - return throttleRAF>(((event) => { - unstable_batchedUpdates(func, event); - }) as TFunction); -}; - //https://stackoverflow.com/a/9462382/8418 export const nFormatter = (num: number, digits: number): string => { const si = [ @@ -939,36 +910,6 @@ export const memoize = , R extends any>( return ret as typeof func & { clear: () => void }; }; -export const isRenderThrottlingEnabled = (() => { - // we don't want to throttle in react < 18 because of #5439 and it was - // getting more complex to maintain the fix - let IS_REACT_18_AND_UP: boolean; - try { - const version = React.version.split("."); - IS_REACT_18_AND_UP = Number(version[0]) > 17; - } catch { - IS_REACT_18_AND_UP = false; - } - - let hasWarned = false; - - return () => { - if (window.EXCALIDRAW_THROTTLE_RENDER === true) { - if (!IS_REACT_18_AND_UP) { - if (!hasWarned) { - hasWarned = true; - console.warn( - "Excalidraw: render throttling is disabled on React versions < 18.", - ); - } - return false; - } - return true; - } - return false; - }; -})(); - /** Checks if value is inside given collection. Useful for type-safety. */ export const isMemberOf = ( /** Set/Map/Array/Object */ From 1aaa40087606570ac28f17362422b42714c54a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=91CAT?= Date: Thu, 11 Jan 2024 20:09:33 +0900 Subject: [PATCH 37/79] docs: fix extra space in UIOptions/tools (#7537) fix typo in UIOptions/tools --- dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx index 5c2c40ccb..9d77e390a 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx @@ -73,9 +73,9 @@ function App() { ## tools -This `prop ` controls the visibility of the tools in the editor. +This `prop` controls the visibility of the tools in the editor. Currently you can control the visibility of `image` tool via this prop. | Prop | Type | Default | Description | | --- | --- | --- | --- | -| image | boolean | true | Decides whether `image` tool should be visible. \ No newline at end of file +| image | boolean | true | Decides whether `image` tool should be visible. From 3ecf72a50750e04b28c62c45ce8bf1388b9d9020 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 11 Jan 2024 16:40:45 +0530 Subject: [PATCH 38/79] docs: add changelog for ESM build (#7542) * docs: add changelog for ESM build * move to breaking change --- packages/excalidraw/CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index 652c77848..3fd3f3783 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -17,6 +17,38 @@ Please add the latest change on the top under the correct section. ### Breaking Changes +- Create an `ESM` build for `@excalidraw/excalidraw`. The API is in progress and subject to change before stable release. There are some changes on how the package will be consumed + + #### Bundler + + - CSS needs to be imported so you will need to import the css along with the excalidraw component + + ```js + import { Excalidraw } from "@excalidraw/excalidraw"; + import "@excalidraw/excalidraw/index.css"; + ``` + + - The `types` path is updated + + Instead of importing from `@excalidraw/excalidraw/types/`, you will need to import from `@excalidraw/excalidraw/dist/excalidraw` or `@excalidraw/excalidraw/dist/utils` depending on the types you are using. + + However this we will be fixing before stable release, so in case you want to try it out you will need to update the types for now. + + #### Browser + + - Since its `ESM` so now script type `module` can be used to load it and css needs to be loaded as well. + + ```html + + + ``` + - `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) ## 0.17.1 (2023-11-28) From 872973f145267a3a17d95d611baa7e3613ee293c Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:00:07 +0100 Subject: [PATCH 39/79] fix: do not modify elements while erasing (#7531) --- packages/excalidraw/components/App.tsx | 168 +++++++----------- packages/excalidraw/renderer/renderElement.ts | 35 +++- packages/excalidraw/scene/export.ts | 1 + packages/excalidraw/scene/types.ts | 2 + packages/excalidraw/types.ts | 8 +- 5 files changed, 101 insertions(+), 113 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 792db29f7..2d8967a4c 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -57,7 +57,6 @@ import { DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, - ELEMENT_READY_TO_ERASE_OPACITY, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, @@ -247,6 +246,7 @@ import { ToolType, OnUserFollowedPayload, UnsubscribeCallback, + ElementsPendingErasure, } from "../types"; import { debounce, @@ -402,6 +402,7 @@ import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; import FollowMode from "./FollowMode/FollowMode"; import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; +import { getRenderOpacity } from "../renderer/renderElement"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); @@ -527,6 +528,8 @@ class App extends React.Component { private iFrameRefs = new Map(); private initializedEmbeds = new Set(); + private elementsPendingErasure: ElementsPendingErasure = new Set(); + hitLinkElement?: NonDeletedExcalidrawElement; lastPointerDownEvent: React.PointerEvent | null = null; lastPointerUpEvent: React.PointerEvent | PointerEvent | null = @@ -1075,7 +1078,11 @@ class App extends React.Component { }px) scale(${scale})` : "none", display: isVisible ? "block" : "none", - opacity: el.opacity / 100, + opacity: getRenderOpacity( + el, + getContainingFrame(el), + this.elementsPendingErasure, + ), ["--embeddable-radius" as string]: `${getCornerRadius( Math.min(el.width, el.height), el, @@ -1583,6 +1590,7 @@ class App extends React.Component { renderGrid: true, canvasBackgroundColor: this.state.viewBackgroundColor, + elementsPendingErasure: this.elementsPendingErasure, }} /> { pointerDownState: PointerDownState, scenePointer: { x: number; y: number }, ) => { - const updateElementIds = (elements: ExcalidrawElement[]) => { - elements.forEach((element) => { + let didChange = false; + + const processElements = (elements: ExcalidrawElement[]) => { + for (const element of elements) { if (element.locked) { return; } - idsToUpdate.push(element.id); if (event.altKey) { - if ( - pointerDownState.elementIdsToErase[element.id] && - pointerDownState.elementIdsToErase[element.id].erase - ) { - pointerDownState.elementIdsToErase[element.id].erase = false; + if (this.elementsPendingErasure.delete(element.id)) { + didChange = true; } - } else if (!pointerDownState.elementIdsToErase[element.id]) { - pointerDownState.elementIdsToErase[element.id] = { - erase: true, - opacity: element.opacity, - }; + } else if (!this.elementsPendingErasure.has(element.id)) { + didChange = true; + this.elementsPendingErasure.add(element.id); } - }); + } }; - const idsToUpdate: Array = []; - const distance = distance2d( pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, @@ -5098,7 +5100,7 @@ class App extends React.Component { let samplingInterval = 0; while (samplingInterval <= distance) { const hitElements = this.getElementsAtPosition(point.x, point.y); - updateElementIds(hitElements); + processElements(hitElements); // Exit since we reached current point if (samplingInterval === distance) { @@ -5117,35 +5119,31 @@ class App extends React.Component { point.y = nextY; } - const elements = this.scene.getElementsIncludingDeleted().map((ele) => { - const id = - isBoundToContainer(ele) && idsToUpdate.includes(ele.containerId) - ? ele.containerId - : ele.id; - if (idsToUpdate.includes(id)) { - if (event.altKey) { - if ( - pointerDownState.elementIdsToErase[id] && - pointerDownState.elementIdsToErase[id].erase === false - ) { - return newElementWith(ele, { - opacity: pointerDownState.elementIdsToErase[id].opacity, - }); - } - } else { - return newElementWith(ele, { - opacity: ELEMENT_READY_TO_ERASE_OPACITY, - }); - } - } - return ele; - }); - - this.scene.replaceAllElements(elements); - pointerDownState.lastCoords.x = scenePointer.x; pointerDownState.lastCoords.y = scenePointer.y; + + if (didChange) { + for (const element of this.scene.getNonDeletedElements()) { + if ( + isBoundToContainer(element) && + (this.elementsPendingErasure.has(element.id) || + this.elementsPendingErasure.has(element.containerId)) + ) { + if (event.altKey) { + this.elementsPendingErasure.delete(element.id); + this.elementsPendingErasure.delete(element.containerId); + } else { + this.elementsPendingErasure.add(element.id); + this.elementsPendingErasure.add(element.containerId); + } + } + } + + this.elementsPendingErasure = new Set(this.elementsPendingErasure); + this.onSceneUpdated(); + } }; + // set touch moving for mobile context menu private handleTouchMove = (event: React.TouchEvent) => { invalidateContextMenu = true; @@ -5831,7 +5829,6 @@ class App extends React.Component { boxSelection: { hasOccurred: false, }, - elementIdsToErase: {}, }; } @@ -7815,18 +7812,14 @@ class App extends React.Component { scenePointer.x, scenePointer.y, ); - hitElements.forEach( - (hitElement) => - (pointerDownState.elementIdsToErase[hitElement.id] = { - erase: true, - opacity: hitElement.opacity, - }), + hitElements.forEach((hitElement) => + this.elementsPendingErasure.add(hitElement.id), ); } - this.eraseElements(pointerDownState); + this.eraseElements(); return; - } else if (Object.keys(pointerDownState.elementIdsToErase).length) { - this.restoreReadyToEraseElements(pointerDownState); + } else if (this.elementsPendingErasure.size) { + this.restoreReadyToEraseElements(); } if ( @@ -8087,65 +8080,32 @@ class App extends React.Component { }); } - private restoreReadyToEraseElements = ( - pointerDownState: PointerDownState, - ) => { - const elements = this.scene.getElementsIncludingDeleted().map((ele) => { - if ( - pointerDownState.elementIdsToErase[ele.id] && - pointerDownState.elementIdsToErase[ele.id].erase - ) { - return newElementWith(ele, { - opacity: pointerDownState.elementIdsToErase[ele.id].opacity, - }); - } else if ( - isBoundToContainer(ele) && - pointerDownState.elementIdsToErase[ele.containerId] && - pointerDownState.elementIdsToErase[ele.containerId].erase - ) { - return newElementWith(ele, { - opacity: pointerDownState.elementIdsToErase[ele.containerId].opacity, - }); - } else if ( - ele.frameId && - pointerDownState.elementIdsToErase[ele.frameId] && - pointerDownState.elementIdsToErase[ele.frameId].erase - ) { - return newElementWith(ele, { - opacity: pointerDownState.elementIdsToErase[ele.frameId].opacity, - }); - } - return ele; - }); - - this.scene.replaceAllElements(elements); + private restoreReadyToEraseElements = () => { + this.elementsPendingErasure = new Set(); + this.onSceneUpdated(); }; - private eraseElements = (pointerDownState: PointerDownState) => { + private eraseElements = () => { + let didChange = false; const elements = this.scene.getElementsIncludingDeleted().map((ele) => { if ( - pointerDownState.elementIdsToErase[ele.id] && - pointerDownState.elementIdsToErase[ele.id].erase - ) { - return newElementWith(ele, { isDeleted: true }); - } else if ( - isBoundToContainer(ele) && - pointerDownState.elementIdsToErase[ele.containerId] && - pointerDownState.elementIdsToErase[ele.containerId].erase - ) { - return newElementWith(ele, { isDeleted: true }); - } else if ( - ele.frameId && - pointerDownState.elementIdsToErase[ele.frameId] && - pointerDownState.elementIdsToErase[ele.frameId].erase + this.elementsPendingErasure.has(ele.id) || + (ele.frameId && this.elementsPendingErasure.has(ele.frameId)) || + (isBoundToContainer(ele) && + this.elementsPendingErasure.has(ele.containerId)) ) { + didChange = true; return newElementWith(ele, { isDeleted: true }); } return ele; }); - this.history.resumeRecording(); - this.scene.replaceAllElements(elements); + this.elementsPendingErasure = new Set(); + + if (didChange) { + this.history.resumeRecording(); + this.scene.replaceAllElements(elements); + } }; private initializeImage = async ({ diff --git a/packages/excalidraw/renderer/renderElement.ts b/packages/excalidraw/renderer/renderElement.ts index 2617d4694..94eda49f9 100644 --- a/packages/excalidraw/renderer/renderElement.ts +++ b/packages/excalidraw/renderer/renderElement.ts @@ -5,6 +5,7 @@ import { ExcalidrawFreeDrawElement, ExcalidrawImageElement, ExcalidrawTextElementWithContainer, + ExcalidrawFrameLikeElement, } from "../element/types"; import { isTextElement, @@ -36,10 +37,12 @@ import { BinaryFiles, Zoom, InteractiveCanvasAppState, + ElementsPendingErasure, } from "../types"; import { getDefaultAppState } from "../appState"; import { BOUND_TEXT_PADDING, + ELEMENT_READY_TO_ERASE_OPACITY, FRAME_STYLE, MAX_DECIMALS_FOR_SVG_EXPORT, MIME_TYPES, @@ -94,6 +97,27 @@ const shouldResetImageFilter = ( const getCanvasPadding = (element: ExcalidrawElement) => element.type === "freedraw" ? element.strokeWidth * 12 : 20; +export const getRenderOpacity = ( + element: ExcalidrawElement, + containingFrame: ExcalidrawFrameLikeElement | null, + elementsPendingErasure: ElementsPendingErasure, +) => { + // multiplying frame opacity with element opacity to combine them + // (e.g. frame 50% and element 50% opacity should result in 25% opacity) + let opacity = ((containingFrame?.opacity ?? 100) * element.opacity) / 10000; + + // if pending erasure, multiply again to combine further + // (so that erasing always results in lower opacity than original) + if ( + elementsPendingErasure.has(element.id) || + (containingFrame && elementsPendingErasure.has(containingFrame.id)) + ) { + opacity *= ELEMENT_READY_TO_ERASE_OPACITY / 100; + } + + return opacity; +}; + export interface ExcalidrawElementWithCanvas { element: ExcalidrawElement | ExcalidrawTextElement; canvas: HTMLCanvasElement; @@ -269,8 +293,6 @@ const drawElementOnCanvas = ( renderConfig: StaticCanvasRenderConfig, appState: StaticCanvasAppState, ) => { - context.globalAlpha = - ((getContainingFrame(element)?.opacity ?? 100) * element.opacity) / 10000; switch (element.type) { case "rectangle": case "iframe": @@ -372,7 +394,6 @@ const drawElementOnCanvas = ( } } } - context.globalAlpha = 1; }; export const elementWithCanvasCache = new WeakMap< @@ -595,6 +616,12 @@ export const renderElement = ( renderConfig: StaticCanvasRenderConfig, appState: StaticCanvasAppState, ) => { + context.globalAlpha = getRenderOpacity( + element, + getContainingFrame(element), + renderConfig.elementsPendingErasure, + ); + switch (element.type) { case "magicframe": case "frame": { @@ -831,6 +858,8 @@ export const renderElement = ( throw new Error(`Unimplemented type ${element.type}`); } } + + context.globalAlpha = 1; }; const roughSVGDrawWithPrecision = ( diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index 9bfab7e77..6220c59da 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -266,6 +266,7 @@ export const exportToCanvas = async ( imageCache, renderGrid: false, isExporting: true, + elementsPendingErasure: new Set(), }, }); diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index b4320866c..401ab86d5 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -7,6 +7,7 @@ import { import { AppClassProperties, AppState, + ElementsPendingErasure, InteractiveCanvasAppState, StaticCanvasAppState, } from "../types"; @@ -20,6 +21,7 @@ export type StaticCanvasRenderConfig = { /** when exporting the behavior is slightly different (e.g. we can't use CSS filters), and we disable render optimizations for best output */ isExporting: boolean; + elementsPendingErasure: ElementsPendingErasure; }; export type SVGRenderConfig = { diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 2ba9bd68d..3da06bec4 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -633,12 +633,6 @@ export type PointerDownState = Readonly<{ boxSelection: { hasOccurred: boolean; }; - elementIdsToErase: { - [key: ExcalidrawElement["id"]]: { - opacity: ExcalidrawElement["opacity"]; - erase: boolean; - }; - }; }>; export type UnsubscribeCallback = () => void; @@ -751,3 +745,5 @@ export type Primitive = | undefined; export type JSONValue = string | number | boolean | null | object; + +export type ElementsPendingErasure = Set; From 86cfeb714c63bf7efccf5c26bf1f30522395f5db Mon Sep 17 00:00:00 2001 From: Are Date: Thu, 11 Jan 2024 17:10:15 +0100 Subject: [PATCH 40/79] feat: add eraser tool trail (#7511) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/animated-trail.ts | 148 +++++++++ .../excalidraw/animation-frame-handler.ts | 79 +++++ packages/excalidraw/components/App.tsx | 59 +++- .../{LaserTool => }/LaserPointerButton.tsx | 6 +- .../components/LaserTool/LaserPathManager.ts | 310 ------------------ .../components/LaserTool/LaserTool.tsx | 27 -- packages/excalidraw/components/LayerUI.tsx | 2 +- .../LaserToolOverlay.scss => SVGLayer.scss} | 6 +- packages/excalidraw/components/SVGLayer.tsx | 33 ++ packages/excalidraw/laser-trails.ts | 124 +++++++ packages/excalidraw/package.json | 2 +- packages/excalidraw/utils.ts | 34 ++ yarn.lock | 8 +- 13 files changed, 482 insertions(+), 356 deletions(-) create mode 100644 packages/excalidraw/animated-trail.ts create mode 100644 packages/excalidraw/animation-frame-handler.ts rename packages/excalidraw/components/{LaserTool => }/LaserPointerButton.tsx (87%) delete mode 100644 packages/excalidraw/components/LaserTool/LaserPathManager.ts delete mode 100644 packages/excalidraw/components/LaserTool/LaserTool.tsx rename packages/excalidraw/components/{LaserTool/LaserToolOverlay.scss => SVGLayer.scss} (80%) create mode 100644 packages/excalidraw/components/SVGLayer.tsx create mode 100644 packages/excalidraw/laser-trails.ts diff --git a/packages/excalidraw/animated-trail.ts b/packages/excalidraw/animated-trail.ts new file mode 100644 index 000000000..de5fd08fd --- /dev/null +++ b/packages/excalidraw/animated-trail.ts @@ -0,0 +1,148 @@ +import { LaserPointer, LaserPointerOptions } from "@excalidraw/laser-pointer"; +import { AnimationFrameHandler } from "./animation-frame-handler"; +import { AppState } from "./types"; +import { getSvgPathFromStroke, sceneCoordsToViewportCoords } from "./utils"; +import type App from "./components/App"; +import { SVG_NS } from "./constants"; + +export interface Trail { + start(container: SVGSVGElement): void; + stop(): void; + + startPath(x: number, y: number): void; + addPointToPath(x: number, y: number): void; + endPath(): void; +} + +export interface AnimatedTrailOptions { + fill: (trail: AnimatedTrail) => string; +} + +export class AnimatedTrail implements Trail { + private currentTrail?: LaserPointer; + private pastTrails: LaserPointer[] = []; + + private container?: SVGSVGElement; + private trailElement: SVGPathElement; + + constructor( + private animationFrameHandler: AnimationFrameHandler, + private app: App, + private options: Partial & + Partial, + ) { + this.animationFrameHandler.register(this, this.onFrame.bind(this)); + + this.trailElement = document.createElementNS(SVG_NS, "path"); + } + + get hasCurrentTrail() { + return !!this.currentTrail; + } + + hasLastPoint(x: number, y: number) { + if (this.currentTrail) { + const len = this.currentTrail.originalPoints.length; + return ( + this.currentTrail.originalPoints[len - 1][0] === x && + this.currentTrail.originalPoints[len - 1][1] === y + ); + } + + return false; + } + + start(container?: SVGSVGElement) { + if (container) { + this.container = container; + } + + if (this.trailElement.parentNode !== this.container && this.container) { + this.container.appendChild(this.trailElement); + } + + this.animationFrameHandler.start(this); + } + + stop() { + this.animationFrameHandler.stop(this); + + if (this.trailElement.parentNode === this.container) { + this.container?.removeChild(this.trailElement); + } + } + + startPath(x: number, y: number) { + this.currentTrail = new LaserPointer(this.options); + + this.currentTrail.addPoint([x, y, performance.now()]); + + this.update(); + } + + addPointToPath(x: number, y: number) { + if (this.currentTrail) { + this.currentTrail.addPoint([x, y, performance.now()]); + this.update(); + } + } + + endPath() { + if (this.currentTrail) { + this.currentTrail.close(); + this.currentTrail.options.keepHead = false; + this.pastTrails.push(this.currentTrail); + this.currentTrail = undefined; + this.update(); + } + } + + private update() { + this.start(); + } + + private onFrame() { + const paths: string[] = []; + + for (const trail of this.pastTrails) { + paths.push(this.drawTrail(trail, this.app.state)); + } + + if (this.currentTrail) { + const currentPath = this.drawTrail(this.currentTrail, this.app.state); + + paths.push(currentPath); + } + + this.pastTrails = this.pastTrails.filter((trail) => { + return trail.getStrokeOutline().length !== 0; + }); + + if (paths.length === 0) { + this.stop(); + } + + const svgPaths = paths.join(" ").trim(); + + this.trailElement.setAttribute("d", svgPaths); + this.trailElement.setAttribute( + "fill", + (this.options.fill ?? (() => "black"))(this), + ); + } + + private drawTrail(trail: LaserPointer, state: AppState): string { + const stroke = trail + .getStrokeOutline(trail.options.size / state.zoom.value) + .map(([x, y]) => { + const result = sceneCoordsToViewportCoords( + { sceneX: x, sceneY: y }, + state, + ); + + return [result.x, result.y]; + }); + + return getSvgPathFromStroke(stroke, true); + } +} diff --git a/packages/excalidraw/animation-frame-handler.ts b/packages/excalidraw/animation-frame-handler.ts new file mode 100644 index 000000000..b1a984466 --- /dev/null +++ b/packages/excalidraw/animation-frame-handler.ts @@ -0,0 +1,79 @@ +export type AnimationCallback = (timestamp: number) => void | boolean; + +export type AnimationTarget = { + callback: AnimationCallback; + stopped: boolean; +}; + +export class AnimationFrameHandler { + private targets = new WeakMap(); + private rafIds = new WeakMap(); + + register(key: object, callback: AnimationCallback) { + this.targets.set(key, { callback, stopped: true }); + } + + start(key: object) { + const target = this.targets.get(key); + + if (!target) { + return; + } + + if (this.rafIds.has(key)) { + return; + } + + this.targets.set(key, { ...target, stopped: false }); + this.scheduleFrame(key); + } + + stop(key: object) { + const target = this.targets.get(key); + if (target && !target.stopped) { + this.targets.set(key, { ...target, stopped: true }); + } + + this.cancelFrame(key); + } + + private constructFrame(key: object): FrameRequestCallback { + return (timestamp: number) => { + const target = this.targets.get(key); + + if (!target) { + return; + } + + const shouldAbort = this.onFrame(target, timestamp); + + if (!target.stopped && !shouldAbort) { + this.scheduleFrame(key); + } else { + this.cancelFrame(key); + } + }; + } + + private scheduleFrame(key: object) { + const rafId = requestAnimationFrame(this.constructFrame(key)); + + this.rafIds.set(key, rafId); + } + + private cancelFrame(key: object) { + if (this.rafIds.has(key)) { + const rafId = this.rafIds.get(key)!; + + cancelAnimationFrame(rafId); + } + + this.rafIds.delete(key); + } + + private onFrame(target: AnimationTarget, timestamp: number): boolean { + const shouldAbort = target.callback(timestamp); + + return shouldAbort ?? false; + } +} diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 2d8967a4c..2d88d3a63 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -384,8 +384,7 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar"; import { StaticCanvas, InteractiveCanvas } from "./canvases"; import { Renderer } from "../scene/Renderer"; import { ShapeCache } from "../scene/ShapeCache"; -import { LaserToolOverlay } from "./LaserTool/LaserTool"; -import { LaserPathManager } from "./LaserTool/LaserPathManager"; +import { SVGLayer } from "./SVGLayer"; import { setEraserCursor, setCursor, @@ -401,6 +400,10 @@ import { ElementCanvasButton } from "./MagicButton"; import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; import { EditorLocalStorage } from "../data/EditorLocalStorage"; import FollowMode from "./FollowMode/FollowMode"; + +import { AnimationFrameHandler } from "../animation-frame-handler"; +import { AnimatedTrail } from "../animated-trail"; +import { LaserTrails } from "../laser-trails"; import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; import { getRenderOpacity } from "../renderer/renderElement"; @@ -537,7 +540,29 @@ class App extends React.Component { lastPointerMoveEvent: PointerEvent | null = null; lastViewportPosition = { x: 0, y: 0 }; - laserPathManager: LaserPathManager = new LaserPathManager(this); + animationFrameHandler = new AnimationFrameHandler(); + + laserTrails = new LaserTrails(this.animationFrameHandler, this); + eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, { + streamline: 0.2, + size: 5, + keepHead: true, + sizeMapping: (c) => { + const DECAY_TIME = 200; + const DECAY_LENGTH = 10; + const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME); + const l = + (DECAY_LENGTH - + Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / + DECAY_LENGTH; + + return Math.min(easeOut(l), easeOut(t)); + }, + fill: () => + this.state.theme === THEME.LIGHT + ? "rgba(0, 0, 0, 0.2)" + : "rgba(255, 255, 255, 0.2)", + }); onChangeEmitter = new Emitter< [ @@ -1471,7 +1496,9 @@ class App extends React.Component {
- + {selectedElements.length === 1 && this.state.showHyperlinkPopup && ( { this.removeEventListeners(); this.scene.destroy(); this.library.destroy(); - this.laserPathManager.destroy(); + this.laserTrails.stop(); + this.eraserTrail.stop(); this.onChangeEmitter.clear(); ShapeCache.destroy(); SnapCache.destroy(); @@ -2619,6 +2647,10 @@ class App extends React.Component { this.updateLanguage(); } + if (isEraserActive(prevState) && !isEraserActive(this.state)) { + this.eraserTrail.endPath(); + } + if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) { this.setState({ viewModeEnabled: !!this.props.viewModeEnabled }); } @@ -5070,6 +5102,8 @@ class App extends React.Component { pointerDownState: PointerDownState, scenePointer: { x: number; y: number }, ) => { + this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y); + let didChange = false; const processElements = (elements: ExcalidrawElement[]) => { @@ -5500,7 +5534,7 @@ class App extends React.Component { this.state.activeTool.type, ); } else if (this.state.activeTool.type === "laser") { - this.laserPathManager.startPath( + this.laserTrails.startPath( pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, ); @@ -5521,6 +5555,13 @@ class App extends React.Component { event, ); + if (this.state.activeTool.type === "eraser") { + this.eraserTrail.startPath( + pointerDownState.lastCoords.x, + pointerDownState.lastCoords.y, + ); + } + const onPointerMove = this.onPointerMoveFromPointerDownHandler(pointerDownState); @@ -6784,7 +6825,7 @@ class App extends React.Component { } if (this.state.activeTool.type === "laser") { - this.laserPathManager.addPointToPath(pointerCoords.x, pointerCoords.y); + this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y); } const [gridX, gridY] = getGridPoint( @@ -7793,6 +7834,8 @@ class App extends React.Component { const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent; if (isEraserActive(this.state) && pointerStart && pointerEnd) { + this.eraserTrail.endPath(); + const draggedDistance = distance2d( pointerStart.clientX, pointerStart.clientY, @@ -8041,7 +8084,7 @@ class App extends React.Component { } if (activeTool.type === "laser") { - this.laserPathManager.endPath(); + this.laserTrails.endPath(); return; } diff --git a/packages/excalidraw/components/LaserTool/LaserPointerButton.tsx b/packages/excalidraw/components/LaserPointerButton.tsx similarity index 87% rename from packages/excalidraw/components/LaserTool/LaserPointerButton.tsx rename to packages/excalidraw/components/LaserPointerButton.tsx index dbb843293..ae3cfb31a 100644 --- a/packages/excalidraw/components/LaserTool/LaserPointerButton.tsx +++ b/packages/excalidraw/components/LaserPointerButton.tsx @@ -1,8 +1,8 @@ -import "../ToolIcon.scss"; +import "./ToolIcon.scss"; import clsx from "clsx"; -import { ToolButtonSize } from "../ToolButton"; -import { laserPointerToolIcon } from "../icons"; +import { ToolButtonSize } from "./ToolButton"; +import { laserPointerToolIcon } from "./icons"; type LaserPointerIconProps = { title?: string; diff --git a/packages/excalidraw/components/LaserTool/LaserPathManager.ts b/packages/excalidraw/components/LaserTool/LaserPathManager.ts deleted file mode 100644 index b6e462aa3..000000000 --- a/packages/excalidraw/components/LaserTool/LaserPathManager.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { LaserPointer } from "@excalidraw/laser-pointer"; - -import { sceneCoordsToViewportCoords } from "../../utils"; -import App from "../App"; -import { getClientColor } from "../../clients"; -import { SocketId } from "../../types"; - -// decay time in milliseconds -const DECAY_TIME = 1000; -// length of line in points before it starts decaying -const DECAY_LENGTH = 50; - -const average = (a: number, b: number) => (a + b) / 2; -function getSvgPathFromStroke(points: number[][], closed = true) { - const len = points.length; - - if (len < 4) { - return ``; - } - - let a = points[0]; - let b = points[1]; - const c = points[2]; - - let result = `M${a[0].toFixed(2)},${a[1].toFixed(2)} Q${b[0].toFixed( - 2, - )},${b[1].toFixed(2)} ${average(b[0], c[0]).toFixed(2)},${average( - b[1], - c[1], - ).toFixed(2)} T`; - - for (let i = 2, max = len - 1; i < max; i++) { - a = points[i]; - b = points[i + 1]; - result += `${average(a[0], b[0]).toFixed(2)},${average(a[1], b[1]).toFixed( - 2, - )} `; - } - - if (closed) { - result += "Z"; - } - - return result; -} - -declare global { - interface Window { - LPM: LaserPathManager; - } -} - -function easeOutCubic(t: number) { - return 1 - Math.pow(1 - t, 3); -} - -function instantiateCollabolatorState(): CollabolatorState { - return { - currentPath: undefined, - finishedPaths: [], - lastPoint: [-10000, -10000], - svg: document.createElementNS("http://www.w3.org/2000/svg", "path"), - }; -} - -function instantiatePath() { - LaserPointer.constants.cornerDetectionMaxAngle = 70; - - return new LaserPointer({ - simplify: 0, - streamline: 0.4, - sizeMapping: (c) => { - const pt = DECAY_TIME; - const pl = DECAY_LENGTH; - const t = Math.max(0, 1 - (performance.now() - c.pressure) / pt); - const l = (pl - Math.min(pl, c.totalLength - c.currentIndex)) / pl; - - return Math.min(easeOutCubic(l), easeOutCubic(t)); - }, - }); -} - -type CollabolatorState = { - currentPath: LaserPointer | undefined; - finishedPaths: LaserPointer[]; - lastPoint: [number, number]; - svg: SVGPathElement; -}; - -export class LaserPathManager { - private ownState: CollabolatorState; - private collaboratorsState: Map = new Map(); - - private rafId: number | undefined; - private isDrawing = false; - private container: SVGSVGElement | undefined; - - constructor(private app: App) { - this.ownState = instantiateCollabolatorState(); - } - - destroy() { - this.stop(); - this.isDrawing = false; - this.ownState = instantiateCollabolatorState(); - this.collaboratorsState = new Map(); - } - - startPath(x: number, y: number) { - this.ownState.currentPath = instantiatePath(); - this.ownState.currentPath.addPoint([x, y, performance.now()]); - this.updatePath(this.ownState); - } - - addPointToPath(x: number, y: number) { - if (this.ownState.currentPath) { - this.ownState.currentPath?.addPoint([x, y, performance.now()]); - this.updatePath(this.ownState); - } - } - - endPath() { - if (this.ownState.currentPath) { - this.ownState.currentPath.close(); - this.ownState.finishedPaths.push(this.ownState.currentPath); - this.updatePath(this.ownState); - } - } - - private updatePath(state: CollabolatorState) { - this.isDrawing = true; - - if (!this.isRunning) { - this.start(); - } - } - - private isRunning = false; - - start(svg?: SVGSVGElement) { - if (svg) { - this.container = svg; - this.container.appendChild(this.ownState.svg); - } - - this.stop(); - this.isRunning = true; - this.loop(); - } - - stop() { - this.isRunning = false; - if (this.rafId) { - cancelAnimationFrame(this.rafId); - } - this.rafId = undefined; - } - - loop() { - this.rafId = requestAnimationFrame(this.loop.bind(this)); - - this.updateCollabolatorsState(); - - if (this.isDrawing) { - this.update(); - } else { - this.isRunning = false; - } - } - - draw(path: LaserPointer) { - const stroke = path - .getStrokeOutline(path.options.size / this.app.state.zoom.value) - .map(([x, y]) => { - const result = sceneCoordsToViewportCoords( - { sceneX: x, sceneY: y }, - this.app.state, - ); - - return [result.x, result.y]; - }); - - return getSvgPathFromStroke(stroke, true); - } - - updateCollabolatorsState() { - if (!this.container || !this.app.state.collaborators.size) { - return; - } - - for (const [key, collabolator] of this.app.state.collaborators.entries()) { - if (!this.collaboratorsState.has(key)) { - const state = instantiateCollabolatorState(); - this.container.appendChild(state.svg); - this.collaboratorsState.set(key, state); - - this.updatePath(state); - } - - const state = this.collaboratorsState.get(key)!; - - if (collabolator.pointer && collabolator.pointer.tool === "laser") { - if (collabolator.button === "down" && state.currentPath === undefined) { - state.lastPoint = [collabolator.pointer.x, collabolator.pointer.y]; - state.currentPath = instantiatePath(); - state.currentPath.addPoint([ - collabolator.pointer.x, - collabolator.pointer.y, - performance.now(), - ]); - - this.updatePath(state); - } - - if (collabolator.button === "down" && state.currentPath !== undefined) { - if ( - collabolator.pointer.x !== state.lastPoint[0] || - collabolator.pointer.y !== state.lastPoint[1] - ) { - state.lastPoint = [collabolator.pointer.x, collabolator.pointer.y]; - state.currentPath.addPoint([ - collabolator.pointer.x, - collabolator.pointer.y, - performance.now(), - ]); - - this.updatePath(state); - } - } - - if (collabolator.button === "up" && state.currentPath !== undefined) { - state.lastPoint = [collabolator.pointer.x, collabolator.pointer.y]; - state.currentPath.addPoint([ - collabolator.pointer.x, - collabolator.pointer.y, - performance.now(), - ]); - state.currentPath.close(); - - state.finishedPaths.push(state.currentPath); - state.currentPath = undefined; - - this.updatePath(state); - } - } - } - } - - update() { - if (!this.container) { - return; - } - - let somePathsExist = false; - - for (const [key, state] of this.collaboratorsState.entries()) { - if (!this.app.state.collaborators.has(key)) { - state.svg.remove(); - this.collaboratorsState.delete(key); - continue; - } - - state.finishedPaths = state.finishedPaths.filter((path) => { - const lastPoint = path.originalPoints[path.originalPoints.length - 1]; - - return !(lastPoint && lastPoint[2] < performance.now() - DECAY_TIME); - }); - - let paths = state.finishedPaths.map((path) => this.draw(path)).join(" "); - - if (state.currentPath) { - paths += ` ${this.draw(state.currentPath)}`; - } - - if (paths.trim()) { - somePathsExist = true; - } - - state.svg.setAttribute("d", paths); - state.svg.setAttribute("fill", getClientColor(key)); - } - - this.ownState.finishedPaths = this.ownState.finishedPaths.filter((path) => { - const lastPoint = path.originalPoints[path.originalPoints.length - 1]; - - return !(lastPoint && lastPoint[2] < performance.now() - DECAY_TIME); - }); - - let paths = this.ownState.finishedPaths - .map((path) => this.draw(path)) - .join(" "); - - if (this.ownState.currentPath) { - paths += ` ${this.draw(this.ownState.currentPath)}`; - } - - paths = paths.trim(); - - if (paths) { - somePathsExist = true; - } - - this.ownState.svg.setAttribute("d", paths); - this.ownState.svg.setAttribute("fill", "red"); - - if (!somePathsExist) { - this.isDrawing = false; - } - } -} diff --git a/packages/excalidraw/components/LaserTool/LaserTool.tsx b/packages/excalidraw/components/LaserTool/LaserTool.tsx deleted file mode 100644 index e93d72dfc..000000000 --- a/packages/excalidraw/components/LaserTool/LaserTool.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect, useRef } from "react"; -import { LaserPathManager } from "./LaserPathManager"; -import "./LaserToolOverlay.scss"; - -type LaserToolOverlayProps = { - manager: LaserPathManager; -}; - -export const LaserToolOverlay = ({ manager }: LaserToolOverlayProps) => { - const svgRef = useRef(null); - - useEffect(() => { - if (svgRef.current) { - manager.start(svgRef.current); - } - - return () => { - manager.stop(); - }; - }, [manager]); - - return ( -
- -
- ); -}; diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index 7dd362063..8cedf689d 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -60,7 +60,7 @@ import "./Toolbar.scss"; import { mutateElement } from "../element/mutateElement"; import { ShapeCache } from "../scene/ShapeCache"; import Scene from "../scene/Scene"; -import { LaserPointerButton } from "./LaserTool/LaserPointerButton"; +import { LaserPointerButton } from "./LaserPointerButton"; import { MagicSettings } from "./MagicSettings"; import { TTDDialog } from "./TTDDialog/TTDDialog"; diff --git a/packages/excalidraw/components/LaserTool/LaserToolOverlay.scss b/packages/excalidraw/components/SVGLayer.scss similarity index 80% rename from packages/excalidraw/components/LaserTool/LaserToolOverlay.scss rename to packages/excalidraw/components/SVGLayer.scss index da874b452..5eb0353aa 100644 --- a/packages/excalidraw/components/LaserTool/LaserToolOverlay.scss +++ b/packages/excalidraw/components/SVGLayer.scss @@ -1,5 +1,5 @@ .excalidraw { - .LaserToolOverlay { + .SVGLayer { pointer-events: none; width: 100vw; height: 100vh; @@ -9,10 +9,12 @@ z-index: 2; - .LaserToolOverlayCanvas { + & svg { image-rendering: auto; overflow: visible; position: absolute; + width: 100%; + height: 100%; top: 0; left: 0; } diff --git a/packages/excalidraw/components/SVGLayer.tsx b/packages/excalidraw/components/SVGLayer.tsx new file mode 100644 index 000000000..feaebaf94 --- /dev/null +++ b/packages/excalidraw/components/SVGLayer.tsx @@ -0,0 +1,33 @@ +import { useEffect, useRef } from "react"; +import { Trail } from "../animated-trail"; + +import "./SVGLayer.scss"; + +type SVGLayerProps = { + trails: Trail[]; +}; + +export const SVGLayer = ({ trails }: SVGLayerProps) => { + const svgRef = useRef(null); + + useEffect(() => { + if (svgRef.current) { + for (const trail of trails) { + trail.start(svgRef.current); + } + } + + return () => { + for (const trail of trails) { + trail.stop(); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, trails); + + return ( +
+ +
+ ); +}; diff --git a/packages/excalidraw/laser-trails.ts b/packages/excalidraw/laser-trails.ts new file mode 100644 index 000000000..49a0de5be --- /dev/null +++ b/packages/excalidraw/laser-trails.ts @@ -0,0 +1,124 @@ +import { LaserPointerOptions } from "@excalidraw/laser-pointer"; +import { AnimatedTrail, Trail } from "./animated-trail"; +import { AnimationFrameHandler } from "./animation-frame-handler"; +import type App from "./components/App"; +import { SocketId } from "./types"; +import { easeOut } from "./utils"; +import { getClientColor } from "./clients"; + +export class LaserTrails implements Trail { + public localTrail: AnimatedTrail; + private collabTrails = new Map(); + + private container?: SVGSVGElement; + + constructor( + private animationFrameHandler: AnimationFrameHandler, + private app: App, + ) { + this.animationFrameHandler.register(this, this.onFrame.bind(this)); + + this.localTrail = new AnimatedTrail(animationFrameHandler, app, { + ...this.getTrailOptions(), + fill: () => "red", + }); + } + + private getTrailOptions() { + return { + simplify: 0, + streamline: 0.4, + sizeMapping: (c) => { + const DECAY_TIME = 1000; + const DECAY_LENGTH = 50; + const t = Math.max( + 0, + 1 - (performance.now() - c.pressure) / DECAY_TIME, + ); + const l = + (DECAY_LENGTH - + Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / + DECAY_LENGTH; + + return Math.min(easeOut(l), easeOut(t)); + }, + } as Partial; + } + + startPath(x: number, y: number): void { + this.localTrail.startPath(x, y); + } + + addPointToPath(x: number, y: number): void { + this.localTrail.addPointToPath(x, y); + } + + endPath(): void { + this.localTrail.endPath(); + } + + start(container: SVGSVGElement) { + this.container = container; + + this.animationFrameHandler.start(this); + this.localTrail.start(container); + } + + stop() { + this.animationFrameHandler.stop(this); + this.localTrail.stop(); + } + + onFrame() { + this.updateCollabTrails(); + } + + private updateCollabTrails() { + if (!this.container || this.app.state.collaborators.size === 0) { + return; + } + + for (const [key, collabolator] of this.app.state.collaborators.entries()) { + let trail!: AnimatedTrail; + + if (!this.collabTrails.has(key)) { + trail = new AnimatedTrail(this.animationFrameHandler, this.app, { + ...this.getTrailOptions(), + fill: () => getClientColor(key), + }); + trail.start(this.container); + + this.collabTrails.set(key, trail); + } else { + trail = this.collabTrails.get(key)!; + } + + if (collabolator.pointer && collabolator.pointer.tool === "laser") { + if (collabolator.button === "down" && !trail.hasCurrentTrail) { + trail.startPath(collabolator.pointer.x, collabolator.pointer.y); + } + + if ( + collabolator.button === "down" && + trail.hasCurrentTrail && + !trail.hasLastPoint(collabolator.pointer.x, collabolator.pointer.y) + ) { + trail.addPointToPath(collabolator.pointer.x, collabolator.pointer.y); + } + + if (collabolator.button === "up" && trail.hasCurrentTrail) { + trail.addPointToPath(collabolator.pointer.x, collabolator.pointer.y); + trail.endPath(); + } + } + } + + for (const key of this.collabTrails.keys()) { + if (!this.app.state.collaborators.has(key)) { + const trail = this.collabTrails.get(key)!; + trail.stop(); + this.collabTrails.delete(key); + } + } + } +} diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index 1cd837fdd..7ec828cc1 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -57,7 +57,7 @@ }, "dependencies": { "@braintree/sanitize-url": "6.0.2", - "@excalidraw/laser-pointer": "1.2.0", + "@excalidraw/laser-pointer": "1.3.1", "@excalidraw/mermaid-to-excalidraw": "0.2.0", "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.0.3", diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index c2afedb32..4630c5bce 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -1013,6 +1013,40 @@ export function addEventListener( }; } +const average = (a: number, b: number) => (a + b) / 2; +export function getSvgPathFromStroke(points: number[][], closed = true) { + const len = points.length; + + if (len < 4) { + return ``; + } + + let a = points[0]; + let b = points[1]; + const c = points[2]; + + let result = `M${a[0].toFixed(2)},${a[1].toFixed(2)} Q${b[0].toFixed( + 2, + )},${b[1].toFixed(2)} ${average(b[0], c[0]).toFixed(2)},${average( + b[1], + c[1], + ).toFixed(2)} T`; + + for (let i = 2, max = len - 1; i < max; i++) { + a = points[i]; + b = points[i + 1]; + result += `${average(a[0], b[0]).toFixed(2)},${average(a[1], b[1]).toFixed( + 2, + )} `; + } + + if (closed) { + result += "Z"; + } + + return result; +} + export const normalizeEOL = (str: string) => { return str.replace(/\r?\n|\r/g, "\n"); }; diff --git a/yarn.lock b/yarn.lock index 87493423e..f857f7fb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2247,10 +2247,10 @@ resolved "https://registry.yarnpkg.com/@excalidraw/eslint-config/-/eslint-config-1.0.3.tgz#2122ef7413ae77874ae9848ce0f1c6b3f0d8bbbd" integrity sha512-GemHNF5Z6ga0BWBSX7GJaNBUchLu6RwTcAB84eX1MeckRNhNasAsPCdelDlFalz27iS4RuYEQh0bPE8SRxJgbQ== -"@excalidraw/laser-pointer@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@excalidraw/laser-pointer/-/laser-pointer-1.2.0.tgz#cd34ea7d24b11743c726488cc1fcb28c161cacba" - integrity sha512-WjFFwLk9ahmKRKku7U0jqYpeM3fe9ZS1K43pfwPREHk4/FYU3iKDKVeS8m4tEAASnRlBt3hhLCBQLBF2uvgOnw== +"@excalidraw/laser-pointer@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz#7c40836598e8e6ad91f01057883ed8b88fb9266c" + integrity sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g== "@excalidraw/markdown-to-text@0.1.2": version "0.1.2" From 0c24a7042f3abdbada1281dc5dfa61160f6b881b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:42:51 +0100 Subject: [PATCH 41/79] feat: remove `ExcalidrawEmbeddableElement.validated` flag (#7539) --- packages/excalidraw/CHANGELOG.md | 5 ++- packages/excalidraw/components/App.tsx | 33 +++++++++++++++---- packages/excalidraw/data/restore.ts | 5 +-- packages/excalidraw/element/Hyperlink.tsx | 15 +++++---- packages/excalidraw/element/newElement.ts | 6 +--- packages/excalidraw/element/types.ts | 8 ----- packages/excalidraw/renderer/renderScene.ts | 4 ++- packages/excalidraw/scene/Shape.ts | 25 +++++++++++--- packages/excalidraw/scene/ShapeCache.ts | 4 ++- packages/excalidraw/scene/export.ts | 17 +++++++++- packages/excalidraw/scene/types.ts | 3 ++ .../tests/fixtures/elementFixture.ts | 1 - packages/excalidraw/tests/helpers/api.ts | 1 - packages/excalidraw/types.ts | 6 ++++ 14 files changed, 94 insertions(+), 39 deletions(-) diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index 3fd3f3783..9f59bd4af 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -14,9 +14,12 @@ Please add the latest change on the top under the correct section. ## Unreleased - Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450) +- Remove `ExcalidrawEmbeddableElement.validated` attribute. [#7539](https://github.com/excalidraw/excalidraw/pull/7539) ### Breaking Changes +- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539) + - Create an `ESM` build for `@excalidraw/excalidraw`. The API is in progress and subject to change before stable release. There are some changes on how the package will be consumed #### Bundler @@ -265,7 +268,7 @@ define: { - Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546) - Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691) -- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateEmbeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691) +- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691) - Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581). - Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728). - Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 2d88d3a63..295c9dc08 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -246,6 +246,7 @@ import { ToolType, OnUserFollowedPayload, UnsubscribeCallback, + EmbedsValidationStatus, ElementsPendingErasure, } from "../types"; import { @@ -529,6 +530,15 @@ class App extends React.Component { public files: BinaryFiles = {}; public imageCache: AppClassProperties["imageCache"] = new Map(); private iFrameRefs = new Map(); + /** + * Indicates whether the embeddable's url has been validated for rendering. + * If value not set, indicates that the validation is pending. + * Initially or on url change the flag is not reset so that we can guarantee + * the validation came from a trusted source (the editor). + **/ + private embedsValidationStatus: EmbedsValidationStatus = new Map(); + /** embeds that have been inserted to DOM (as a perf optim, we don't want to + * insert to DOM before user initially scrolls to them) */ private initializedEmbeds = new Set(); private elementsPendingErasure: ElementsPendingErasure = new Set(); @@ -869,6 +879,14 @@ class App extends React.Component { ); } + private updateEmbedValidationStatus = ( + element: ExcalidrawEmbeddableElement, + status: boolean, + ) => { + this.embedsValidationStatus.set(element.id, status); + ShapeCache.delete(element); + }; + private updateEmbeddables = () => { const iframeLikes = new Set(); @@ -876,7 +894,7 @@ class App extends React.Component { this.scene.getNonDeletedElements().filter((element) => { if (isEmbeddableElement(element)) { iframeLikes.add(element.id); - if (element.validated == null) { + if (!this.embedsValidationStatus.has(element.id)) { updated = true; const validated = embeddableURLValidator( @@ -884,8 +902,7 @@ class App extends React.Component { this.props.validateEmbeddable, ); - mutateElement(element, { validated }, false); - ShapeCache.delete(element); + this.updateEmbedValidationStatus(element, validated); } } else if (isIframeElement(element)) { iframeLikes.add(element.id); @@ -914,7 +931,9 @@ class App extends React.Component { .getNonDeletedElements() .filter( (el): el is NonDeleted => - (isEmbeddableElement(el) && !!el.validated) || isIframeElement(el), + (isEmbeddableElement(el) && + this.embedsValidationStatus.get(el.id) === true) || + isIframeElement(el), ); return ( @@ -1507,6 +1526,9 @@ class App extends React.Component { setAppState={this.setAppState} onLinkOpen={this.props.onLinkOpen} setToast={this.setToast} + updateEmbedValidationStatus={ + this.updateEmbedValidationStatus + } /> )} {this.props.aiEnabled !== false && @@ -1617,6 +1639,7 @@ class App extends React.Component { renderGrid: true, canvasBackgroundColor: this.state.viewBackgroundColor, + embedsValidationStatus: this.embedsValidationStatus, elementsPendingErasure: this.elementsPendingErasure, }} /> @@ -6425,7 +6448,6 @@ class App extends React.Component { width: embedLink.intrinsicSize.w, height: embedLink.intrinsicSize.h, link, - validated: null, }); this.scene.replaceAllElements([ @@ -6659,7 +6681,6 @@ class App extends React.Component { if (elementType === "embeddable") { element = newEmbeddableElement({ type: "embeddable", - validated: null, ...baseElementAttributes, }); } else { diff --git a/packages/excalidraw/data/restore.ts b/packages/excalidraw/data/restore.ts index 76a8c32ca..ff4063593 100644 --- a/packages/excalidraw/data/restore.ts +++ b/packages/excalidraw/data/restore.ts @@ -295,11 +295,8 @@ const restoreElement = ( case "rectangle": case "diamond": case "iframe": - return restoreElementWithProperties(element, {}); case "embeddable": - return restoreElementWithProperties(element, { - validated: null, - }); + return restoreElementWithProperties(element, {}); case "magicframe": case "frame": return restoreElementWithProperties(element, { diff --git a/packages/excalidraw/element/Hyperlink.tsx b/packages/excalidraw/element/Hyperlink.tsx index e5e7a5a14..a69fdeb83 100644 --- a/packages/excalidraw/element/Hyperlink.tsx +++ b/packages/excalidraw/element/Hyperlink.tsx @@ -39,7 +39,6 @@ import "./Hyperlink.scss"; import { trackEvent } from "../analytics"; import { useAppProps, useExcalidrawAppState } from "../components/App"; import { isEmbeddableElement } from "./typeChecks"; -import { ShapeCache } from "../scene/ShapeCache"; const CONTAINER_WIDTH = 320; const SPACE_BOTTOM = 85; @@ -64,6 +63,7 @@ export const Hyperlink = ({ setAppState, onLinkOpen, setToast, + updateEmbedValidationStatus, }: { element: NonDeletedExcalidrawElement; setAppState: React.Component["setState"]; @@ -71,6 +71,10 @@ export const Hyperlink = ({ setToast: ( toast: { message: string; closable?: boolean; duration?: number } | null, ) => void; + updateEmbedValidationStatus: ( + element: ExcalidrawEmbeddableElement, + status: boolean, + ) => void; }) => { const appState = useExcalidrawAppState(); const appProps = useAppProps(); @@ -98,9 +102,9 @@ export const Hyperlink = ({ } if (!link) { mutateElement(element, { - validated: false, link: null, }); + updateEmbedValidationStatus(element, false); return; } @@ -110,10 +114,9 @@ export const Hyperlink = ({ } element.link && embeddableLinkCache.set(element.id, element.link); mutateElement(element, { - validated: false, link, }); - ShapeCache.delete(element); + updateEmbedValidationStatus(element, false); } else { const { width, height } = element; const embedLink = getEmbedLink(link); @@ -142,10 +145,9 @@ export const Hyperlink = ({ : height, } : {}), - validated: true, link, }); - ShapeCache.delete(element); + updateEmbedValidationStatus(element, true); if (embeddableLinkCache.has(element.id)) { embeddableLinkCache.delete(element.id); } @@ -159,6 +161,7 @@ export const Hyperlink = ({ appProps.validateEmbeddable, appState.activeEmbeddable, setAppState, + updateEmbedValidationStatus, ]); useLayoutEffect(() => { diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index 91b30beb7..00cae296c 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -136,13 +136,9 @@ export const newElement = ( export const newEmbeddableElement = ( opts: { type: "embeddable"; - validated: ExcalidrawEmbeddableElement["validated"]; } & ElementConstructorOpts, ): NonDeleted => { - return { - ..._newElementBase("embeddable", opts), - validated: opts.validated, - }; + return _newElementBase("embeddable", opts); }; export const newIframeElement = ( diff --git a/packages/excalidraw/element/types.ts b/packages/excalidraw/element/types.ts index 38be1bda6..c468eac82 100644 --- a/packages/excalidraw/element/types.ts +++ b/packages/excalidraw/element/types.ts @@ -88,14 +88,6 @@ export type ExcalidrawEllipseElement = _ExcalidrawElementBase & { export type ExcalidrawEmbeddableElement = _ExcalidrawElementBase & Readonly<{ type: "embeddable"; - /** - * indicates whether the embeddable src (url) has been validated for rendering. - * null value indicates that the validation is pending. We reset the - * value on each restore (or url change) so that we can guarantee - * the validation came from a trusted source (the editor). Also because we - * may not have access to host-app supplied url validator during restore. - */ - validated: boolean | null; }>; export type ExcalidrawIframeElement = _ExcalidrawElementBase & diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index c41d59bd3..a5b78d3b8 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -1007,7 +1007,9 @@ const _renderStaticScene = ({ if ( isIframeLikeElement(element) && (isExporting || - (isEmbeddableElement(element) && !element.validated)) && + (isEmbeddableElement(element) && + renderConfig.embedsValidationStatus.get(element.id) !== + true)) && element.width && element.height ) { diff --git a/packages/excalidraw/scene/Shape.ts b/packages/excalidraw/scene/Shape.ts index 190f7562f..1d43aef71 100644 --- a/packages/excalidraw/scene/Shape.ts +++ b/packages/excalidraw/scene/Shape.ts @@ -21,6 +21,7 @@ import { isLinearElement, } from "../element/typeChecks"; import { canChangeRoundness } from "./comparisons"; +import { EmbedsValidationStatus } from "../types"; const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth]; @@ -118,10 +119,13 @@ export const generateRoughOptions = ( const modifyIframeLikeForRoughOptions = ( element: NonDeletedExcalidrawElement, isExporting: boolean, + embedsValidationStatus: EmbedsValidationStatus | null, ) => { if ( isIframeLikeElement(element) && - (isExporting || (isEmbeddableElement(element) && !element.validated)) && + (isExporting || + (isEmbeddableElement(element) && + embedsValidationStatus?.get(element.id) !== true)) && isTransparent(element.backgroundColor) && isTransparent(element.strokeColor) ) { @@ -278,7 +282,12 @@ export const _generateElementShape = ( { isExporting, canvasBackgroundColor, - }: { isExporting: boolean; canvasBackgroundColor: string }, + embedsValidationStatus, + }: { + isExporting: boolean; + canvasBackgroundColor: string; + embedsValidationStatus: EmbedsValidationStatus | null; + }, ): Drawable | Drawable[] | null => { switch (element.type) { case "rectangle": @@ -299,7 +308,11 @@ export const _generateElementShape = ( h - r } L 0 ${r} Q 0 0, ${r} 0`, generateRoughOptions( - modifyIframeLikeForRoughOptions(element, isExporting), + modifyIframeLikeForRoughOptions( + element, + isExporting, + embedsValidationStatus, + ), true, ), ); @@ -310,7 +323,11 @@ export const _generateElementShape = ( element.width, element.height, generateRoughOptions( - modifyIframeLikeForRoughOptions(element, isExporting), + modifyIframeLikeForRoughOptions( + element, + isExporting, + embedsValidationStatus, + ), false, ), ); diff --git a/packages/excalidraw/scene/ShapeCache.ts b/packages/excalidraw/scene/ShapeCache.ts index e5a08c1f2..3bca88e85 100644 --- a/packages/excalidraw/scene/ShapeCache.ts +++ b/packages/excalidraw/scene/ShapeCache.ts @@ -8,7 +8,7 @@ import { elementWithCanvasCache } from "../renderer/renderElement"; import { _generateElementShape } from "./Shape"; import { ElementShape, ElementShapes } from "./types"; import { COLOR_PALETTE } from "../colors"; -import { AppState } from "../types"; +import { AppState, EmbedsValidationStatus } from "../types"; export class ShapeCache { private static rg = new RoughGenerator(); @@ -51,6 +51,7 @@ export class ShapeCache { renderConfig: { isExporting: boolean; canvasBackgroundColor: AppState["viewBackgroundColor"]; + embedsValidationStatus: EmbedsValidationStatus; } | null, ) => { // when exporting, always regenerated to guarantee the latest shape @@ -72,6 +73,7 @@ export class ShapeCache { renderConfig || { isExporting: false, canvasBackgroundColor: COLOR_PALETTE.white, + embedsValidationStatus: null, }, ) as T["type"] extends keyof ElementShapes ? ElementShapes[T["type"]] diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index 6220c59da..cc84569a6 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -266,6 +266,8 @@ export const exportToCanvas = async ( imageCache, renderGrid: false, isExporting: true, + // empty disables embeddable rendering + embedsValidationStatus: new Map(), elementsPendingErasure: new Set(), }, }); @@ -288,6 +290,9 @@ export const exportToSvg = async ( }, files: BinaryFiles | null, opts?: { + /** + * if true, all embeddables passed in will be rendered when possible. + */ renderEmbeddables?: boolean; exportingFrame?: ExcalidrawFrameLikeElement | null; }, @@ -428,14 +433,24 @@ export const exportToSvg = async ( } const rsvg = rough.svg(svgRoot); + + const renderEmbeddables = opts?.renderEmbeddables ?? false; + renderSceneToSvg(elementsForRender, rsvg, svgRoot, files || {}, { offsetX, offsetY, isExporting: true, exportWithDarkMode, - renderEmbeddables: opts?.renderEmbeddables ?? false, + renderEmbeddables, frameRendering, canvasBackgroundColor: viewBackgroundColor, + embedsValidationStatus: renderEmbeddables + ? new Map( + elementsForRender + .filter((element) => isFrameLikeElement(element)) + .map((element) => [element.id, true]), + ) + : new Map(), }); tempScene.destroy(); diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index 401ab86d5..57a52fbd4 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -7,6 +7,7 @@ import { import { AppClassProperties, AppState, + EmbedsValidationStatus, ElementsPendingErasure, InteractiveCanvasAppState, StaticCanvasAppState, @@ -21,6 +22,7 @@ export type StaticCanvasRenderConfig = { /** when exporting the behavior is slightly different (e.g. we can't use CSS filters), and we disable render optimizations for best output */ isExporting: boolean; + embedsValidationStatus: EmbedsValidationStatus; elementsPendingErasure: ElementsPendingErasure; }; @@ -32,6 +34,7 @@ export type SVGRenderConfig = { renderEmbeddables: boolean; frameRendering: AppState["frameRendering"]; canvasBackgroundColor: AppState["viewBackgroundColor"]; + embedsValidationStatus: EmbedsValidationStatus; }; export type InteractiveCanvasRenderConfig = { diff --git a/packages/excalidraw/tests/fixtures/elementFixture.ts b/packages/excalidraw/tests/fixtures/elementFixture.ts index 7f1231a83..ddd7b8b9d 100644 --- a/packages/excalidraw/tests/fixtures/elementFixture.ts +++ b/packages/excalidraw/tests/fixtures/elementFixture.ts @@ -34,7 +34,6 @@ export const rectangleFixture: ExcalidrawElement = { export const embeddableFixture: ExcalidrawElement = { ...elementBase, type: "embeddable", - validated: null, }; export const ellipseFixture: ExcalidrawElement = { ...elementBase, diff --git a/packages/excalidraw/tests/helpers/api.ts b/packages/excalidraw/tests/helpers/api.ts index 2a18805ab..d22d3f221 100644 --- a/packages/excalidraw/tests/helpers/api.ts +++ b/packages/excalidraw/tests/helpers/api.ts @@ -205,7 +205,6 @@ export class API { element = newEmbeddableElement({ type: "embeddable", ...base, - validated: null, }); break; case "iframe": diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 3da06bec4..201a186ba 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -19,6 +19,7 @@ import { ExcalidrawMagicFrameElement, ExcalidrawFrameLikeElement, ExcalidrawElementType, + ExcalidrawIframeLikeElement, } from "./element/types"; import { Action } from "./actions/types"; import { Point as RoughPoint } from "roughjs/bin/geometry"; @@ -746,4 +747,9 @@ export type Primitive = export type JSONValue = string | number | boolean | null | object; +export type EmbedsValidationStatus = Map< + ExcalidrawIframeLikeElement["id"], + boolean +>; + export type ElementsPendingErasure = Set; From 5245276409b8d163c656c9d3a5e9028b8322a59b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:43:04 +0100 Subject: [PATCH 42/79] feat: erase groups atomically (#7545) --- packages/excalidraw/components/App.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 295c9dc08..acbe56741 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -5129,6 +5129,9 @@ class App extends React.Component { let didChange = false; + const processedGroups = new Set(); + const nonDeletedElements = this.scene.getNonDeletedElements(); + const processElements = (elements: ExcalidrawElement[]) => { for (const element of elements) { if (element.locked) { @@ -5143,6 +5146,25 @@ class App extends React.Component { didChange = true; this.elementsPendingErasure.add(element.id); } + + // (un)erase groups atomically + if (didChange && element.groupIds?.length) { + const shallowestGroupId = element.groupIds.at(-1)!; + if (!processedGroups.has(shallowestGroupId)) { + processedGroups.add(shallowestGroupId); + const elems = getElementsInGroup( + nonDeletedElements, + shallowestGroupId, + ); + for (const elem of elems) { + if (event.altKey) { + this.elementsPendingErasure.delete(elem.id); + } else { + this.elementsPendingErasure.add(elem.id); + } + } + } + } } }; From 8ead8559e059c25d93d4dea58ba9b494535faae5 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 11 Jan 2024 21:08:17 +0100 Subject: [PATCH 43/79] feat: redirect font requests to cdn (#7549) --- vercel.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vercel.json b/vercel.json index dc077e41b..b8edf9f3b 100644 --- a/vercel.json +++ b/vercel.json @@ -28,6 +28,18 @@ "source": "/webex/:match*", "destination": "https://for-webex.excalidraw.com" }, + { + "source": "/Virgil.woff2", + "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Virgil.woff2" + }, + { + "source": "/Cascadia.woff2", + "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Cascadia.woff2" + }, + { + "source": "/Assistant-Regular.woff2", + "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Assistant-Regular.woff2" + }, { "source": "/:path*", "has": [ From 41cc7468856be773e0f25271f8916a35dd3930e8 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 11 Jan 2024 21:29:29 +0100 Subject: [PATCH 44/79] fix: host font assets from root (#7548) --- public/Assistant-Regular.woff2 | Bin 0 -> 20232 bytes public/Cascadia.woff2 | Bin 0 -> 86812 bytes public/Virgil.woff2 | Bin 0 -> 61248 bytes vercel.json | 12 ------------ 4 files changed, 12 deletions(-) create mode 100644 public/Assistant-Regular.woff2 create mode 100644 public/Cascadia.woff2 create mode 100644 public/Virgil.woff2 diff --git a/public/Assistant-Regular.woff2 b/public/Assistant-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e17d6eccaa7e85cb2a516565fa415ebb46d0e57a GIT binary patch literal 20232 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∈%?q7f-cLnRe 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 TcQlRP`t>*4_1V8ZM$4EBNmmhp literal 0 HcmV?d00001 diff --git a/public/Cascadia.woff2 b/public/Cascadia.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..b2eae9f40b917f56468f63766bb8aff907d9d2dc GIT binary patch literal 86812 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

H4;(BXGc?le%FC2c4K(amM!3jrZ$`#9?hR@ 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

o#<~uUPluf;eW3^a zA`lI2%dzmt|M!w8=%AE2YgE1L*2es1nB2Z&ORHK zc3rXKqs?#~7gmd0(76%8Y@UNKH4IJAaq6Gs95jC-i1rMyK>=&t(&tzg*!EL>WmPD+ zPa3Z{5B)EtfBxAq+NbZfdH>42dff3t861lYz?Q*l%G=?!Nq35sneiur)^16Y;i~hX z-;tZk#p8YQW99p`eS6#y-w^e>{P0sqX@ZJ&tt zAZB4~$+rD+Fd3WQJ&W$_o%?!G0M~eC!!Q=`zWd-4rq_rOV*fs2KdtC#&Pj$|b&nuZk$qeo*9pV>3g7^m39_~X#5p%e z<~z9`sMGG`BCy-V#_nQIZ?jS9Nsk0AWzwML8@Uj07=`7r07qzk!9$g$aZy+ViG+X! zR*Z6lYXJL=Dvw5pG~i2>^`3pPoypNB2Xsc3P4Zq^h-d1?*V7~a zxsmA+AsjxLX)6)0$Yjj;TP@ zf?;*opij&*_drN_z1&OrRgW?1yzpYnWU-3oErVwJ!+bAy3zqTOuGNgXonb$@-jqQM zX!^UP&?7I}cCel}$V;(b8Q$hh*9PLgN7Xh=_NZovF6=iCZ#@u(;`G29Ut6V^h+U)9 z_6`Qa6YU$x@yU?>My-$c+n!)xyz&BN2w1z($>DCk1&a(PZMkO4?=^;PE6E<{*?fG( zM(rrt(c+#xbJAL=PYcy@40cmFJv=>|hV8I}`$xDzDh&b0OVxbuzzZ7VqMVeUSc6-W zT_gz&Ua$`-=wlc*$2-GvgybgDQk-$o)ri+KzPozSB+y{M#h|H00-w~9xRAVPv61-m z(GYZ(66ZUho4sX-`S_Xov>HxhsB`~I@>x*BbrBebyWY!=Qv~^q` z>XUi)!s>CcT(L;r54uyXY&^P zQ`5fjn*Y0Fy!$|r?54&}4R_Wy2H*1x9JhAx*^%9m_R_Nt*`mJ=1uS8om-Dmb31

  • _{zDz&p>3TDQc)6hETbC-{f_tzLHj$*#VYkB| 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 literal 0 HcmV?d00001 diff --git a/vercel.json b/vercel.json index b8edf9f3b..dc077e41b 100644 --- a/vercel.json +++ b/vercel.json @@ -28,18 +28,6 @@ "source": "/webex/:match*", "destination": "https://for-webex.excalidraw.com" }, - { - "source": "/Virgil.woff2", - "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Virgil.woff2" - }, - { - "source": "/Cascadia.woff2", - "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Cascadia.woff2" - }, - { - "source": "/Assistant-Regular.woff2", - "destination": "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/Assistant-Regular.woff2" - }, { "source": "/:path*", "has": [ From 0fa5f5de4cbafcf9405b5983b3e2419f6f6c4d4b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 13 Jan 2024 21:28:54 +0100 Subject: [PATCH 45/79] fix: translating frames containing grouped text containers (#7557) --- packages/excalidraw/element/dragElements.ts | 30 ++++++--------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/packages/excalidraw/element/dragElements.ts b/packages/excalidraw/element/dragElements.ts index c91ad64c6..ecec4d083 100644 --- a/packages/excalidraw/element/dragElements.ts +++ b/packages/excalidraw/element/dragElements.ts @@ -5,14 +5,9 @@ import { getPerfectElementSize } from "./sizeHelpers"; import { NonDeletedExcalidrawElement } from "./types"; import { AppState, PointerDownState } from "../types"; import { getBoundTextElement } from "./textElement"; -import { isSelectedViaGroup } from "../groups"; import { getGridPoint } from "../math"; import Scene from "../scene/Scene"; -import { - isArrowElement, - isBoundToContainer, - isFrameLikeElement, -} from "./typeChecks"; +import { isArrowElement, isFrameLikeElement } from "./typeChecks"; export const dragSelectedElements = ( pointerDownState: PointerDownState, @@ -37,13 +32,11 @@ export const dragSelectedElements = ( .map((f) => f.id); if (frames.length > 0) { - const elementsInFrames = scene - .getNonDeletedElements() - .filter((e) => !isBoundToContainer(e)) - .filter((e) => e.frameId !== null) - .filter((e) => frames.includes(e.frameId!)); - - elementsInFrames.forEach((element) => elementsToUpdate.add(element)); + for (const element of scene.getNonDeletedElements()) { + if (element.frameId !== null && frames.includes(element.frameId)) { + elementsToUpdate.add(element); + } + } } const commonBounds = getCommonBounds( @@ -60,16 +53,9 @@ export const dragSelectedElements = ( elementsToUpdate.forEach((element) => { updateElementCoords(pointerDownState, element, adjustedOffset); - // update coords of bound text only if we're dragging the container directly - // (we don't drag the group that it's part of) if ( - // Don't update coords of arrow label since we calculate its position during render - !isArrowElement(element) && - // container isn't part of any group - // (perf optim so we don't check `isSelectedViaGroup()` in every case) - (!element.groupIds.length || - // container is part of a group, but we're dragging the container directly - (appState.editingGroupId && !isSelectedViaGroup(appState, element))) + // skip arrow labels since we calculate its position during render + !isArrowElement(element) ) { const textElement = getBoundTextElement(element); if (textElement) { From a4e5e46dd17e854fbb891d45edcb57405171c861 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 15 Jan 2024 14:52:04 +0530 Subject: [PATCH 46/79] fix: move default to last so its compatible with nextjs (#7561) --- packages/excalidraw/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index 7ec828cc1..5e5c52b21 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -7,8 +7,8 @@ "exports": { ".": { "development": "./dist/dev/index.js", - "default": "./dist/prod/index.js", - "types": "./dist/excalidraw/index.d.ts" + "types": "./dist/excalidraw/index.d.ts", + "default": "./dist/prod/index.js" }, "./index.css": { "development": "./dist/dev/index.css", From dd530737a2b3fd8c6c0ae645b552115e72d126c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=91CAT?= Date: Wed, 17 Jan 2024 19:49:42 +0900 Subject: [PATCH 47/79] docs: fix "canvas actions" link in Props page (#7536) fix "canvas actions" link in Props page --- dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx index 40773a1a2..766c723e4 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx @@ -23,7 +23,7 @@ All `props` are _optional_. | [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to | | [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component | | [`name`](#name) | `string` | | Name of the drawing | -| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) | +| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) | | [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. | | [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. | | [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load | From 3b0593baa79f49a8439831e6ea27f04c2db7acae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=91CAT?= Date: Fri, 19 Jan 2024 22:41:08 +0900 Subject: [PATCH 48/79] fix: Prevent the library label from being collapsed (#7579) --- packages/excalidraw/components/Sidebar/SidebarTrigger.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/excalidraw/components/Sidebar/SidebarTrigger.scss b/packages/excalidraw/components/Sidebar/SidebarTrigger.scss index 7197af7f2..5b003cdc5 100644 --- a/packages/excalidraw/components/Sidebar/SidebarTrigger.scss +++ b/packages/excalidraw/components/Sidebar/SidebarTrigger.scss @@ -29,6 +29,7 @@ .default-sidebar-trigger .sidebar-trigger__label { display: block; + white-space: nowrap; } &.excalidraw--mobile .default-sidebar-trigger .sidebar-trigger__label { From 46da032626c36df89949683691930fca846609c0 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:41:22 +0100 Subject: [PATCH 49/79] fix: exporting frame-overlapping elements belonging to other frames (#7584) --- packages/excalidraw/components/App.tsx | 12 ++-- packages/excalidraw/data/index.ts | 8 +-- packages/excalidraw/frame.ts | 21 ++++++- packages/excalidraw/scene/export.ts | 8 +-- .../excalidraw/tests/scene/export.test.ts | 62 +++++++++++++++++++ 5 files changed, 92 insertions(+), 19 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index acbe56741..d9471d657 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -348,6 +348,7 @@ import { updateFrameMembershipOfSelectedElements, isElementInFrame, getFrameLikeTitle, + getElementsOverlappingFrame, } from "../frame"; import { excludeElementsInFramesFromSelection, @@ -395,7 +396,7 @@ import { import { Emitter } from "../emitter"; import { ElementCanvasButtons } from "../element/ElementCanvasButtons"; import { MagicCacheData, diagramToHTML } from "../data/magic"; -import { elementsOverlappingBBox, exportToBlob } from "../../utils/export"; +import { exportToBlob } from "../../utils/export"; import { COLOR_PALETTE } from "../colors"; import { ElementCanvasButton } from "./MagicButton"; import { MagicIcon, copyIcon, fullscreenIcon } from "./icons"; @@ -1803,11 +1804,10 @@ class App extends React.Component { return; } - const magicFrameChildren = elementsOverlappingBBox({ - elements: this.scene.getNonDeletedElements(), - bounds: magicFrame, - type: "overlap", - }).filter((el) => !isMagicFrameElement(el)); + const magicFrameChildren = getElementsOverlappingFrame( + this.scene.getNonDeletedElements(), + magicFrame, + ).filter((el) => !isMagicFrameElement(el)); if (!magicFrameChildren.length) { if (source === "button") { diff --git a/packages/excalidraw/data/index.ts b/packages/excalidraw/data/index.ts index 7e93542ac..0c63053a9 100644 --- a/packages/excalidraw/data/index.ts +++ b/packages/excalidraw/data/index.ts @@ -11,7 +11,6 @@ import { NonDeletedExcalidrawElement, } from "../element/types"; import { t } from "../i18n"; -import { elementsOverlappingBBox } from "../../utils/export"; import { isSomeElementSelected, getSelectedElements } from "../scene"; import { exportToCanvas, exportToSvg } from "../scene/export"; import { ExportType } from "../scene/types"; @@ -20,6 +19,7 @@ import { cloneJSON } from "../utils"; import { canvasToBlob } from "./blob"; import { fileSave, FileSystemHandle } from "./filesystem"; import { serializeAsJSON } from "./json"; +import { getElementsOverlappingFrame } from "../frame"; export { loadFromBlob } from "./blob"; export { loadFromJSON, saveAsJSON } from "./json"; @@ -56,11 +56,7 @@ export const prepareElementsForExport = ( isFrameLikeElement(exportedElements[0]) ) { exportingFrame = exportedElements[0]; - exportedElements = elementsOverlappingBBox({ - elements, - bounds: exportingFrame, - type: "overlap", - }); + exportedElements = getElementsOverlappingFrame(elements, exportingFrame); } else if (exportedElements.length > 1) { exportedElements = getSelectedElements( elements, diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 3818e6684..db58bb626 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -21,7 +21,10 @@ import { getElementsWithinSelection, getSelectedElements } from "./scene"; import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups"; import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene"; import { getElementLineSegments } from "./element/bounds"; -import { doLineSegmentsIntersect } from "../utils/export"; +import { + doLineSegmentsIntersect, + elementsOverlappingBBox, +} from "../utils/export"; import { isFrameElement, isFrameLikeElement } from "./element/typeChecks"; // --------------------------- Frame State ------------------------------------ @@ -664,3 +667,19 @@ export const getFrameLikeTitle = ( // TODO name frames AI only is specific to AI frames return isFrameElement(element) ? `Frame ${frameIdx}` : `AI Frame ${frameIdx}`; }; + +export const getElementsOverlappingFrame = ( + elements: readonly ExcalidrawElement[], + frame: ExcalidrawFrameLikeElement, +) => { + return ( + elementsOverlappingBBox({ + elements, + bounds: frame, + type: "overlap", + }) + // removes elements who are overlapping, but are in a different frame, + // and thus invisible in target frame + .filter((el) => !el.frameId || el.frameId === frame.id) + ); +}; diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index cc84569a6..9c357a21f 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -26,8 +26,8 @@ import { getInitializedImageElements, updateImageCache, } from "../element/image"; -import { elementsOverlappingBBox } from "../../utils/export"; import { + getElementsOverlappingFrame, getFrameLikeElements, getFrameLikeTitle, getRootElements, @@ -168,11 +168,7 @@ const prepareElementsForRender = ({ let nextElements: readonly ExcalidrawElement[]; if (exportingFrame) { - nextElements = elementsOverlappingBBox({ - elements, - bounds: exportingFrame, - type: "overlap", - }); + nextElements = getElementsOverlappingFrame(elements, exportingFrame); } else if (frameRendering.enabled && frameRendering.name) { nextElements = addFrameLabelsAsTextElements(elements, { exportWithDarkMode, diff --git a/packages/excalidraw/tests/scene/export.test.ts b/packages/excalidraw/tests/scene/export.test.ts index 5287aa8cf..ec9a0e6bf 100644 --- a/packages/excalidraw/tests/scene/export.test.ts +++ b/packages/excalidraw/tests/scene/export.test.ts @@ -406,5 +406,67 @@ describe("exporting frames", () => { (frame.height + getFrameNameHeight("svg")).toString(), ); }); + + it("should not export frame-overlapping elements belonging to different frame", async () => { + const frame1 = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const frame2 = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 200, + y: 0, + }); + + const frame1Child = API.createElement({ + type: "rectangle", + width: 150, + height: 100, + x: 0, + y: 50, + frameId: frame1.id, + }); + const frame2Child = API.createElement({ + type: "rectangle", + width: 150, + height: 100, + x: 50, + y: 0, + frameId: frame2.id, + }); + + // low-level exportToSvg api expects elements to be pre-filtered, so let's + // use the filter we use in the editor + const { exportedElements, exportingFrame } = prepareElementsForExport( + [frame1Child, frame1, frame2Child, frame2], + { + selectedElementIds: { [frame1.id]: true }, + }, + true, + ); + + const svg = await exportToSvg({ + elements: exportedElements, + files: null, + exportPadding: 0, + exportingFrame, + }); + + // frame shouldn't be exported + expect(svg.querySelector(`[data-id="${frame1.id}"]`)).toBeNull(); + // frame1 child should be epxorted + expect(svg.querySelector(`[data-id="${frame1Child.id}"]`)).not.toBeNull(); + // frame2 child should not be exported even if it physically overlaps with + // frame1 + expect(svg.querySelector(`[data-id="${frame2Child.id}"]`)).toBeNull(); + + expect(svg.getAttribute("width")).toBe(frame1.width.toString()); + expect(svg.getAttribute("height")).toBe(frame1.height.toString()); + }); }); }); From 1e7df58b5b83fc2aba4c71ccc2478a0003db650f Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sun, 21 Jan 2024 14:01:43 +0100 Subject: [PATCH 50/79] feat: add pasted elements to frame under cursor (#7590) --- packages/excalidraw/components/App.tsx | 10 +++++-- packages/excalidraw/frame.ts | 16 +++++++++++ packages/excalidraw/tests/clipboard.test.tsx | 30 ++++++++++++++++++++ packages/excalidraw/tests/helpers/ui.ts | 2 ++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index d9471d657..9e8a26eed 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3099,12 +3099,18 @@ class App extends React.Component { }, ); - const nextElements = [ + const allElements = [ ...this.scene.getElementsIncludingDeleted(), ...newElements, ]; - this.scene.replaceAllElements(nextElements); + const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y }); + + if (topLayerFrame) { + addElementsToFrame(allElements, newElements, topLayerFrame); + } + + this.scene.replaceAllElements(allElements); newElements.forEach((newElement) => { if (isTextElement(newElement) && isBoundToContainer(newElement)) { diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index db58bb626..7056574f4 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -398,12 +398,28 @@ export const addElementsToFrame = ( const finalElementsToAdd: ExcalidrawElement[] = []; + const otherFrames = new Set(); + + for (const element of elementsToAdd) { + if (isFrameLikeElement(element) && element.id !== frame.id) { + otherFrames.add(element.id); + } + } + // - add bound text elements if not already in the array // - filter out elements that are already in the frame for (const element of omitGroupsContainingFrameLikes( allElements, elementsToAdd, )) { + // don't add frames or their children + if ( + isFrameLikeElement(element) || + (element.frameId && otherFrames.has(element.frameId)) + ) { + continue; + } + if (!currTargetFrameChildrenMap.has(element.id)) { finalElementsToAdd.push(element); } diff --git a/packages/excalidraw/tests/clipboard.test.tsx b/packages/excalidraw/tests/clipboard.test.tsx index 38d7b49d5..ce00e2da5 100644 --- a/packages/excalidraw/tests/clipboard.test.tsx +++ b/packages/excalidraw/tests/clipboard.test.tsx @@ -263,3 +263,33 @@ describe("Paste bound text container", () => { }); }); }); + +describe("pasting & frames", () => { + it("should add pasted elements to frame under cursor", async () => { + const frame = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const rect = API.createElement({ type: "rectangle" }); + + h.elements = [frame]; + + const clipboardJSON = await serializeAsClipboardJSON({ + elements: [rect], + files: null, + }); + + mouse.moveTo(50, 50); + + pasteWithCtrlCmdV(clipboardJSON); + + await waitFor(() => { + expect(h.elements.length).toBe(2); + expect(h.elements[1].type).toBe(rect.type); + expect(h.elements[1].frameId).toBe(frame.id); + }); + }); +}); diff --git a/packages/excalidraw/tests/helpers/ui.ts b/packages/excalidraw/tests/helpers/ui.ts index f37ac0019..58579fe93 100644 --- a/packages/excalidraw/tests/helpers/ui.ts +++ b/packages/excalidraw/tests/helpers/ui.ts @@ -206,6 +206,8 @@ export class Pointer { moveTo(x: number = this.clientX, y: number = this.clientY) { this.clientX = x; this.clientY = y; + // fire "mousemove" to update editor cursor position + fireEvent.mouseMove(document, this.getEvent()); fireEvent.pointerMove(GlobalTestState.interactiveCanvas, this.getEvent()); } From b66daae1f3552b88e9b4a2c81fcb1d9f1a43c717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Moln=C3=A1r?= <38168628+barnabasmolnar@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:36:09 +0100 Subject: [PATCH 51/79] fix: Truncate collaborator name in dropdown. (#7576) --- packages/excalidraw/actions/actionNavigate.tsx | 4 +++- packages/excalidraw/components/UserList.scss | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx index 4ce79b96f..ea65584fe 100644 --- a/packages/excalidraw/actions/actionNavigate.tsx +++ b/packages/excalidraw/actions/actionNavigate.tsx @@ -57,7 +57,9 @@ export const actionGoToCollaborator = register({ isBeingFollowed={isBeingFollowed} isCurrentUser={collaborator.isCurrentUser === true} /> - {collaborator.username} +

    + {collaborator.username} +
    Date: Mon, 22 Jan 2024 03:55:28 +0800 Subject: [PATCH 52/79] fix: frame name editing inconvenience (#7437) --- packages/excalidraw/frame.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 7056574f4..115cfef06 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -676,12 +676,12 @@ export const getFrameLikeTitle = ( element: ExcalidrawFrameLikeElement, frameIdx: number, ) => { - const existingName = element.name?.trim(); - if (existingName) { - return existingName; - } // TODO name frames AI only is specific to AI frames - return isFrameElement(element) ? `Frame ${frameIdx}` : `AI Frame ${frameIdx}`; + return element.name === null + ? isFrameElement(element) + ? `Frame ${frameIdx}` + : `AI Frame $${frameIdx}` + : element.name; }; export const getElementsOverlappingFrame = ( From 740a1654529cb24ea6e17770f53c056f4a7269b0 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:55:57 +0100 Subject: [PATCH 53/79] fix: filter out elements not overlapping frame on paste (#7591) --- packages/excalidraw/components/App.tsx | 7 +- packages/excalidraw/frame.ts | 61 ++++++++- packages/excalidraw/tests/clipboard.test.tsx | 137 +++++++++++++++++++ 3 files changed, 198 insertions(+), 7 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 9e8a26eed..77c972882 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -349,6 +349,7 @@ import { isElementInFrame, getFrameLikeTitle, getElementsOverlappingFrame, + filterElementsEligibleAsFrameChildren, } from "../frame"; import { excludeElementsInFramesFromSelection, @@ -3107,7 +3108,11 @@ class App extends React.Component { const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y }); if (topLayerFrame) { - addElementsToFrame(allElements, newElements, topLayerFrame); + const eligibleElements = filterElementsEligibleAsFrameChildren( + newElements, + topLayerFrame, + ); + addElementsToFrame(allElements, eligibleElements, topLayerFrame); } this.scene.replaceAllElements(allElements); diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 115cfef06..a5e743711 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -107,17 +107,16 @@ export const elementsAreInFrameBounds = ( elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, ) => { - const [selectionX1, selectionY1, selectionX2, selectionY2] = - getElementAbsoluteCoords(frame); + const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(frame); const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(elements); return ( - selectionX1 <= elementX1 && - selectionY1 <= elementY1 && - selectionX2 >= elementX2 && - selectionY2 >= elementY2 + frameX1 <= elementX1 && + frameY1 <= elementY1 && + frameX2 >= elementX2 && + frameY2 >= elementY2 ); }; @@ -372,6 +371,56 @@ export const getContainingFrame = ( // --------------------------- Frame Operations ------------------------------- +/** */ +export const filterElementsEligibleAsFrameChildren = ( + elements: readonly ExcalidrawElement[], + frame: ExcalidrawFrameLikeElement, +) => { + const otherFrames = new Set(); + + elements = omitGroupsContainingFrameLikes(elements); + + for (const element of elements) { + if (isFrameLikeElement(element) && element.id !== frame.id) { + otherFrames.add(element.id); + } + } + + const processedGroups = new Set(); + + const eligibleElements: ExcalidrawElement[] = []; + + for (const element of elements) { + // don't add frames or their children + if ( + isFrameLikeElement(element) || + (element.frameId && otherFrames.has(element.frameId)) + ) { + continue; + } + + if (element.groupIds.length) { + const shallowestGroupId = element.groupIds.at(-1)!; + if (!processedGroups.has(shallowestGroupId)) { + processedGroups.add(shallowestGroupId); + const groupElements = getElementsInGroup(elements, shallowestGroupId); + if (groupElements.some((el) => elementOverlapsWithFrame(el, frame))) { + for (const child of groupElements) { + eligibleElements.push(child); + } + } + } + } else { + const overlaps = elementOverlapsWithFrame(element, frame); + if (overlaps) { + eligibleElements.push(element); + } + } + } + + return eligibleElements; +}; + /** * Retains (or repairs for target frame) the ordering invriant where children * elements come right before the parent frame: diff --git a/packages/excalidraw/tests/clipboard.test.tsx b/packages/excalidraw/tests/clipboard.test.tsx index ce00e2da5..149ebcd1e 100644 --- a/packages/excalidraw/tests/clipboard.test.tsx +++ b/packages/excalidraw/tests/clipboard.test.tsx @@ -292,4 +292,141 @@ describe("pasting & frames", () => { expect(h.elements[1].frameId).toBe(frame.id); }); }); + + it("should filter out elements not overlapping frame", async () => { + const frame = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const rect = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + }); + const rect2 = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + x: 100, + y: 100, + }); + + h.elements = [frame]; + + const clipboardJSON = await serializeAsClipboardJSON({ + elements: [rect, rect2], + files: null, + }); + + mouse.moveTo(90, 90); + + pasteWithCtrlCmdV(clipboardJSON); + + await waitFor(() => { + expect(h.elements.length).toBe(3); + expect(h.elements[1].type).toBe(rect.type); + expect(h.elements[1].frameId).toBe(frame.id); + expect(h.elements[2].type).toBe(rect2.type); + expect(h.elements[2].frameId).toBe(null); + }); + }); + + it("should not filter out elements not overlapping frame if part of group", async () => { + const frame = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const rect = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + groupIds: ["g1"], + }); + const rect2 = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + x: 100, + y: 100, + groupIds: ["g1"], + }); + + h.elements = [frame]; + + const clipboardJSON = await serializeAsClipboardJSON({ + elements: [rect, rect2], + files: null, + }); + + mouse.moveTo(90, 90); + + pasteWithCtrlCmdV(clipboardJSON); + + await waitFor(() => { + expect(h.elements.length).toBe(3); + expect(h.elements[1].type).toBe(rect.type); + expect(h.elements[1].frameId).toBe(frame.id); + expect(h.elements[2].type).toBe(rect2.type); + expect(h.elements[2].frameId).toBe(frame.id); + }); + }); + + it("should not filter out other frames and their children", async () => { + const frame = API.createElement({ + type: "frame", + width: 100, + height: 100, + x: 0, + y: 0, + }); + const rect = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + groupIds: ["g1"], + }); + + const frame2 = API.createElement({ + type: "frame", + width: 75, + height: 75, + x: 0, + y: 0, + }); + const rect2 = API.createElement({ + type: "rectangle", + width: 50, + height: 50, + x: 55, + y: 55, + frameId: frame2.id, + }); + + h.elements = [frame]; + + const clipboardJSON = await serializeAsClipboardJSON({ + elements: [rect, rect2, frame2], + files: null, + }); + + mouse.moveTo(90, 90); + + pasteWithCtrlCmdV(clipboardJSON); + + await waitFor(() => { + expect(h.elements.length).toBe(4); + expect(h.elements[1].type).toBe(rect.type); + expect(h.elements[1].frameId).toBe(frame.id); + expect(h.elements[2].type).toBe(rect2.type); + expect(h.elements[2].frameId).toBe(h.elements[3].id); + expect(h.elements[3].type).toBe(frame2.type); + expect(h.elements[3].frameId).toBe(null); + }); + }); }); From 0415c616b1ec9ec003ae53049b4f98df844dfb5f Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:23:02 +0100 Subject: [PATCH 54/79] refactor: decoupling global Scene state part-1 (#7577) --- packages/excalidraw/actions/actionFlip.ts | 38 +++- packages/excalidraw/actions/actionFrame.ts | 6 +- packages/excalidraw/actions/actionGroup.tsx | 7 +- .../excalidraw/actions/actionProperties.tsx | 42 ++-- packages/excalidraw/components/Actions.tsx | 18 +- packages/excalidraw/components/App.tsx | 52 ++--- packages/excalidraw/components/LayerUI.tsx | 2 +- packages/excalidraw/components/MobileMenu.tsx | 2 +- .../components/canvases/InteractiveCanvas.tsx | 9 +- .../components/canvases/StaticCanvas.tsx | 13 +- packages/excalidraw/data/restore.ts | 21 +- packages/excalidraw/element/bounds.ts | 13 +- packages/excalidraw/element/embeddable.ts | 24 +-- packages/excalidraw/element/newElement.ts | 6 +- packages/excalidraw/element/resizeElements.ts | 53 +++-- packages/excalidraw/element/textElement.ts | 49 ++--- packages/excalidraw/element/textWysiwyg.tsx | 17 +- packages/excalidraw/element/types.ts | 30 ++- packages/excalidraw/frame.ts | 112 ++++++----- packages/excalidraw/groups.ts | 13 +- packages/excalidraw/renderer/renderElement.ts | 13 +- packages/excalidraw/renderer/renderScene.ts | 190 ++++++++++-------- packages/excalidraw/scene/Fonts.ts | 9 +- packages/excalidraw/scene/Renderer.ts | 62 +++--- packages/excalidraw/scene/Scene.ts | 72 +++++-- packages/excalidraw/scene/export.ts | 56 ++++-- packages/excalidraw/scene/scrollbars.ts | 7 +- packages/excalidraw/scene/selection.ts | 17 +- packages/excalidraw/scene/types.ts | 11 +- packages/excalidraw/utility-types.ts | 8 + packages/excalidraw/utils.ts | 42 +++- 31 files changed, 630 insertions(+), 384 deletions(-) diff --git a/packages/excalidraw/actions/actionFlip.ts b/packages/excalidraw/actions/actionFlip.ts index 12d5e2e48..81476e241 100644 --- a/packages/excalidraw/actions/actionFlip.ts +++ b/packages/excalidraw/actions/actionFlip.ts @@ -1,9 +1,13 @@ import { register } from "./register"; import { getSelectedElements } from "../scene"; import { getNonDeletedElements } from "../element"; -import { ExcalidrawElement, NonDeleted } from "../element/types"; +import { + ExcalidrawElement, + NonDeleted, + NonDeletedElementsMap, +} from "../element/types"; import { resizeMultipleElements } from "../element/resizeElements"; -import { AppState, PointerDownState } from "../types"; +import { AppState } from "../types"; import { arrayToMap } from "../utils"; import { CODES, KEYS } from "../keys"; import { getCommonBoundingBox } from "../element/bounds"; @@ -20,7 +24,12 @@ export const actionFlipHorizontal = register({ perform: (elements, appState, _, app) => { return { elements: updateFrameMembershipOfSelectedElements( - flipSelectedElements(elements, appState, "horizontal"), + flipSelectedElements( + elements, + app.scene.getNonDeletedElementsMap(), + appState, + "horizontal", + ), appState, app, ), @@ -38,7 +47,12 @@ export const actionFlipVertical = register({ perform: (elements, appState, _, app) => { return { elements: updateFrameMembershipOfSelectedElements( - flipSelectedElements(elements, appState, "vertical"), + flipSelectedElements( + elements, + app.scene.getNonDeletedElementsMap(), + appState, + "vertical", + ), appState, app, ), @@ -53,6 +67,7 @@ export const actionFlipVertical = register({ const flipSelectedElements = ( elements: readonly ExcalidrawElement[], + elementsMap: NonDeletedElementsMap, appState: Readonly, flipDirection: "horizontal" | "vertical", ) => { @@ -67,6 +82,7 @@ const flipSelectedElements = ( const updatedElements = flipElements( selectedElements, + elementsMap, appState, flipDirection, ); @@ -79,15 +95,17 @@ const flipSelectedElements = ( }; const flipElements = ( - elements: NonDeleted[], + selectedElements: NonDeleted[], + elementsMap: NonDeletedElementsMap, appState: AppState, flipDirection: "horizontal" | "vertical", ): ExcalidrawElement[] => { - const { minX, minY, maxX, maxY } = getCommonBoundingBox(elements); + const { minX, minY, maxX, maxY } = getCommonBoundingBox(selectedElements); resizeMultipleElements( - { originalElements: arrayToMap(elements) } as PointerDownState, - elements, + elementsMap, + selectedElements, + elementsMap, "nw", true, flipDirection === "horizontal" ? maxX : minX, @@ -96,7 +114,7 @@ const flipElements = ( (isBindingEnabled(appState) ? bindOrUnbindSelectedElements - : unbindLinearElements)(elements); + : unbindLinearElements)(selectedElements); - return elements; + return selectedElements; }; diff --git a/packages/excalidraw/actions/actionFrame.ts b/packages/excalidraw/actions/actionFrame.ts index 4cddb2ac0..8232db3cd 100644 --- a/packages/excalidraw/actions/actionFrame.ts +++ b/packages/excalidraw/actions/actionFrame.ts @@ -63,11 +63,7 @@ export const actionRemoveAllElementsFromFrame = register({ if (isFrameLikeElement(selectedElement)) { return { - elements: removeAllElementsFromFrame( - elements, - selectedElement, - appState, - ), + elements: removeAllElementsFromFrame(elements, selectedElement), appState: { ...appState, selectedElementIds: { diff --git a/packages/excalidraw/actions/actionGroup.tsx b/packages/excalidraw/actions/actionGroup.tsx index e6cb05840..42bd26efe 100644 --- a/packages/excalidraw/actions/actionGroup.tsx +++ b/packages/excalidraw/actions/actionGroup.tsx @@ -105,11 +105,7 @@ export const actionGroup = register({ const frameElementsMap = groupByFrameLikes(selectedElements); frameElementsMap.forEach((elementsInFrame, frameId) => { - nextElements = removeElementsFromFrame( - nextElements, - elementsInFrame, - appState, - ); + removeElementsFromFrame(elementsInFrame); }); } @@ -229,7 +225,6 @@ export const actionUngroup = register({ nextElements, getElementsInResizingFrame(nextElements, frame, appState), frame, - appState, ); } }); diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 9489970ae..c2a47802f 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -1,4 +1,4 @@ -import { AppState, Primitive } from "../types"; +import { AppClassProperties, AppState, Primitive } from "../types"; import { DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE, DEFAULT_ELEMENT_BACKGROUND_PICKS, @@ -66,7 +66,6 @@ import { import { mutateElement, newElementWith } from "../element/mutateElement"; import { getBoundTextElement, - getContainerElement, getDefaultLineHeight, } from "../element/textElement"; import { @@ -189,6 +188,7 @@ const offsetElementAfterFontResize = ( const changeFontSize = ( elements: readonly ExcalidrawElement[], appState: AppState, + app: AppClassProperties, getNewFontSize: (element: ExcalidrawTextElement) => number, fallbackValue?: ExcalidrawTextElement["fontSize"], ) => { @@ -206,7 +206,10 @@ const changeFontSize = ( let newElement: ExcalidrawTextElement = newElementWith(oldElement, { fontSize: newFontSize, }); - redrawTextBoundingBox(newElement, getContainerElement(oldElement)); + redrawTextBoundingBox( + newElement, + app.scene.getContainerElement(oldElement), + ); newElement = offsetElementAfterFontResize(oldElement, newElement); @@ -600,8 +603,8 @@ export const actionChangeOpacity = register({ export const actionChangeFontSize = register({ name: "changeFontSize", trackEvent: false, - perform: (elements, appState, value) => { - return changeFontSize(elements, appState, () => value, value); + perform: (elements, appState, value, app) => { + return changeFontSize(elements, appState, app, () => value, value); }, PanelComponent: ({ elements, appState, updateData }) => (
    @@ -663,8 +666,8 @@ export const actionChangeFontSize = register({ export const actionDecreaseFontSize = register({ name: "decreaseFontSize", trackEvent: false, - perform: (elements, appState, value) => { - return changeFontSize(elements, appState, (element) => + perform: (elements, appState, value, app) => { + return changeFontSize(elements, appState, app, (element) => Math.round( // get previous value before relative increase (doesn't work fully // due to rounding and float precision issues) @@ -685,8 +688,8 @@ export const actionDecreaseFontSize = register({ export const actionIncreaseFontSize = register({ name: "increaseFontSize", trackEvent: false, - perform: (elements, appState, value) => { - return changeFontSize(elements, appState, (element) => + perform: (elements, appState, value, app) => { + return changeFontSize(elements, appState, app, (element) => Math.round(element.fontSize * (1 + FONT_SIZE_RELATIVE_INCREASE_STEP)), ); }, @@ -703,7 +706,7 @@ export const actionIncreaseFontSize = register({ export const actionChangeFontFamily = register({ name: "changeFontFamily", trackEvent: false, - perform: (elements, appState, value) => { + perform: (elements, appState, value, app) => { return { elements: changeProperty( elements, @@ -717,7 +720,10 @@ export const actionChangeFontFamily = register({ lineHeight: getDefaultLineHeight(value), }, ); - redrawTextBoundingBox(newElement, getContainerElement(oldElement)); + redrawTextBoundingBox( + newElement, + app.scene.getContainerElement(oldElement), + ); return newElement; } @@ -795,7 +801,7 @@ export const actionChangeFontFamily = register({ export const actionChangeTextAlign = register({ name: "changeTextAlign", trackEvent: false, - perform: (elements, appState, value) => { + perform: (elements, appState, value, app) => { return { elements: changeProperty( elements, @@ -806,7 +812,10 @@ export const actionChangeTextAlign = register({ oldElement, { textAlign: value }, ); - redrawTextBoundingBox(newElement, getContainerElement(oldElement)); + redrawTextBoundingBox( + newElement, + app.scene.getContainerElement(oldElement), + ); return newElement; } @@ -875,7 +884,7 @@ export const actionChangeTextAlign = register({ export const actionChangeVerticalAlign = register({ name: "changeVerticalAlign", trackEvent: { category: "element" }, - perform: (elements, appState, value) => { + perform: (elements, appState, value, app) => { return { elements: changeProperty( elements, @@ -887,7 +896,10 @@ export const actionChangeVerticalAlign = register({ { verticalAlign: value }, ); - redrawTextBoundingBox(newElement, getContainerElement(oldElement)); + redrawTextBoundingBox( + newElement, + app.scene.getContainerElement(oldElement), + ); return newElement; } diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index f07664f1a..d67c8893d 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -1,7 +1,6 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { ActionManager } from "../actions/manager"; -import { getNonDeletedElements } from "../element"; -import { ExcalidrawElement, ExcalidrawElementType } from "../element/types"; +import { ExcalidrawElementType, NonDeletedElementsMap } from "../element/types"; import { t } from "../i18n"; import { useDevice } from "./App"; import { @@ -44,17 +43,14 @@ import { useTunnels } from "../context/tunnels"; export const SelectedShapeActions = ({ appState, - elements, + elementsMap, renderAction, }: { appState: UIAppState; - elements: readonly ExcalidrawElement[]; + elementsMap: NonDeletedElementsMap; renderAction: ActionManager["renderAction"]; }) => { - const targetElements = getTargetElements( - getNonDeletedElements(elements), - appState, - ); + const targetElements = getTargetElements(elementsMap, appState); let isSingleElementBoundContainer = false; if ( @@ -137,12 +133,12 @@ export const SelectedShapeActions = ({ {renderAction("changeFontFamily")} {(appState.activeTool.type === "text" || - suppportsHorizontalAlign(targetElements)) && + suppportsHorizontalAlign(targetElements, elementsMap)) && renderAction("changeTextAlign")} )} - {shouldAllowVerticalAlign(targetElements) && + {shouldAllowVerticalAlign(targetElements, elementsMap) && renderAction("changeVerticalAlign")} {(canHaveArrowheads(appState.activeTool.type) || targetElements.some((element) => canHaveArrowheads(element.type))) && ( diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 77c972882..9e3ff5dac 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1417,7 +1417,7 @@ class App extends React.Component { const { renderTopRightUI, renderCustomStats } = this.props; const versionNonce = this.scene.getVersionNonce(); - const { canvasElements, visibleElements } = + const { elementsMap, visibleElements } = this.renderer.getRenderableElements({ versionNonce, zoom: this.state.zoom, @@ -1627,7 +1627,7 @@ class App extends React.Component { { { private renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars, - elements, + elementsMap, }: RenderInteractiveSceneCallback) => { if (scrollBars) { currentScrollBars = scrollBars; @@ -2789,7 +2789,7 @@ class App extends React.Component { // hide when editing text isTextElement(this.state.editingElement) ? false - : !atLeastOneVisibleElement && elements.length > 0; + : !atLeastOneVisibleElement && elementsMap.size > 0; if (this.state.scrolledOutside !== scrolledOutside) { this.setState({ scrolledOutside }); } @@ -3119,7 +3119,10 @@ class App extends React.Component { newElements.forEach((newElement) => { if (isTextElement(newElement) && isBoundToContainer(newElement)) { - const container = getContainerElement(newElement); + const container = getContainerElement( + newElement, + this.scene.getElementsMapIncludingDeleted(), + ); redrawTextBoundingBox(newElement, container); } }); @@ -4183,11 +4186,18 @@ class App extends React.Component { this.scene.replaceAllElements([ ...this.scene.getElementsIncludingDeleted().map((_element) => { if (_element.id === element.id && isTextElement(_element)) { - return updateTextElement(_element, { - text, - isDeleted, - originalText, - }); + return updateTextElement( + _element, + getContainerElement( + _element, + this.scene.getElementsMapIncludingDeleted(), + ), + { + text, + isDeleted, + originalText, + }, + ); } return _element; }), @@ -7700,13 +7710,9 @@ class App extends React.Component { groupIds: [], }); - this.scene.replaceAllElements( - removeElementsFromFrame( - this.scene.getElementsIncludingDeleted(), - [linearElement], - this.state, - ), - ); + removeElementsFromFrame([linearElement]); + + this.scene.informMutation(); } } } @@ -7716,7 +7722,7 @@ class App extends React.Component { this.getTopLayerFrameAtSceneCoords(sceneCoords); const selectedElements = this.scene.getSelectedElements(this.state); - let nextElements = this.scene.getElementsIncludingDeleted(); + let nextElements = this.scene.getElementsMapIncludingDeleted(); const updateGroupIdsAfterEditingGroup = ( elements: ExcalidrawElement[], @@ -7809,7 +7815,7 @@ class App extends React.Component { this.scene.replaceAllElements( addElementsToFrame( - this.scene.getElementsIncludingDeleted(), + this.scene.getElementsMapIncludingDeleted(), elementsInsideFrame, draggingElement, ), @@ -7857,7 +7863,6 @@ class App extends React.Component { this.state, ), frame, - this.state, ); } @@ -9137,10 +9142,10 @@ class App extends React.Component { if ( transformElements( - pointerDownState, + pointerDownState.originalElements, transformHandleType, selectedElements, - pointerDownState.resize.arrowDirection, + this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0]) @@ -9150,7 +9155,6 @@ class App extends React.Component { resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y, - this.state, ) ) { this.maybeSuggestBindingForAll(selectedElements); diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index 8cedf689d..7247e8bf1 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -226,7 +226,7 @@ const LayerUI = ({ > diff --git a/packages/excalidraw/components/MobileMenu.tsx b/packages/excalidraw/components/MobileMenu.tsx index 91d0c518c..98f85a9ac 100644 --- a/packages/excalidraw/components/MobileMenu.tsx +++ b/packages/excalidraw/components/MobileMenu.tsx @@ -183,7 +183,7 @@ export const MobileMenu = ({
    diff --git a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx index 0aaa52c7c..0782b92b9 100644 --- a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx +++ b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx @@ -7,6 +7,7 @@ import type { DOMAttributes } from "react"; import type { AppState, InteractiveCanvasAppState } from "../../types"; import type { InteractiveCanvasRenderConfig, + RenderableElementsMap, RenderInteractiveSceneCallback, } from "../../scene/types"; import type { NonDeletedExcalidrawElement } from "../../element/types"; @@ -15,7 +16,7 @@ import { isRenderThrottlingEnabled } from "../../reactUtils"; type InteractiveCanvasProps = { containerRef: React.RefObject; canvas: HTMLCanvasElement | null; - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: RenderableElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; selectedElements: readonly NonDeletedExcalidrawElement[]; versionNonce: number | undefined; @@ -113,7 +114,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => { renderInteractiveScene( { canvas: props.canvas, - elements: props.elements, + elementsMap: props.elementsMap, visibleElements: props.visibleElements, selectedElements: props.selectedElements, scale: window.devicePixelRatio, @@ -201,10 +202,10 @@ const areEqual = ( prevProps.selectionNonce !== nextProps.selectionNonce || prevProps.versionNonce !== nextProps.versionNonce || prevProps.scale !== nextProps.scale || - // we need to memoize on element arrays because they may have renewed + // we need to memoize on elementsMap because they may have renewed // even if versionNonce didn't change (e.g. we filter elements out based // on appState) - prevProps.elements !== nextProps.elements || + prevProps.elementsMap !== nextProps.elementsMap || prevProps.visibleElements !== nextProps.visibleElements || prevProps.selectedElements !== nextProps.selectedElements ) { diff --git a/packages/excalidraw/components/canvases/StaticCanvas.tsx b/packages/excalidraw/components/canvases/StaticCanvas.tsx index c8174566b..3dc5b9175 100644 --- a/packages/excalidraw/components/canvases/StaticCanvas.tsx +++ b/packages/excalidraw/components/canvases/StaticCanvas.tsx @@ -3,14 +3,17 @@ import { RoughCanvas } from "roughjs/bin/canvas"; import { renderStaticScene } from "../../renderer/renderScene"; import { isShallowEqual } from "../../utils"; import type { AppState, StaticCanvasAppState } from "../../types"; -import type { StaticCanvasRenderConfig } from "../../scene/types"; +import type { + RenderableElementsMap, + StaticCanvasRenderConfig, +} from "../../scene/types"; import type { NonDeletedExcalidrawElement } from "../../element/types"; import { isRenderThrottlingEnabled } from "../../reactUtils"; type StaticCanvasProps = { canvas: HTMLCanvasElement; rc: RoughCanvas; - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: RenderableElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; versionNonce: number | undefined; selectionNonce: number | undefined; @@ -63,7 +66,7 @@ const StaticCanvas = (props: StaticCanvasProps) => { canvas, rc: props.rc, scale: props.scale, - elements: props.elements, + elementsMap: props.elementsMap, visibleElements: props.visibleElements, appState: props.appState, renderConfig: props.renderConfig, @@ -106,10 +109,10 @@ const areEqual = ( if ( prevProps.versionNonce !== nextProps.versionNonce || prevProps.scale !== nextProps.scale || - // we need to memoize on element arrays because they may have renewed + // we need to memoize on elementsMap because they may have renewed // even if versionNonce didn't change (e.g. we filter elements out based // on appState) - prevProps.elements !== nextProps.elements || + prevProps.elementsMap !== nextProps.elementsMap || prevProps.visibleElements !== nextProps.visibleElements ) { return false; diff --git a/packages/excalidraw/data/restore.ts b/packages/excalidraw/data/restore.ts index ff4063593..12e7f1af1 100644 --- a/packages/excalidraw/data/restore.ts +++ b/packages/excalidraw/data/restore.ts @@ -40,6 +40,7 @@ import { arrayToMap } from "../utils"; import { MarkOptional, Mutable } from "../utility-types"; import { detectLineHeight, + getContainerElement, getDefaultLineHeight, measureBaseline, } from "../element/textElement"; @@ -179,7 +180,6 @@ const restoreElementWithProperties = < const restoreElement = ( element: Exclude, - refreshDimensions = false, ): typeof element | null => { switch (element.type) { case "text": @@ -232,10 +232,6 @@ const restoreElement = ( element = bumpVersion(element); } - if (refreshDimensions) { - element = { ...element, ...refreshTextDimensions(element) }; - } - return element; case "freedraw": { return restoreElementWithProperties(element, { @@ -426,10 +422,7 @@ export const restoreElements = ( // filtering out selection, which is legacy, no longer kept in elements, // and causing issues if retained if (element.type !== "selection" && !isInvisiblySmallElement(element)) { - let migratedElement: ExcalidrawElement | null = restoreElement( - element, - opts?.refreshDimensions, - ); + let migratedElement: ExcalidrawElement | null = restoreElement(element); if (migratedElement) { const localElement = localElementsMap?.get(element.id); if (localElement && localElement.version > migratedElement.version) { @@ -462,6 +455,16 @@ export const restoreElements = ( } else if (element.boundElements) { repairContainerElement(element, restoredElementsMap); } + + if (opts.refreshDimensions && isTextElement(element)) { + Object.assign( + element, + refreshTextDimensions( + element, + getContainerElement(element, restoredElementsMap), + ), + ); + } } return restoredElements; diff --git a/packages/excalidraw/element/bounds.ts b/packages/excalidraw/element/bounds.ts index 292fc995d..673649e5f 100644 --- a/packages/excalidraw/element/bounds.ts +++ b/packages/excalidraw/element/bounds.ts @@ -5,6 +5,7 @@ import { ExcalidrawFreeDrawElement, NonDeleted, ExcalidrawTextElementWithContainer, + ElementsMapOrArray, } from "./types"; import { distance2d, rotate, rotatePoint } from "../math"; import rough from "roughjs/bin/rough"; @@ -161,7 +162,11 @@ export const getElementAbsoluteCoords = ( includeBoundText, ); } else if (isTextElement(element)) { - const container = getContainerElement(element); + const elementsMap = + Scene.getScene(element)?.getElementsMapIncludingDeleted(); + const container = elementsMap + ? getContainerElement(element, elementsMap) + : null; if (isArrowElement(container)) { const coords = LinearElementEditor.getBoundTextElementPosition( container, @@ -729,10 +734,8 @@ const getLinearElementRotatedBounds = ( export const getElementBounds = (element: ExcalidrawElement): Bounds => { return ElementBounds.getBounds(element); }; -export const getCommonBounds = ( - elements: readonly ExcalidrawElement[], -): Bounds => { - if (!elements.length) { +export const getCommonBounds = (elements: ElementsMapOrArray): Bounds => { + if ("size" in elements ? !elements.size : !elements.length) { return [0, 0, 0, 0]; } diff --git a/packages/excalidraw/element/embeddable.ts b/packages/excalidraw/element/embeddable.ts index 025ed4901..f62b0f95f 100644 --- a/packages/excalidraw/element/embeddable.ts +++ b/packages/excalidraw/element/embeddable.ts @@ -5,17 +5,12 @@ import { ExcalidrawProps } from "../types"; import { getFontString, updateActiveTool } from "../utils"; import { setCursorForShape } from "../cursor"; import { newTextElement } from "./newElement"; -import { getContainerElement, wrapText } from "./textElement"; -import { - isFrameLikeElement, - isIframeElement, - isIframeLikeElement, -} from "./typeChecks"; +import { wrapText } from "./textElement"; +import { isIframeElement } from "./typeChecks"; import { ExcalidrawElement, ExcalidrawIframeLikeElement, IframeData, - NonDeletedExcalidrawElement, } from "./types"; const embeddedLinkCache = new Map(); @@ -217,21 +212,6 @@ export const getEmbedLink = ( return { link, intrinsicSize: aspectRatio, type }; }; -export const isIframeLikeOrItsLabel = ( - element: NonDeletedExcalidrawElement, -): Boolean => { - if (isIframeLikeElement(element)) { - return true; - } - if (element.type === "text") { - const container = getContainerElement(element); - if (container && isFrameLikeElement(container)) { - return true; - } - } - return false; -}; - export const createPlaceholderEmbeddableLabel = ( element: ExcalidrawIframeLikeElement, ): ExcalidrawElement => { diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index 00cae296c..3158c064c 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -31,7 +31,6 @@ import { getElementAbsoluteCoords } from "."; import { adjustXYWithRotation } from "../math"; import { getResizedElementAbsoluteCoords } from "./bounds"; import { - getContainerElement, measureText, normalizeText, wrapText, @@ -333,12 +332,12 @@ const getAdjustedDimensions = ( export const refreshTextDimensions = ( textElement: ExcalidrawTextElement, + container: ExcalidrawTextContainer | null, text = textElement.text, ) => { if (textElement.isDeleted) { return; } - const container = getContainerElement(textElement); if (container) { text = wrapText( text, @@ -352,6 +351,7 @@ export const refreshTextDimensions = ( export const updateTextElement = ( textElement: ExcalidrawTextElement, + container: ExcalidrawTextContainer | null, { text, isDeleted, @@ -365,7 +365,7 @@ export const updateTextElement = ( return newElementWith(textElement, { originalText, isDeleted: isDeleted ?? textElement.isDeleted, - ...refreshTextDimensions(textElement, originalText), + ...refreshTextDimensions(textElement, container, originalText), }); }; diff --git a/packages/excalidraw/element/resizeElements.ts b/packages/excalidraw/element/resizeElements.ts index 883382933..46b891aca 100644 --- a/packages/excalidraw/element/resizeElements.ts +++ b/packages/excalidraw/element/resizeElements.ts @@ -15,6 +15,7 @@ import { ExcalidrawElement, ExcalidrawTextElementWithContainer, ExcalidrawImageElement, + ElementsMap, } from "./types"; import type { Mutable } from "../utility-types"; import { @@ -41,7 +42,7 @@ import { MaybeTransformHandleType, TransformHandleDirection, } from "./transformHandles"; -import { AppState, Point, PointerDownState } from "../types"; +import { Point, PointerDownState } from "../types"; import Scene from "../scene/Scene"; import { getApproxMinLineWidth, @@ -68,10 +69,10 @@ export const normalizeAngle = (angle: number): number => { // Returns true when transform (resizing/rotation) happened export const transformElements = ( - pointerDownState: PointerDownState, + originalElements: PointerDownState["originalElements"], transformHandleType: MaybeTransformHandleType, selectedElements: readonly NonDeletedExcalidrawElement[], - resizeArrowDirection: "origin" | "end", + elementsMap: ElementsMap, shouldRotateWithDiscreteAngle: boolean, shouldResizeFromCenter: boolean, shouldMaintainAspectRatio: boolean, @@ -79,7 +80,6 @@ export const transformElements = ( pointerY: number, centerX: number, centerY: number, - appState: AppState, ) => { if (selectedElements.length === 1) { const [element] = selectedElements; @@ -89,7 +89,6 @@ export const transformElements = ( pointerX, pointerY, shouldRotateWithDiscreteAngle, - pointerDownState.originalElements, ); updateBoundElements(element); } else if ( @@ -101,6 +100,7 @@ export const transformElements = ( ) { resizeSingleTextElement( element, + elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, @@ -109,9 +109,10 @@ export const transformElements = ( updateBoundElements(element); } else if (transformHandleType) { resizeSingleElement( - pointerDownState.originalElements, + originalElements, shouldMaintainAspectRatio, element, + elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, @@ -123,7 +124,7 @@ export const transformElements = ( } else if (selectedElements.length > 1) { if (transformHandleType === "rotation") { rotateMultipleElements( - pointerDownState, + originalElements, selectedElements, pointerX, pointerY, @@ -139,8 +140,9 @@ export const transformElements = ( transformHandleType === "se" ) { resizeMultipleElements( - pointerDownState, + originalElements, selectedElements, + elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, @@ -157,7 +159,6 @@ const rotateSingleElement = ( pointerX: number, pointerY: number, shouldRotateWithDiscreteAngle: boolean, - originalElements: Map>, ) => { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); const cx = (x1 + x2) / 2; @@ -207,6 +208,7 @@ const rescalePointsInElement = ( const measureFontSizeFromWidth = ( element: NonDeleted, + elementsMap: ElementsMap, nextWidth: number, nextHeight: number, ): { size: number; baseline: number } | null => { @@ -215,7 +217,7 @@ const measureFontSizeFromWidth = ( const hasContainer = isBoundToContainer(element); if (hasContainer) { - const container = getContainerElement(element); + const container = getContainerElement(element, elementsMap); if (container) { width = getBoundTextMaxWidth(container); } @@ -257,6 +259,7 @@ const getSidesForTransformHandle = ( const resizeSingleTextElement = ( element: NonDeleted, + elementsMap: ElementsMap, transformHandleType: "nw" | "ne" | "sw" | "se", shouldResizeFromCenter: boolean, pointerX: number, @@ -303,7 +306,12 @@ const resizeSingleTextElement = ( if (scale > 0) { const nextWidth = element.width * scale; const nextHeight = element.height * scale; - const metrics = measureFontSizeFromWidth(element, nextWidth, nextHeight); + const metrics = measureFontSizeFromWidth( + element, + elementsMap, + nextWidth, + nextHeight, + ); if (metrics === null) { return; } @@ -342,6 +350,7 @@ export const resizeSingleElement = ( originalElements: PointerDownState["originalElements"], shouldMaintainAspectRatio: boolean, element: NonDeletedExcalidrawElement, + elementsMap: ElementsMap, transformHandleDirection: TransformHandleDirection, shouldResizeFromCenter: boolean, pointerX: number, @@ -448,6 +457,7 @@ export const resizeSingleElement = ( const nextFont = measureFontSizeFromWidth( boundTextElement, + elementsMap, getBoundTextMaxWidth(updatedElement), getBoundTextMaxHeight(updatedElement, boundTextElement), ); @@ -637,8 +647,9 @@ export const resizeSingleElement = ( }; export const resizeMultipleElements = ( - pointerDownState: PointerDownState, + originalElements: PointerDownState["originalElements"], selectedElements: readonly NonDeletedExcalidrawElement[], + elementsMap: ElementsMap, transformHandleType: "nw" | "ne" | "sw" | "se", shouldResizeFromCenter: boolean, pointerX: number, @@ -658,7 +669,7 @@ export const resizeMultipleElements = ( }[], element, ) => { - const origElement = pointerDownState.originalElements.get(element.id); + const origElement = originalElements.get(element.id); if (origElement) { acc.push({ orig: origElement, latest: element }); } @@ -679,7 +690,7 @@ export const resizeMultipleElements = ( if (!textId) { return acc; } - const text = pointerDownState.originalElements.get(textId) ?? null; + const text = originalElements.get(textId) ?? null; if (!isBoundToContainer(text)) { return acc; } @@ -825,7 +836,12 @@ export const resizeMultipleElements = ( } if (isTextElement(orig)) { - const metrics = measureFontSizeFromWidth(orig, width, height); + const metrics = measureFontSizeFromWidth( + orig, + elementsMap, + width, + height, + ); if (!metrics) { return; } @@ -833,7 +849,7 @@ export const resizeMultipleElements = ( update.baseline = metrics.baseline; } - const boundTextElement = pointerDownState.originalElements.get( + const boundTextElement = originalElements.get( getBoundTextElementId(orig) ?? "", ) as ExcalidrawTextElementWithContainer | undefined; @@ -884,7 +900,7 @@ export const resizeMultipleElements = ( }; const rotateMultipleElements = ( - pointerDownState: PointerDownState, + originalElements: PointerDownState["originalElements"], elements: readonly NonDeletedExcalidrawElement[], pointerX: number, pointerY: number, @@ -906,8 +922,7 @@ const rotateMultipleElements = ( const cx = (x1 + x2) / 2; const cy = (y1 + y2) / 2; const origAngle = - pointerDownState.originalElements.get(element.id)?.angle ?? - element.angle; + originalElements.get(element.id)?.angle ?? element.angle; const [rotatedCX, rotatedCY] = rotate( cx, cy, diff --git a/packages/excalidraw/element/textElement.ts b/packages/excalidraw/element/textElement.ts index e084dfba3..da1348ec2 100644 --- a/packages/excalidraw/element/textElement.ts +++ b/packages/excalidraw/element/textElement.ts @@ -1,5 +1,6 @@ import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils"; import { + ElementsMap, ExcalidrawElement, ExcalidrawElementType, ExcalidrawTextContainer, @@ -682,17 +683,15 @@ export const getBoundTextElement = (element: ExcalidrawElement | null) => { }; export const getContainerElement = ( - element: - | (ExcalidrawElement & { - containerId: ExcalidrawElement["id"] | null; - }) - | null, -) => { + element: ExcalidrawTextElement | null, + elementsMap: ElementsMap, +): ExcalidrawTextContainer | null => { if (!element) { return null; } if (element.containerId) { - return Scene.getScene(element)?.getElement(element.containerId) || null; + return (elementsMap.get(element.containerId) || + null) as ExcalidrawTextContainer | null; } return null; }; @@ -752,28 +751,16 @@ export const getContainerCoords = (container: NonDeletedExcalidrawElement) => { }; }; -export const getTextElementAngle = (textElement: ExcalidrawTextElement) => { - const container = getContainerElement(textElement); +export const getTextElementAngle = ( + textElement: ExcalidrawTextElement, + container: ExcalidrawTextContainer | null, +) => { if (!container || isArrowElement(container)) { return textElement.angle; } return container.angle; }; -export const getBoundTextElementOffset = ( - boundTextElement: ExcalidrawTextElement | null, -) => { - const container = getContainerElement(boundTextElement); - if (!container || !boundTextElement) { - return 0; - } - if (isArrowElement(container)) { - return BOUND_TEXT_PADDING * 8; - } - - return BOUND_TEXT_PADDING; -}; - export const getBoundTextElementPosition = ( container: ExcalidrawElement, boundTextElement: ExcalidrawTextElementWithContainer, @@ -788,12 +775,12 @@ export const getBoundTextElementPosition = ( export const shouldAllowVerticalAlign = ( selectedElements: NonDeletedExcalidrawElement[], + elementsMap: ElementsMap, ) => { return selectedElements.some((element) => { - const hasBoundContainer = isBoundToContainer(element); - if (hasBoundContainer) { - const container = getContainerElement(element); - if (isTextElement(element) && isArrowElement(container)) { + if (isBoundToContainer(element)) { + const container = getContainerElement(element, elementsMap); + if (isArrowElement(container)) { return false; } return true; @@ -804,12 +791,12 @@ export const shouldAllowVerticalAlign = ( export const suppportsHorizontalAlign = ( selectedElements: NonDeletedExcalidrawElement[], + elementsMap: ElementsMap, ) => { return selectedElements.some((element) => { - const hasBoundContainer = isBoundToContainer(element); - if (hasBoundContainer) { - const container = getContainerElement(element); - if (isTextElement(element) && isArrowElement(container)) { + if (isBoundToContainer(element)) { + const container = getContainerElement(element, elementsMap); + if (isArrowElement(container)) { return false; } return true; diff --git a/packages/excalidraw/element/textWysiwyg.tsx b/packages/excalidraw/element/textWysiwyg.tsx index 52f89e0b9..801f0c440 100644 --- a/packages/excalidraw/element/textWysiwyg.tsx +++ b/packages/excalidraw/element/textWysiwyg.tsx @@ -153,7 +153,10 @@ export const textWysiwyg = ({ if (updatedTextElement && isTextElement(updatedTextElement)) { let coordX = updatedTextElement.x; let coordY = updatedTextElement.y; - const container = getContainerElement(updatedTextElement); + const container = getContainerElement( + updatedTextElement, + app.scene.getElementsMapIncludingDeleted(), + ); let maxWidth = updatedTextElement.width; let maxHeight = updatedTextElement.height; @@ -277,7 +280,7 @@ export const textWysiwyg = ({ transform: getTransform( textElementWidth, textElementHeight, - getTextElementAngle(updatedTextElement), + getTextElementAngle(updatedTextElement, container), appState, maxWidth, editorMaxHeight, @@ -348,7 +351,10 @@ export const textWysiwyg = ({ if (!data) { return; } - const container = getContainerElement(element); + const container = getContainerElement( + element, + app.scene.getElementsMapIncludingDeleted(), + ); const font = getFontString({ fontSize: app.state.currentItemFontSize, @@ -528,7 +534,10 @@ export const textWysiwyg = ({ return; } let text = editable.value; - const container = getContainerElement(updateElement); + const container = getContainerElement( + updateElement, + app.scene.getElementsMapIncludingDeleted(), + ); if (container) { text = updateElement.text; diff --git a/packages/excalidraw/element/types.ts b/packages/excalidraw/element/types.ts index c468eac82..7659ad1e9 100644 --- a/packages/excalidraw/element/types.ts +++ b/packages/excalidraw/element/types.ts @@ -6,7 +6,7 @@ import { THEME, VERTICAL_ALIGN, } from "../constants"; -import { MarkNonNullable, ValueOf } from "../utility-types"; +import { MakeBrand, MarkNonNullable, ValueOf } from "../utility-types"; import { MagicCacheData } from "../data/magic"; export type ChartType = "bar" | "line"; @@ -254,3 +254,31 @@ export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase & export type FileId = string & { _brand: "FileId" }; export type ExcalidrawElementType = ExcalidrawElement["type"]; + +/** + * Map of excalidraw elements. + * Unspecified whether deleted or non-deleted. + * Can be a subset of Scene elements. + */ +export type ElementsMap = Map; + +/** + * Map of non-deleted elements. + * Can be a subset of Scene elements. + */ +export type NonDeletedElementsMap = Map< + ExcalidrawElement["id"], + NonDeletedExcalidrawElement +> & + MakeBrand<"NonDeletedElementsMap">; + +/** + * Map of all excalidraw Scene elements, including deleted. + * Not a subset. Use this type when you need access to current Scene elements. + */ +export type SceneElementsMap = Map & + MakeBrand<"SceneElementsMap">; + +export type ElementsMapOrArray = + | readonly ExcalidrawElement[] + | Readonly; diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index a5e743711..ecb70ef1e 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -4,6 +4,8 @@ import { isTextElement, } from "./element"; import { + ElementsMap, + ElementsMapOrArray, ExcalidrawElement, ExcalidrawFrameLikeElement, NonDeleted, @@ -26,6 +28,7 @@ import { elementsOverlappingBBox, } from "../utils/export"; import { isFrameElement, isFrameLikeElement } from "./element/typeChecks"; +import { ReadonlySetLike } from "./utility-types"; // --------------------------- Frame State ------------------------------------ export const bindElementsToFramesAfterDuplication = ( @@ -211,9 +214,17 @@ export const groupByFrameLikes = (elements: readonly ExcalidrawElement[]) => { }; export const getFrameChildren = ( - allElements: ExcalidrawElementsIncludingDeleted, + allElements: ElementsMapOrArray, frameId: string, -) => allElements.filter((element) => element.frameId === frameId); +) => { + const frameChildren: ExcalidrawElement[] = []; + for (const element of allElements.values()) { + if (element.frameId === frameId) { + frameChildren.push(element); + } + } + return frameChildren; +}; export const getFrameLikeElements = ( allElements: ExcalidrawElementsIncludingDeleted, @@ -425,23 +436,20 @@ export const filterElementsEligibleAsFrameChildren = ( * Retains (or repairs for target frame) the ordering invriant where children * elements come right before the parent frame: * [el, el, child, child, frame, el] + * + * @returns mutated allElements (same data structure) */ -export const addElementsToFrame = ( - allElements: ExcalidrawElementsIncludingDeleted, +export const addElementsToFrame = ( + allElements: T, elementsToAdd: NonDeletedExcalidrawElement[], frame: ExcalidrawFrameLikeElement, -) => { - const { currTargetFrameChildrenMap } = allElements.reduce( - (acc, element, index) => { - if (element.frameId === frame.id) { - acc.currTargetFrameChildrenMap.set(element.id, true); - } - return acc; - }, - { - currTargetFrameChildrenMap: new Map(), - }, - ); +): T => { + const currTargetFrameChildrenMap = new Map(); + for (const element of allElements.values()) { + if (element.frameId === frame.id) { + currTargetFrameChildrenMap.set(element.id, true); + } + } const suppliedElementsToAddSet = new Set(elementsToAdd.map((el) => el.id)); @@ -492,13 +500,12 @@ export const addElementsToFrame = ( false, ); } - return allElements.slice(); + + return allElements; }; export const removeElementsFromFrame = ( - allElements: ExcalidrawElementsIncludingDeleted, - elementsToRemove: NonDeletedExcalidrawElement[], - appState: AppState, + elementsToRemove: ReadonlySetLike, ) => { const _elementsToRemove = new Map< ExcalidrawElement["id"], @@ -536,35 +543,34 @@ export const removeElementsFromFrame = ( false, ); } - - return allElements.slice(); }; -export const removeAllElementsFromFrame = ( - allElements: ExcalidrawElementsIncludingDeleted, +export const removeAllElementsFromFrame = ( + allElements: readonly T[], frame: ExcalidrawFrameLikeElement, - appState: AppState, ) => { const elementsInFrame = getFrameChildren(allElements, frame.id); - return removeElementsFromFrame(allElements, elementsInFrame, appState); + removeElementsFromFrame(elementsInFrame); + return allElements; }; -export const replaceAllElementsInFrame = ( - allElements: ExcalidrawElementsIncludingDeleted, +export const replaceAllElementsInFrame = ( + allElements: readonly T[], nextElementsInFrame: ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, - appState: AppState, -) => { +): T[] => { return addElementsToFrame( - removeAllElementsFromFrame(allElements, frame, appState), + removeAllElementsFromFrame(allElements, frame), nextElementsInFrame, frame, - ); + ).slice(); }; /** does not mutate elements, but returns new ones */ -export const updateFrameMembershipOfSelectedElements = ( - allElements: ExcalidrawElementsIncludingDeleted, +export const updateFrameMembershipOfSelectedElements = < + T extends ElementsMapOrArray, +>( + allElements: T, appState: AppState, app: AppClassProperties, ) => { @@ -589,19 +595,22 @@ export const updateFrameMembershipOfSelectedElements = ( const elementsToRemove = new Set(); + const elementsMap = arrayToMap(allElements); + elementsToFilter.forEach((element) => { if ( element.frameId && !isFrameLikeElement(element) && - !isElementInFrame(element, allElements, appState) + !isElementInFrame(element, elementsMap, appState) ) { elementsToRemove.add(element); } }); - return elementsToRemove.size > 0 - ? removeElementsFromFrame(allElements, [...elementsToRemove], appState) - : allElements; + if (elementsToRemove.size > 0) { + removeElementsFromFrame(elementsToRemove); + } + return allElements; }; /** @@ -609,14 +618,16 @@ export const updateFrameMembershipOfSelectedElements = ( * anywhere in the group tree */ export const omitGroupsContainingFrameLikes = ( - allElements: ExcalidrawElementsIncludingDeleted, + allElements: ElementsMapOrArray, /** subset of elements you want to filter. Optional perf optimization so we * don't have to filter all elements unnecessarily */ selectedElements?: readonly ExcalidrawElement[], ) => { const uniqueGroupIds = new Set(); - for (const el of selectedElements || allElements) { + const elements = selectedElements || allElements; + + for (const el of elements.values()) { const topMostGroupId = el.groupIds[el.groupIds.length - 1]; if (topMostGroupId) { uniqueGroupIds.add(topMostGroupId); @@ -634,9 +645,15 @@ export const omitGroupsContainingFrameLikes = ( } } - return (selectedElements || allElements).filter( - (el) => !rejectedGroupIds.has(el.groupIds[el.groupIds.length - 1]), - ); + const ret: ExcalidrawElement[] = []; + + for (const element of elements.values()) { + if (!rejectedGroupIds.has(element.groupIds[element.groupIds.length - 1])) { + ret.push(element); + } + } + + return ret; }; /** @@ -645,10 +662,11 @@ export const omitGroupsContainingFrameLikes = ( */ export const getTargetFrame = ( element: ExcalidrawElement, + elementsMap: ElementsMap, appState: StaticCanvasAppState, ) => { const _element = isTextElement(element) - ? getContainerElement(element) || element + ? getContainerElement(element, elementsMap) || element : element; return appState.selectedElementIds[_element.id] && @@ -661,12 +679,12 @@ export const getTargetFrame = ( // given an element, return if the element is in some frame export const isElementInFrame = ( element: ExcalidrawElement, - allElements: ExcalidrawElementsIncludingDeleted, + allElements: ElementsMap, appState: StaticCanvasAppState, ) => { - const frame = getTargetFrame(element, appState); + const frame = getTargetFrame(element, allElements, appState); const _element = isTextElement(element) - ? getContainerElement(element) || element + ? getContainerElement(element, allElements) || element : element; if (frame) { diff --git a/packages/excalidraw/groups.ts b/packages/excalidraw/groups.ts index dd5512ba1..b0bedc4f9 100644 --- a/packages/excalidraw/groups.ts +++ b/packages/excalidraw/groups.ts @@ -3,6 +3,7 @@ import { ExcalidrawElement, NonDeleted, NonDeletedExcalidrawElement, + ElementsMapOrArray, } from "./element/types"; import { AppClassProperties, @@ -270,9 +271,17 @@ export const isElementInGroup = (element: ExcalidrawElement, groupId: string) => element.groupIds.includes(groupId); export const getElementsInGroup = ( - elements: readonly ExcalidrawElement[], + elements: ElementsMapOrArray, groupId: string, -) => elements.filter((element) => isElementInGroup(element, groupId)); +) => { + const elementsInGroup: ExcalidrawElement[] = []; + for (const element of elements.values()) { + if (isElementInGroup(element, groupId)) { + elementsInGroup.push(element); + } + } + return elementsInGroup; +}; export const getSelectedGroupIdForElement = ( element: ExcalidrawElement, diff --git a/packages/excalidraw/renderer/renderElement.ts b/packages/excalidraw/renderer/renderElement.ts index 94eda49f9..39e6c4974 100644 --- a/packages/excalidraw/renderer/renderElement.ts +++ b/packages/excalidraw/renderer/renderElement.ts @@ -21,7 +21,11 @@ import type { RoughCanvas } from "roughjs/bin/canvas"; import type { Drawable } from "roughjs/bin/core"; import type { RoughSVG } from "roughjs/bin/svg"; -import { SVGRenderConfig, StaticCanvasRenderConfig } from "../scene/types"; +import { + SVGRenderConfig, + StaticCanvasRenderConfig, + RenderableElementsMap, +} from "../scene/types"; import { distance, getFontString, @@ -611,6 +615,7 @@ export const renderSelectionElement = ( export const renderElement = ( element: NonDeletedExcalidrawElement, + elementsMap: RenderableElementsMap, rc: RoughCanvas, context: CanvasRenderingContext2D, renderConfig: StaticCanvasRenderConfig, @@ -715,7 +720,7 @@ export const renderElement = ( let shiftX = (x2 - x1) / 2 - (element.x - x1); let shiftY = (y2 - y1) / 2 - (element.y - y1); if (isTextElement(element)) { - const container = getContainerElement(element); + const container = getContainerElement(element, elementsMap); if (isArrowElement(container)) { const boundTextCoords = LinearElementEditor.getBoundTextElementPosition( @@ -900,6 +905,7 @@ const maybeWrapNodesInFrameClipPath = ( export const renderElementToSvg = ( element: NonDeletedExcalidrawElement, + elementsMap: RenderableElementsMap, rsvg: RoughSVG, svgRoot: SVGElement, files: BinaryFiles, @@ -912,7 +918,7 @@ export const renderElementToSvg = ( let cx = (x2 - x1) / 2 - (element.x - x1); let cy = (y2 - y1) / 2 - (element.y - y1); if (isTextElement(element)) { - const container = getContainerElement(element); + const container = getContainerElement(element, elementsMap); if (isArrowElement(container)) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(container); @@ -1013,6 +1019,7 @@ export const renderElementToSvg = ( createPlaceholderEmbeddableLabel(element); renderElementToSvg( label, + elementsMap, rsvg, root, files, diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index a5b78d3b8..0a066bfd4 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -33,6 +33,7 @@ import { SVGRenderConfig, StaticCanvasRenderConfig, StaticSceneRenderConfig, + RenderableElementsMap, } from "../scene/types"; import { getScrollBars, @@ -61,7 +62,7 @@ import { TransformHandles, TransformHandleType, } from "../element/transformHandles"; -import { throttleRAF } from "../utils"; +import { arrayToMap, throttleRAF } from "../utils"; import { UserIdleState } from "../types"; import { FRAME_STYLE, THEME_FILTER } from "../constants"; import { @@ -75,10 +76,7 @@ import { isIframeLikeElement, isLinearElement, } from "../element/typeChecks"; -import { - isIframeLikeOrItsLabel, - createPlaceholderEmbeddableLabel, -} from "../element/embeddable"; +import { createPlaceholderEmbeddableLabel } from "../element/embeddable"; import { elementOverlapsWithFrame, getTargetFrame, @@ -446,7 +444,7 @@ const bootstrapCanvas = ({ const _renderInteractiveScene = ({ canvas, - elements, + elementsMap, visibleElements, selectedElements, scale, @@ -454,7 +452,7 @@ const _renderInteractiveScene = ({ renderConfig, }: InteractiveSceneRenderConfig) => { if (canvas === null) { - return { atLeastOneVisibleElement: false, elements }; + return { atLeastOneVisibleElement: false, elementsMap }; } const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions( @@ -562,75 +560,64 @@ const _renderInteractiveScene = ({ if (showBoundingBox) { // Optimisation for finding quickly relevant element ids - const locallySelectedIds = selectedElements.reduce( - (acc: Record, element) => { - acc[element.id] = true; - return acc; - }, - {}, - ); + const locallySelectedIds = arrayToMap(selectedElements); - const selections = elements.reduce( - ( - acc: { - angle: number; - elementX1: number; - elementY1: number; - elementX2: number; - elementY2: number; - selectionColors: string[]; - dashed?: boolean; - cx: number; - cy: number; - activeEmbeddable: boolean; - }[], - element, - ) => { - const selectionColors = []; - // local user - if ( - locallySelectedIds[element.id] && - !isSelectedViaGroup(appState, element) - ) { - selectionColors.push(selectionColor); - } - // remote users - if (renderConfig.remoteSelectedElementIds[element.id]) { - selectionColors.push( - ...renderConfig.remoteSelectedElementIds[element.id].map( - (socketId: string) => { - const background = getClientColor(socketId); - return background; - }, - ), - ); - } + const selections: { + angle: number; + elementX1: number; + elementY1: number; + elementX2: number; + elementY2: number; + selectionColors: string[]; + dashed?: boolean; + cx: number; + cy: number; + activeEmbeddable: boolean; + }[] = []; - if (selectionColors.length) { - const [elementX1, elementY1, elementX2, elementY2, cx, cy] = - getElementAbsoluteCoords(element, true); - acc.push({ - angle: element.angle, - elementX1, - elementY1, - elementX2, - elementY2, - selectionColors, - dashed: !!renderConfig.remoteSelectedElementIds[element.id], - cx, - cy, - activeEmbeddable: - appState.activeEmbeddable?.element === element && - appState.activeEmbeddable.state === "active", - }); - } - return acc; - }, - [], - ); + for (const element of elementsMap.values()) { + const selectionColors = []; + // local user + if ( + locallySelectedIds.has(element.id) && + !isSelectedViaGroup(appState, element) + ) { + selectionColors.push(selectionColor); + } + // remote users + if (renderConfig.remoteSelectedElementIds[element.id]) { + selectionColors.push( + ...renderConfig.remoteSelectedElementIds[element.id].map( + (socketId: string) => { + const background = getClientColor(socketId); + return background; + }, + ), + ); + } + + if (selectionColors.length) { + const [elementX1, elementY1, elementX2, elementY2, cx, cy] = + getElementAbsoluteCoords(element, true); + selections.push({ + angle: element.angle, + elementX1, + elementY1, + elementX2, + elementY2, + selectionColors, + dashed: !!renderConfig.remoteSelectedElementIds[element.id], + cx, + cy, + activeEmbeddable: + appState.activeEmbeddable?.element === element && + appState.activeEmbeddable.state === "active", + }); + } + } const addSelectionForGroupId = (groupId: GroupId) => { - const groupElements = getElementsInGroup(elements, groupId); + const groupElements = getElementsInGroup(elementsMap, groupId); const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(groupElements); selections.push({ @@ -870,7 +857,7 @@ const _renderInteractiveScene = ({ let scrollBars; if (renderConfig.renderScrollbars) { scrollBars = getScrollBars( - elements, + elementsMap, normalizedWidth, normalizedHeight, appState, @@ -897,14 +884,14 @@ const _renderInteractiveScene = ({ return { scrollBars, atLeastOneVisibleElement: visibleElements.length > 0, - elements, + elementsMap, }; }; const _renderStaticScene = ({ canvas, rc, - elements, + elementsMap, visibleElements, scale, appState, @@ -965,7 +952,7 @@ const _renderStaticScene = ({ // Paint visible elements visibleElements - .filter((el) => !isIframeLikeOrItsLabel(el)) + .filter((el) => !isIframeLikeElement(el)) .forEach((element) => { try { const frameId = element.frameId || appState.frameToHighlight?.id; @@ -977,16 +964,30 @@ const _renderStaticScene = ({ ) { context.save(); - const frame = getTargetFrame(element, appState); + const frame = getTargetFrame(element, elementsMap, appState); // TODO do we need to check isElementInFrame here? - if (frame && isElementInFrame(element, elements, appState)) { + if (frame && isElementInFrame(element, elementsMap, appState)) { frameClip(frame, context, renderConfig, appState); } - renderElement(element, rc, context, renderConfig, appState); + renderElement( + element, + elementsMap, + rc, + context, + renderConfig, + appState, + ); context.restore(); } else { - renderElement(element, rc, context, renderConfig, appState); + renderElement( + element, + elementsMap, + rc, + context, + renderConfig, + appState, + ); } if (!isExporting) { renderLinkIcon(element, context, appState); @@ -998,11 +999,18 @@ const _renderStaticScene = ({ // render embeddables on top visibleElements - .filter((el) => isIframeLikeOrItsLabel(el)) + .filter((el) => isIframeLikeElement(el)) .forEach((element) => { try { const render = () => { - renderElement(element, rc, context, renderConfig, appState); + renderElement( + element, + elementsMap, + rc, + context, + renderConfig, + appState, + ); if ( isIframeLikeElement(element) && @@ -1014,7 +1022,14 @@ const _renderStaticScene = ({ element.height ) { const label = createPlaceholderEmbeddableLabel(element); - renderElement(label, rc, context, renderConfig, appState); + renderElement( + label, + elementsMap, + rc, + context, + renderConfig, + appState, + ); } if (!isExporting) { renderLinkIcon(element, context, appState); @@ -1032,9 +1047,9 @@ const _renderStaticScene = ({ ) { context.save(); - const frame = getTargetFrame(element, appState); + const frame = getTargetFrame(element, elementsMap, appState); - if (frame && isElementInFrame(element, elements, appState)) { + if (frame && isElementInFrame(element, elementsMap, appState)) { frameClip(frame, context, renderConfig, appState); } render(); @@ -1448,6 +1463,7 @@ const renderLinkIcon = ( // This should be only called for exporting purposes export const renderSceneToSvg = ( elements: readonly NonDeletedExcalidrawElement[], + elementsMap: RenderableElementsMap, rsvg: RoughSVG, svgRoot: SVGElement, files: BinaryFiles, @@ -1459,12 +1475,13 @@ export const renderSceneToSvg = ( // render elements elements - .filter((el) => !isIframeLikeOrItsLabel(el)) + .filter((el) => !isIframeLikeElement(el)) .forEach((element) => { if (!element.isDeleted) { try { renderElementToSvg( element, + elementsMap, rsvg, svgRoot, files, @@ -1486,6 +1503,7 @@ export const renderSceneToSvg = ( try { renderElementToSvg( element, + elementsMap, rsvg, svgRoot, files, diff --git a/packages/excalidraw/scene/Fonts.ts b/packages/excalidraw/scene/Fonts.ts index 05dddadc4..1a97c06e0 100644 --- a/packages/excalidraw/scene/Fonts.ts +++ b/packages/excalidraw/scene/Fonts.ts @@ -1,5 +1,6 @@ import { isTextElement, refreshTextDimensions } from "../element"; import { newElementWith } from "../element/mutateElement"; +import { getContainerElement } from "../element/textElement"; import { isBoundToContainer } from "../element/typeChecks"; import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; import { getFontString } from "../utils"; @@ -57,7 +58,13 @@ export class Fonts { ShapeCache.delete(element); didUpdate = true; return newElementWith(element, { - ...refreshTextDimensions(element), + ...refreshTextDimensions( + element, + getContainerElement( + element, + this.scene.getElementsMapIncludingDeleted(), + ), + ), }); } return element; diff --git a/packages/excalidraw/scene/Renderer.ts b/packages/excalidraw/scene/Renderer.ts index 152224951..1593d6d2e 100644 --- a/packages/excalidraw/scene/Renderer.ts +++ b/packages/excalidraw/scene/Renderer.ts @@ -1,10 +1,14 @@ import { isElementInViewport } from "../element/sizeHelpers"; import { isImageElement } from "../element/typeChecks"; -import { NonDeletedExcalidrawElement } from "../element/types"; +import { + NonDeletedElementsMap, + NonDeletedExcalidrawElement, +} from "../element/types"; import { cancelRender } from "../renderer/renderScene"; import { AppState } from "../types"; -import { memoize } from "../utils"; +import { memoize, toBrandedType } from "../utils"; import Scene from "./Scene"; +import { RenderableElementsMap } from "./types"; export class Renderer { private scene: Scene; @@ -15,7 +19,7 @@ export class Renderer { public getRenderableElements = (() => { const getVisibleCanvasElements = ({ - elements, + elementsMap, zoom, offsetLeft, offsetTop, @@ -24,7 +28,7 @@ export class Renderer { height, width, }: { - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: NonDeletedElementsMap; zoom: AppState["zoom"]; offsetLeft: AppState["offsetLeft"]; offsetTop: AppState["offsetTop"]; @@ -33,43 +37,55 @@ export class Renderer { height: AppState["height"]; width: AppState["width"]; }): readonly NonDeletedExcalidrawElement[] => { - return elements.filter((element) => - isElementInViewport(element, width, height, { - zoom, - offsetLeft, - offsetTop, - scrollX, - scrollY, - }), - ); + const visibleElements: NonDeletedExcalidrawElement[] = []; + for (const element of elementsMap.values()) { + if ( + isElementInViewport(element, width, height, { + zoom, + offsetLeft, + offsetTop, + scrollX, + scrollY, + }) + ) { + visibleElements.push(element); + } + } + return visibleElements; }; - const getCanvasElements = ({ - editingElement, + const getRenderableElements = ({ elements, + editingElement, pendingImageElementId, }: { elements: readonly NonDeletedExcalidrawElement[]; editingElement: AppState["editingElement"]; pendingImageElementId: AppState["pendingImageElementId"]; }) => { - return elements.filter((element) => { + const elementsMap = toBrandedType(new Map()); + + for (const element of elements) { if (isImageElement(element)) { if ( // => not placed on canvas yet (but in elements array) pendingImageElementId === element.id ) { - return false; + continue; } } + // we don't want to render text element that's being currently edited // (it's rendered on remote only) - return ( + if ( !editingElement || editingElement.type !== "text" || element.id !== editingElement.id - ); - }); + ) { + elementsMap.set(element.id, element); + } + } + return elementsMap; }; return memoize( @@ -100,14 +116,14 @@ export class Renderer { }) => { const elements = this.scene.getNonDeletedElements(); - const canvasElements = getCanvasElements({ + const elementsMap = getRenderableElements({ elements, editingElement, pendingImageElementId, }); const visibleElements = getVisibleCanvasElements({ - elements: canvasElements, + elementsMap, zoom, offsetLeft, offsetTop, @@ -117,7 +133,7 @@ export class Renderer { width, }); - return { canvasElements, visibleElements }; + return { elementsMap, visibleElements }; }, ); })(); diff --git a/packages/excalidraw/scene/Scene.ts b/packages/excalidraw/scene/Scene.ts index 814638e7e..326f98c7f 100644 --- a/packages/excalidraw/scene/Scene.ts +++ b/packages/excalidraw/scene/Scene.ts @@ -3,14 +3,18 @@ import { NonDeletedExcalidrawElement, NonDeleted, ExcalidrawFrameLikeElement, + ElementsMapOrArray, + NonDeletedElementsMap, + SceneElementsMap, } from "../element/types"; -import { getNonDeletedElements, isNonDeletedElement } from "../element"; +import { isNonDeletedElement } from "../element"; import { LinearElementEditor } from "../element/linearElementEditor"; import { isFrameLikeElement } from "../element/typeChecks"; import { getSelectedElements } from "./selection"; import { AppState } from "../types"; import { Assert, SameType } from "../utility-types"; import { randomInteger } from "../random"; +import { toBrandedType } from "../utils"; type ElementIdKey = InstanceType["elementId"]; type ElementKey = ExcalidrawElement | ElementIdKey; @@ -20,6 +24,20 @@ type SceneStateCallbackRemover = () => void; type SelectionHash = string & { __brand: "selectionHash" }; +const getNonDeletedElements = ( + allElements: readonly T[], +) => { + const elementsMap = new Map() as NonDeletedElementsMap; + const elements: T[] = []; + for (const element of allElements) { + if (!element.isDeleted) { + elements.push(element as NonDeleted); + elementsMap.set(element.id, element as NonDeletedExcalidrawElement); + } + } + return { elementsMap, elements }; +}; + const hashSelectionOpts = ( opts: Parameters["getSelectedElements"]>[0], ) => { @@ -102,11 +120,13 @@ class Scene { private callbacks: Set = new Set(); private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = []; + private nonDeletedElementsMap: NonDeletedElementsMap = + new Map() as NonDeletedElementsMap; private elements: readonly ExcalidrawElement[] = []; private nonDeletedFramesLikes: readonly NonDeleted[] = []; private frames: readonly ExcalidrawFrameLikeElement[] = []; - private elementsMap = new Map(); + private elementsMap = toBrandedType(new Map()); private selectedElementsCache: { selectedElementIds: AppState["selectedElementIds"] | null; elements: readonly NonDeletedExcalidrawElement[] | null; @@ -118,6 +138,14 @@ class Scene { }; private versionNonce: number | undefined; + getElementsMapIncludingDeleted() { + return this.elementsMap; + } + + getNonDeletedElementsMap() { + return this.nonDeletedElementsMap; + } + getElementsIncludingDeleted() { return this.elements; } @@ -138,7 +166,7 @@ class Scene { * scene state. This in effect will likely result in cache-miss, and * the cache won't be updated in this case. */ - elements?: readonly ExcalidrawElement[]; + elements?: ElementsMapOrArray; // selection-related options includeBoundTextElement?: boolean; includeElementsInFrames?: boolean; @@ -227,23 +255,27 @@ class Scene { return didChange; } - replaceAllElements( - nextElements: readonly ExcalidrawElement[], - mapElementIds = true, - ) { - this.elements = nextElements; + replaceAllElements(nextElements: ElementsMapOrArray, mapElementIds = true) { + this.elements = + // ts doesn't like `Array.isArray` of `instanceof Map` + nextElements instanceof Array + ? nextElements + : Array.from(nextElements.values()); const nextFrameLikes: ExcalidrawFrameLikeElement[] = []; this.elementsMap.clear(); - nextElements.forEach((element) => { + this.elements.forEach((element) => { if (isFrameLikeElement(element)) { nextFrameLikes.push(element); } this.elementsMap.set(element.id, element); - Scene.mapElementToScene(element, this); + Scene.mapElementToScene(element, this, mapElementIds); }); - this.nonDeletedElements = getNonDeletedElements(this.elements); + const nonDeletedElements = getNonDeletedElements(this.elements); + this.nonDeletedElements = nonDeletedElements.elements; + this.nonDeletedElementsMap = nonDeletedElements.elementsMap; + this.frames = nextFrameLikes; - this.nonDeletedFramesLikes = getNonDeletedElements(this.frames); + this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements; this.informMutation(); } @@ -332,6 +364,22 @@ class Scene { getElementIndex(elementId: string) { return this.elements.findIndex((element) => element.id === elementId); } + + getContainerElement = ( + element: + | (ExcalidrawElement & { + containerId: ExcalidrawElement["id"] | null; + }) + | null, + ) => { + if (!element) { + return null; + } + if (element.containerId) { + return this.getElement(element.containerId) || null; + } + return null; + }; } export default Scene; diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index 9c357a21f..61ba8580d 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -11,7 +11,13 @@ import { getElementAbsoluteCoords, } from "../element/bounds"; import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene"; -import { cloneJSON, distance, getFontString } from "../utils"; +import { + arrayToMap, + cloneJSON, + distance, + getFontString, + toBrandedType, +} from "../utils"; import { AppState, BinaryFiles } from "../types"; import { DEFAULT_EXPORT_PADDING, @@ -37,6 +43,7 @@ import { Mutable } from "../utility-types"; import { newElementWith } from "../element/mutateElement"; import Scene from "./Scene"; import { isFrameElement, isFrameLikeElement } from "../element/typeChecks"; +import { RenderableElementsMap } from "./types"; const SVG_EXPORT_TAG = ``; @@ -59,7 +66,7 @@ const __createSceneForElementsHack__ = ( // ids to Scene instances so that we don't override the editor elements // mapping. // We still need to clone the objects themselves to regen references. - scene.replaceAllElements(cloneJSON(elements), false); + scene.replaceAllElements(cloneJSON(elements)); return scene; }; @@ -241,10 +248,14 @@ export const exportToCanvas = async ( files, }); + const elementsMap = toBrandedType( + arrayToMap(elementsForRender), + ); + renderStaticScene({ canvas, rc: rough.canvas(canvas), - elements: elementsForRender, + elementsMap, visibleElements: elementsForRender, scale, appState: { @@ -432,22 +443,29 @@ export const exportToSvg = async ( const renderEmbeddables = opts?.renderEmbeddables ?? false; - renderSceneToSvg(elementsForRender, rsvg, svgRoot, files || {}, { - offsetX, - offsetY, - isExporting: true, - exportWithDarkMode, - renderEmbeddables, - frameRendering, - canvasBackgroundColor: viewBackgroundColor, - embedsValidationStatus: renderEmbeddables - ? new Map( - elementsForRender - .filter((element) => isFrameLikeElement(element)) - .map((element) => [element.id, true]), - ) - : new Map(), - }); + renderSceneToSvg( + elementsForRender, + toBrandedType(arrayToMap(elementsForRender)), + rsvg, + svgRoot, + files || {}, + { + offsetX, + offsetY, + isExporting: true, + exportWithDarkMode, + renderEmbeddables, + frameRendering, + canvasBackgroundColor: viewBackgroundColor, + embedsValidationStatus: renderEmbeddables + ? new Map( + elementsForRender + .filter((element) => isFrameLikeElement(element)) + .map((element) => [element.id, true]), + ) + : new Map(), + }, + ); tempScene.destroy(); diff --git a/packages/excalidraw/scene/scrollbars.ts b/packages/excalidraw/scene/scrollbars.ts index 1d93f688f..14009588b 100644 --- a/packages/excalidraw/scene/scrollbars.ts +++ b/packages/excalidraw/scene/scrollbars.ts @@ -1,7 +1,6 @@ -import { ExcalidrawElement } from "../element/types"; import { getCommonBounds } from "../element"; import { InteractiveCanvasAppState } from "../types"; -import { ScrollBars } from "./types"; +import { RenderableElementsMap, ScrollBars } from "./types"; import { getGlobalCSSVariable } from "../utils"; import { getLanguage } from "../i18n"; @@ -10,12 +9,12 @@ export const SCROLLBAR_WIDTH = 6; export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; export const getScrollBars = ( - elements: readonly ExcalidrawElement[], + elements: RenderableElementsMap, viewportWidth: number, viewportHeight: number, appState: InteractiveCanvasAppState, ): ScrollBars => { - if (elements.length === 0) { + if (!elements.size) { return { horizontal: null, vertical: null, diff --git a/packages/excalidraw/scene/selection.ts b/packages/excalidraw/scene/selection.ts index 7a620155f..ae021f6aa 100644 --- a/packages/excalidraw/scene/selection.ts +++ b/packages/excalidraw/scene/selection.ts @@ -1,4 +1,5 @@ import { + ElementsMapOrArray, ExcalidrawElement, NonDeletedExcalidrawElement, } from "../element/types"; @@ -166,26 +167,28 @@ export const getCommonAttributeOfSelectedElements = ( }; export const getSelectedElements = ( - elements: readonly NonDeletedExcalidrawElement[], + elements: ElementsMapOrArray, appState: Pick, opts?: { includeBoundTextElement?: boolean; includeElementsInFrames?: boolean; }, ) => { - const selectedElements = elements.filter((element) => { + const selectedElements: ExcalidrawElement[] = []; + for (const element of elements.values()) { if (appState.selectedElementIds[element.id]) { - return element; + selectedElements.push(element); + continue; } if ( opts?.includeBoundTextElement && isBoundToContainer(element) && appState.selectedElementIds[element?.containerId] ) { - return element; + selectedElements.push(element); + continue; } - return null; - }); + } if (opts?.includeElementsInFrames) { const elementsToInclude: ExcalidrawElement[] = []; @@ -205,7 +208,7 @@ export const getSelectedElements = ( }; export const getTargetElements = ( - elements: readonly NonDeletedExcalidrawElement[], + elements: ElementsMapOrArray, appState: Pick, ) => appState.editingElement diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index 57a52fbd4..957b080b3 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -2,6 +2,7 @@ import type { RoughCanvas } from "roughjs/bin/canvas"; import { Drawable } from "roughjs/bin/core"; import { ExcalidrawTextElement, + NonDeletedElementsMap, NonDeletedExcalidrawElement, } from "../element/types"; import { @@ -12,6 +13,10 @@ import { InteractiveCanvasAppState, StaticCanvasAppState, } from "../types"; +import { MakeBrand } from "../utility-types"; + +export type RenderableElementsMap = NonDeletedElementsMap & + MakeBrand<"RenderableElementsMap">; export type StaticCanvasRenderConfig = { canvasBackgroundColor: AppState["viewBackgroundColor"]; @@ -53,14 +58,14 @@ export type InteractiveCanvasRenderConfig = { export type RenderInteractiveSceneCallback = { atLeastOneVisibleElement: boolean; - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: RenderableElementsMap; scrollBars?: ScrollBars; }; export type StaticSceneRenderConfig = { canvas: HTMLCanvasElement; rc: RoughCanvas; - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: RenderableElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; scale: number; appState: StaticCanvasAppState; @@ -69,7 +74,7 @@ export type StaticSceneRenderConfig = { export type InteractiveSceneRenderConfig = { canvas: HTMLCanvasElement | null; - elements: readonly NonDeletedExcalidrawElement[]; + elementsMap: RenderableElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; selectedElements: readonly NonDeletedExcalidrawElement[]; scale: number; diff --git a/packages/excalidraw/utility-types.ts b/packages/excalidraw/utility-types.ts index 860d818ef..576769634 100644 --- a/packages/excalidraw/utility-types.ts +++ b/packages/excalidraw/utility-types.ts @@ -54,3 +54,11 @@ export type Assert = T; export type NestedKeyOf = K extends keyof T & (string | number) ? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf}` : never) : never; + +export type SetLike = Set | T[]; +export type ReadonlySetLike = ReadonlySet | readonly T[]; + +export type MakeBrand = { + /** @private using ~ to sort last in intellisense */ + [K in `~brand~${T}`]: T; +}; diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 4630c5bce..47fa52311 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -650,8 +650,11 @@ export const getUpdatedTimestamp = () => (isTestEnv() ? 1 : Date.now()); * or array of ids (strings), into a Map, keyd by `id`. */ export const arrayToMap = ( - items: readonly T[], + items: readonly T[] | Map, ) => { + if (items instanceof Map) { + return items; + } return items.reduce((acc: Map, element) => { acc.set(typeof element === "string" ? element : element.id, element); return acc; @@ -1050,3 +1053,40 @@ export function getSvgPathFromStroke(points: number[][], closed = true) { export const normalizeEOL = (str: string) => { return str.replace(/\r?\n|\r/g, "\n"); }; + +// ----------------------------------------------------------------------------- +type HasBrand = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + [K in keyof T]: K extends `~brand${infer _}` ? true : never; +}[keyof T]; + +type RemoveAllBrands = HasBrand extends true + ? { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + [K in keyof T as K extends `~brand~${infer _}` ? never : K]: T[K]; + } + : never; + +// adapted from https://github.com/colinhacks/zod/discussions/1994#discussioncomment-6068940 +// currently does not cover all types (e.g. tuples, promises...) +type Unbrand = T extends Map + ? Map + : T extends Set + ? Set + : T extends Array + ? Array + : RemoveAllBrands; + +/** + * Makes type into a branded type, ensuring that value is assignable to + * the base ubranded type. Optionally you can explicitly supply current value + * type to combine both (useful for composite branded types. Make sure you + * compose branded types which are not composite themselves.) + */ +export const toBrandedType = ( + value: Unbrand, +) => { + return value as CurrentType & BrandedType; +}; + +// ----------------------------------------------------------------------------- From c6fdac131b06c2d542a7068d1798f8ac83a41cfd Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 22 Jan 2024 17:01:00 +0530 Subject: [PATCH 55/79] ci: add the workspace ignore check to install actions as dependency for auto release (#7593) --- .github/workflows/autorelease-excalidraw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autorelease-excalidraw.yml b/.github/workflows/autorelease-excalidraw.yml index 4eaeb11f1..5ff5690eb 100644 --- a/.github/workflows/autorelease-excalidraw.yml +++ b/.github/workflows/autorelease-excalidraw.yml @@ -23,5 +23,5 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Auto release run: | - yarn add @actions/core + yarn add @actions/core -W yarn autorelease From 89bd6181f29c783a59ad7943bb2a85f829dd9d49 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:23:00 +0100 Subject: [PATCH 56/79] fix: revert `mapElementIds` flag removal (#7594) --- packages/excalidraw/scene/export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index 61ba8580d..9f1f12a22 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -66,7 +66,7 @@ const __createSceneForElementsHack__ = ( // ids to Scene instances so that we don't override the editor elements // mapping. // We still need to clone the objects themselves to regen references. - scene.replaceAllElements(cloneJSON(elements)); + scene.replaceAllElements(cloneJSON(elements), false); return scene; }; From f3f82171252af7407fe1e8ab6790a72bbbd14477 Mon Sep 17 00:00:00 2001 From: halocean96 <146062795+halocean96@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:50:51 +0900 Subject: [PATCH 57/79] docs: toggleSidebar api fix (#7575) --- .../docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx index c27e96146..ffff19fb0 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx @@ -37,7 +37,7 @@ You can use this prop when you want to access some [Excalidraw APIs](https://git | [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool | | [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas | | [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas | -| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off | +| [toggleSidebar](#toggleSidebar) | `function` | Toggles specific sidebar on/off | | [onChange](#onChange) | `function` | Subscribes to change events | | [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events | | [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events | From 4f0a2a9593520111614bf84d95c4f35b97e82453 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 24 Jan 2024 17:07:54 +0530 Subject: [PATCH 58/79] docs: add next js with app router example (#7552) * move the existing example to with-script-in-browser * Add example with next js app router * disable ssr for excalidraw client comp * typo * update output dir * don't include nextjs example in tsconfig * remove meta.json * lint * remove example.ts * port * move the examples outside packages and use the deps as workspaces in examples * update gitignore * fix example * update path of build dir * fix * fix scripts * try local path * fix * update commands * fix * fix * fix script * skip ts * disable ts * add vercel.json * install * update tsconfig * fix lint * remove console.log * lets see if this works * revert * remove ts nocheck * add types and some utils in nextjs example * fix types * updatw example and remove nextjs dynamic syntax so we don't import excal twice * move both examples to workspaces and create generic example to be used by browser and next js both * copy the static assets to nextjs * fix ts config * render custom menu items * fix custom footer * fix types in browser example * use regular imports for importing excal and import it using dynamic next js in app router instead * Add example for pages router * fix css discrepancies * fix css * configure output dir * fix * fix css * rename to with-nextjs * move components to examples/excalidraw/components --- .gitignore | 1 + .../excalidraw/components}/App.scss | 21 +- .../excalidraw/components}/App.tsx | 301 +++++++++-------- .../excalidraw/components}/CustomFooter.tsx | 27 +- .../excalidraw/components/MobileFooter.tsx | 27 ++ .../components}/sidebar/ExampleSidebar.scss | 0 .../components}/sidebar/ExampleSidebar.tsx | 5 +- .../excalidraw}/initialData.tsx | 4 +- examples/excalidraw/package.json | 13 + examples/excalidraw/tsconfig.json | 3 + examples/excalidraw/utils.ts | 146 ++++++++ examples/excalidraw/with-nextjs/.gitignore | 36 ++ examples/excalidraw/with-nextjs/README.md | 36 ++ .../excalidraw/with-nextjs/next.config.js | 12 + examples/excalidraw/with-nextjs/package.json | 25 ++ .../with-nextjs}/public/images/doremon.png | Bin .../with-nextjs}/public/images/excalibot.png | Bin .../with-nextjs}/public/images/pika.jpeg | Bin .../with-nextjs}/public/images/rocket.jpeg | Bin .../with-nextjs/src/app/favicon.ico | Bin 0 -> 25931 bytes .../excalidraw/with-nextjs/src/app/layout.tsx | 11 + .../excalidraw/with-nextjs/src/app/page.tsx | 23 ++ .../excalidraw/with-nextjs/src/common.scss | 15 + .../with-nextjs/src/excalidrawWrapper.tsx | 22 ++ .../src/pages/excalidraw-in-pages.tsx | 22 ++ examples/excalidraw/with-nextjs/tsconfig.json | 28 ++ examples/excalidraw/with-nextjs/vercel.json | 3 + examples/excalidraw/with-nextjs/yarn.lock | 252 ++++++++++++++ .../with-script-in-browser}/index.html | 10 +- .../with-script-in-browser/index.tsx | 28 ++ .../with-script-in-browser/package.json | 19 ++ .../public/images/doremon.png | Bin 0 -> 201946 bytes .../public/images/excalibot.png | Bin 0 -> 30330 bytes .../public/images/pika.jpeg | Bin 0 -> 6250 bytes .../public/images/rocket.jpeg | Bin 0 -> 40368 bytes .../with-script-in-browser}/vercel.json | 2 +- .../with-script-in-browser/vite.config.mts | 11 + examples/excalidraw/yarn.lock | 313 ++++++++++++++++++ package.json | 4 +- packages/excalidraw/.gitignore | 2 - packages/excalidraw/components/App.tsx | 5 +- packages/excalidraw/constants.ts | 1 - packages/excalidraw/example/MobileFooter.tsx | 20 -- packages/excalidraw/example/index.tsx | 17 - packages/excalidraw/index.tsx | 9 +- packages/excalidraw/renderer/renderScene.ts | 1 - packages/excalidraw/tsconfig.json | 2 +- packages/excalidraw/vite.config.mts | 15 - scripts/buildExample.mjs | 7 +- tsconfig.json | 2 +- yarn.lock | 159 ++++++++- 51 files changed, 1431 insertions(+), 229 deletions(-) rename {packages/excalidraw/example => examples/excalidraw/components}/App.scss (83%) rename {packages/excalidraw/example => examples/excalidraw/components}/App.tsx (83%) rename {packages/excalidraw/example => examples/excalidraw/components}/CustomFooter.tsx (79%) create mode 100644 examples/excalidraw/components/MobileFooter.tsx rename {packages/excalidraw/example => examples/excalidraw/components}/sidebar/ExampleSidebar.scss (100%) rename {packages/excalidraw/example => examples/excalidraw/components}/sidebar/ExampleSidebar.tsx (90%) rename {packages/excalidraw/example => examples/excalidraw}/initialData.tsx (99%) create mode 100644 examples/excalidraw/package.json create mode 100644 examples/excalidraw/tsconfig.json create mode 100644 examples/excalidraw/utils.ts create mode 100644 examples/excalidraw/with-nextjs/.gitignore create mode 100644 examples/excalidraw/with-nextjs/README.md create mode 100644 examples/excalidraw/with-nextjs/next.config.js create mode 100644 examples/excalidraw/with-nextjs/package.json rename {packages/excalidraw/example => examples/excalidraw/with-nextjs}/public/images/doremon.png (100%) rename {packages/excalidraw/example => examples/excalidraw/with-nextjs}/public/images/excalibot.png (100%) rename {packages/excalidraw/example => examples/excalidraw/with-nextjs}/public/images/pika.jpeg (100%) rename {packages/excalidraw/example => examples/excalidraw/with-nextjs}/public/images/rocket.jpeg (100%) create mode 100644 examples/excalidraw/with-nextjs/src/app/favicon.ico create mode 100644 examples/excalidraw/with-nextjs/src/app/layout.tsx create mode 100644 examples/excalidraw/with-nextjs/src/app/page.tsx create mode 100644 examples/excalidraw/with-nextjs/src/common.scss create mode 100644 examples/excalidraw/with-nextjs/src/excalidrawWrapper.tsx create mode 100644 examples/excalidraw/with-nextjs/src/pages/excalidraw-in-pages.tsx create mode 100644 examples/excalidraw/with-nextjs/tsconfig.json create mode 100644 examples/excalidraw/with-nextjs/vercel.json create mode 100644 examples/excalidraw/with-nextjs/yarn.lock rename {packages/excalidraw/example/public => examples/excalidraw/with-script-in-browser}/index.html (67%) create mode 100644 examples/excalidraw/with-script-in-browser/index.tsx create mode 100644 examples/excalidraw/with-script-in-browser/package.json create mode 100644 examples/excalidraw/with-script-in-browser/public/images/doremon.png create mode 100644 examples/excalidraw/with-script-in-browser/public/images/excalibot.png create mode 100644 examples/excalidraw/with-script-in-browser/public/images/pika.jpeg create mode 100644 examples/excalidraw/with-script-in-browser/public/images/rocket.jpeg rename {packages/excalidraw => examples/excalidraw/with-script-in-browser}/vercel.json (50%) create mode 100644 examples/excalidraw/with-script-in-browser/vite.config.mts create mode 100644 examples/excalidraw/yarn.lock delete mode 100644 packages/excalidraw/example/MobileFooter.tsx delete mode 100644 packages/excalidraw/example/index.tsx delete mode 100644 packages/excalidraw/vite.config.mts diff --git a/.gitignore b/.gitignore index 17e3e7dcf..21d2730a2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ packages/excalidraw/types coverage dev-dist html +examples/**/bundle.* \ No newline at end of file diff --git a/packages/excalidraw/example/App.scss b/examples/excalidraw/components/App.scss similarity index 83% rename from packages/excalidraw/example/App.scss rename to examples/excalidraw/components/App.scss index 7f37540d8..e41a77ccc 100644 --- a/packages/excalidraw/example/App.scss +++ b/examples/excalidraw/components/App.scss @@ -15,14 +15,23 @@ border-radius: 50%; } } + .app-title { + margin-block-start: 0.83em; + margin-block-end: 0.83em; + } } -.button-wrapper button { - z-index: 1; - height: 40px; - max-width: 200px; - margin: 10px; - padding: 5px; +.button-wrapper { + input[type="checkbox"] { + margin: 5px; + } + button { + z-index: 1; + height: 40px; + max-width: 200px; + margin: 10px; + padding: 5px; + } } .excalidraw .App-menu_top .buttonList { diff --git a/packages/excalidraw/example/App.tsx b/examples/excalidraw/components/App.tsx similarity index 83% rename from packages/excalidraw/example/App.tsx rename to examples/excalidraw/components/App.tsx index 50dc5b9a3..eea0da6ca 100644 --- a/packages/excalidraw/example/App.tsx +++ b/examples/excalidraw/components/App.tsx @@ -1,15 +1,30 @@ +import React, { + useEffect, + useState, + useRef, + useCallback, + Children, + cloneElement, +} from "react"; import ExampleSidebar from "./sidebar/ExampleSidebar"; -import type * as TExcalidraw from "../index"; +import type * as TExcalidraw from "@excalidraw/excalidraw"; -import "./App.scss"; -import initialData from "./initialData"; import { nanoid } from "nanoid"; -import { resolvablePromise, ResolvablePromise } from "../utils"; -import { EVENT, ROUNDNESS } from "../constants"; -import { distance2d } from "../math"; -import { fileOpen } from "../data/filesystem"; -import { loadSceneOrLibraryFromBlob } from "../../utils"; + +import { + resolvablePromise, + ResolvablePromise, + distance2d, + fileOpen, + withBatchedUpdates, + withBatchedUpdatesThrottled, +} from "../utils"; + +import CustomFooter from "./CustomFooter"; +import MobileFooter from "./MobileFooter"; +import initialData from "../initialData"; + import type { AppState, BinaryFileData, @@ -18,19 +33,14 @@ import type { Gesture, LibraryItems, PointerDownState as ExcalidrawPointerDownState, -} from "../types"; -import type { NonDeletedExcalidrawElement, Theme } from "../element/types"; -import { ImportedLibraryData } from "../data/types"; -import CustomFooter from "./CustomFooter"; -import MobileFooter from "./MobileFooter"; -import { KEYS } from "../keys"; -import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; +} from "@excalidraw/excalidraw/dist/excalidraw/types"; +import type { + NonDeletedExcalidrawElement, + Theme, +} from "@excalidraw/excalidraw/dist/excalidraw/element/types"; +import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types"; -declare global { - interface Window { - ExcalidrawLib: typeof TExcalidraw; - } -} +import "./App.scss"; type Comment = { x: number; @@ -51,31 +61,6 @@ type PointerDownState = { }; }; -const { useEffect, useState, useRef, useCallback } = window.React; - -// This is so that we use the bundled excalidraw.development.js file instead -// of the actual source code -const { - exportToCanvas, - exportToSvg, - exportToBlob, - exportToClipboard, - Excalidraw, - useHandleLibrary, - MIME_TYPES, - sceneCoordsToViewportCoords, - viewportCoordsToSceneCoords, - restoreElements, - Sidebar, - Footer, - WelcomeScreen, - MainMenu, - LiveCollaborationTrigger, - convertToExcalidrawElements, - TTDDialog, - TTDDialogTrigger, -} = window.ExcalidrawLib; - const COMMENT_ICON_DIMENSION = 32; const COMMENT_INPUT_HEIGHT = 50; const COMMENT_INPUT_WIDTH = 150; @@ -84,8 +69,38 @@ export interface AppProps { appTitle: string; useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void; customArgs?: any[]; + children: React.ReactNode; + excalidrawLib: typeof TExcalidraw; } -export default function App({ appTitle, useCustom, customArgs }: AppProps) { + +export default function App({ + appTitle, + useCustom, + customArgs, + children, + excalidrawLib, +}: AppProps) { + const { + exportToCanvas, + exportToSvg, + exportToBlob, + exportToClipboard, + useHandleLibrary, + MIME_TYPES, + sceneCoordsToViewportCoords, + viewportCoordsToSceneCoords, + restoreElements, + Sidebar, + Footer, + WelcomeScreen, + MainMenu, + LiveCollaborationTrigger, + convertToExcalidrawElements, + TTDDialog, + TTDDialogTrigger, + ROUNDNESS, + loadSceneOrLibraryFromBlob, + } = excalidrawLib; const appRef = useRef(null); const [viewModeEnabled, setViewModeEnabled] = useState(false); const [zenModeEnabled, setZenModeEnabled] = useState(false); @@ -147,8 +162,105 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { }; }; fetchData(); - }, [excalidrawAPI]); + }, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]); + const renderExcalidraw = (children: React.ReactNode) => { + const Excalidraw: any = Children.toArray(children).find( + (child) => + React.isValidElement(child) && + typeof child.type !== "string" && + //@ts-ignore + child.type.displayName === "Excalidraw", + ); + if (!Excalidraw) { + return; + } + const newElement = cloneElement( + Excalidraw, + { + excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api), + initialData: initialStatePromiseRef.current.promise, + onChange: ( + elements: NonDeletedExcalidrawElement[], + state: AppState, + ) => { + console.info("Elements :", elements, "State : ", state); + }, + onPointerUpdate: (payload: { + pointer: { x: number; y: number }; + button: "down" | "up"; + pointersMap: Gesture["pointers"]; + }) => setPointerData(payload), + viewModeEnabled, + zenModeEnabled, + gridModeEnabled, + theme, + name: "Custom name of drawing", + UIOptions: { + canvasActions: { + loadScene: false, + }, + tools: { image: !disableImageTool }, + }, + renderTopRightUI, + onLinkOpen, + onPointerDown, + onScrollChange: rerenderCommentIcons, + validateEmbeddable: true, + }, + <> + {excalidrawAPI && ( +
    + +
    + )} + + + + + Tab one! + Tab two! + + One + Two + + + + + Toggle Custom Sidebar + + {renderMenu()} + {excalidrawAPI && ( + 😀}> + Text to diagram + + )} + { + console.info("submit"); + // sleep for 2s + await new Promise((resolve) => setTimeout(resolve, 2000)); + throw new Error("error, go away now"); + // return "dummy"; + }} + /> + , + ); + return newElement; + }; const renderTopRightUI = (isMobile: boolean) => { return ( <> @@ -332,8 +444,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { pointerDownState: PointerDownState, ) => { return withBatchedUpdates((event) => { - window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove); - window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp); + window.removeEventListener("pointermove", pointerDownState.onMove); + window.removeEventListener("pointerup", pointerDownState.onUp); excalidrawAPI?.setActiveTool({ type: "selection" }); const distance = distance2d( pointerDownState.x, @@ -397,8 +509,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { onPointerMoveFromPointerDownHandler(pointerDownState); const onPointerUp = onPointerUpFromPointerDownHandler(pointerDownState); - window.addEventListener(EVENT.POINTER_MOVE, onPointerMove); - window.addEventListener(EVENT.POINTER_UP, onPointerUp); + window.addEventListener("pointermove", onPointerMove); + window.addEventListener("pointerup", onPointerUp); pointerDownState.onMove = onPointerMove; pointerDownState.onUp = onPointerUp; @@ -490,7 +602,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { }} onBlur={saveComment} onKeyDown={(event) => { - if (!event.shiftKey && event.key === KEYS.ENTER) { + if (!event.shiftKey && event.key === "Enter") { event.preventDefault(); saveComment(); } @@ -523,7 +635,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) { - {excalidrawAPI && } + {excalidrawAPI && ( + + )} ); }; @@ -672,83 +789,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
  • - - setExcalidrawAPI(api) - } - initialData={initialStatePromiseRef.current.promise} - onChange={(elements, state) => { - // console.info("Elements :", elements, "State : ", state); - }} - onPointerUpdate={(payload: { - pointer: { x: number; y: number }; - button: "down" | "up"; - pointersMap: Gesture["pointers"]; - }) => setPointerData(payload)} - viewModeEnabled={viewModeEnabled} - zenModeEnabled={zenModeEnabled} - gridModeEnabled={gridModeEnabled} - theme={theme} - name="Custom name of drawing" - UIOptions={{ - canvasActions: { - loadScene: false, - }, - tools: { image: !disableImageTool }, - }} - renderTopRightUI={renderTopRightUI} - onLinkOpen={onLinkOpen} - onPointerDown={onPointerDown} - onScrollChange={rerenderCommentIcons} - // allow all urls - validateEmbeddable={true} - > - {excalidrawAPI && ( -
    - -
    - )} - - - - - Tab one! - Tab two! - - One - Two - - - - - Toggle Custom Sidebar - - {renderMenu()} - {excalidrawAPI && ( - 😀}> - Text to diagram - - )} - { - console.info("submit"); - // sleep for 2s - await new Promise((resolve) => setTimeout(resolve, 2000)); - throw new Error("error, go away now"); - // return "dummy"; - }} - /> -
    + {renderExcalidraw(children)} {Object.keys(commentIcons || []).length > 0 && renderCommentIcons()} {comment && renderComment()}
    diff --git a/packages/excalidraw/example/CustomFooter.tsx b/examples/excalidraw/components/CustomFooter.tsx similarity index 79% rename from packages/excalidraw/example/CustomFooter.tsx rename to examples/excalidraw/components/CustomFooter.tsx index c4ff5b642..30d51ecf0 100644 --- a/packages/excalidraw/example/CustomFooter.tsx +++ b/examples/excalidraw/components/CustomFooter.tsx @@ -1,6 +1,6 @@ -import type { ExcalidrawImperativeAPI } from "../types"; +import type * as TExcalidraw from "@excalidraw/excalidraw"; +import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types"; -const { Button, MIME_TYPES } = window.ExcalidrawLib; const COMMENT_SVG = ( ); + const CustomFooter = ({ excalidrawAPI, + excalidrawLib, }: { excalidrawAPI: ExcalidrawImperativeAPI; + excalidrawLib: typeof TExcalidraw; }) => { + const { Button, MIME_TYPES } = excalidrawLib; + return ( <> - - + ); }; diff --git a/examples/excalidraw/components/MobileFooter.tsx b/examples/excalidraw/components/MobileFooter.tsx new file mode 100644 index 000000000..7ab62b918 --- /dev/null +++ b/examples/excalidraw/components/MobileFooter.tsx @@ -0,0 +1,27 @@ +import { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types"; +import CustomFooter from "./CustomFooter"; +import type * as TExcalidraw from "@excalidraw/excalidraw"; + +const MobileFooter = ({ + excalidrawAPI, + excalidrawLib, +}: { + excalidrawAPI: ExcalidrawImperativeAPI; + excalidrawLib: typeof TExcalidraw; +}) => { + const { useDevice, Footer } = excalidrawLib; + + const device = useDevice(); + if (device.editor.isMobile) { + return ( +
    + +
    + ); + } + return null; +}; +export default MobileFooter; diff --git a/packages/excalidraw/example/sidebar/ExampleSidebar.scss b/examples/excalidraw/components/sidebar/ExampleSidebar.scss similarity index 100% rename from packages/excalidraw/example/sidebar/ExampleSidebar.scss rename to examples/excalidraw/components/sidebar/ExampleSidebar.scss diff --git a/packages/excalidraw/example/sidebar/ExampleSidebar.tsx b/examples/excalidraw/components/sidebar/ExampleSidebar.tsx similarity index 90% rename from packages/excalidraw/example/sidebar/ExampleSidebar.tsx rename to examples/excalidraw/components/sidebar/ExampleSidebar.tsx index a6e1b6475..8b475f16f 100644 --- a/packages/excalidraw/example/sidebar/ExampleSidebar.tsx +++ b/examples/excalidraw/components/sidebar/ExampleSidebar.tsx @@ -1,9 +1,8 @@ +import { useState } from "react"; import "./ExampleSidebar.scss"; -const React = window.React; - export default function Sidebar({ children }: { children: React.ReactNode }) { - const [open, setOpen] = React.useState(false); + const [open, setOpen] = useState(false); return ( <> diff --git a/packages/excalidraw/example/initialData.tsx b/examples/excalidraw/initialData.tsx similarity index 99% rename from packages/excalidraw/example/initialData.tsx rename to examples/excalidraw/initialData.tsx index 0299e4959..3cb5e7af4 100644 --- a/packages/excalidraw/example/initialData.tsx +++ b/examples/excalidraw/initialData.tsx @@ -1,5 +1,5 @@ -import type { ExcalidrawElementSkeleton } from "../data/transform"; -import type { FileId } from "../element/types"; +import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/data/transform"; +import type { FileId } from "@excalidraw/excalidraw/element/types"; const elements: ExcalidrawElementSkeleton[] = [ { diff --git a/examples/excalidraw/package.json b/examples/excalidraw/package.json new file mode 100644 index 000000000..fe48d5532 --- /dev/null +++ b/examples/excalidraw/package.json @@ -0,0 +1,13 @@ +{ + "name": "examples", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "18.2.0", + "react-dom": "18.2.0", + "@excalidraw/excalidraw": "*" + }, + "devDependencies": { + "typescript": "^5" + } +} diff --git a/examples/excalidraw/tsconfig.json b/examples/excalidraw/tsconfig.json new file mode 100644 index 000000000..41716a7dd --- /dev/null +++ b/examples/excalidraw/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig" +} diff --git a/examples/excalidraw/utils.ts b/examples/excalidraw/utils.ts new file mode 100644 index 000000000..822be29b7 --- /dev/null +++ b/examples/excalidraw/utils.ts @@ -0,0 +1,146 @@ +import { unstable_batchedUpdates } from "react-dom"; +import { fileOpen as _fileOpen } from "browser-fs-access"; +import type { MIME_TYPES } from "@excalidraw/excalidraw"; +import { AbortError } from "../../packages/excalidraw/errors"; + +type FILE_EXTENSION = Exclude; + +const INPUT_CHANGE_INTERVAL_MS = 500; + +export type ResolvablePromise = Promise & { + resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void; + reject: (error: Error) => void; +}; +export const resolvablePromise = () => { + let resolve!: any; + let reject!: any; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + (promise as any).resolve = resolve; + (promise as any).reject = reject; + return promise as ResolvablePromise; +}; + +export const distance2d = (x1: number, y1: number, x2: number, y2: number) => { + const xd = x2 - x1; + const yd = y2 - y1; + return Math.hypot(xd, yd); +}; + +export const fileOpen = (opts: { + extensions?: FILE_EXTENSION[]; + description: string; + multiple?: M; +}): Promise => { + // an unsafe TS hack, alas not much we can do AFAIK + type RetType = M extends false | undefined ? File : File[]; + + const mimeTypes = opts.extensions?.reduce((mimeTypes, type) => { + mimeTypes.push(MIME_TYPES[type]); + + return mimeTypes; + }, [] as string[]); + + const extensions = opts.extensions?.reduce((acc, ext) => { + if (ext === "jpg") { + return acc.concat(".jpg", ".jpeg"); + } + return acc.concat(`.${ext}`); + }, [] as string[]); + + return _fileOpen({ + description: opts.description, + extensions, + mimeTypes, + multiple: opts.multiple ?? false, + legacySetup: (resolve, reject, input) => { + const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS); + const focusHandler = () => { + checkForFile(); + document.addEventListener("keyup", scheduleRejection); + document.addEventListener("pointerup", scheduleRejection); + scheduleRejection(); + }; + const checkForFile = () => { + // this hack might not work when expecting multiple files + if (input.files?.length) { + const ret = opts.multiple ? [...input.files] : input.files[0]; + resolve(ret as RetType); + } + }; + requestAnimationFrame(() => { + window.addEventListener("focus", focusHandler); + }); + const interval = window.setInterval(() => { + checkForFile(); + }, INPUT_CHANGE_INTERVAL_MS); + return (rejectPromise) => { + clearInterval(interval); + scheduleRejection.cancel(); + window.removeEventListener("focus", focusHandler); + document.removeEventListener("keyup", scheduleRejection); + document.removeEventListener("pointerup", scheduleRejection); + if (rejectPromise) { + // so that something is shown in console if we need to debug this + console.warn("Opening the file was canceled (legacy-fs)."); + rejectPromise(new AbortError()); + } + }; + }, + }) as Promise; +}; + +export const debounce = ( + fn: (...args: T) => void, + timeout: number, +) => { + let handle = 0; + let lastArgs: T | null = null; + const ret = (...args: T) => { + lastArgs = args; + clearTimeout(handle); + handle = window.setTimeout(() => { + lastArgs = null; + fn(...args); + }, timeout); + }; + ret.flush = () => { + clearTimeout(handle); + if (lastArgs) { + const _lastArgs = lastArgs; + lastArgs = null; + fn(..._lastArgs); + } + }; + ret.cancel = () => { + lastArgs = null; + clearTimeout(handle); + }; + return ret; +}; + +export const withBatchedUpdates = < + TFunction extends ((event: any) => void) | (() => void), +>( + func: Parameters["length"] extends 0 | 1 ? TFunction : never, +) => + ((event) => { + unstable_batchedUpdates(func as TFunction, event); + }) as TFunction; + +/** + * barches React state updates and throttles the calls to a single call per + * animation frame + */ +export const withBatchedUpdatesThrottled = < + TFunction extends ((event: any) => void) | (() => void), +>( + func: Parameters["length"] extends 0 | 1 ? TFunction : never, +) => { + // @ts-ignore + return throttleRAF>(((event) => { + unstable_batchedUpdates(func, event); + }) as TFunction); +}; diff --git a/examples/excalidraw/with-nextjs/.gitignore b/examples/excalidraw/with-nextjs/.gitignore new file mode 100644 index 000000000..fd3dbb571 --- /dev/null +++ b/examples/excalidraw/with-nextjs/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/excalidraw/with-nextjs/README.md b/examples/excalidraw/with-nextjs/README.md new file mode 100644 index 000000000..9e8d9b96d --- /dev/null +++ b/examples/excalidraw/with-nextjs/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3005) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/examples/excalidraw/with-nextjs/next.config.js b/examples/excalidraw/with-nextjs/next.config.js new file mode 100644 index 000000000..701438ebf --- /dev/null +++ b/examples/excalidraw/with-nextjs/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + distDir: "build", + typescript: { + // The ts config doesn't work with `jsx: preserve" and if updated to `react-jsx` it gets ovewritten by next js throwing ts errors hence I am ignoring build errors until this is fixed. + ignoreBuildErrors: true, + }, + // This is needed as in pages router the code for importing types throws error as its outside next js app + transpilePackages: ["../"], +}; + +module.exports = nextConfig; diff --git a/examples/excalidraw/with-nextjs/package.json b/examples/excalidraw/with-nextjs/package.json new file mode 100644 index 000000000..177952407 --- /dev/null +++ b/examples/excalidraw/with-nextjs/package.json @@ -0,0 +1,25 @@ +{ + "name": "with-nextjs", + "version": "0.1.0", + "private": true, + "scripts": { + "build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm", + "dev": "yarn build:workspace && next dev -p 3005", + "build": "yarn build:workspace && next build", + "start": "next start -p 3006", + "lint": "next lint" + }, + "dependencies": { + "@excalidraw/excalidraw": "*", + "next": "14.1", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "path2d-polyfill": "2.0.1", + "typescript": "^5" + } +} diff --git a/packages/excalidraw/example/public/images/doremon.png b/examples/excalidraw/with-nextjs/public/images/doremon.png similarity index 100% rename from packages/excalidraw/example/public/images/doremon.png rename to examples/excalidraw/with-nextjs/public/images/doremon.png diff --git a/packages/excalidraw/example/public/images/excalibot.png b/examples/excalidraw/with-nextjs/public/images/excalibot.png similarity index 100% rename from packages/excalidraw/example/public/images/excalibot.png rename to examples/excalidraw/with-nextjs/public/images/excalibot.png diff --git a/packages/excalidraw/example/public/images/pika.jpeg b/examples/excalidraw/with-nextjs/public/images/pika.jpeg similarity index 100% rename from packages/excalidraw/example/public/images/pika.jpeg rename to examples/excalidraw/with-nextjs/public/images/pika.jpeg diff --git a/packages/excalidraw/example/public/images/rocket.jpeg b/examples/excalidraw/with-nextjs/public/images/rocket.jpeg similarity index 100% rename from packages/excalidraw/example/public/images/rocket.jpeg rename to examples/excalidraw/with-nextjs/public/images/rocket.jpeg diff --git a/examples/excalidraw/with-nextjs/src/app/favicon.ico b/examples/excalidraw/with-nextjs/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/examples/excalidraw/with-nextjs/src/app/layout.tsx b/examples/excalidraw/with-nextjs/src/app/layout.tsx new file mode 100644 index 000000000..225b6038d --- /dev/null +++ b/examples/excalidraw/with-nextjs/src/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/examples/excalidraw/with-nextjs/src/app/page.tsx b/examples/excalidraw/with-nextjs/src/app/page.tsx new file mode 100644 index 000000000..bc8c98fcf --- /dev/null +++ b/examples/excalidraw/with-nextjs/src/app/page.tsx @@ -0,0 +1,23 @@ +import dynamic from "next/dynamic"; +import "../common.scss"; + +// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically +// with ssr false +const ExcalidrawWithClientOnly = dynamic( + async () => (await import("../excalidrawWrapper")).default, + { + ssr: false, + }, +); + +export default function Page() { + return ( + <> + Switch to Pages router +

    App Router

    + + {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */} + + + ); +} diff --git a/examples/excalidraw/with-nextjs/src/common.scss b/examples/excalidraw/with-nextjs/src/common.scss new file mode 100644 index 000000000..1a77600a9 --- /dev/null +++ b/examples/excalidraw/with-nextjs/src/common.scss @@ -0,0 +1,15 @@ +* { + box-sizing: border-box; + font-family: sans-serif; +} + +a { + color: #1c7ed6; + font-size: 20px; + text-decoration: none; + font-weight: 550; +} + +.page-title { + text-align: center; +} diff --git a/examples/excalidraw/with-nextjs/src/excalidrawWrapper.tsx b/examples/excalidraw/with-nextjs/src/excalidrawWrapper.tsx new file mode 100644 index 000000000..40af9f0cc --- /dev/null +++ b/examples/excalidraw/with-nextjs/src/excalidrawWrapper.tsx @@ -0,0 +1,22 @@ +"use client"; +import * as excalidrawLib from "@excalidraw/excalidraw"; +import { Excalidraw } from "@excalidraw/excalidraw"; +import App from "../../components/App"; + +import "@excalidraw/excalidraw/index.css"; + +const ExcalidrawWrapper: React.FC = () => { + return ( + <> + {}} + excalidrawLib={excalidrawLib} + > + + + + ); +}; + +export default ExcalidrawWrapper; diff --git a/examples/excalidraw/with-nextjs/src/pages/excalidraw-in-pages.tsx b/examples/excalidraw/with-nextjs/src/pages/excalidraw-in-pages.tsx new file mode 100644 index 000000000..527a346b9 --- /dev/null +++ b/examples/excalidraw/with-nextjs/src/pages/excalidraw-in-pages.tsx @@ -0,0 +1,22 @@ +import dynamic from "next/dynamic"; +import "../common.scss"; + +// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically +// with ssr false +const Excalidraw = dynamic( + async () => (await import("../excalidrawWrapper")).default, + { + ssr: false, + }, +); + +export default function Page() { + return ( + <> + Switch to App router +

    Pages Router

    + {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */} + + + ); +} diff --git a/examples/excalidraw/with-nextjs/tsconfig.json b/examples/excalidraw/with-nextjs/tsconfig.json new file mode 100644 index 000000000..09ae73d2e --- /dev/null +++ b/examples/excalidraw/with-nextjs/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + }, + "forceConsistentCasingInFileNames": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "build/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/excalidraw/with-nextjs/vercel.json b/examples/excalidraw/with-nextjs/vercel.json new file mode 100644 index 000000000..bd885f4a5 --- /dev/null +++ b/examples/excalidraw/with-nextjs/vercel.json @@ -0,0 +1,3 @@ +{ + "outputDirectory": "build" +} diff --git a/examples/excalidraw/with-nextjs/yarn.lock b/examples/excalidraw/with-nextjs/yarn.lock new file mode 100644 index 000000000..0072235c0 --- /dev/null +++ b/examples/excalidraw/with-nextjs/yarn.lock @@ -0,0 +1,252 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@excalidraw/excalidraw@workspace:^": + version "0.17.2" + resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.2.tgz#9a636a1e6bb3c88c5883347d3a7e75e9cce8ab96" + integrity sha512-7pqUWD8+mPjDhF4XxG3gw4rvE2JGaLW3Vss5UZfTbITPxAtFaGEc1K081bncitnaYhUwN9ENJE0i87QB3poDwQ== + +"@next/env@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a" + integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ== + +"@next/swc-darwin-arm64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618" + integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg== + +"@next/swc-darwin-x64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b" + integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw== + +"@next/swc-linux-arm64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21" + integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w== + +"@next/swc-linux-arm64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd" + integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ== + +"@next/swc-linux-x64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32" + integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A== + +"@next/swc-linux-x64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247" + integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw== + +"@next/swc-win32-arm64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3" + integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w== + +"@next/swc-win32-ia32-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600" + integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg== + +"@next/swc-win32-x64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1" + integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A== + +"@swc/helpers@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d" + integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw== + dependencies: + tslib "^2.4.0" + +"@types/node@^20": + version "20.11.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f" + integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ== + dependencies: + undici-types "~5.26.4" + +"@types/prop-types@*": + version "15.7.11" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + +"@types/react-dom@^18": + version "18.2.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" + integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18": + version "18.2.47" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40" + integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== + +busboy@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +caniuse-lite@^1.0.30001406: + version "1.0.30001576" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" + integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== + +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +graceful-fs@^4.1.2, graceful-fs@^4.2.11: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +nanoid@^3.3.6: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +next@14.0.4: + version "14.0.4" + resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc" + integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA== + dependencies: + "@next/env" "14.0.4" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001406" + graceful-fs "^4.2.11" + postcss "8.4.31" + styled-jsx "5.1.1" + watchpack "2.4.0" + optionalDependencies: + "@next/swc-darwin-arm64" "14.0.4" + "@next/swc-darwin-x64" "14.0.4" + "@next/swc-linux-arm64-gnu" "14.0.4" + "@next/swc-linux-arm64-musl" "14.0.4" + "@next/swc-linux-x64-gnu" "14.0.4" + "@next/swc-linux-x64-musl" "14.0.4" + "@next/swc-win32-arm64-msvc" "14.0.4" + "@next/swc-win32-ia32-msvc" "14.0.4" + "@next/swc-win32-x64-msvc" "14.0.4" + +path2d-polyfill@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391" + integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +react-dom@^18: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react@^18: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +styled-jsx@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f" + integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== + dependencies: + client-only "0.0.1" + +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +typescript@^5: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +watchpack@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" diff --git a/packages/excalidraw/example/public/index.html b/examples/excalidraw/with-script-in-browser/index.html similarity index 67% rename from packages/excalidraw/example/public/index.html rename to examples/excalidraw/with-script-in-browser/index.html index 0fbf45e9e..a56d7f421 100644 --- a/packages/excalidraw/example/public/index.html +++ b/examples/excalidraw/with-script-in-browser/index.html @@ -13,20 +13,20 @@ window.name = "codesandbox"; -
    - - + - + diff --git a/examples/excalidraw/with-script-in-browser/index.tsx b/examples/excalidraw/with-script-in-browser/index.tsx new file mode 100644 index 000000000..e8584d7ca --- /dev/null +++ b/examples/excalidraw/with-script-in-browser/index.tsx @@ -0,0 +1,28 @@ +import App from "../components/App"; +import React, { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import type * as TExcalidraw from "@excalidraw/excalidraw"; + +import "@excalidraw/excalidraw/index.css"; + +declare global { + interface Window { + ExcalidrawLib: typeof TExcalidraw; + } +} + +const rootElement = document.getElementById("root")!; +const root = createRoot(rootElement); +const { Excalidraw } = window.ExcalidrawLib; +root.render( + + {}} + excalidrawLib={window.ExcalidrawLib} + > + + + , +); diff --git a/examples/excalidraw/with-script-in-browser/package.json b/examples/excalidraw/with-script-in-browser/package.json new file mode 100644 index 000000000..490b0f796 --- /dev/null +++ b/examples/excalidraw/with-script-in-browser/package.json @@ -0,0 +1,19 @@ +{ + "name": "with-script-in-browser", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "18.2.0", + "react-dom": "18.2.0", + "@excalidraw/excalidraw": "*" + }, + "devDependencies": { + "vite": "5.0.6", + "typescript": "^5" + }, + "scripts": { + "start": "yarn workspace @excalidraw/excalidraw run build:esm && vite", + "build": "yarn workspace @excalidraw/excalidraw run build:esm && vite build", + "build:preview": "yarn build && vite preview --port 5002" + } +} diff --git a/examples/excalidraw/with-script-in-browser/public/images/doremon.png b/examples/excalidraw/with-script-in-browser/public/images/doremon.png new file mode 100644 index 0000000000000000000000000000000000000000..36208a4665fb89e44247292e92a1143a52cdf38c GIT binary patch literal 201946 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

    >rqP?iYp>X2@Ky7x^}8x-)S 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

    O1!W$4;+)P9SasnI-`V8^ZFleat5n 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­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 zk308F

    hW@ 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 N3HSdn;j;Xm_;1tNja&c# literal 0 HcmV?d00001 diff --git a/packages/excalidraw/vercel.json b/examples/excalidraw/with-script-in-browser/vercel.json similarity index 50% rename from packages/excalidraw/vercel.json rename to examples/excalidraw/with-script-in-browser/vercel.json index a262682b8..139f31ef0 100644 --- a/packages/excalidraw/vercel.json +++ b/examples/excalidraw/with-script-in-browser/vercel.json @@ -1,4 +1,4 @@ { - "outputDirectory": "example/public", + "outputDirectory": "dist", "installCommand": "yarn install" } diff --git a/examples/excalidraw/with-script-in-browser/vite.config.mts b/examples/excalidraw/with-script-in-browser/vite.config.mts new file mode 100644 index 000000000..e2e5e19ac --- /dev/null +++ b/examples/excalidraw/with-script-in-browser/vite.config.mts @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 3001, + // open the browser + open: true, + }, + publicDir: "public", +}); diff --git a/examples/excalidraw/yarn.lock b/examples/excalidraw/yarn.lock new file mode 100644 index 000000000..1eb584205 --- /dev/null +++ b/examples/excalidraw/yarn.lock @@ -0,0 +1,313 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/aix-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" + integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g== + +"@esbuild/android-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220" + integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q== + +"@esbuild/android-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c" + integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw== + +"@esbuild/android-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2" + integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg== + +"@esbuild/darwin-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf" + integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ== + +"@esbuild/darwin-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e" + integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g== + +"@esbuild/freebsd-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a" + integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA== + +"@esbuild/freebsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2" + integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw== + +"@esbuild/linux-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545" + integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg== + +"@esbuild/linux-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3" + integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q== + +"@esbuild/linux-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4" + integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA== + +"@esbuild/linux-loong64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121" + integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg== + +"@esbuild/linux-mips64el@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9" + integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg== + +"@esbuild/linux-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912" + integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA== + +"@esbuild/linux-riscv64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916" + integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ== + +"@esbuild/linux-s390x@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8" + integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q== + +"@esbuild/linux-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766" + integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA== + +"@esbuild/netbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d" + integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ== + +"@esbuild/openbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2" + integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw== + +"@esbuild/sunos-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767" + integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ== + +"@esbuild/win32-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee" + integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ== + +"@esbuild/win32-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c" + integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg== + +"@esbuild/win32-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" + integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== + +"@rollup/rollup-android-arm-eabi@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz#b752b6c88a14ccfcbdf3f48c577ccc3a7f0e66b9" + integrity sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA== + +"@rollup/rollup-android-arm64@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz#33757c3a448b9ef77b6f6292d8b0ec45c87e9c1a" + integrity sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg== + +"@rollup/rollup-darwin-arm64@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz#5234ba62665a3f443143bc8bcea9df2cc58f55fb" + integrity sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w== + +"@rollup/rollup-darwin-x64@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz#981256c054d3247b83313724938d606798a919d1" + integrity sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA== + +"@rollup/rollup-linux-arm-gnueabihf@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz#120678a5a2b3a283a548dbb4d337f9187a793560" + integrity sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g== + +"@rollup/rollup-linux-arm64-gnu@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz#c99d857e2372ece544b6f60b85058ad259f64114" + integrity sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA== + +"@rollup/rollup-linux-arm64-musl@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz#3064060f568a5718c2a06858cd6e6d24f2ff8632" + integrity sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ== + +"@rollup/rollup-linux-riscv64-gnu@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz#987d30b5d2b992fff07d055015991a57ff55fbad" + integrity sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA== + +"@rollup/rollup-linux-x64-gnu@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz#85946ee4d068bd12197aeeec2c6f679c94978a49" + integrity sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA== + +"@rollup/rollup-linux-x64-musl@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz#fe0b20f9749a60eb1df43d20effa96c756ddcbd4" + integrity sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg== + +"@rollup/rollup-win32-arm64-msvc@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz#422661ef0e16699a234465d15b2c1089ef963b2a" + integrity sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ== + +"@rollup/rollup-win32-ia32-msvc@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz#7b73a145891c202fbcc08759248983667a035d85" + integrity sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA== + +"@rollup/rollup-win32-x64-msvc@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602" + integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ== + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +esbuild@^0.19.3: + version "0.19.11" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96" + integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.11" + "@esbuild/android-arm" "0.19.11" + "@esbuild/android-arm64" "0.19.11" + "@esbuild/android-x64" "0.19.11" + "@esbuild/darwin-arm64" "0.19.11" + "@esbuild/darwin-x64" "0.19.11" + "@esbuild/freebsd-arm64" "0.19.11" + "@esbuild/freebsd-x64" "0.19.11" + "@esbuild/linux-arm" "0.19.11" + "@esbuild/linux-arm64" "0.19.11" + "@esbuild/linux-ia32" "0.19.11" + "@esbuild/linux-loong64" "0.19.11" + "@esbuild/linux-mips64el" "0.19.11" + "@esbuild/linux-ppc64" "0.19.11" + "@esbuild/linux-riscv64" "0.19.11" + "@esbuild/linux-s390x" "0.19.11" + "@esbuild/linux-x64" "0.19.11" + "@esbuild/netbsd-x64" "0.19.11" + "@esbuild/openbsd-x64" "0.19.11" + "@esbuild/sunos-x64" "0.19.11" + "@esbuild/win32-arm64" "0.19.11" + "@esbuild/win32-ia32" "0.19.11" + "@esbuild/win32-x64" "0.19.11" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@^8.4.32: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +rollup@^4.2.0: + version "4.9.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.5.tgz#62999462c90f4c8b5d7c38fc7161e63b29101b05" + integrity sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.9.5" + "@rollup/rollup-android-arm64" "4.9.5" + "@rollup/rollup-darwin-arm64" "4.9.5" + "@rollup/rollup-darwin-x64" "4.9.5" + "@rollup/rollup-linux-arm-gnueabihf" "4.9.5" + "@rollup/rollup-linux-arm64-gnu" "4.9.5" + "@rollup/rollup-linux-arm64-musl" "4.9.5" + "@rollup/rollup-linux-riscv64-gnu" "4.9.5" + "@rollup/rollup-linux-x64-gnu" "4.9.5" + "@rollup/rollup-linux-x64-musl" "4.9.5" + "@rollup/rollup-win32-arm64-msvc" "4.9.5" + "@rollup/rollup-win32-ia32-msvc" "4.9.5" + "@rollup/rollup-win32-x64-msvc" "4.9.5" + fsevents "~2.3.2" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +vite@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c" + integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" diff --git a/package.json b/package.json index 3154e69f2..a440c97b7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "workspaces": [ "excalidraw-app", "packages/excalidraw", - "packages/utils" + "packages/utils", + "examples/excalidraw", + "examples/excalidraw/*" ], "dependencies": { "@excalidraw/random-username": "1.0.0", diff --git a/packages/excalidraw/.gitignore b/packages/excalidraw/.gitignore index f714ecd1d..971fcb7d3 100644 --- a/packages/excalidraw/.gitignore +++ b/packages/excalidraw/.gitignore @@ -1,4 +1,2 @@ node_modules types -bundle.js -bundle.css diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 9e3ff5dac..1618cd2ae 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -5780,7 +5780,10 @@ class App extends React.Component { event.preventDefault(); let nextPastePrevented = false; - const isLinux = /Linux/.test(window.navigator.platform); + const isLinux = + typeof window === undefined + ? false + : /Linux/.test(window.navigator.platform); setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING); let { clientX: lastX, clientY: lastY } = event; diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts index 72286e698..c4df44797 100644 --- a/packages/excalidraw/constants.ts +++ b/packages/excalidraw/constants.ts @@ -2,7 +2,6 @@ import cssVariables from "./css/variables.module.scss"; import { AppProps } from "./types"; import { ExcalidrawElement, FontFamilyValues } from "./element/types"; import { COLOR_PALETTE } from "./colors"; - export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform); export const isWindows = /^Win/.test(navigator.platform); export const isAndroid = /\b(android)\b/i.test(navigator.userAgent); diff --git a/packages/excalidraw/example/MobileFooter.tsx b/packages/excalidraw/example/MobileFooter.tsx deleted file mode 100644 index e7e0f8d69..000000000 --- a/packages/excalidraw/example/MobileFooter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ExcalidrawImperativeAPI } from "../types"; -import CustomFooter from "./CustomFooter"; -const { useDevice, Footer } = window.ExcalidrawLib; - -const MobileFooter = ({ - excalidrawAPI, -}: { - excalidrawAPI: ExcalidrawImperativeAPI; -}) => { - const device = useDevice(); - if (device.editor.isMobile) { - return ( -

    - ); - } - return null; -}; -export default MobileFooter; diff --git a/packages/excalidraw/example/index.tsx b/packages/excalidraw/example/index.tsx deleted file mode 100644 index fcc781289..000000000 --- a/packages/excalidraw/example/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import App from "./App"; - -const { StrictMode } = window.React; -//@ts-ignore -const { createRoot } = window.ReactDOM; - -const rootElement = document.getElementById("root")!; -const root = createRoot(rootElement); - -root.render( - - {}} - /> - , -); diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index 6524873a2..b45084693 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -80,6 +80,13 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { } useEffect(() => { + const importPolyfill = async () => { + //@ts-ignore + await import("canvas-roundrect-polyfill"); + }; + + importPolyfill(); + // Block pinch-zooming on iOS outside of the content area const handleTouchMove = (event: TouchEvent) => { // @ts-ignore @@ -223,7 +230,7 @@ export { } from "../utils/export"; export { isLinearElement } from "./element/typeChecks"; -export { FONT_FAMILY, THEME, MIME_TYPES } from "./constants"; +export { FONT_FAMILY, THEME, MIME_TYPES, ROUNDNESS } from "./constants"; export { mutateElement, diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index 0a066bfd4..6c358591b 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -82,7 +82,6 @@ import { getTargetFrame, isElementInFrame, } from "../frame"; -import "canvas-roundrect-polyfill"; export const DEFAULT_SPACING = 2; diff --git a/packages/excalidraw/tsconfig.json b/packages/excalidraw/tsconfig.json index 28e276c35..4d7d4b3c1 100644 --- a/packages/excalidraw/tsconfig.json +++ b/packages/excalidraw/tsconfig.json @@ -1,5 +1,5 @@ { - "exclude": ["**/*.test.*", "tests", "types", "example", "dist"], + "exclude": ["**/*.test.*", "tests", "types", "examples", "dist"], "compilerOptions": { "target": "ESNext", "strict": true, diff --git a/packages/excalidraw/vite.config.mts b/packages/excalidraw/vite.config.mts deleted file mode 100644 index 9639966b2..000000000 --- a/packages/excalidraw/vite.config.mts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig, loadEnv } from "vite"; -import react from "@vitejs/plugin-react"; - -// To load .env.local variables -const envVars = loadEnv("", `../../`); -// https://vitejs.dev/config/ -export default defineConfig({ - root: "example/public", - server: { - port: 3001, - // open the browser - open: true, - }, - publicDir: "public", -}); diff --git a/scripts/buildExample.mjs b/scripts/buildExample.mjs index cfcbe8420..5cc50c6c6 100644 --- a/scripts/buildExample.mjs +++ b/scripts/buildExample.mjs @@ -4,8 +4,9 @@ import { execSync } from "child_process"; const createDevBuild = async () => { return await esbuild.build({ - entryPoints: ["example/index.tsx"], - outfile: "example/public/bundle.js", + entryPoints: ["../../examples/excalidraw/with-script-in-browser/index.tsx"], + outfile: + "../../examples/excalidraw/with-script-in-browser/public/bundle.js", define: { "import.meta.env": "{}", }, @@ -26,7 +27,7 @@ const startServer = async (ctx) => { }); }; execSync( - `rm -rf example/public/dist && yarn build:esm && cp -r dist example/public`, + `rm -rf ../../examples/excalidraw/with-script-in-browser/public/dist && yarn build:esm && cp -r dist ../../examples/excalidraw/with-script-in-browser/public`, ); const ctx = await createDevBuild(); diff --git a/tsconfig.json b/tsconfig.json index 10ac4b9a8..585fa4cdb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,5 @@ "jsx": "react-jsx" }, "include": ["packages", "excalidraw-app"], - "exclude": ["packages/excalidraw/types"] + "exclude": ["packages/excalidraw/types", "examples"] } diff --git a/yarn.lock b/yarn.lock index f857f7fb4..83b861b95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2649,6 +2649,56 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@next/env@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.0.tgz#43d92ebb53bc0ae43dcc64fb4d418f8f17d7a341" + integrity sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw== + +"@next/swc-darwin-arm64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz#70a57c87ab1ae5aa963a3ba0f4e59e18f4ecea39" + integrity sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ== + +"@next/swc-darwin-x64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz#0863a22feae1540e83c249384b539069fef054e9" + integrity sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g== + +"@next/swc-linux-arm64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz#893da533d3fce4aec7116fe772d4f9b95232423c" + integrity sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ== + +"@next/swc-linux-arm64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz#d81ddcf95916310b8b0e4ad32b637406564244c0" + integrity sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g== + +"@next/swc-linux-x64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz#18967f100ec19938354332dcb0268393cbacf581" + integrity sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ== + +"@next/swc-linux-x64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz#77077cd4ba8dda8f349dc7ceb6230e68ee3293cf" + integrity sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg== + +"@next/swc-win32-arm64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz#5f0b8cf955644104621e6d7cc923cad3a4c5365a" + integrity sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ== + +"@next/swc-win32-ia32-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz#21f4de1293ac5e5a168a412b139db5d3420a89d0" + integrity sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw== + +"@next/swc-win32-x64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz#e561fb330466d41807123d932b365cf3d33ceba2" + integrity sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg== + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -3290,6 +3340,13 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" +"@swc/helpers@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d" + integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw== + dependencies: + tslib "^2.4.0" + "@testing-library/dom@^8.0.0": version "8.20.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.1.tgz#2e52a32e46fc88369eef7eef634ac2a192decd9f" @@ -3487,6 +3544,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^20": + version "20.11.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.1.tgz#6a93f94abeda166f688d3d2aca18012afbe5f850" + integrity sha512-DsXojJUES2M+FE8CpptJTKpg+r54moV9ZEncPstni1WHFmTcCzeFLnMFfyhCVS8XNOy/OQG+8lVxRLRrVHmV5A== + dependencies: + undici-types "~5.26.4" + "@types/pako@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.3.tgz#2e61c2b02020b5f44e2e5e946dfac74f4ec33c58" @@ -3521,6 +3585,13 @@ dependencies: "@types/react" "^17" +"@types/react-dom@^18": + version "18.2.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" + integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@18.0.15": version "18.0.15" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe" @@ -3539,6 +3610,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18": + version "18.2.48" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" + integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resize-observer-browser@0.1.7": version "0.1.7" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3" @@ -4669,6 +4749,13 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +busboy@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + bytes-iec@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083" @@ -4707,6 +4794,11 @@ caniuse-lite@^1.0.30001449: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz#0ef8a1cf8b16be47a0f9fc4ecfc952232724b32a" integrity sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw== +caniuse-lite@^1.0.30001579: + version "1.0.30001579" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz#45c065216110f46d6274311a4b3fcf6278e0852a" + integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA== + canvas-roundrect-polyfill@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/canvas-roundrect-polyfill/-/canvas-roundrect-polyfill-0.0.1.tgz#70bf107ebe2037f26d839d7f809a26f4a95f5696" @@ -4849,6 +4941,11 @@ cli-truncate@^3.1.0: slice-ansi "^5.0.0" string-width "^5.0.0" +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -6617,7 +6714,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -8143,6 +8240,29 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next@14.1: + version "14.1.0" + resolved "https://registry.yarnpkg.com/next/-/next-14.1.0.tgz#b31c0261ff9caa6b4a17c5af019ed77387174b69" + integrity sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q== + dependencies: + "@next/env" "14.1.0" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + graceful-fs "^4.2.11" + postcss "8.4.31" + styled-jsx "5.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "14.1.0" + "@next/swc-darwin-x64" "14.1.0" + "@next/swc-linux-arm64-gnu" "14.1.0" + "@next/swc-linux-arm64-musl" "14.1.0" + "@next/swc-linux-x64-gnu" "14.1.0" + "@next/swc-linux-x64-musl" "14.1.0" + "@next/swc-win32-arm64-msvc" "14.1.0" + "@next/swc-win32-ia32-msvc" "14.1.0" + "@next/swc-win32-x64-msvc" "14.1.0" + node-fetch@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -8407,6 +8527,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2d-polyfill@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391" + integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA== + pathe@^1.1.0, pathe@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" @@ -8571,6 +8696,15 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + postcss@^8.4.32, postcss@^8.4.7: version "8.4.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" @@ -8751,7 +8885,7 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -react-dom@18.2.0: +react-dom@18.2.0, react-dom@^18: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -8807,7 +8941,7 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" -react@18.2.0: +react@18.2.0, react@^18: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -9441,6 +9575,11 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + string-argv@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -9591,6 +9730,13 @@ style-loader@3.3.3: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff" integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw== +styled-jsx@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f" + integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== + dependencies: + client-only "0.0.1" + stylis@^4.1.3: version "4.3.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" @@ -9857,7 +10003,7 @@ tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: +tslib@^2.0.0, tslib@^2.4.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -9922,6 +10068,11 @@ typescript@4.9.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +typescript@^5: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + typeson-registry@^1.0.0-alpha.20: version "1.0.0-alpha.39" resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211" From 966f9aead912615a8ea518911b2b51688012fcea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:28:11 +0530 Subject: [PATCH 59/79] build(deps-dev): bump vite from 5.0.6 to 5.0.12 in /examples/excalidraw/with-script-in-browser (#7603) build(deps-dev): bump vite Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/excalidraw/with-script-in-browser/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/excalidraw/with-script-in-browser/package.json b/examples/excalidraw/with-script-in-browser/package.json index 490b0f796..d721ac162 100644 --- a/examples/excalidraw/with-script-in-browser/package.json +++ b/examples/excalidraw/with-script-in-browser/package.json @@ -8,7 +8,7 @@ "@excalidraw/excalidraw": "*" }, "devDependencies": { - "vite": "5.0.6", + "vite": "5.0.12", "typescript": "^5" }, "scripts": { From 678bb2b8192975c935cf313f9cf9a7837dd03d37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:29:50 +0530 Subject: [PATCH 60/79] build(deps-dev): bump vite from 5.0.6 to 5.0.12 (#7586) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 148 ++------------------------------------------------- 2 files changed, 6 insertions(+), 144 deletions(-) diff --git a/package.json b/package.json index a440c97b7..350f1469f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "prettier": "2.6.2", "rewire": "6.0.0", "typescript": "4.9.4", - "vite": "5.0.6", + "vite": "5.0.12", "vite-plugin-checker": "0.6.1", "vite-plugin-ejs": "1.7.0", "vite-plugin-pwa": "0.17.4", diff --git a/yarn.lock b/yarn.lock index 83b861b95..61def89e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2000,221 +2000,111 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz#ef31015416dd79398082409b77aaaa2ade4d531a" integrity sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q== -"@esbuild/android-arm64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz#fb7130103835b6d43ea499c3f30cfb2b2ed58456" - integrity sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA== - "@esbuild/android-arm@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.10.tgz#1c23c7e75473aae9fb323be5d9db225142f47f52" integrity sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w== -"@esbuild/android-arm@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.8.tgz#b46e4d9e984e6d6db6c4224d72c86b7757e35bcb" - integrity sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA== - "@esbuild/android-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.10.tgz#df6a4e6d6eb8da5595cfce16d4e3f6bc24464707" integrity sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw== -"@esbuild/android-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.8.tgz#a13db9441b5a4f4e4fec4a6f8ffacfea07888db7" - integrity sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A== - "@esbuild/darwin-arm64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz#8462a55db07c1b2fad61c8244ce04469ef1043be" integrity sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA== -"@esbuild/darwin-arm64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz#49f5718d36541f40dd62bfdf84da9c65168a0fc2" - integrity sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw== - "@esbuild/darwin-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz#d1de20bfd41bb75b955ba86a6b1004539e8218c1" integrity sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA== -"@esbuild/darwin-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz#75c5c88371eea4bfc1f9ecfd0e75104c74a481ac" - integrity sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q== - "@esbuild/freebsd-arm64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz#16904879e34c53a2e039d1284695d2db3e664d57" integrity sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg== -"@esbuild/freebsd-arm64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz#9d7259fea4fd2b5f7437b52b542816e89d7c8575" - integrity sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw== - "@esbuild/freebsd-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz#8ad9e5ca9786ca3f1ef1411bfd10b08dcd9d4cef" integrity sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag== -"@esbuild/freebsd-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz#abac03e1c4c7c75ee8add6d76ec592f46dbb39e3" - integrity sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg== - "@esbuild/linux-arm64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz#d82cf2c590faece82d28bbf1cfbe36f22ae25bd2" integrity sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ== -"@esbuild/linux-arm64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz#c577932cf4feeaa43cb9cec27b89cbe0df7d9098" - integrity sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ== - "@esbuild/linux-arm@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz#477b8e7c7bcd34369717b04dd9ee6972c84f4029" integrity sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg== -"@esbuild/linux-arm@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz#d6014d8b98b5cbc96b95dad3d14d75bb364fdc0f" - integrity sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ== - "@esbuild/linux-ia32@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz#d55ff822cf5b0252a57112f86857ff23be6cab0e" integrity sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg== -"@esbuild/linux-ia32@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz#2379a0554307d19ac4a6cdc15b08f0ea28e7a40d" - integrity sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ== - "@esbuild/linux-loong64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz#a9ad057d7e48d6c9f62ff50f6f208e331c4543c7" integrity sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA== -"@esbuild/linux-loong64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz#e2a5bbffe15748b49356a6cd7b2d5bf60c5a7123" - integrity sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ== - "@esbuild/linux-mips64el@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz#b011a96924773d60ebab396fbd7a08de66668179" integrity sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A== -"@esbuild/linux-mips64el@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz#1359331e6f6214f26f4b08db9b9df661c57cfa24" - integrity sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q== - "@esbuild/linux-ppc64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz#5d8b59929c029811e473f2544790ea11d588d4dd" integrity sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ== -"@esbuild/linux-ppc64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz#9ba436addc1646dc89dae48c62d3e951ffe70951" - integrity sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg== - "@esbuild/linux-riscv64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz#292b06978375b271bd8bc0a554e0822957508d22" integrity sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA== -"@esbuild/linux-riscv64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz#fbcf0c3a0b20f40b5fc31c3b7695f0769f9de66b" - integrity sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg== - "@esbuild/linux-s390x@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz#d30af63530f8d4fa96930374c9dd0d62bf59e069" integrity sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA== -"@esbuild/linux-s390x@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz#989e8a05f7792d139d5564ffa7ff898ac6f20a4a" - integrity sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg== - "@esbuild/linux-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz#898c72eeb74d9f2fb43acf316125b475548b75ce" integrity sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA== -"@esbuild/linux-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz#b187295393a59323397fe5ff51e769ec4e72212b" - integrity sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg== - "@esbuild/netbsd-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz#fd473a5ae261b43eab6dad4dbd5a3155906e6c91" integrity sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q== -"@esbuild/netbsd-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz#c1ec0e24ea82313cb1c7bae176bd5acd5bde7137" - integrity sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw== - "@esbuild/openbsd-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz#96eb8992e526717b5272321eaad3e21f3a608e46" integrity sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg== -"@esbuild/openbsd-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz#0c5b696ac66c6d70cf9ee17073a581a28af9e18d" - integrity sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ== - "@esbuild/sunos-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz#c16ee1c167f903eaaa6acf7372bee42d5a89c9bc" integrity sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA== -"@esbuild/sunos-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz#2a697e1f77926ff09fcc457d8f29916d6cd48fb1" - integrity sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w== - "@esbuild/win32-arm64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz#7e417d1971dbc7e469b4eceb6a5d1d667b5e3dcc" integrity sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw== -"@esbuild/win32-arm64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz#ec029e62a2fca8c071842ecb1bc5c2dd20b066f1" - integrity sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg== - "@esbuild/win32-ia32@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz#2b52dfec6cd061ecb36171c13bae554888b439e5" integrity sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ== -"@esbuild/win32-ia32@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz#cbb9a3146bde64dc15543e48afe418c7a3214851" - integrity sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw== - "@esbuild/win32-x64@0.19.10": version "0.19.10" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz#bd123a74f243d2f3a1f046447bb9b363ee25d072" integrity sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA== -"@esbuild/win32-x64@0.19.8": - version "0.19.8" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz#c8285183dbdb17008578dbacb6e22748709b4822" - integrity sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA== - "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -5890,7 +5780,7 @@ esbuild-sass-plugin@2.16.0: resolve "^1.22.6" sass "^1.7.3" -esbuild@0.19.10: +esbuild@0.19.10, esbuild@^0.19.3: version "0.19.10" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.10.tgz#55e83e4a6b702e3498b9f872d84bfb4ebcb6d16e" integrity sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA== @@ -5919,34 +5809,6 @@ esbuild@0.19.10: "@esbuild/win32-ia32" "0.19.10" "@esbuild/win32-x64" "0.19.10" -esbuild@^0.19.3: - version "0.19.8" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.8.tgz#ad05b72281d84483fa6b5345bd246c27a207b8f1" - integrity sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w== - optionalDependencies: - "@esbuild/android-arm" "0.19.8" - "@esbuild/android-arm64" "0.19.8" - "@esbuild/android-x64" "0.19.8" - "@esbuild/darwin-arm64" "0.19.8" - "@esbuild/darwin-x64" "0.19.8" - "@esbuild/freebsd-arm64" "0.19.8" - "@esbuild/freebsd-x64" "0.19.8" - "@esbuild/linux-arm" "0.19.8" - "@esbuild/linux-arm64" "0.19.8" - "@esbuild/linux-ia32" "0.19.8" - "@esbuild/linux-loong64" "0.19.8" - "@esbuild/linux-mips64el" "0.19.8" - "@esbuild/linux-ppc64" "0.19.8" - "@esbuild/linux-riscv64" "0.19.8" - "@esbuild/linux-s390x" "0.19.8" - "@esbuild/linux-x64" "0.19.8" - "@esbuild/netbsd-x64" "0.19.8" - "@esbuild/openbsd-x64" "0.19.8" - "@esbuild/sunos-x64" "0.19.8" - "@esbuild/win32-arm64" "0.19.8" - "@esbuild/win32-ia32" "0.19.8" - "@esbuild/win32-x64" "0.19.8" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -10334,10 +10196,10 @@ vite-plugin-svgr@2.4.0: "@rollup/pluginutils" "^5.0.2" "@svgr/core" "^6.5.1" -vite@5.0.6, "vite@^5.0.0-beta.15 || ^5.0.0", "vite@^5.0.0-beta.19 || ^5.0.0": - version "5.0.6" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c" - integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ== +vite@5.0.12, "vite@^5.0.0-beta.15 || ^5.0.0", "vite@^5.0.0-beta.19 || ^5.0.0": + version "5.0.12" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.12.tgz#8a2ffd4da36c132aec4adafe05d7adde38333c47" + integrity sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w== dependencies: esbuild "^0.19.3" postcss "^8.4.32" From 2789d08154a384ae98d9e9d9f70a01334d45ade9 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 25 Jan 2024 20:26:48 +0530 Subject: [PATCH 61/79] docs: update the docs for next js integration (#7605) * docs: update the docs for next js integration * update * update * update docs with tabbed examples * fix --- .../@excalidraw/excalidraw/integration.mdx | 101 ++++++++++++++---- 1 file changed, 79 insertions(+), 22 deletions(-) diff --git a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx index 87eb3777d..d6bf3fd0d 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx @@ -32,15 +32,9 @@ function App() { ### Next.js -Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`. +Since Excalidraw doesn't support `server side rendering` so it should be rendered only on `client`. The way to achieve this in next.js is using `next.js dynamic import`. -Here are two ways on how you can render **Excalidraw** on **Next.js**. - - - -1. Using **Next.js Dynamic** import [Recommended]. - -Since Excalidraw doesn't support server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`. +If you want to only import `Excalidraw` component you can do :point_down: ```jsx showLineNumbers import dynamic from "next/dynamic"; @@ -55,25 +49,88 @@ export default function App() { } ``` -Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-dynamic-k8yjq2). +However the above component only works for named component exports. If you want to import some util / constant or something else apart from Excalidraw, then this approach will not work. Instead you can write a wrapper over Excalidraw and import the wrapper dynamically. +If you are using `pages router` then importing the wrapper dynamically would work, where as if you are using `app router` then you will have to also add `useClient` directive on top of the file in addition to dynamically importing the wrapper as shown :point_down: -2. Importing Excalidraw once **client** is rendered. + + -```jsx showLineNumbers -import { useState, useEffect } from "react"; -export default function App() { - const [Excalidraw, setExcalidraw] = useState(null); - useEffect(() => { - import("@excalidraw/excalidraw").then((comp) => - setExcalidraw(comp.Excalidraw), + ```jsx showLineNumbers + "use client"; + import { Excalidraw. convertToExcalidrawElements } from "@excalidraw/excalidraw"; + + import "@excalidraw/excalidraw/index.css"; + + const ExcalidrawWrapper: React.FC = () => { + console.info(convertToExcalidrawElements([{ + type: "rectangle", + id: "rect-1", + width: 186.47265625, + height: 141.9765625, + },])); + return ( +
    +
    ); - }, []); - return <>{Excalidraw && }; -} -``` + }; + export default ExcalidrawWrapper; + ``` + +
    + + + + ```jsx showLineNumbers + import dynamic from "next/dynamic"; + + // Since client components get prerenderd on server as well hence importing + // the excalidraw stuff dynamically with ssr false + + const ExcalidrawWrapper = dynamic( + async () => (await import("../excalidrawWrapper")).default, + { + ssr: false, + }, + ); + + export default function Page() { + return ( + + ); + } + ``` + + + + + ```jsx showLineNumbers + import dynamic from "next/dynamic"; + + // Since client components get prerenderd on server as well hence importing + // the excalidraw stuff dynamically with ssr false + + const ExcalidrawWrapper = dynamic( + async () => (await import("../excalidrawWrapper")).default, + { + ssr: false, + }, + ); + + export default function Page() { + return ( + + ); + } + ``` + + +
    + + +Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/). -Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d) The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm) From 10bd08ef19a049abcb4a7da96fc69a052c9520ee Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Fri, 26 Jan 2024 11:29:07 +0530 Subject: [PATCH 62/79] fix: make getBoundTextElement and related helpers pure (#7601) * fix: make getBoundTextElement pure * updating args * fix * pass boundTextElement to getBoundTextMaxWidth * fix labelled arrows * lint * pass elementsMap to removeElementsFromFrame * pass elementsMap to getMaximumGroups, alignElements and distributeElements * lint * pass allElementsMap to renderElement * lint * feat: make more typesafe * fix: remove unnecessary assertion * fix: remove unused params --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/actions/actionAlign.tsx | 7 +- .../excalidraw/actions/actionBoundText.tsx | 8 +- .../excalidraw/actions/actionDistribute.tsx | 6 +- .../actions/actionDuplicateSelection.tsx | 2 +- packages/excalidraw/actions/actionFlip.ts | 5 +- packages/excalidraw/actions/actionGroup.tsx | 6 +- .../excalidraw/actions/actionProperties.tsx | 50 ++++++++--- packages/excalidraw/actions/actionStyles.ts | 9 +- packages/excalidraw/align.ts | 9 +- packages/excalidraw/components/Actions.tsx | 8 +- packages/excalidraw/components/App.tsx | 57 ++++++++++-- .../components/canvases/StaticCanvas.tsx | 7 +- packages/excalidraw/data/transform.ts | 5 +- packages/excalidraw/distribute.ts | 5 +- packages/excalidraw/element/binding.ts | 11 ++- packages/excalidraw/element/bounds.ts | 27 ++++-- packages/excalidraw/element/collision.ts | 10 ++- packages/excalidraw/element/dragElements.ts | 5 +- .../excalidraw/element/linearElementEditor.ts | 25 ++++-- packages/excalidraw/element/newElement.ts | 2 +- packages/excalidraw/element/resizeElements.ts | 15 ++-- .../excalidraw/element/textElement.test.ts | 6 +- packages/excalidraw/element/textElement.ts | 44 ++++------ packages/excalidraw/element/textWysiwyg.tsx | 10 ++- packages/excalidraw/element/types.ts | 10 +++ packages/excalidraw/frame.ts | 11 ++- packages/excalidraw/groups.ts | 5 +- packages/excalidraw/renderer/renderElement.ts | 28 ++++-- packages/excalidraw/renderer/renderScene.ts | 16 +++- packages/excalidraw/scene/Scene.ts | 9 +- packages/excalidraw/scene/export.ts | 12 +-- packages/excalidraw/scene/types.ts | 2 + packages/excalidraw/snapping.ts | 8 +- .../tests/linearElementEditor.test.tsx | 88 +++++++++++++++---- 34 files changed, 385 insertions(+), 143 deletions(-) diff --git a/packages/excalidraw/actions/actionAlign.tsx b/packages/excalidraw/actions/actionAlign.tsx index 137f68ae9..8d7d36217 100644 --- a/packages/excalidraw/actions/actionAlign.tsx +++ b/packages/excalidraw/actions/actionAlign.tsx @@ -40,8 +40,13 @@ const alignSelectedElements = ( alignment: Alignment, ) => { const selectedElements = app.scene.getSelectedElements(appState); + const elementsMap = arrayToMap(elements); - const updatedElements = alignElements(selectedElements, alignment); + const updatedElements = alignElements( + selectedElements, + elementsMap, + alignment, + ); const updatedElementsMap = arrayToMap(updatedElements); diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx index b42169544..05dd9c786 100644 --- a/packages/excalidraw/actions/actionBoundText.tsx +++ b/packages/excalidraw/actions/actionBoundText.tsx @@ -45,8 +45,9 @@ export const actionUnbindText = register({ }, perform: (elements, appState, _, app) => { const selectedElements = app.scene.getSelectedElements(appState); + const elementsMap = app.scene.getNonDeletedElementsMap(); selectedElements.forEach((element) => { - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { const { width, height, baseline } = measureText( boundTextElement.originalText, @@ -106,7 +107,10 @@ export const actionBindText = register({ if ( textElement && bindingContainer && - getBoundTextElement(bindingContainer) === null + getBoundTextElement( + bindingContainer, + app.scene.getNonDeletedElementsMap(), + ) === null ) { return true; } diff --git a/packages/excalidraw/actions/actionDistribute.tsx b/packages/excalidraw/actions/actionDistribute.tsx index bf51bedf4..be48bc870 100644 --- a/packages/excalidraw/actions/actionDistribute.tsx +++ b/packages/excalidraw/actions/actionDistribute.tsx @@ -32,7 +32,11 @@ const distributeSelectedElements = ( ) => { const selectedElements = app.scene.getSelectedElements(appState); - const updatedElements = distributeElements(selectedElements, distribution); + const updatedElements = distributeElements( + selectedElements, + app.scene.getNonDeletedElementsMap(), + distribution, + ); const updatedElementsMap = arrayToMap(updatedElements); diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index ba079168e..7126f549e 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -139,7 +139,7 @@ const duplicateElements = ( continue; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, arrayToMap(elements)); const isElementAFrameLike = isFrameLikeElement(element); if (idsOfElementsToDuplicate.get(element.id)) { diff --git a/packages/excalidraw/actions/actionFlip.ts b/packages/excalidraw/actions/actionFlip.ts index 81476e241..c760af44d 100644 --- a/packages/excalidraw/actions/actionFlip.ts +++ b/packages/excalidraw/actions/actionFlip.ts @@ -5,6 +5,7 @@ import { ExcalidrawElement, NonDeleted, NonDeletedElementsMap, + NonDeletedSceneElementsMap, } from "../element/types"; import { resizeMultipleElements } from "../element/resizeElements"; import { AppState } from "../types"; @@ -67,7 +68,7 @@ export const actionFlipVertical = register({ const flipSelectedElements = ( elements: readonly ExcalidrawElement[], - elementsMap: NonDeletedElementsMap, + elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap, appState: Readonly, flipDirection: "horizontal" | "vertical", ) => { @@ -96,7 +97,7 @@ const flipSelectedElements = ( const flipElements = ( selectedElements: NonDeleted[], - elementsMap: NonDeletedElementsMap, + elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap, appState: AppState, flipDirection: "horizontal" | "vertical", ): ExcalidrawElement[] => { diff --git a/packages/excalidraw/actions/actionGroup.tsx b/packages/excalidraw/actions/actionGroup.tsx index 42bd26efe..44523857a 100644 --- a/packages/excalidraw/actions/actionGroup.tsx +++ b/packages/excalidraw/actions/actionGroup.tsx @@ -105,7 +105,10 @@ export const actionGroup = register({ const frameElementsMap = groupByFrameLikes(selectedElements); frameElementsMap.forEach((elementsInFrame, frameId) => { - removeElementsFromFrame(elementsInFrame); + removeElementsFromFrame( + elementsInFrame, + app.scene.getNonDeletedElementsMap(), + ); }); } @@ -225,6 +228,7 @@ export const actionUngroup = register({ nextElements, getElementsInResizingFrame(nextElements, frame, appState), frame, + app, ); } }); diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index c2a47802f..79e50aa68 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -606,7 +606,7 @@ export const actionChangeFontSize = register({ perform: (elements, appState, value, app) => { return changeFontSize(elements, appState, app, () => value, value); }, - PanelComponent: ({ elements, appState, updateData }) => ( + PanelComponent: ({ elements, appState, updateData, app }) => (
    {t("labels.fontSize")} - isTextElement(element) || getBoundTextElement(element) !== null, + isTextElement(element) || + getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ) !== null, (hasSelection) => hasSelection ? null @@ -738,7 +745,7 @@ export const actionChangeFontFamily = register({ commitToHistory: true, }; }, - PanelComponent: ({ elements, appState, updateData }) => { + PanelComponent: ({ elements, appState, updateData, app }) => { const options: { value: FontFamilyValues; text: string; @@ -778,14 +785,21 @@ export const actionChangeFontFamily = register({ if (isTextElement(element)) { return element.fontFamily; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ); if (boundTextElement) { return boundTextElement.fontFamily; } return null; }, (element) => - isTextElement(element) || getBoundTextElement(element) !== null, + isTextElement(element) || + getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ) !== null, (hasSelection) => hasSelection ? null @@ -830,7 +844,8 @@ export const actionChangeTextAlign = register({ commitToHistory: true, }; }, - PanelComponent: ({ elements, appState, updateData }) => { + PanelComponent: ({ elements, appState, updateData, app }) => { + const elementsMap = app.scene.getNonDeletedElementsMap(); return (
    {t("labels.textAlign")} @@ -863,14 +878,18 @@ export const actionChangeTextAlign = register({ if (isTextElement(element)) { return element.textAlign; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement( + element, + elementsMap, + ); if (boundTextElement) { return boundTextElement.textAlign; } return null; }, (element) => - isTextElement(element) || getBoundTextElement(element) !== null, + isTextElement(element) || + getBoundTextElement(element, elementsMap) !== null, (hasSelection) => hasSelection ? null : appState.currentItemTextAlign, )} @@ -913,7 +932,7 @@ export const actionChangeVerticalAlign = register({ commitToHistory: true, }; }, - PanelComponent: ({ elements, appState, updateData }) => { + PanelComponent: ({ elements, appState, updateData, app }) => { return (
    @@ -945,14 +964,21 @@ export const actionChangeVerticalAlign = register({ if (isTextElement(element) && element.containerId) { return element.verticalAlign; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ); if (boundTextElement) { return boundTextElement.verticalAlign; } return null; }, (element) => - isTextElement(element) || getBoundTextElement(element) !== null, + isTextElement(element) || + getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ) !== null, (hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE), )} onChange={(value) => updateData(value)} diff --git a/packages/excalidraw/actions/actionStyles.ts b/packages/excalidraw/actions/actionStyles.ts index 9c6589bbc..25a6baf2a 100644 --- a/packages/excalidraw/actions/actionStyles.ts +++ b/packages/excalidraw/actions/actionStyles.ts @@ -32,12 +32,15 @@ export let copiedStyles: string = "{}"; export const actionCopyStyles = register({ name: "copyStyles", trackEvent: { category: "element" }, - perform: (elements, appState) => { + perform: (elements, appState, formData, app) => { const elementsCopied = []; const element = elements.find((el) => appState.selectedElementIds[el.id]); elementsCopied.push(element); if (element && hasBoundTextElement(element)) { - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement( + element, + app.scene.getNonDeletedElementsMap(), + ); elementsCopied.push(boundTextElement); } if (element) { @@ -59,7 +62,7 @@ export const actionCopyStyles = register({ export const actionPasteStyles = register({ name: "pasteStyles", trackEvent: { category: "element" }, - perform: (elements, appState) => { + perform: (elements, appState, formData, app) => { const elementsCopied = JSON.parse(copiedStyles); const pastedElement = elementsCopied[0]; const boundTextElement = elementsCopied[1]; diff --git a/packages/excalidraw/align.ts b/packages/excalidraw/align.ts index 06382838f..90ecabb11 100644 --- a/packages/excalidraw/align.ts +++ b/packages/excalidraw/align.ts @@ -1,4 +1,4 @@ -import { ExcalidrawElement } from "./element/types"; +import { ElementsMap, ExcalidrawElement } from "./element/types"; import { newElementWith } from "./element/mutateElement"; import { BoundingBox, getCommonBoundingBox } from "./element/bounds"; import { getMaximumGroups } from "./groups"; @@ -10,10 +10,13 @@ export interface Alignment { export const alignElements = ( selectedElements: ExcalidrawElement[], + elementsMap: ElementsMap, alignment: Alignment, ): ExcalidrawElement[] => { - const groups: ExcalidrawElement[][] = getMaximumGroups(selectedElements); - + const groups: ExcalidrawElement[][] = getMaximumGroups( + selectedElements, + elementsMap, + ); const selectionBoundingBox = getCommonBoundingBox(selectedElements); return groups.flatMap((group) => { diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index d67c8893d..c11d64d04 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -1,6 +1,10 @@ import { useState } from "react"; import { ActionManager } from "../actions/manager"; -import { ExcalidrawElementType, NonDeletedElementsMap } from "../element/types"; +import { + ExcalidrawElementType, + NonDeletedElementsMap, + NonDeletedSceneElementsMap, +} from "../element/types"; import { t } from "../i18n"; import { useDevice } from "./App"; import { @@ -47,7 +51,7 @@ export const SelectedShapeActions = ({ renderAction, }: { appState: UIAppState; - elementsMap: NonDeletedElementsMap; + elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap; renderAction: ActionManager["renderAction"]; }) => { const targetElements = getTargetElements(elementsMap, appState); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 1618cd2ae..30f86c24e 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1431,6 +1431,8 @@ class App extends React.Component { pendingImageElementId: this.state.pendingImageElementId, }); + const allElementsMap = this.scene.getNonDeletedElementsMap(); + const shouldBlockPointerEvents = !( this.state.editingElement && isLinearElement(this.state.editingElement) @@ -1628,6 +1630,7 @@ class App extends React.Component { canvas={this.canvas} rc={this.rc} elementsMap={elementsMap} + allElementsMap={allElementsMap} visibleElements={visibleElements} versionNonce={versionNonce} selectionNonce={ @@ -3869,7 +3872,11 @@ class App extends React.Component { if (!isTextElement(selectedElement)) { container = selectedElement as ExcalidrawTextContainer; } - const midPoint = getContainerCenter(selectedElement, this.state); + const midPoint = getContainerCenter( + selectedElement, + this.state, + this.scene.getNonDeletedElementsMap(), + ); const sceneX = midPoint.x; const sceneY = midPoint.y; this.startTextEditing({ @@ -4333,6 +4340,7 @@ class App extends React.Component { this.frameNameBoundsCache, x, y, + this.scene.getNonDeletedElementsMap(), ) ? allHitElements[allHitElements.length - 2] : elementWithHighestZIndex; @@ -4362,7 +4370,14 @@ class App extends React.Component { ); return getElementsAtPosition(elements, (element) => - hitTest(element, this.state, this.frameNameBoundsCache, x, y), + hitTest( + element, + this.state, + this.frameNameBoundsCache, + x, + y, + this.scene.getNonDeletedElementsMap(), + ), ).filter((element) => { // hitting a frame's element from outside the frame is not considered a hit const containingFrame = getContainingFrame(element); @@ -4399,7 +4414,10 @@ class App extends React.Component { container, ); if (container && parentCenterPosition) { - const boundTextElementToContainer = getBoundTextElement(container); + const boundTextElementToContainer = getBoundTextElement( + container, + this.scene.getNonDeletedElementsMap(), + ); if (!boundTextElementToContainer) { shouldBindToContainer = true; } @@ -4412,7 +4430,10 @@ class App extends React.Component { if (isTextElement(selectedElements[0])) { existingTextElement = selectedElements[0]; } else if (container) { - existingTextElement = getBoundTextElement(selectedElements[0]); + existingTextElement = getBoundTextElement( + selectedElements[0], + this.scene.getNonDeletedElementsMap(), + ); } else { existingTextElement = this.getTextElementAtPosition(sceneX, sceneY); } @@ -4621,7 +4642,11 @@ class App extends React.Component { [sceneX, sceneY], ) ) { - const midPoint = getContainerCenter(container, this.state); + const midPoint = getContainerCenter( + container, + this.state, + this.scene.getNonDeletedElementsMap(), + ); sceneX = midPoint.x; sceneY = midPoint.y; @@ -5257,8 +5282,8 @@ class App extends React.Component { const element = LinearElementEditor.getElement( linearElementEditor.elementId, ); - - const boundTextElement = getBoundTextElement(element); + const elementsMap = this.scene.getNonDeletedElementsMap(); + const boundTextElement = getBoundTextElement(element, elementsMap); if (!element) { return; @@ -5285,6 +5310,7 @@ class App extends React.Component { linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, + this.scene.getNonDeletedElementsMap(), ); if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) { @@ -5300,6 +5326,7 @@ class App extends React.Component { this.frameNameBoundsCache, scenePointerX, scenePointerY, + elementsMap, ) ) { setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); @@ -5311,6 +5338,7 @@ class App extends React.Component { this.frameNameBoundsCache, scenePointerX, scenePointerY, + this.scene.getNonDeletedElementsMap(), ) ) { setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE); @@ -6060,6 +6088,7 @@ class App extends React.Component { this.history, pointerDownState.origin, linearElementEditor, + this.scene.getNonDeletedElementsMap(), ); if (ret.hitElement) { pointerDownState.hit.element = ret.hitElement; @@ -6995,6 +7024,7 @@ class App extends React.Component { ); }, linearElementEditor, + this.scene.getNonDeletedElementsMap(), ); if (didDrag) { pointerDownState.lastCoords.x = pointerCoords.x; @@ -7713,7 +7743,10 @@ class App extends React.Component { groupIds: [], }); - removeElementsFromFrame([linearElement]); + removeElementsFromFrame( + [linearElement], + this.scene.getNonDeletedElementsMap(), + ); this.scene.informMutation(); } @@ -7866,6 +7899,7 @@ class App extends React.Component { this.state, ), frame, + this, ); } @@ -8093,6 +8127,7 @@ class App extends React.Component { this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, + this.scene.getNonDeletedElementsMap(), )) || (!hitElement && pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements)) @@ -9334,7 +9369,11 @@ class App extends React.Component { let elementCenterX = container.x + container.width / 2; let elementCenterY = container.y + container.height / 2; - const elementCenter = getContainerCenter(container, appState); + const elementCenter = getContainerCenter( + container, + appState, + this.scene.getNonDeletedElementsMap(), + ); if (elementCenter) { elementCenterX = elementCenter.x; elementCenterY = elementCenter.y; diff --git a/packages/excalidraw/components/canvases/StaticCanvas.tsx b/packages/excalidraw/components/canvases/StaticCanvas.tsx index 3dc5b9175..bfdb669e6 100644 --- a/packages/excalidraw/components/canvases/StaticCanvas.tsx +++ b/packages/excalidraw/components/canvases/StaticCanvas.tsx @@ -7,13 +7,17 @@ import type { RenderableElementsMap, StaticCanvasRenderConfig, } from "../../scene/types"; -import type { NonDeletedExcalidrawElement } from "../../element/types"; +import type { + NonDeletedExcalidrawElement, + NonDeletedSceneElementsMap, +} from "../../element/types"; import { isRenderThrottlingEnabled } from "../../reactUtils"; type StaticCanvasProps = { canvas: HTMLCanvasElement; rc: RoughCanvas; elementsMap: RenderableElementsMap; + allElementsMap: NonDeletedSceneElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; versionNonce: number | undefined; selectionNonce: number | undefined; @@ -67,6 +71,7 @@ const StaticCanvas = (props: StaticCanvasProps) => { rc: props.rc, scale: props.scale, elementsMap: props.elementsMap, + allElementsMap: props.allElementsMap, visibleElements: props.visibleElements, appState: props.appState, renderConfig: props.renderConfig, diff --git a/packages/excalidraw/data/transform.ts b/packages/excalidraw/data/transform.ts index 7b5286923..8ce842300 100644 --- a/packages/excalidraw/data/transform.ts +++ b/packages/excalidraw/data/transform.ts @@ -24,6 +24,7 @@ import { normalizeText, } from "../element/textElement"; import { + ElementsMap, ExcalidrawArrowElement, ExcalidrawBindableElement, ExcalidrawElement, @@ -42,7 +43,7 @@ import { VerticalAlign, } from "../element/types"; import { MarkOptional } from "../utility-types"; -import { assertNever, cloneJSON, getFontString } from "../utils"; +import { arrayToMap, assertNever, cloneJSON, getFontString } from "../utils"; import { getSizeFromPoints } from "../points"; import { randomId } from "../random"; @@ -202,6 +203,7 @@ const DEFAULT_DIMENSION = 100; const bindTextToContainer = ( container: ExcalidrawElement, textProps: { text: string } & MarkOptional, + elementsMap: ElementsMap, ) => { const textElement: ExcalidrawTextElement = newTextElement({ x: 0, @@ -623,6 +625,7 @@ export const convertToExcalidrawElements = ( let [container, text] = bindTextToContainer( excalidrawElement, element?.label, + arrayToMap(elementStore.getElements()), ); elementStore.add(container); elementStore.add(text); diff --git a/packages/excalidraw/distribute.ts b/packages/excalidraw/distribute.ts index acad09b2d..368b2f24d 100644 --- a/packages/excalidraw/distribute.ts +++ b/packages/excalidraw/distribute.ts @@ -1,7 +1,7 @@ -import { ExcalidrawElement } from "./element/types"; import { newElementWith } from "./element/mutateElement"; import { getMaximumGroups } from "./groups"; import { getCommonBoundingBox } from "./element/bounds"; +import type { ElementsMap, ExcalidrawElement } from "./element/types"; export interface Distribution { space: "between"; @@ -10,6 +10,7 @@ export interface Distribution { export const distributeElements = ( selectedElements: ExcalidrawElement[], + elementsMap: ElementsMap, distribution: Distribution, ): ExcalidrawElement[] => { const [start, mid, end, extent] = @@ -18,7 +19,7 @@ export const distributeElements = ( : (["minY", "midY", "maxY", "height"] as const); const bounds = getCommonBoundingBox(selectedElements); - const groups = getMaximumGroups(selectedElements) + const groups = getMaximumGroups(selectedElements, elementsMap) .map((group) => [group, getCommonBoundingBox(group)] as const) .sort((a, b) => a[1][mid] - b[1][mid]); diff --git a/packages/excalidraw/element/binding.ts b/packages/excalidraw/element/binding.ts index 3f6cf0022..66d29f3f6 100644 --- a/packages/excalidraw/element/binding.ts +++ b/packages/excalidraw/element/binding.ts @@ -321,9 +321,9 @@ export const updateBoundElements = ( const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds( simultaneouslyUpdated, ); - + const scene = Scene.getScene(changedElement)!; getNonDeletedElements( - Scene.getScene(changedElement)!, + scene, boundLinearElements.map((el) => el.id), ).forEach((element) => { if (!isLinearElement(element)) { @@ -362,9 +362,12 @@ export const updateBoundElements = ( endBinding, changedElement as ExcalidrawBindableElement, ); - const boundText = getBoundTextElement(element); + const boundText = getBoundTextElement( + element, + scene.getNonDeletedElementsMap(), + ); if (boundText) { - handleBindTextResize(element, false); + handleBindTextResize(element, scene.getNonDeletedElementsMap(), false); } }); }; diff --git a/packages/excalidraw/element/bounds.ts b/packages/excalidraw/element/bounds.ts index 673649e5f..f892089f7 100644 --- a/packages/excalidraw/element/bounds.ts +++ b/packages/excalidraw/element/bounds.ts @@ -6,6 +6,7 @@ import { NonDeleted, ExcalidrawTextElementWithContainer, ElementsMapOrArray, + ElementsMap, } from "./types"; import { distance2d, rotate, rotatePoint } from "../math"; import rough from "roughjs/bin/rough"; @@ -74,13 +75,16 @@ export class ElementBounds { ) { return cachedBounds.bounds; } - - const bounds = ElementBounds.calculateBounds(element); + const scene = Scene.getScene(element); + const bounds = ElementBounds.calculateBounds( + element, + scene?.getNonDeletedElementsMap() || new Map(), + ); // hack to ensure that downstream checks could retrieve element Scene // so as to have correctly calculated bounds // FIXME remove when we get rid of all the id:Scene / element:Scene mapping - const shouldCache = Scene.getScene(element); + const shouldCache = !!scene; if (shouldCache) { ElementBounds.boundsCache.set(element, { @@ -92,7 +96,10 @@ export class ElementBounds { return bounds; } - private static calculateBounds(element: ExcalidrawElement): Bounds { + private static calculateBounds( + element: ExcalidrawElement, + elementsMap: ElementsMap, + ): Bounds { let bounds: Bounds; const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element); @@ -111,7 +118,7 @@ export class ElementBounds { maxY + element.y, ]; } else if (isLinearElement(element)) { - bounds = getLinearElementRotatedBounds(element, cx, cy); + bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap); } else if (element.type === "diamond") { const [x11, y11] = rotate(cx, y1, cx, cy, element.angle); const [x12, y12] = rotate(cx, y2, cx, cy, element.angle); @@ -154,16 +161,17 @@ export const getElementAbsoluteCoords = ( element: ExcalidrawElement, includeBoundText: boolean = false, ): [number, number, number, number, number, number] => { + const elementsMap = + Scene.getScene(element)?.getElementsMapIncludingDeleted() || new Map(); if (isFreeDrawElement(element)) { return getFreeDrawElementAbsoluteCoords(element); } else if (isLinearElement(element)) { return LinearElementEditor.getElementAbsoluteCoords( element, + elementsMap, includeBoundText, ); } else if (isTextElement(element)) { - const elementsMap = - Scene.getScene(element)?.getElementsMapIncludingDeleted(); const container = elementsMap ? getContainerElement(element, elementsMap) : null; @@ -677,7 +685,10 @@ const getLinearElementRotatedBounds = ( element: ExcalidrawLinearElement, cx: number, cy: number, + elementsMap: ElementsMap, ): Bounds => { + const boundTextElement = getBoundTextElement(element, elementsMap); + if (element.points.length < 2) { const [pointX, pointY] = element.points[0]; const [x, y] = rotate( @@ -689,7 +700,6 @@ const getLinearElementRotatedBounds = ( ); let coords: Bounds = [x, y, x, y]; - const boundTextElement = getBoundTextElement(element); if (boundTextElement) { const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText( element, @@ -714,7 +724,6 @@ const getLinearElementRotatedBounds = ( rotate(element.x + x, element.y + y, cx, cy, element.angle); const res = getMinMaxXYFromCurvePathOps(ops, transformXY); let coords: Bounds = [res[0], res[1], res[2], res[3]]; - const boundTextElement = getBoundTextElement(element); if (boundTextElement) { const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText( element, diff --git a/packages/excalidraw/element/collision.ts b/packages/excalidraw/element/collision.ts index 709781b22..b8c07e3ab 100644 --- a/packages/excalidraw/element/collision.ts +++ b/packages/excalidraw/element/collision.ts @@ -28,6 +28,7 @@ import { StrokeRoundness, ExcalidrawFrameLikeElement, ExcalidrawIframeLikeElement, + ElementsMap, } from "./types"; import { @@ -78,6 +79,7 @@ export const hitTest = ( frameNameBoundsCache: FrameNameBoundsCache, x: number, y: number, + elementsMap: ElementsMap, ): boolean => { // How many pixels off the shape boundary we still consider a hit const threshold = 10 / appState.zoom.value; @@ -95,7 +97,7 @@ export const hitTest = ( ); } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { const isHittingBoundTextElement = hitTest( boundTextElement, @@ -103,6 +105,7 @@ export const hitTest = ( frameNameBoundsCache, x, y, + elementsMap, ); if (isHittingBoundTextElement) { return true; @@ -122,15 +125,16 @@ export const isHittingElementBoundingBoxWithoutHittingElement = ( frameNameBoundsCache: FrameNameBoundsCache, x: number, y: number, + elementsMap: ElementsMap, ): boolean => { const threshold = 10 / appState.zoom.value; // So that bound text element hit is considered within bounding box of container even if its outside actual bounding box of element // eg for linear elements text can be outside the element bounding box - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if ( boundTextElement && - hitTest(boundTextElement, appState, frameNameBoundsCache, x, y) + hitTest(boundTextElement, appState, frameNameBoundsCache, x, y, elementsMap) ) { return false; } diff --git a/packages/excalidraw/element/dragElements.ts b/packages/excalidraw/element/dragElements.ts index ecec4d083..0144f55a4 100644 --- a/packages/excalidraw/element/dragElements.ts +++ b/packages/excalidraw/element/dragElements.ts @@ -57,7 +57,10 @@ export const dragSelectedElements = ( // skip arrow labels since we calculate its position during render !isArrowElement(element) ) { - const textElement = getBoundTextElement(element); + const textElement = getBoundTextElement( + element, + scene.getNonDeletedElementsMap(), + ); if (textElement) { updateElementCoords(pointerDownState, textElement, adjustedOffset); } diff --git a/packages/excalidraw/element/linearElementEditor.ts b/packages/excalidraw/element/linearElementEditor.ts index bf64ee732..5c3c6acaa 100644 --- a/packages/excalidraw/element/linearElementEditor.ts +++ b/packages/excalidraw/element/linearElementEditor.ts @@ -5,6 +5,7 @@ import { PointBinding, ExcalidrawBindableElement, ExcalidrawTextElementWithContainer, + ElementsMap, } from "./types"; import { distance2d, @@ -193,6 +194,7 @@ export class LinearElementEditor { pointSceneCoords: { x: number; y: number }[], ) => void, linearElementEditor: LinearElementEditor, + elementsMap: ElementsMap, ): boolean { if (!linearElementEditor) { return false; @@ -272,9 +274,9 @@ export class LinearElementEditor { ); } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { - handleBindTextResize(element, false); + handleBindTextResize(element, elementsMap, false); } // suggest bindings for first and last point if selected @@ -404,9 +406,10 @@ export class LinearElementEditor { static getEditorMidPoints = ( element: NonDeleted, + elementsMap: ElementsMap, appState: InteractiveCanvasAppState, ): typeof editorMidPointsCache["points"] => { - const boundText = getBoundTextElement(element); + const boundText = getBoundTextElement(element, elementsMap); // Since its not needed outside editor unless 2 pointer lines or bound text if ( @@ -465,6 +468,7 @@ export class LinearElementEditor { linearElementEditor: LinearElementEditor, scenePointer: { x: number; y: number }, appState: AppState, + elementsMap: ElementsMap, ) => { const { elementId } = linearElementEditor; const element = LinearElementEditor.getElement(elementId); @@ -503,7 +507,7 @@ export class LinearElementEditor { } let index = 0; const midPoints: typeof editorMidPointsCache["points"] = - LinearElementEditor.getEditorMidPoints(element, appState); + LinearElementEditor.getEditorMidPoints(element, elementsMap, appState); while (index < midPoints.length) { if (midPoints[index] !== null) { const distance = distance2d( @@ -581,6 +585,7 @@ export class LinearElementEditor { linearElementEditor: LinearElementEditor, appState: AppState, midPoint: Point, + elementsMap: ElementsMap, ) { const element = LinearElementEditor.getElement( linearElementEditor.elementId, @@ -588,7 +593,11 @@ export class LinearElementEditor { if (!element) { return -1; } - const midPoints = LinearElementEditor.getEditorMidPoints(element, appState); + const midPoints = LinearElementEditor.getEditorMidPoints( + element, + elementsMap, + appState, + ); let index = 0; while (index < midPoints.length) { if (LinearElementEditor.arePointsEqual(midPoint, midPoints[index])) { @@ -605,6 +614,7 @@ export class LinearElementEditor { history: History, scenePointer: { x: number; y: number }, linearElementEditor: LinearElementEditor, + elementsMap: ElementsMap, ): { didAddPoint: boolean; hitElement: NonDeleted | null; @@ -630,6 +640,7 @@ export class LinearElementEditor { linearElementEditor, scenePointer, appState, + elementsMap, ); let segmentMidpointIndex = null; if (segmentMidpoint) { @@ -637,6 +648,7 @@ export class LinearElementEditor { linearElementEditor, appState, segmentMidpoint, + elementsMap, ); } if (event.altKey && appState.editingLinearElement) { @@ -1418,6 +1430,7 @@ export class LinearElementEditor { static getElementAbsoluteCoords = ( element: ExcalidrawLinearElement, + elementsMap: ElementsMap, includeBoundText: boolean = false, ): [number, number, number, number, number, number] => { let coords: [number, number, number, number, number, number]; @@ -1462,7 +1475,7 @@ export class LinearElementEditor { if (!includeBoundText) { return coords; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { coords = LinearElementEditor.getMinMaxXYWithBoundText( element, diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index 3158c064c..447a07993 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -342,7 +342,7 @@ export const refreshTextDimensions = ( text = wrapText( text, getFontString(textElement), - getBoundTextMaxWidth(container), + getBoundTextMaxWidth(container, textElement), ); } const dimensions = getAdjustedDimensions(textElement, text); diff --git a/packages/excalidraw/element/resizeElements.ts b/packages/excalidraw/element/resizeElements.ts index 46b891aca..deb5fead3 100644 --- a/packages/excalidraw/element/resizeElements.ts +++ b/packages/excalidraw/element/resizeElements.ts @@ -126,6 +126,7 @@ export const transformElements = ( rotateMultipleElements( originalElements, selectedElements, + elementsMap, pointerX, pointerY, shouldRotateWithDiscreteAngle, @@ -219,7 +220,7 @@ const measureFontSizeFromWidth = ( if (hasContainer) { const container = getContainerElement(element, elementsMap); if (container) { - width = getBoundTextMaxWidth(container); + width = getBoundTextMaxWidth(container, element); } } const nextFontSize = element.fontSize * (nextWidth / width); @@ -394,7 +395,7 @@ export const resizeSingleElement = ( let scaleY = atStartBoundsHeight / boundsCurrentHeight; let boundTextFont: { fontSize?: number; baseline?: number } = {}; - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (transformHandleDirection.includes("e")) { scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth; @@ -458,7 +459,7 @@ export const resizeSingleElement = ( const nextFont = measureFontSizeFromWidth( boundTextElement, elementsMap, - getBoundTextMaxWidth(updatedElement), + getBoundTextMaxWidth(updatedElement, boundTextElement), getBoundTextMaxHeight(updatedElement, boundTextElement), ); if (nextFont === null) { @@ -640,6 +641,7 @@ export const resizeSingleElement = ( } handleBindTextResize( element, + elementsMap, transformHandleDirection, shouldMaintainAspectRatio, ); @@ -882,7 +884,7 @@ export const resizeMultipleElements = ( newSize: { width, height }, }); - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement && boundTextFontSize) { mutateElement( boundTextElement, @@ -892,7 +894,7 @@ export const resizeMultipleElements = ( }, false, ); - handleBindTextResize(element, transformHandleType, true); + handleBindTextResize(element, elementsMap, transformHandleType, true); } } @@ -902,6 +904,7 @@ export const resizeMultipleElements = ( const rotateMultipleElements = ( originalElements: PointerDownState["originalElements"], elements: readonly NonDeletedExcalidrawElement[], + elementsMap: ElementsMap, pointerX: number, pointerY: number, shouldRotateWithDiscreteAngle: boolean, @@ -941,7 +944,7 @@ const rotateMultipleElements = ( ); updateBoundElements(element, { simultaneouslyUpdated: elements }); - const boundText = getBoundTextElement(element); + const boundText = getBoundTextElement(element, elementsMap); if (boundText && !isArrowElement(element)) { mutateElement( boundText, diff --git a/packages/excalidraw/element/textElement.test.ts b/packages/excalidraw/element/textElement.test.ts index b6221336d..2f3a2dcc7 100644 --- a/packages/excalidraw/element/textElement.test.ts +++ b/packages/excalidraw/element/textElement.test.ts @@ -319,17 +319,17 @@ describe("Test measureText", () => { it("should return max width when container is rectangle", () => { const container = API.createElement({ type: "rectangle", ...params }); - expect(getBoundTextMaxWidth(container)).toBe(168); + expect(getBoundTextMaxWidth(container, null)).toBe(168); }); it("should return max width when container is ellipse", () => { const container = API.createElement({ type: "ellipse", ...params }); - expect(getBoundTextMaxWidth(container)).toBe(116); + expect(getBoundTextMaxWidth(container, null)).toBe(116); }); it("should return max width when container is diamond", () => { const container = API.createElement({ type: "diamond", ...params }); - expect(getBoundTextMaxWidth(container)).toBe(79); + expect(getBoundTextMaxWidth(container, null)).toBe(79); }); }); diff --git a/packages/excalidraw/element/textElement.ts b/packages/excalidraw/element/textElement.ts index da1348ec2..b264c0d59 100644 --- a/packages/excalidraw/element/textElement.ts +++ b/packages/excalidraw/element/textElement.ts @@ -23,7 +23,6 @@ import { VERTICAL_ALIGN, } from "../constants"; import { MaybeTransformHandleType } from "./transformHandles"; -import Scene from "../scene/Scene"; import { isTextElement } from "."; import { isBoundToContainer, isArrowElement } from "./typeChecks"; import { LinearElementEditor } from "./linearElementEditor"; @@ -89,7 +88,7 @@ export const redrawTextBoundingBox = ( container, textElement as ExcalidrawTextElementWithContainer, ); - const maxContainerWidth = getBoundTextMaxWidth(container); + const maxContainerWidth = getBoundTextMaxWidth(container, textElement); if (!isArrowElement(container) && metrics.height > maxContainerHeight) { const nextHeight = computeContainerDimensionForBoundText( @@ -162,6 +161,7 @@ export const bindTextToShapeAfterDuplication = ( export const handleBindTextResize = ( container: NonDeletedExcalidrawElement, + elementsMap: ElementsMap, transformHandleType: MaybeTransformHandleType, shouldMaintainAspectRatio = false, ) => { @@ -170,25 +170,17 @@ export const handleBindTextResize = ( return; } resetOriginalContainerCache(container.id); - let textElement = Scene.getScene(container)!.getElement( - boundTextElementId, - ) as ExcalidrawTextElement; + const textElement = getBoundTextElement(container, elementsMap); if (textElement && textElement.text) { if (!container) { return; } - textElement = Scene.getScene(container)!.getElement( - boundTextElementId, - ) as ExcalidrawTextElement; let text = textElement.text; let nextHeight = textElement.height; let nextWidth = textElement.width; - const maxWidth = getBoundTextMaxWidth(container); - const maxHeight = getBoundTextMaxHeight( - container, - textElement as ExcalidrawTextElementWithContainer, - ); + const maxWidth = getBoundTextMaxWidth(container, textElement); + const maxHeight = getBoundTextMaxHeight(container, textElement); let containerHeight = container.height; let nextBaseLine = textElement.baseline; if ( @@ -243,10 +235,7 @@ export const handleBindTextResize = ( if (!isArrowElement(container)) { mutateElement( textElement, - computeBoundTextPosition( - container, - textElement as ExcalidrawTextElementWithContainer, - ), + computeBoundTextPosition(container, textElement), ); } } @@ -264,7 +253,7 @@ export const computeBoundTextPosition = ( } const containerCoords = getContainerCoords(container); const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement); - const maxContainerWidth = getBoundTextMaxWidth(container); + const maxContainerWidth = getBoundTextMaxWidth(container, boundTextElement); let x; let y; @@ -667,17 +656,18 @@ export const getBoundTextElementId = (container: ExcalidrawElement | null) => { : null; }; -export const getBoundTextElement = (element: ExcalidrawElement | null) => { +export const getBoundTextElement = ( + element: ExcalidrawElement | null, + elementsMap: ElementsMap, +) => { if (!element) { return null; } const boundTextElementId = getBoundTextElementId(element); + if (boundTextElementId) { - return ( - (Scene.getScene(element)?.getElement( - boundTextElementId, - ) as ExcalidrawTextElementWithContainer) || null - ); + return (elementsMap.get(boundTextElementId) || + null) as ExcalidrawTextElementWithContainer | null; } return null; }; @@ -699,6 +689,7 @@ export const getContainerElement = ( export const getContainerCenter = ( container: ExcalidrawElement, appState: AppState, + elementsMap: ElementsMap, ) => { if (!isArrowElement(container)) { return { @@ -718,6 +709,7 @@ export const getContainerCenter = ( const index = container.points.length / 2 - 1; let midSegmentMidpoint = LinearElementEditor.getEditorMidPoints( container, + elementsMap, appState, )[index]; if (!midSegmentMidpoint) { @@ -877,9 +869,7 @@ export const computeContainerDimensionForBoundText = ( export const getBoundTextMaxWidth = ( container: ExcalidrawElement, - boundTextElement: ExcalidrawTextElement | null = getBoundTextElement( - container, - ), + boundTextElement: ExcalidrawTextElement | null, ) => { const { width } = container; if (isArrowElement(container)) { diff --git a/packages/excalidraw/element/textWysiwyg.tsx b/packages/excalidraw/element/textWysiwyg.tsx index 801f0c440..d12d34f89 100644 --- a/packages/excalidraw/element/textWysiwyg.tsx +++ b/packages/excalidraw/element/textWysiwyg.tsx @@ -34,6 +34,7 @@ import { computeContainerDimensionForBoundText, detectLineHeight, computeBoundTextPosition, + getBoundTextElement, } from "./textElement"; import { actionDecreaseFontSize, @@ -196,7 +197,8 @@ export const textWysiwyg = ({ } } - maxWidth = getBoundTextMaxWidth(container); + maxWidth = getBoundTextMaxWidth(container, updatedTextElement); + maxHeight = getBoundTextMaxHeight( container, updatedTextElement as ExcalidrawTextElementWithContainer, @@ -361,10 +363,14 @@ export const textWysiwyg = ({ fontFamily: app.state.currentItemFontFamily, }); if (container) { + const boundTextElement = getBoundTextElement( + container, + app.scene.getNonDeletedElementsMap(), + ); const wrappedText = wrapText( `${editable.value}${data}`, font, - getBoundTextMaxWidth(container), + getBoundTextMaxWidth(container, boundTextElement), ); const width = getTextWidth(wrappedText, font); editable.style.width = `${width}px`; diff --git a/packages/excalidraw/element/types.ts b/packages/excalidraw/element/types.ts index 7659ad1e9..f89e8d5f2 100644 --- a/packages/excalidraw/element/types.ts +++ b/packages/excalidraw/element/types.ts @@ -279,6 +279,16 @@ export type NonDeletedElementsMap = Map< export type SceneElementsMap = Map & MakeBrand<"SceneElementsMap">; +/** + * Map of all non-deleted Scene elements. + * Not a subset. Use this type when you need access to current Scene elements. + */ +export type NonDeletedSceneElementsMap = Map< + ExcalidrawElement["id"], + NonDeletedExcalidrawElement +> & + MakeBrand<"NonDeletedSceneElementsMap">; + export type ElementsMapOrArray = | readonly ExcalidrawElement[] | Readonly; diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index ecb70ef1e..1457c4ecf 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -444,6 +444,7 @@ export const addElementsToFrame = ( elementsToAdd: NonDeletedExcalidrawElement[], frame: ExcalidrawFrameLikeElement, ): T => { + const elementsMap = arrayToMap(allElements); const currTargetFrameChildrenMap = new Map(); for (const element of allElements.values()) { if (element.frameId === frame.id) { @@ -481,7 +482,7 @@ export const addElementsToFrame = ( finalElementsToAdd.push(element); } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if ( boundTextElement && !suppliedElementsToAddSet.has(boundTextElement.id) && @@ -506,6 +507,7 @@ export const addElementsToFrame = ( export const removeElementsFromFrame = ( elementsToRemove: ReadonlySetLike, + elementsMap: ElementsMap, ) => { const _elementsToRemove = new Map< ExcalidrawElement["id"], @@ -524,7 +526,7 @@ export const removeElementsFromFrame = ( const arr = toRemoveElementsByFrame.get(element.frameId) || []; arr.push(element); - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { _elementsToRemove.set(boundTextElement.id, boundTextElement); arr.push(boundTextElement); @@ -550,7 +552,7 @@ export const removeAllElementsFromFrame = ( frame: ExcalidrawFrameLikeElement, ) => { const elementsInFrame = getFrameChildren(allElements, frame.id); - removeElementsFromFrame(elementsInFrame); + removeElementsFromFrame(elementsInFrame, arrayToMap(allElements)); return allElements; }; @@ -558,6 +560,7 @@ export const replaceAllElementsInFrame = ( allElements: readonly T[], nextElementsInFrame: ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, + app: AppClassProperties, ): T[] => { return addElementsToFrame( removeAllElementsFromFrame(allElements, frame), @@ -608,7 +611,7 @@ export const updateFrameMembershipOfSelectedElements = < }); if (elementsToRemove.size > 0) { - removeElementsFromFrame(elementsToRemove); + removeElementsFromFrame(elementsToRemove, elementsMap); } return allElements; }; diff --git a/packages/excalidraw/groups.ts b/packages/excalidraw/groups.ts index b0bedc4f9..f8c0eddb9 100644 --- a/packages/excalidraw/groups.ts +++ b/packages/excalidraw/groups.ts @@ -4,6 +4,7 @@ import { NonDeleted, NonDeletedExcalidrawElement, ElementsMapOrArray, + ElementsMap, } from "./element/types"; import { AppClassProperties, @@ -329,12 +330,12 @@ export const removeFromSelectedGroups = ( export const getMaximumGroups = ( elements: ExcalidrawElement[], + elementsMap: ElementsMap, ): ExcalidrawElement[][] => { const groups: Map = new Map< String, ExcalidrawElement[] >(); - elements.forEach((element: ExcalidrawElement) => { const groupId = element.groupIds.length === 0 @@ -344,7 +345,7 @@ export const getMaximumGroups = ( const currentGroupMembers = groups.get(groupId) || []; // Include bound text if present when grouping - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { currentGroupMembers.push(boundTextElement); } diff --git a/packages/excalidraw/renderer/renderElement.ts b/packages/excalidraw/renderer/renderElement.ts index 39e6c4974..5ab3f3ca5 100644 --- a/packages/excalidraw/renderer/renderElement.ts +++ b/packages/excalidraw/renderer/renderElement.ts @@ -6,6 +6,7 @@ import { ExcalidrawImageElement, ExcalidrawTextElementWithContainer, ExcalidrawFrameLikeElement, + NonDeletedSceneElementsMap, } from "../element/types"; import { isTextElement, @@ -190,6 +191,7 @@ const cappedElementCanvasSize = ( const generateElementCanvas = ( element: NonDeletedExcalidrawElement, + elementsMap: RenderableElementsMap, zoom: Zoom, renderConfig: StaticCanvasRenderConfig, appState: StaticCanvasAppState, @@ -247,7 +249,8 @@ const generateElementCanvas = ( zoomValue: zoom.value, canvasOffsetX, canvasOffsetY, - boundTextElementVersion: getBoundTextElement(element)?.version || null, + boundTextElementVersion: + getBoundTextElement(element, elementsMap)?.version || null, containingFrameOpacity: getContainingFrame(element)?.opacity || 100, }; }; @@ -407,6 +410,7 @@ export const elementWithCanvasCache = new WeakMap< const generateElementWithCanvas = ( element: NonDeletedExcalidrawElement, + elementsMap: RenderableElementsMap, renderConfig: StaticCanvasRenderConfig, appState: StaticCanvasAppState, ) => { @@ -416,7 +420,9 @@ const generateElementWithCanvas = ( prevElementWithCanvas && prevElementWithCanvas.zoomValue !== zoom.value && !appState?.shouldCacheIgnoreZoom; - const boundTextElementVersion = getBoundTextElement(element)?.version || null; + const boundTextElementVersion = + getBoundTextElement(element, elementsMap)?.version || null; + const containingFrameOpacity = getContainingFrame(element)?.opacity || 100; if ( @@ -428,6 +434,7 @@ const generateElementWithCanvas = ( ) { const elementWithCanvas = generateElementCanvas( element, + elementsMap, zoom, renderConfig, appState, @@ -445,6 +452,7 @@ const drawElementFromCanvas = ( context: CanvasRenderingContext2D, renderConfig: StaticCanvasRenderConfig, appState: StaticCanvasAppState, + allElementsMap: NonDeletedSceneElementsMap, ) => { const element = elementWithCanvas.element; const padding = getCanvasPadding(element); @@ -464,7 +472,8 @@ const drawElementFromCanvas = ( context.save(); context.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio); - const boundTextElement = getBoundTextElement(element); + + const boundTextElement = getBoundTextElement(element, allElementsMap); if (isArrowElement(element) && boundTextElement) { const tempCanvas = document.createElement("canvas"); @@ -511,7 +520,6 @@ const drawElementFromCanvas = ( offsetY - padding * zoom; tempCanvasContext.translate(-shiftX, -shiftY); - // Clear the bound text area tempCanvasContext.clearRect( -(boundTextElement.width / 2 + BOUND_TEXT_PADDING) * @@ -573,6 +581,7 @@ const drawElementFromCanvas = ( ) { const textElement = getBoundTextElement( element, + allElementsMap, ) as ExcalidrawTextElementWithContainer; const coords = getContainerCoords(element); context.strokeStyle = "#c92a2a"; @@ -580,7 +589,7 @@ const drawElementFromCanvas = ( context.strokeRect( (coords.x + appState.scrollX) * window.devicePixelRatio, (coords.y + appState.scrollY) * window.devicePixelRatio, - getBoundTextMaxWidth(element) * window.devicePixelRatio, + getBoundTextMaxWidth(element, textElement) * window.devicePixelRatio, getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio, ); } @@ -616,6 +625,7 @@ export const renderSelectionElement = ( export const renderElement = ( element: NonDeletedExcalidrawElement, elementsMap: RenderableElementsMap, + allElementsMap: NonDeletedSceneElementsMap, rc: RoughCanvas, context: CanvasRenderingContext2D, renderConfig: StaticCanvasRenderConfig, @@ -687,6 +697,7 @@ export const renderElement = ( } else { const elementWithCanvas = generateElementWithCanvas( element, + elementsMap, renderConfig, appState, ); @@ -695,6 +706,7 @@ export const renderElement = ( context, renderConfig, appState, + allElementsMap, ); } @@ -737,7 +749,7 @@ export const renderElement = ( if (shouldResetImageFilter(element, renderConfig, appState)) { context.filter = "none"; } - const boundTextElement = getBoundTextElement(element); + const boundTextElement = getBoundTextElement(element, elementsMap); if (isArrowElement(element) && boundTextElement) { const tempCanvas = document.createElement("canvas"); @@ -820,6 +832,7 @@ export const renderElement = ( } else { const elementWithCanvas = generateElementWithCanvas( element, + elementsMap, renderConfig, appState, ); @@ -851,6 +864,7 @@ export const renderElement = ( context, renderConfig, appState, + allElementsMap, ); // reset @@ -1096,7 +1110,7 @@ export const renderElementToSvg = ( } case "line": case "arrow": { - const boundText = getBoundTextElement(element); + const boundText = getBoundTextElement(element, elementsMap); const maskPath = svgRoot.ownerDocument!.createElementNS(SVG_NS, "mask"); if (boundText) { maskPath.setAttribute("id", `mask-${element.id}`); diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index 6c358591b..0fa56829f 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -246,6 +246,7 @@ const renderLinearPointHandles = ( context: CanvasRenderingContext2D, appState: InteractiveCanvasAppState, element: NonDeleted, + elementsMap: RenderableElementsMap, ) => { if (!appState.selectedLinearElement) { return; @@ -269,6 +270,7 @@ const renderLinearPointHandles = ( //Rendering segment mid points const midPoints = LinearElementEditor.getEditorMidPoints( element, + elementsMap, appState, ).filter((midPoint) => midPoint !== null) as Point[]; @@ -485,7 +487,12 @@ const _renderInteractiveScene = ({ }); if (editingLinearElement) { - renderLinearPointHandles(context, appState, editingLinearElement); + renderLinearPointHandles( + context, + appState, + editingLinearElement, + elementsMap, + ); } // Paint selection element @@ -528,6 +535,7 @@ const _renderInteractiveScene = ({ context, appState, selectedElements[0] as NonDeleted, + elementsMap, ); } @@ -553,6 +561,7 @@ const _renderInteractiveScene = ({ context, appState, selectedElements[0] as ExcalidrawLinearElement, + elementsMap, ); } const selectionColor = renderConfig.selectionColor || oc.black; @@ -891,6 +900,7 @@ const _renderStaticScene = ({ canvas, rc, elementsMap, + allElementsMap, visibleElements, scale, appState, @@ -972,6 +982,7 @@ const _renderStaticScene = ({ renderElement( element, elementsMap, + allElementsMap, rc, context, renderConfig, @@ -982,6 +993,7 @@ const _renderStaticScene = ({ renderElement( element, elementsMap, + allElementsMap, rc, context, renderConfig, @@ -1005,6 +1017,7 @@ const _renderStaticScene = ({ renderElement( element, elementsMap, + allElementsMap, rc, context, renderConfig, @@ -1024,6 +1037,7 @@ const _renderStaticScene = ({ renderElement( label, elementsMap, + allElementsMap, rc, context, renderConfig, diff --git a/packages/excalidraw/scene/Scene.ts b/packages/excalidraw/scene/Scene.ts index 326f98c7f..88c3d8996 100644 --- a/packages/excalidraw/scene/Scene.ts +++ b/packages/excalidraw/scene/Scene.ts @@ -4,8 +4,8 @@ import { NonDeleted, ExcalidrawFrameLikeElement, ElementsMapOrArray, - NonDeletedElementsMap, SceneElementsMap, + NonDeletedSceneElementsMap, } from "../element/types"; import { isNonDeletedElement } from "../element"; import { LinearElementEditor } from "../element/linearElementEditor"; @@ -27,7 +27,7 @@ type SelectionHash = string & { __brand: "selectionHash" }; const getNonDeletedElements = ( allElements: readonly T[], ) => { - const elementsMap = new Map() as NonDeletedElementsMap; + const elementsMap = new Map() as NonDeletedSceneElementsMap; const elements: T[] = []; for (const element of allElements) { if (!element.isDeleted) { @@ -120,8 +120,9 @@ class Scene { private callbacks: Set = new Set(); private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = []; - private nonDeletedElementsMap: NonDeletedElementsMap = - new Map() as NonDeletedElementsMap; + private nonDeletedElementsMap = toBrandedType( + new Map(), + ); private elements: readonly ExcalidrawElement[] = []; private nonDeletedFramesLikes: readonly NonDeleted[] = []; diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index 9f1f12a22..d463e2597 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -4,6 +4,7 @@ import { ExcalidrawFrameLikeElement, ExcalidrawTextElement, NonDeletedExcalidrawElement, + NonDeletedSceneElementsMap, } from "../element/types"; import { Bounds, @@ -248,14 +249,15 @@ export const exportToCanvas = async ( files, }); - const elementsMap = toBrandedType( - arrayToMap(elementsForRender), - ); - renderStaticScene({ canvas, rc: rough.canvas(canvas), - elementsMap, + elementsMap: toBrandedType( + arrayToMap(elementsForRender), + ), + allElementsMap: toBrandedType( + arrayToMap(elements), + ), visibleElements: elementsForRender, scale, appState: { diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index 957b080b3..02aa3b7bf 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -4,6 +4,7 @@ import { ExcalidrawTextElement, NonDeletedElementsMap, NonDeletedExcalidrawElement, + NonDeletedSceneElementsMap, } from "../element/types"; import { AppClassProperties, @@ -66,6 +67,7 @@ export type StaticSceneRenderConfig = { canvas: HTMLCanvasElement; rc: RoughCanvas; elementsMap: RenderableElementsMap; + allElementsMap: NonDeletedSceneElementsMap; visibleElements: readonly NonDeletedExcalidrawElement[]; scale: number; appState: StaticCanvasAppState; diff --git a/packages/excalidraw/snapping.ts b/packages/excalidraw/snapping.ts index e7ff9b787..7557145ae 100644 --- a/packages/excalidraw/snapping.ts +++ b/packages/excalidraw/snapping.ts @@ -16,6 +16,7 @@ import { KEYS } from "./keys"; import { rangeIntersection, rangesOverlap, rotatePoint } from "./math"; import { getVisibleAndNonSelectedElements } from "./scene/selection"; import { AppState, KeyboardModifiersObject, Point } from "./types"; +import { arrayToMap } from "./utils"; const SNAP_DISTANCE = 8; @@ -286,7 +287,10 @@ export const getVisibleGaps = ( appState, ); - const referenceBounds = getMaximumGroups(referenceElements) + const referenceBounds = getMaximumGroups( + referenceElements, + arrayToMap(elements), + ) .filter( (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])), @@ -572,7 +576,7 @@ export const getReferenceSnapPoints = ( appState, ); - return getMaximumGroups(referenceElements) + return getMaximumGroups(referenceElements, arrayToMap(elements)) .filter( (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])), diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index f4ddeafd2..ce0e1c856 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -24,6 +24,7 @@ import { import * as textElementUtils from "../element/textElement"; import { ROUNDNESS, VERTICAL_ALIGN } from "../constants"; import { vi } from "vitest"; +import { arrayToMap } from "../utils"; const renderInteractiveScene = vi.spyOn(Renderer, "renderInteractiveScene"); const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene"); @@ -307,6 +308,7 @@ describe("Test Linear Elements", () => { const midPointsWithSharpEdge = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); @@ -320,6 +322,7 @@ describe("Test Linear Elements", () => { const midPointsWithRoundEdge = LinearElementEditor.getEditorMidPoints( h.elements[0] as ExcalidrawLinearElement, + h.app.scene.getNonDeletedElementsMap(), h.state, ); expect(midPointsWithRoundEdge[0]).not.toEqual(midPointsWithSharpEdge[0]); @@ -351,7 +354,11 @@ describe("Test Linear Elements", () => { const points = LinearElementEditor.getPointsGlobalCoordinates(line); expect([line.x, line.y]).toEqual(points[0]); - const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + const midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const startPoint = centerPoint(points[0], midPoints[0] as Point); const deltaX = 50; @@ -373,6 +380,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); expect(midPoints[0]).not.toEqual(newMidPoints[0]); @@ -458,7 +466,11 @@ describe("Test Linear Elements", () => { 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 midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const hitCoords: Point = [points[0][0], points[0][1]]; @@ -478,6 +490,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); @@ -487,7 +500,11 @@ describe("Test Linear Elements", () => { 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 midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const hitCoords: Point = [points[0][0], points[0][1]]; @@ -507,6 +524,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); // This midpoint is hidden since the points are too close @@ -526,7 +544,11 @@ describe("Test Linear Elements", () => { ]); expect(line.points.length).toEqual(4); - const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + const midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); // delete 3rd point deletePoint(points[2]); @@ -538,6 +560,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); expect(newMidPoints.length).toEqual(2); @@ -615,7 +638,11 @@ describe("Test Linear Elements", () => { 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 midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const hitCoords: Point = [points[0][0], points[0][1]]; @@ -630,6 +657,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); @@ -651,7 +679,11 @@ describe("Test Linear Elements", () => { 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 midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const hitCoords: Point = [points[0][0], points[0][1]]; @@ -671,6 +703,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); // This mid point is hidden due to point being too close @@ -685,7 +718,11 @@ describe("Test Linear Elements", () => { ]); expect(line.points.length).toEqual(4); - const midPoints = LinearElementEditor.getEditorMidPoints(line, h.state); + const midPoints = LinearElementEditor.getEditorMidPoints( + line, + h.app.scene.getNonDeletedElementsMap(), + h.state, + ); const points = LinearElementEditor.getPointsGlobalCoordinates(line); // delete 3rd point @@ -694,6 +731,7 @@ describe("Test Linear Elements", () => { const newMidPoints = LinearElementEditor.getEditorMidPoints( line, + h.app.scene.getNonDeletedElementsMap(), h.state, ); expect(newMidPoints.length).toEqual(2); @@ -762,7 +800,7 @@ describe("Test Linear Elements", () => { type: "text", x: 0, y: 0, - text: wrapText(text, font, getBoundTextMaxWidth(container)), + text: wrapText(text, font, getBoundTextMaxWidth(container, null)), containerId: container.id, width: 30, height: 20, @@ -986,8 +1024,13 @@ describe("Test Linear Elements", () => { collaboration made easy" `); - expect(LinearElementEditor.getElementAbsoluteCoords(container, true)) - .toMatchInlineSnapshot(` + expect( + LinearElementEditor.getElementAbsoluteCoords( + container, + h.app.scene.getNonDeletedElementsMap(), + true, + ), + ).toMatchInlineSnapshot(` [ 20, 20, @@ -1020,8 +1063,13 @@ describe("Test Linear Elements", () => { "Online whiteboard collaboration made easy" `); - expect(LinearElementEditor.getElementAbsoluteCoords(container, true)) - .toMatchInlineSnapshot(` + expect( + LinearElementEditor.getElementAbsoluteCoords( + container, + h.app.scene.getNonDeletedElementsMap(), + true, + ), + ).toMatchInlineSnapshot(` [ 20, 35, @@ -1121,7 +1169,11 @@ describe("Test Linear Elements", () => { expect(rect.x).toBe(400); expect(rect.y).toBe(0); expect( - wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), + wrapText( + textElement.originalText, + font, + getBoundTextMaxWidth(arrow, null), + ), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made easy" @@ -1140,11 +1192,17 @@ describe("Test Linear Elements", () => { expect(rect.x).toBe(200); expect(rect.y).toBe(0); expect(handleBindTextResizeSpy).toHaveBeenCalledWith( - h.elements[1], + h.elements[0], + arrayToMap(h.elements), + "nw", false, ); expect( - wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), + wrapText( + textElement.originalText, + font, + getBoundTextMaxWidth(arrow, null), + ), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made From 626fe252ab0c2d0cb295c849b887bd8c76133f40 Mon Sep 17 00:00:00 2001 From: Andran1k <91144891+Andran1k@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:57:22 +0400 Subject: [PATCH 63/79] fix: frame name field (#7457) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/components/App.tsx | 6 ++---- packages/excalidraw/frame.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 30f86c24e..71bfaa5d5 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1299,10 +1299,7 @@ class App extends React.Component { const FRAME_NAME_EDIT_PADDING = 6; const reset = () => { - if (f.name?.trim() === "") { - mutateElement(f, { name: null }); - } - + mutateElement(f, { name: f.name?.trim() || null }); this.setState({ editingFrame: null }); }; @@ -1325,6 +1322,7 @@ class App extends React.Component { name: e.target.value, }); }} + onFocus={(e) => e.target.select()} onBlur={() => reset()} onKeyDown={(event) => { // for some inexplicable reason, `onBlur` triggered on ESC diff --git a/packages/excalidraw/frame.ts b/packages/excalidraw/frame.ts index 1457c4ecf..c4a5a259d 100644 --- a/packages/excalidraw/frame.ts +++ b/packages/excalidraw/frame.ts @@ -746,7 +746,7 @@ export const getFrameLikeTitle = ( element: ExcalidrawFrameLikeElement, frameIdx: number, ) => { - // TODO name frames AI only is specific to AI frames + // TODO name frames "AI" only if specific to AI frames return element.name === null ? isFrameElement(element) ? `Frame ${frameIdx}` From 2409c091fff0bd359c003e3e366de1834d0b7c92 Mon Sep 17 00:00:00 2001 From: Aashir Israr <63807168+aashirisrar@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:27:07 +0500 Subject: [PATCH 64/79] feat: support roundness for images (#7558) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/element/typeChecks.ts | 5 ++- packages/excalidraw/renderer/renderElement.ts | 36 +++++++++++++++++++ packages/excalidraw/scene/comparisons.ts | 3 +- .../tests/__snapshots__/export.test.tsx.snap | 2 +- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/excalidraw/element/typeChecks.ts b/packages/excalidraw/element/typeChecks.ts index ef1bcd3db..7193e251b 100644 --- a/packages/excalidraw/element/typeChecks.ts +++ b/packages/excalidraw/element/typeChecks.ts @@ -214,7 +214,10 @@ export const isBoundToContainer = ( }; export const isUsingAdaptiveRadius = (type: string) => - type === "rectangle" || type === "embeddable" || type === "iframe"; + type === "rectangle" || + type === "embeddable" || + type === "iframe" || + type === "image"; export const isUsingProportionalRadius = (type: string) => type === "line" || type === "arrow" || type === "diamond"; diff --git a/packages/excalidraw/renderer/renderElement.ts b/packages/excalidraw/renderer/renderElement.ts index 5ab3f3ca5..de4bcfe53 100644 --- a/packages/excalidraw/renderer/renderElement.ts +++ b/packages/excalidraw/renderer/renderElement.ts @@ -344,6 +344,17 @@ const drawElementOnCanvas = ( ? renderConfig.imageCache.get(element.fileId)?.image : undefined; if (img != null && !(img instanceof Promise)) { + if (element.roundness && context.roundRect) { + context.beginPath(); + context.roundRect( + 0, + 0, + element.width, + element.height, + getCornerRadius(Math.min(element.width, element.height), element), + ); + context.clip(); + } context.drawImage( img, 0 /* hardcoded for the selection box*/, @@ -1301,6 +1312,31 @@ export const renderElementToSvg = ( }) rotate(${degree} ${cx} ${cy})`, ); + if (element.roundness) { + const clipPath = svgRoot.ownerDocument!.createElementNS( + SVG_NS, + "clipPath", + ); + clipPath.id = `image-clipPath-${element.id}`; + + const clipRect = svgRoot.ownerDocument!.createElementNS( + SVG_NS, + "rect", + ); + const radius = getCornerRadius( + Math.min(element.width, element.height), + element, + ); + clipRect.setAttribute("width", `${element.width}`); + clipRect.setAttribute("height", `${element.height}`); + clipRect.setAttribute("rx", `${radius}`); + clipRect.setAttribute("ry", `${radius}`); + clipPath.appendChild(clipRect); + addToRoot(clipPath, element); + + g.setAttributeNS(SVG_NS, "clip-path", `url(#${clipPath.id})`); + } + const clipG = maybeWrapNodesInFrameClipPath( element, root, diff --git a/packages/excalidraw/scene/comparisons.ts b/packages/excalidraw/scene/comparisons.ts index 551aa2e6e..cb14d5810 100644 --- a/packages/excalidraw/scene/comparisons.ts +++ b/packages/excalidraw/scene/comparisons.ts @@ -42,7 +42,8 @@ export const canChangeRoundness = (type: ElementOrToolType) => type === "embeddable" || type === "arrow" || type === "line" || - type === "diamond"; + type === "diamond" || + type === "image"; export const canHaveArrowheads = (type: ElementOrToolType) => type === "arrow"; diff --git a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap index 72b379b8a..57dff6c1c 100644 --- a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap @@ -21,5 +21,5 @@ exports[`export > exporting svg containing transformed images > svg export outpu - " + " `; From d426cc968d49071749c0d831490501cf572eb571 Mon Sep 17 00:00:00 2001 From: Milos Vetesnik Date: Mon, 29 Jan 2024 16:37:09 +0100 Subject: [PATCH 65/79] refactor: remove portal as it is no longer needed (#7623) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- .env.development | 5 +---- .env.production | 7 ++----- excalidraw-app/collab/Collab.tsx | 9 ++------- excalidraw-app/data/index.ts | 29 ---------------------------- excalidraw-app/tests/collab.test.tsx | 11 ----------- 5 files changed, 5 insertions(+), 56 deletions(-) diff --git a/.env.development b/.env.development index 44955884f..bab59ee07 100644 --- a/.env.development +++ b/.env.development @@ -5,10 +5,7 @@ VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries # collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room) -VITE_APP_WS_SERVER_URL=http://localhost:3002 - -# set this only if using the collaboration workflow we use on excalidraw.com -VITE_APP_PORTAL_URL= +VITE_APP_WS_SERVER_URL=http://localhost:3020 VITE_APP_PLUS_LP=https://plus.excalidraw.com VITE_APP_PLUS_APP=https://app.excalidraw.com diff --git a/.env.production b/.env.production index 26b46a52a..0c715854a 100644 --- a/.env.production +++ b/.env.production @@ -4,16 +4,13 @@ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/ VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries -VITE_APP_PORTAL_URL=https://portal.excalidraw.com - VITE_APP_PLUS_LP=https://plus.excalidraw.com VITE_APP_PLUS_APP=https://app.excalidraw.com VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com -# Fill to set socket server URL used for collaboration. -# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow -VITE_APP_WS_SERVER_URL= +# socket server URL used for collaboration +VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}' diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 92d94dbc9..267dee66c 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -36,7 +36,6 @@ import { import { generateCollaborationLinkData, getCollaborationLink, - getCollabServer, getSyncableElements, SocketUpdateDataSource, SyncableExcalidrawElement, @@ -452,13 +451,9 @@ class Collab extends PureComponent { this.fallbackInitializationHandler = fallbackInitializationHandler; try { - const socketServerData = await getCollabServer(); - this.portal.socket = this.portal.open( - socketIOClient(socketServerData.url, { - transports: socketServerData.polling - ? ["websocket", "polling"] - : ["websocket"], + socketIOClient(import.meta.env.VITE_APP_WS_SERVER_URL, { + transports: ["websocket", "polling"], }), roomId, roomKey, diff --git a/excalidraw-app/data/index.ts b/excalidraw-app/data/index.ts index 0f54ee880..5699568b4 100644 --- a/excalidraw-app/data/index.ts +++ b/excalidraw-app/data/index.ts @@ -65,35 +65,6 @@ const generateRoomId = async () => { return bytesToHexString(buffer); }; -/** - * Right now the reason why we resolve connection params (url, polling...) - * from upstream is to allow changing the params immediately when needed without - * having to wait for clients to update the SW. - * - * If REACT_APP_WS_SERVER_URL env is set, we use that instead (useful for forks) - */ -export const getCollabServer = async (): Promise<{ - url: string; - polling: boolean; -}> => { - if (import.meta.env.VITE_APP_WS_SERVER_URL) { - return { - url: import.meta.env.VITE_APP_WS_SERVER_URL, - polling: true, - }; - } - - try { - const resp = await fetch( - `${import.meta.env.VITE_APP_PORTAL_URL}/collab-server`, - ); - return await resp.json(); - } catch (error) { - console.error(error); - throw new Error(t("errors.cannotResolveCollabServer")); - } -}; - export type EncryptedData = { data: ArrayBuffer; iv: Uint8Array; diff --git a/excalidraw-app/tests/collab.test.tsx b/excalidraw-app/tests/collab.test.tsx index 455316aed..c3e94a5ef 100644 --- a/excalidraw-app/tests/collab.test.tsx +++ b/excalidraw-app/tests/collab.test.tsx @@ -20,17 +20,6 @@ Object.defineProperty(window, "crypto", { }, }); -vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => { - const module = (await importActual()) as any; - return { - __esmodule: true, - ...module, - getCollabServer: vi.fn(() => ({ - url: /* doesn't really matter */ "http://localhost:3002", - })), - }; -}); - vi.mock("../../excalidraw-app/data/firebase.ts", () => { const loadFromFirebase = async () => null; const saveToFirebase = () => {}; From e0fefa8025901ff73cb6b690bed3c73072c6f89a Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 31 Jan 2024 16:43:37 +0530 Subject: [PATCH 66/79] fix: don't bundle react-dom when importing from element (#7635) --- packages/excalidraw/components/App.tsx | 2 +- packages/excalidraw/element/index.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 71bfaa5d5..28daae36d 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -115,7 +115,6 @@ import { newLinearElement, newTextElement, newImageElement, - textWysiwyg, transformElements, updateTextElement, redrawTextBoundingBox, @@ -409,6 +408,7 @@ import { AnimatedTrail } from "../animated-trail"; import { LaserTrails } from "../laser-trails"; import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; import { getRenderOpacity } from "../renderer/renderElement"; +import { textWysiwyg } from "../element/textWysiwyg"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); diff --git a/packages/excalidraw/element/index.ts b/packages/excalidraw/element/index.ts index 37d6a077b..093ef4829 100644 --- a/packages/excalidraw/element/index.ts +++ b/packages/excalidraw/element/index.ts @@ -50,7 +50,6 @@ export { dragNewElement, } from "./dragElements"; export { isTextElement, isExcalidrawElement } from "./typeChecks"; -export { textWysiwyg } from "./textWysiwyg"; export { redrawTextBoundingBox } from "./textElement"; export { getPerfectElementSize, From 63b50b3586be121125db4feefbade4096120fd83 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 31 Jan 2024 16:50:35 +0530 Subject: [PATCH 67/79] fix: don't bundle react-dom when importing from transformHandles (#7634) * fix: don't bundle react when importing from transfromHandles * rename to DEFAULT_TRANSFORM_HANDLE_SPACING --- packages/excalidraw/constants.ts | 1 + packages/excalidraw/element/transformHandles.ts | 9 +++++---- packages/excalidraw/renderer/renderScene.ts | 13 ++++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts index c4df44797..021c706a9 100644 --- a/packages/excalidraw/constants.ts +++ b/packages/excalidraw/constants.ts @@ -142,6 +142,7 @@ export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Virgil; export const DEFAULT_TEXT_ALIGN = "left"; export const DEFAULT_VERTICAL_ALIGN = "top"; export const DEFAULT_VERSION = "{version}"; +export const DEFAULT_TRANSFORM_HANDLE_SPACING = 2; export const CANVAS_ONLY_ACTIONS = ["selectAll"]; diff --git a/packages/excalidraw/element/transformHandles.ts b/packages/excalidraw/element/transformHandles.ts index 00ebfacfd..19c60a93f 100644 --- a/packages/excalidraw/element/transformHandles.ts +++ b/packages/excalidraw/element/transformHandles.ts @@ -9,7 +9,7 @@ import { rotate } from "../math"; import { InteractiveCanvasAppState, Zoom } from "../types"; import { isTextElement } from "."; import { isFrameLikeElement, isLinearElement } from "./typeChecks"; -import { DEFAULT_SPACING } from "../renderer/renderScene"; +import { DEFAULT_TRANSFORM_HANDLE_SPACING } from "../constants"; export type TransformHandleDirection = | "n" @@ -106,7 +106,8 @@ export const getTransformHandlesFromCoords = ( const width = x2 - x1; const height = y2 - y1; const dashedLineMargin = margin / zoom.value; - const centeringOffset = (size - DEFAULT_SPACING * 2) / (2 * zoom.value); + const centeringOffset = + (size - DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / (2 * zoom.value); const transformHandles: TransformHandles = { nw: omitSides.nw @@ -263,8 +264,8 @@ export const getTransformHandles = ( }; } const dashedLineMargin = isLinearElement(element) - ? DEFAULT_SPACING + 8 - : DEFAULT_SPACING; + ? DEFAULT_TRANSFORM_HANDLE_SPACING + 8 + : DEFAULT_TRANSFORM_HANDLE_SPACING; return getTransformHandlesFromCoords( getElementAbsoluteCoords(element, true), element.angle, diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index 0fa56829f..d31d69650 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -64,7 +64,11 @@ import { } from "../element/transformHandles"; import { arrayToMap, throttleRAF } from "../utils"; import { UserIdleState } from "../types"; -import { FRAME_STYLE, THEME_FILTER } from "../constants"; +import { + DEFAULT_TRANSFORM_HANDLE_SPACING, + FRAME_STYLE, + THEME_FILTER, +} from "../constants"; import { EXTERNAL_LINK_IMG, getLinkHandleFromCoords, @@ -83,8 +87,6 @@ import { isElementInFrame, } from "../frame"; -export const DEFAULT_SPACING = 2; - const strokeRectWithRotation = ( context: CanvasRenderingContext2D, x: number, @@ -676,7 +678,8 @@ const _renderInteractiveScene = ({ ); } } else if (selectedElements.length > 1 && !appState.isRotating) { - const dashedLinePadding = (DEFAULT_SPACING * 2) / appState.zoom.value; + const dashedLinePadding = + (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value; context.fillStyle = oc.white; const [x1, y1, x2, y2] = getCommonBounds(selectedElements); const initialLineDash = context.getLineDash(); @@ -1191,7 +1194,7 @@ const renderSelectionBorder = ( cy: number; activeEmbeddable: boolean; }, - padding = DEFAULT_SPACING * 2, + padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2, ) => { const { angle, From 1741c234a686983558f01d0ff449251f2810b41c Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Wed, 31 Jan 2024 21:17:41 +0530 Subject: [PATCH 68/79] fix: decouple container cache logic to containerCache. (#7637) --- .../excalidraw/actions/actionBoundText.tsx | 2 +- packages/excalidraw/element/containerCache.ts | 33 +++++++++++++++++ packages/excalidraw/element/textElement.ts | 5 ++- .../excalidraw/element/textWysiwyg.test.tsx | 2 +- packages/excalidraw/element/textWysiwyg.tsx | 37 ++----------------- 5 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 packages/excalidraw/element/containerCache.ts diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx index 05dd9c786..722ad5111 100644 --- a/packages/excalidraw/actions/actionBoundText.tsx +++ b/packages/excalidraw/actions/actionBoundText.tsx @@ -17,7 +17,7 @@ import { getOriginalContainerHeightFromCache, resetOriginalContainerCache, updateOriginalContainerCache, -} from "../element/textWysiwyg"; +} from "../element/containerCache"; import { hasBoundTextElement, isTextBindableContainer, diff --git a/packages/excalidraw/element/containerCache.ts b/packages/excalidraw/element/containerCache.ts new file mode 100644 index 000000000..c744f6c8e --- /dev/null +++ b/packages/excalidraw/element/containerCache.ts @@ -0,0 +1,33 @@ +import { ExcalidrawTextContainer } from "./types"; + +export const originalContainerCache: { + [id: ExcalidrawTextContainer["id"]]: + | { + height: ExcalidrawTextContainer["height"]; + } + | undefined; +} = {}; + +export const updateOriginalContainerCache = ( + id: ExcalidrawTextContainer["id"], + height: ExcalidrawTextContainer["height"], +) => { + const data = + originalContainerCache[id] || (originalContainerCache[id] = { height }); + data.height = height; + return data; +}; + +export const resetOriginalContainerCache = ( + id: ExcalidrawTextContainer["id"], +) => { + if (originalContainerCache[id]) { + delete originalContainerCache[id]; + } +}; + +export const getOriginalContainerHeightFromCache = ( + id: ExcalidrawTextContainer["id"], +) => { + return originalContainerCache[id]?.height ?? null; +}; diff --git a/packages/excalidraw/element/textElement.ts b/packages/excalidraw/element/textElement.ts index b264c0d59..fc4c15f2d 100644 --- a/packages/excalidraw/element/textElement.ts +++ b/packages/excalidraw/element/textElement.ts @@ -31,11 +31,12 @@ import { isTextBindableContainer } from "./typeChecks"; import { getElementAbsoluteCoords } from "."; import { getSelectedElements } from "../scene"; import { isHittingElementNotConsideringBoundingBox } from "./collision"; + +import { ExtractSetType } from "../utility-types"; import { resetOriginalContainerCache, updateOriginalContainerCache, -} from "./textWysiwyg"; -import { ExtractSetType } from "../utility-types"; +} from "./containerCache"; export const normalizeText = (text: string) => { return ( diff --git a/packages/excalidraw/element/textWysiwyg.test.tsx b/packages/excalidraw/element/textWysiwyg.test.tsx index e6b0aa0b2..478fe5c1a 100644 --- a/packages/excalidraw/element/textWysiwyg.test.tsx +++ b/packages/excalidraw/element/textWysiwyg.test.tsx @@ -17,7 +17,7 @@ import { } from "./types"; import { API } from "../tests/helpers/api"; import { mutateElement } from "./mutateElement"; -import { getOriginalContainerHeightFromCache } from "./textWysiwyg"; +import { getOriginalContainerHeightFromCache } from "./containerCache"; import { getTextEditor, updateTextEditor } from "../tests/queries/dom"; // Unmount ReactDOM from root diff --git a/packages/excalidraw/element/textWysiwyg.tsx b/packages/excalidraw/element/textWysiwyg.tsx index d12d34f89..1a628dd46 100644 --- a/packages/excalidraw/element/textWysiwyg.tsx +++ b/packages/excalidraw/element/textWysiwyg.tsx @@ -17,7 +17,6 @@ import { ExcalidrawLinearElement, ExcalidrawTextElementWithContainer, ExcalidrawTextElement, - ExcalidrawTextContainer, } from "./types"; import { AppState } from "../types"; import { bumpVersion, mutateElement } from "./mutateElement"; @@ -44,6 +43,10 @@ import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas"; import App from "../components/App"; import { LinearElementEditor } from "./linearElementEditor"; import { parseClipboard } from "../clipboard"; +import { + originalContainerCache, + updateOriginalContainerCache, +} from "./containerCache"; const getTransform = ( width: number, @@ -66,38 +69,6 @@ const getTransform = ( return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`; }; -const originalContainerCache: { - [id: ExcalidrawTextContainer["id"]]: - | { - height: ExcalidrawTextContainer["height"]; - } - | undefined; -} = {}; - -export const updateOriginalContainerCache = ( - id: ExcalidrawTextContainer["id"], - height: ExcalidrawTextContainer["height"], -) => { - const data = - originalContainerCache[id] || (originalContainerCache[id] = { height }); - data.height = height; - return data; -}; - -export const resetOriginalContainerCache = ( - id: ExcalidrawTextContainer["id"], -) => { - if (originalContainerCache[id]) { - delete originalContainerCache[id]; - } -}; - -export const getOriginalContainerHeightFromCache = ( - id: ExcalidrawTextContainer["id"], -) => { - return originalContainerCache[id]?.height ?? null; -}; - export const textWysiwyg = ({ id, onChange, From 90ad885446314bfb9efa62e46988354f1dc2daaa Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 1 Feb 2024 17:56:55 +0530 Subject: [PATCH 69/79] feat: support onPointerUp prop (#7638) * feat: support onPointerUp prop * update changelog * Update packages/excalidraw/CHANGELOG.md Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com> --------- Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com> --- packages/excalidraw/CHANGELOG.md | 5 ++++- packages/excalidraw/components/App.tsx | 1 + packages/excalidraw/index.tsx | 2 ++ packages/excalidraw/types.ts | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index 9f59bd4af..d2c40c25e 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -13,8 +13,11 @@ Please add the latest change on the top under the correct section. ## Unreleased +### Features + +- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638). + - Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450) -- Remove `ExcalidrawEmbeddableElement.validated` attribute. [#7539](https://github.com/excalidraw/excalidraw/pull/7539) ### Breaking Changes diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 28daae36d..462e803f1 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -7564,6 +7564,7 @@ class App extends React.Component { this.setState({ pendingImageElementId: null }); } + this.props?.onPointerUp?.(activeTool, pointerDownState); this.onPointerUpEmitter.trigger( this.state.activeTool, pointerDownState, diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index b45084693..f7be8affc 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -44,6 +44,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { generateIdForFile, onLinkOpen, onPointerDown, + onPointerUp, onScrollChange, children, validateEmbeddable, @@ -131,6 +132,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { generateIdForFile={generateIdForFile} onLinkOpen={onLinkOpen} onPointerDown={onPointerDown} + onPointerUp={onPointerUp} onScrollChange={onScrollChange} validateEmbeddable={validateEmbeddable} renderEmbeddable={renderEmbeddable} diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 201a186ba..ddd799fb9 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -456,6 +456,10 @@ export interface ExcalidrawProps { activeTool: AppState["activeTool"], pointerDownState: PointerDownState, ) => void; + onPointerUp?: ( + activeTool: AppState["activeTool"], + pointerDownState: PointerDownState, + ) => void; onScrollChange?: (scrollX: number, scrollY: number, zoom: Zoom) => void; onUserFollow?: (payload: OnUserFollowedPayload) => void; children?: React.ReactNode; From 1c39bd57816f1de2b51c35e73a5d0e21b1a74fab Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 1 Feb 2024 18:24:17 +0530 Subject: [PATCH 70/79] fix: don't bundle react and jotai when importing from scene (#7640) * don't bundle react and jotai when importing from scene * fix --- packages/excalidraw/components/App.tsx | 2 +- packages/excalidraw/scene/index.ts | 1 - packages/excalidraw/types.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 462e803f1..c357b4ca3 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -216,7 +216,6 @@ import { getNormalizedZoom, getSelectedElements, hasBackground, - isOverScrollBars, isSomeElementSelected, } from "../scene"; import Scene from "../scene/Scene"; @@ -409,6 +408,7 @@ import { LaserTrails } from "../laser-trails"; import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils"; import { getRenderOpacity } from "../renderer/renderElement"; import { textWysiwyg } from "../element/textWysiwyg"; +import { isOverScrollBars } from "../scene/scrollbars"; const AppContext = React.createContext(null!); const AppPropsContext = React.createContext(null!); diff --git a/packages/excalidraw/scene/index.ts b/packages/excalidraw/scene/index.ts index 5a7b9028a..33399d79e 100644 --- a/packages/excalidraw/scene/index.ts +++ b/packages/excalidraw/scene/index.ts @@ -1,4 +1,3 @@ -export { isOverScrollBars } from "./scrollbars"; export { isSomeElementSelected, getElementsWithinSelection, diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index ddd799fb9..e29bb9f89 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -31,7 +31,7 @@ import type { throttleRAF } from "./utils"; import { Spreadsheet } from "./charts"; import { Language } from "./i18n"; import { ClipboardData } from "./clipboard"; -import { isOverScrollBars } from "./scene"; +import { isOverScrollBars } from "./scene/scrollbars"; import { MaybeTransformHandleType } from "./element/transformHandles"; import Library from "./data/library"; import type { FileSystemHandle } from "./data/filesystem"; From 4888d9d355cb847803cfb15bb1563f99c960b4f7 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:41:38 +0100 Subject: [PATCH 71/79] chore: change default port of collab server (#7641) --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index bab59ee07..95e21ff87 100644 --- a/.env.development +++ b/.env.development @@ -5,7 +5,7 @@ VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries # collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room) -VITE_APP_WS_SERVER_URL=http://localhost:3020 +VITE_APP_WS_SERVER_URL=http://localhost:3002 VITE_APP_PLUS_LP=https://plus.excalidraw.com VITE_APP_PLUS_APP=https://app.excalidraw.com From 0e0f34edd89ca16273b175d16c87831f72dd97b9 Mon Sep 17 00:00:00 2001 From: Milos Vetesnik Date: Thu, 1 Feb 2024 15:03:15 +0100 Subject: [PATCH 72/79] fix: follow mode border for hosts apps (#7642) --- .../components/FollowMode/FollowMode.tsx | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/excalidraw/components/FollowMode/FollowMode.tsx b/packages/excalidraw/components/FollowMode/FollowMode.tsx index da91ad42e..dc1746ca8 100644 --- a/packages/excalidraw/components/FollowMode/FollowMode.tsx +++ b/packages/excalidraw/components/FollowMode/FollowMode.tsx @@ -16,25 +16,20 @@ const FollowMode = ({ onDisconnect, }: FollowModeProps) => { return ( -
    -
    -
    -
    - Following{" "} - - {userToFollow.username} - -
    - + {userToFollow.username} +
    +
    ); From 0c3dffb082c85552758459444a4777dc50a33326 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 1 Feb 2024 21:12:10 +0530 Subject: [PATCH 73/79] fix: make getEmbedLink independent of t function (#7643) * fix: make getEmbedLink independent of t function * rename warning to error and make it type safe --- packages/excalidraw/components/App.tsx | 7 +++++-- packages/excalidraw/element/Hyperlink.tsx | 7 +++++-- packages/excalidraw/element/embeddable.ts | 7 +++---- packages/excalidraw/element/types.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index c357b4ca3..f965a7679 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -6501,8 +6501,11 @@ class App extends React.Component { return; } - if (embedLink.warning) { - this.setToast({ message: embedLink.warning, closable: true }); + if (embedLink.error instanceof URIError) { + this.setToast({ + message: t("toast.unrecognizedLinkFormat"), + closable: true, + }); } const element = newEmbeddableElement({ diff --git a/packages/excalidraw/element/Hyperlink.tsx b/packages/excalidraw/element/Hyperlink.tsx index a69fdeb83..930b87763 100644 --- a/packages/excalidraw/element/Hyperlink.tsx +++ b/packages/excalidraw/element/Hyperlink.tsx @@ -120,8 +120,11 @@ export const Hyperlink = ({ } else { const { width, height } = element; const embedLink = getEmbedLink(link); - if (embedLink?.warning) { - setToast({ message: embedLink.warning, closable: true }); + if (embedLink?.error instanceof URIError) { + setToast({ + message: t("toast.unrecognizedLinkFormat"), + closable: true, + }); } const ar = embedLink ? embedLink.intrinsicSize.w / embedLink.intrinsicSize.h diff --git a/packages/excalidraw/element/embeddable.ts b/packages/excalidraw/element/embeddable.ts index f62b0f95f..fb51c7283 100644 --- a/packages/excalidraw/element/embeddable.ts +++ b/packages/excalidraw/element/embeddable.ts @@ -1,6 +1,5 @@ import { register } from "../actions/register"; import { FONT_FAMILY, VERTICAL_ALIGN } from "../constants"; -import { t } from "../i18n"; import { ExcalidrawProps } from "../types"; import { getFontString, updateActiveTool } from "../utils"; import { setCursorForShape } from "../cursor"; @@ -107,8 +106,8 @@ export const getEmbedLink = ( const vimeoLink = link.match(RE_VIMEO); if (vimeoLink?.[1]) { const target = vimeoLink?.[1]; - const warning = !/^\d+$/.test(target) - ? t("toast.unrecognizedLinkFormat") + const error = !/^\d+$/.test(target) + ? new URIError("Invalid embed link format") : undefined; type = "video"; link = `https://player.vimeo.com/video/${target}?api=1`; @@ -120,7 +119,7 @@ export const getEmbedLink = ( intrinsicSize: aspectRatio, type, }); - return { link, intrinsicSize: aspectRatio, type, warning }; + return { link, intrinsicSize: aspectRatio, type, error }; } const figmaLink = link.match(RE_FIGMA); diff --git a/packages/excalidraw/element/types.ts b/packages/excalidraw/element/types.ts index f89e8d5f2..aae0a8a30 100644 --- a/packages/excalidraw/element/types.ts +++ b/packages/excalidraw/element/types.ts @@ -104,7 +104,7 @@ export type ExcalidrawIframeLikeElement = export type IframeData = | { intrinsicSize: { w: number; h: number }; - warning?: string; + error?: Error; } & ( | { type: "video" | "generic"; link: string } | { type: "document"; srcdoc: (theme: Theme) => string } From d67eaa8710a431f3e9bb9cdf3cef6d900f5edded Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:53:35 +0100 Subject: [PATCH 74/79] fix: file save timing out with big file sizes (#7649) --- packages/excalidraw/data/filesystem.ts | 2 +- packages/excalidraw/data/index.ts | 32 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/excalidraw/data/filesystem.ts b/packages/excalidraw/data/filesystem.ts index fa29604f4..11f64d23e 100644 --- a/packages/excalidraw/data/filesystem.ts +++ b/packages/excalidraw/data/filesystem.ts @@ -76,7 +76,7 @@ export const fileOpen = (opts: { }; export const fileSave = ( - blob: Blob, + blob: Blob | Promise, opts: { /** supply without the extension */ name: string; diff --git a/packages/excalidraw/data/index.ts b/packages/excalidraw/data/index.ts index 0c63053a9..fa2ec9de6 100644 --- a/packages/excalidraw/data/index.ts +++ b/packages/excalidraw/data/index.ts @@ -100,7 +100,7 @@ export const exportCanvas = async ( throw new Error(t("alerts.cannotExportEmptyCanvas")); } if (type === "svg" || type === "clipboard-svg") { - const tempSvg = await exportToSvg( + const svgPromise = exportToSvg( elements, { exportBackground, @@ -113,9 +113,12 @@ export const exportCanvas = async ( files, { exportingFrame }, ); + if (type === "svg") { - return await fileSave( - new Blob([tempSvg.outerHTML], { type: MIME_TYPES.svg }), + return fileSave( + svgPromise.then((svg) => { + return new Blob([svg.outerHTML], { type: MIME_TYPES.svg }); + }), { description: "Export to SVG", name, @@ -124,7 +127,9 @@ export const exportCanvas = async ( }, ); } else if (type === "clipboard-svg") { - await copyTextToSystemClipboard(tempSvg.outerHTML); + await copyTextToSystemClipboard( + await svgPromise.then((svg) => svg.outerHTML), + ); return; } } @@ -137,17 +142,20 @@ export const exportCanvas = async ( }); if (type === "png") { - let blob = await canvasToBlob(tempCanvas); + let blob = canvasToBlob(tempCanvas); + if (appState.exportEmbedScene) { - blob = await ( - await import("./image") - ).encodePngMetadata({ - blob, - metadata: serializeAsJSON(elements, appState, files, "local"), - }); + blob = blob.then((blob) => + import("./image").then(({ encodePngMetadata }) => + encodePngMetadata({ + blob, + metadata: serializeAsJSON(elements, appState, files, "local"), + }), + ), + ); } - return await fileSave(blob, { + return fileSave(blob, { description: "Export to PNG", name, // FIXME reintroduce `excalidraw.png` when most people upgrade away From a289c42830ea6b458a520c861dc5aaa95299c726 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:53:31 +0100 Subject: [PATCH 75/79] feat: add loading state to FilledButton (#7650) --- excalidraw-app/collab/RoomDialog.tsx | 8 +- packages/excalidraw/actions/manager.tsx | 3 +- .../excalidraw/components/FilledButton.scss | 76 ++++++++++++++++--- .../excalidraw/components/FilledButton.tsx | 50 +++++++++--- .../components/ImageExportDialog.scss | 2 + .../components/ImageExportDialog.tsx | 6 +- .../components/ShareableLinkDialog.tsx | 2 +- packages/excalidraw/components/ToolButton.tsx | 3 +- 8 files changed, 119 insertions(+), 31 deletions(-) diff --git a/excalidraw-app/collab/RoomDialog.tsx b/excalidraw-app/collab/RoomDialog.tsx index 48bc12446..f2614674d 100644 --- a/excalidraw-app/collab/RoomDialog.tsx +++ b/excalidraw-app/collab/RoomDialog.tsx @@ -120,7 +120,7 @@ export const RoomModal = ({ size="large" variant="icon" label="Share" - startIcon={getShareIcon()} + icon={getShareIcon()} className="RoomDialog__active__share" onClick={shareRoomLink} /> @@ -130,7 +130,7 @@ export const RoomModal = ({ @@ -166,7 +166,7 @@ export const RoomModal = ({ variant="outlined" color="danger" label={t("roomDialog.button_stopSession")} - startIcon={playerStopFilledIcon} + icon={playerStopFilledIcon} onClick={() => { trackEvent("share", "room closed"); onRoomDestroy(); @@ -195,7 +195,7 @@ export const RoomModal = ({ { trackEvent("share", "room creation", `ui (${getFrame()})`); onRoomCreate(); diff --git a/packages/excalidraw/actions/manager.tsx b/packages/excalidraw/actions/manager.tsx index fc56d1bda..90dfe6088 100644 --- a/packages/excalidraw/actions/manager.tsx +++ b/packages/excalidraw/actions/manager.tsx @@ -10,6 +10,7 @@ import { import { ExcalidrawElement } from "../element/types"; import { AppClassProperties, AppState } from "../types"; import { trackEvent } from "../analytics"; +import { isPromiseLike } from "../utils"; const trackAction = ( action: Action, @@ -55,7 +56,7 @@ export class ActionManager { app: AppClassProperties, ) { this.updater = (actionResult) => { - if (actionResult && "then" in actionResult) { + if (isPromiseLike(actionResult)) { actionResult.then((actionResult) => { return updater(actionResult); }); diff --git a/packages/excalidraw/components/FilledButton.scss b/packages/excalidraw/components/FilledButton.scss index bfa443f89..5891698e8 100644 --- a/packages/excalidraw/components/FilledButton.scss +++ b/packages/excalidraw/components/FilledButton.scss @@ -10,11 +10,39 @@ background-color: var(--back-color); border-color: var(--border-color); + .Spinner { + --spinner-color: var(--color-surface-lowest); + position: absolute; + visibility: visible; + } + + &[disabled] { + pointer-events: none; + + .ExcButton__contents { + visibility: hidden; + } + } + + &__contents { + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + flex-wrap: nowrap; + // needed because of .Spinner + position: relative; + } + &--color-primary { &.ExcButton--variant-filled { --text-color: var(--color-surface-lowest); --back-color: var(--color-primary); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --back-color: var(--color-brand-hover); } @@ -27,9 +55,13 @@ &.ExcButton--variant-outlined, &.ExcButton--variant-icon { --text-color: var(--color-primary); - --border-color: var(--color-border-outline); + --border-color: var(--color-primary); --back-color: transparent; + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --text-color: var(--color-brand-hover); --border-color: var(--color-brand-hover); @@ -47,6 +79,10 @@ --text-color: var(--color-danger-text); --back-color: var(--color-danger-dark); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --back-color: var(--color-danger-darker); } @@ -62,6 +98,10 @@ --border-color: var(--color-danger); --back-color: transparent; + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --text-color: var(--color-danger-darkest); --border-color: var(--color-danger-darkest); @@ -79,6 +119,10 @@ --text-color: var(--island-bg-color); --back-color: var(--color-gray-50); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --back-color: var(--color-gray-60); } @@ -94,6 +138,10 @@ --border-color: var(--color-muted); --back-color: var(--island-bg-color); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --text-color: var(--color-muted-background-darker); --border-color: var(--color-muted-darker); @@ -111,6 +159,10 @@ --text-color: black; --back-color: var(--color-warning-dark); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --back-color: var(--color-warning-darker); } @@ -126,6 +178,10 @@ --border-color: var(--color-warning-dark); --back-color: var(--input-bg-color); + .Spinner { + --spinner-color: var(--text-color); + } + &:hover { --text-color: var(--color-warning-darker); --border-color: var(--color-warning-darker); @@ -138,17 +194,11 @@ } } - display: flex; - justify-content: center; - align-items: center; - flex-shrink: 0; - flex-wrap: nowrap; - border-radius: 0.5rem; border-width: 1px; border-style: solid; - font-family: "Assistant"; + font-family: var(--font-family); user-select: none; @@ -159,9 +209,12 @@ font-size: 0.875rem; min-height: 3rem; padding: 0.5rem 1.5rem; - gap: 0.75rem; letter-spacing: 0.4px; + + .ExcButton__contents { + gap: 0.75rem; + } } &--size-medium { @@ -169,9 +222,12 @@ font-size: 0.75rem; min-height: 2.5rem; padding: 0.5rem 1rem; - gap: 0.5rem; letter-spacing: normal; + + .ExcButton__contents { + gap: 0.5rem; + } } &--variant-icon { diff --git a/packages/excalidraw/components/FilledButton.tsx b/packages/excalidraw/components/FilledButton.tsx index 3f844cf37..ff17db623 100644 --- a/packages/excalidraw/components/FilledButton.tsx +++ b/packages/excalidraw/components/FilledButton.tsx @@ -1,7 +1,10 @@ -import React, { forwardRef } from "react"; +import React, { forwardRef, useState } from "react"; import clsx from "clsx"; import "./FilledButton.scss"; +import { AbortError } from "../errors"; +import Spinner from "./Spinner"; +import { isPromiseLike } from "../utils"; export type ButtonVariant = "filled" | "outlined" | "icon"; export type ButtonColor = "primary" | "danger" | "warning" | "muted"; @@ -11,7 +14,7 @@ export type FilledButtonProps = { label: string; children?: React.ReactNode; - onClick?: () => void; + onClick?: (event: React.MouseEvent) => void; variant?: ButtonVariant; color?: ButtonColor; @@ -19,14 +22,14 @@ export type FilledButtonProps = { className?: string; fullWidth?: boolean; - startIcon?: React.ReactNode; + icon?: React.ReactNode; }; export const FilledButton = forwardRef( ( { children, - startIcon, + icon, onClick, label, variant = "filled", @@ -37,6 +40,27 @@ export const FilledButton = forwardRef( }, ref, ) => { + const [isLoading, setIsLoading] = useState(false); + + const _onClick = async (event: React.MouseEvent) => { + const ret = onClick?.(event); + + if (isPromiseLike(ret)) { + try { + setIsLoading(true); + await ret; + } catch (error: any) { + if (!(error instanceof AbortError)) { + throw error; + } else { + console.warn(error); + } + } finally { + setIsLoading(false); + } + } + }; + return ( ); }, diff --git a/packages/excalidraw/components/ImageExportDialog.scss b/packages/excalidraw/components/ImageExportDialog.scss index c99836599..ea9e74f80 100644 --- a/packages/excalidraw/components/ImageExportDialog.scss +++ b/packages/excalidraw/components/ImageExportDialog.scss @@ -12,6 +12,8 @@ flex-direction: row; justify-content: space-between; + user-select: none; + & h3 { font-family: "Assistant"; font-style: normal; diff --git a/packages/excalidraw/components/ImageExportDialog.tsx b/packages/excalidraw/components/ImageExportDialog.tsx index d0df35193..7ca54e985 100644 --- a/packages/excalidraw/components/ImageExportDialog.tsx +++ b/packages/excalidraw/components/ImageExportDialog.tsx @@ -271,7 +271,7 @@ const ImageExportModal = ({ exportingFrame, }) } - startIcon={downloadIcon} + icon={downloadIcon} > {t("imageExportDialog.button.exportToPng")} @@ -283,7 +283,7 @@ const ImageExportModal = ({ exportingFrame, }) } - startIcon={downloadIcon} + icon={downloadIcon} > {t("imageExportDialog.button.exportToSvg")} @@ -296,7 +296,7 @@ const ImageExportModal = ({ exportingFrame, }) } - startIcon={copyIcon} + icon={copyIcon} > {t("imageExportDialog.button.copyPngToClipboard")} diff --git a/packages/excalidraw/components/ShareableLinkDialog.tsx b/packages/excalidraw/components/ShareableLinkDialog.tsx index 7a53a4a82..cb8ba4cef 100644 --- a/packages/excalidraw/components/ShareableLinkDialog.tsx +++ b/packages/excalidraw/components/ShareableLinkDialog.tsx @@ -66,7 +66,7 @@ export const ShareableLinkDialog = ({ diff --git a/packages/excalidraw/components/ToolButton.tsx b/packages/excalidraw/components/ToolButton.tsx index ffe9a382c..2dace89d7 100644 --- a/packages/excalidraw/components/ToolButton.tsx +++ b/packages/excalidraw/components/ToolButton.tsx @@ -6,6 +6,7 @@ import { useExcalidrawContainer } from "./App"; import { AbortError } from "../errors"; import Spinner from "./Spinner"; import { PointerType } from "../element/types"; +import { isPromiseLike } from "../utils"; export type ToolButtonSize = "small" | "medium"; @@ -65,7 +66,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => { const onClick = async (event: React.MouseEvent) => { const ret = "onClick" in props && props.onClick?.(event); - if (ret && "then" in ret) { + if (isPromiseLike(ret)) { try { setIsLoading(true); await ret; From 0513b647ec13bc2688eaf75d41c2b45049f2624b Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:04:23 +0100 Subject: [PATCH 76/79] feat: change collab trigger & add share dialog (#7647) --- excalidraw-app/App.tsx | 88 ++++-- excalidraw-app/collab/Collab.tsx | 91 +++--- excalidraw-app/components/AppMainMenu.tsx | 4 +- .../components/AppWelcomeScreen.tsx | 4 +- .../ShareDialog.scss} | 27 +- excalidraw-app/share/ShareDialog.tsx | 290 ++++++++++++++++++ packages/excalidraw/assets/lock.svg | 20 -- packages/excalidraw/components/Button.tsx | 1 + .../excalidraw/components/FilledButton.scss | 1 + .../components/JSONExportDialog.tsx | 2 +- .../components/ShareableLinkDialog.scss | 4 +- packages/excalidraw/components/TextField.tsx | 17 +- .../LiveCollaborationTrigger.scss | 8 +- .../LiveCollaborationTrigger.tsx | 8 +- packages/excalidraw/css/variables.module.scss | 1 + packages/excalidraw/locales/en.json | 7 +- packages/excalidraw/types.ts | 1 - packages/excalidraw/utils.ts | 2 +- 18 files changed, 440 insertions(+), 136 deletions(-) rename excalidraw-app/{collab/RoomDialog.scss => share/ShareDialog.scss} (82%) create mode 100644 excalidraw-app/share/ShareDialog.tsx delete mode 100644 packages/excalidraw/assets/lock.svg diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index 4a3d42847..e38dd7a94 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -1,6 +1,6 @@ import polyfill from "../packages/excalidraw/polyfill"; import LanguageDetector from "i18next-browser-languagedetector"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { trackEvent } from "../packages/excalidraw/analytics"; import { getDefaultAppState } from "../packages/excalidraw/appState"; import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog"; @@ -54,7 +54,6 @@ import { import Collab, { CollabAPI, collabAPIAtom, - collabDialogShownAtom, isCollaboratingAtom, isOfflineAtom, } from "./collab/Collab"; @@ -104,6 +103,7 @@ import { ShareableLinkDialog } from "../packages/excalidraw/components/Shareable import { openConfirmModal } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState"; import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm"; import Trans from "../packages/excalidraw/components/Trans"; +import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog"; polyfill(); @@ -305,8 +305,8 @@ const ExcalidrawWrapper = () => { const [excalidrawAPI, excalidrawRefCallback] = useCallbackRefState(); + const [, setShareDialogState] = useAtom(shareDialogStateAtom); const [collabAPI] = useAtom(collabAPIAtom); - const [, setCollabDialogShown] = useAtom(collabDialogShownAtom); const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => { return isCollaborationLink(window.location.href); }); @@ -607,37 +607,38 @@ const ExcalidrawWrapper = () => { exportedElements: readonly NonDeletedExcalidrawElement[], appState: Partial, files: BinaryFiles, - canvas: HTMLCanvasElement, ) => { if (exportedElements.length === 0) { throw new Error(t("alerts.cannotExportEmptyCanvas")); } - if (canvas) { - try { - const { url, errorMessage } = await exportToBackend( - exportedElements, - { - ...appState, - viewBackgroundColor: appState.exportBackground - ? appState.viewBackgroundColor - : getDefaultAppState().viewBackgroundColor, - }, - files, - ); + try { + const { url, errorMessage } = await exportToBackend( + exportedElements, + { + ...appState, + viewBackgroundColor: appState.exportBackground + ? appState.viewBackgroundColor + : getDefaultAppState().viewBackgroundColor, + }, + files, + ); - if (errorMessage) { - throw new Error(errorMessage); - } + if (errorMessage) { + throw new Error(errorMessage); + } - if (url) { - setLatestShareableLink(url); - } - } catch (error: any) { - if (error.name !== "AbortError") { - const { width, height } = canvas; - console.error(error, { width, height }); - throw new Error(error.message); - } + if (url) { + setLatestShareableLink(url); + } + } catch (error: any) { + if (error.name !== "AbortError") { + const { width, height } = appState; + console.error(error, { + width, + height, + devicePixelRatio: window.devicePixelRatio, + }); + throw new Error(error.message); } } }; @@ -666,6 +667,11 @@ const ExcalidrawWrapper = () => { const isOffline = useAtomValue(isOfflineAtom); + const onCollabDialogOpen = useCallback( + () => setShareDialogState({ isOpen: true, type: "collaborationOnly" }), + [setShareDialogState], + ); + // browsers generally prevent infinite self-embedding, there are // cases where it still happens, and while we disallow self-embedding // by not whitelisting our own origin, this serves as an additional guard @@ -741,18 +747,20 @@ const ExcalidrawWrapper = () => { return ( setCollabDialogShown(true)} + onSelect={() => + setShareDialogState({ isOpen: true, type: "share" }) + } /> ); }} > @@ -848,6 +856,24 @@ const ExcalidrawWrapper = () => { {excalidrawAPI && !isCollabDisabled && ( )} + + { + if (excalidrawAPI) { + try { + await onExportToBackend( + excalidrawAPI.getSceneElements(), + excalidrawAPI.getAppState(), + excalidrawAPI.getFiles(), + ); + } catch (error: any) { + setErrorMessage(error.message); + } + } + }} + /> + {errorMessage && ( setErrorMessage("")}> {errorMessage} diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 267dee66c..14538b674 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -52,7 +52,6 @@ import { saveUsernameToLocalStorage, } from "../data/localStorage"; import Portal from "./Portal"; -import RoomDialog from "./RoomDialog"; import { t } from "../../packages/excalidraw/i18n"; import { UserIdleState } from "../../packages/excalidraw/types"; import { @@ -77,23 +76,24 @@ import { import { decryptData } from "../../packages/excalidraw/data/encryption"; import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; -import { atom, useAtom } from "jotai"; +import { atom } from "jotai"; import { appJotaiStore } from "../app-jotai"; import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types"; import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds"; import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils"; export const collabAPIAtom = atom(null); -export const collabDialogShownAtom = atom(false); export const isCollaboratingAtom = atom(false); export const isOfflineAtom = atom(false); interface CollabState { - errorMessage: string; + errorMessage: string | null; username: string; - activeRoomLink: string; + activeRoomLink: string | null; } +export const activeRoomLinkAtom = atom(null); + type CollabInstance = InstanceType; export interface CollabAPI { @@ -104,19 +104,20 @@ export interface CollabAPI { stopCollaboration: CollabInstance["stopCollaboration"]; syncElements: CollabInstance["syncElements"]; fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"]; - setUsername: (username: string) => void; + setUsername: CollabInstance["setUsername"]; + getUsername: CollabInstance["getUsername"]; + getActiveRoomLink: CollabInstance["getActiveRoomLink"]; + setErrorMessage: CollabInstance["setErrorMessage"]; } -interface PublicProps { +interface CollabProps { excalidrawAPI: ExcalidrawImperativeAPI; } -type Props = PublicProps & { modalIsShown: boolean }; - -class Collab extends PureComponent { +class Collab extends PureComponent { portal: Portal; fileManager: FileManager; - excalidrawAPI: Props["excalidrawAPI"]; + excalidrawAPI: CollabProps["excalidrawAPI"]; activeIntervalId: number | null; idleTimeoutId: number | null; @@ -124,12 +125,12 @@ class Collab extends PureComponent { private lastBroadcastedOrReceivedSceneVersion: number = -1; private collaborators = new Map(); - constructor(props: Props) { + constructor(props: CollabProps) { super(props); this.state = { - errorMessage: "", + errorMessage: null, username: importUsernameFromLocalStorage() || "", - activeRoomLink: "", + activeRoomLink: null, }; this.portal = new Portal(this); this.fileManager = new FileManager({ @@ -194,6 +195,9 @@ class Collab extends PureComponent { fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase, stopCollaboration: this.stopCollaboration, setUsername: this.setUsername, + getUsername: this.getUsername, + getActiveRoomLink: this.getActiveRoomLink, + setErrorMessage: this.setErrorMessage, }; appJotaiStore.set(collabAPIAtom, collabAPI); @@ -341,9 +345,7 @@ class Collab extends PureComponent { this.fileManager.reset(); if (!opts?.isUnload) { this.setIsCollaborating(false); - this.setState({ - activeRoomLink: "", - }); + this.setActiveRoomLink(null); this.collaborators = new Map(); this.excalidrawAPI.updateScene({ collaborators: this.collaborators, @@ -409,7 +411,7 @@ class Collab extends PureComponent { if (!this.state.username) { import("@excalidraw/random-username").then(({ getRandomUsername }) => { const username = getRandomUsername(); - this.onUsernameChange(username); + this.setUsername(username); }); } @@ -624,9 +626,7 @@ class Collab extends PureComponent { this.initializeIdleDetector(); - this.setState({ - activeRoomLink: window.location.href, - }); + this.setActiveRoomLink(window.location.href); return scenePromise; }; @@ -909,41 +909,31 @@ class Collab extends PureComponent { { leading: false }, ); - handleClose = () => { - appJotaiStore.set(collabDialogShownAtom, false); - }; - setUsername = (username: string) => { this.setState({ username }); - }; - - onUsernameChange = (username: string) => { - this.setUsername(username); saveUsernameToLocalStorage(username); }; - render() { - const { username, errorMessage, activeRoomLink } = this.state; + getUsername = () => this.state.username; - const { modalIsShown } = this.props; + setActiveRoomLink = (activeRoomLink: string | null) => { + this.setState({ activeRoomLink }); + appJotaiStore.set(activeRoomLinkAtom, activeRoomLink); + }; + + getActiveRoomLink = () => this.state.activeRoomLink; + + setErrorMessage = (errorMessage: string | null) => { + this.setState({ errorMessage }); + }; + + render() { + const { errorMessage } = this.state; return ( <> - {modalIsShown && ( - this.startCollaboration(null)} - onRoomDestroy={this.stopCollaboration} - setErrorMessage={(errorMessage) => { - this.setState({ errorMessage }); - }} - /> - )} - {errorMessage && ( - this.setState({ errorMessage: "" })}> + {errorMessage != null && ( + this.setState({ errorMessage: null })}> {errorMessage} )} @@ -962,11 +952,6 @@ if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) { window.collab = window.collab || ({} as Window["collab"]); } -const _Collab: React.FC = (props) => { - const [collabDialogShown] = useAtom(collabDialogShownAtom); - return ; -}; - -export default _Collab; +export default Collab; export type TCollabClass = Collab; diff --git a/excalidraw-app/components/AppMainMenu.tsx b/excalidraw-app/components/AppMainMenu.tsx index 34a2ee3ae..6806c969c 100644 --- a/excalidraw-app/components/AppMainMenu.tsx +++ b/excalidraw-app/components/AppMainMenu.tsx @@ -4,7 +4,7 @@ import { MainMenu } from "../../packages/excalidraw/index"; import { LanguageList } from "./LanguageList"; export const AppMainMenu: React.FC<{ - setCollabDialogShown: (toggle: boolean) => any; + onCollabDialogOpen: () => any; isCollaborating: boolean; isCollabEnabled: boolean; }> = React.memo((props) => { @@ -17,7 +17,7 @@ export const AppMainMenu: React.FC<{ {props.isCollabEnabled && ( props.setCollabDialogShown(true)} + onSelect={() => props.onCollabDialogOpen()} /> )} diff --git a/excalidraw-app/components/AppWelcomeScreen.tsx b/excalidraw-app/components/AppWelcomeScreen.tsx index a5176c2ff..f74bc14e2 100644 --- a/excalidraw-app/components/AppWelcomeScreen.tsx +++ b/excalidraw-app/components/AppWelcomeScreen.tsx @@ -6,7 +6,7 @@ import { isExcalidrawPlusSignedUser } from "../app_constants"; import { POINTER_EVENTS } from "../../packages/excalidraw/constants"; export const AppWelcomeScreen: React.FC<{ - setCollabDialogShown: (toggle: boolean) => any; + onCollabDialogOpen: () => any; isCollabEnabled: boolean; }> = React.memo((props) => { const { t } = useI18n(); @@ -52,7 +52,7 @@ export const AppWelcomeScreen: React.FC<{ {props.isCollabEnabled && ( props.setCollabDialogShown(true)} + onSelect={() => props.onCollabDialogOpen()} /> )} {!isExcalidrawPlusSignedUser && ( diff --git a/excalidraw-app/collab/RoomDialog.scss b/excalidraw-app/share/ShareDialog.scss similarity index 82% rename from excalidraw-app/collab/RoomDialog.scss rename to excalidraw-app/share/ShareDialog.scss index 61624664b..87fde8491 100644 --- a/excalidraw-app/collab/RoomDialog.scss +++ b/excalidraw-app/share/ShareDialog.scss @@ -1,7 +1,7 @@ @import "../../packages/excalidraw/css/variables.module.scss"; .excalidraw { - .RoomDialog { + .ShareDialog { display: flex; flex-direction: column; gap: 1.5rem; @@ -10,8 +10,25 @@ height: calc(100vh - 5rem); } + &__separator { + border-top: 1px solid var(--dialog-border-color); + text-align: center; + display: flex; + justify-content: center; + align-items: center; + margin-top: 1em; + + span { + background: var(--island-bg-color); + padding: 0px 0.75rem; + transform: translateY(-1ch); + display: inline-flex; + line-height: 1; + } + } + &__popover { - @keyframes RoomDialog__popover__scaleIn { + @keyframes ShareDialog__popover__scaleIn { from { opacity: 0; } @@ -50,10 +67,10 @@ } transform-origin: var(--radix-popover-content-transform-origin); - animation: RoomDialog__popover__scaleIn 150ms ease-out; + animation: ShareDialog__popover__scaleIn 150ms ease-out; } - &__inactive { + &__picker { font-family: "Assistant"; &__illustration { @@ -95,7 +112,7 @@ } } - &__start_session { + &__button { display: flex; align-items: center; diff --git a/excalidraw-app/share/ShareDialog.tsx b/excalidraw-app/share/ShareDialog.tsx new file mode 100644 index 000000000..2fa92dff8 --- /dev/null +++ b/excalidraw-app/share/ShareDialog.tsx @@ -0,0 +1,290 @@ +import { useRef, useState } from "react"; +import * as Popover from "@radix-ui/react-popover"; +import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard"; +import { trackEvent } from "../../packages/excalidraw/analytics"; +import { getFrame } from "../../packages/excalidraw/utils"; +import { useI18n } from "../../packages/excalidraw/i18n"; +import { KEYS } from "../../packages/excalidraw/keys"; +import { Dialog } from "../../packages/excalidraw/components/Dialog"; +import { + copyIcon, + LinkIcon, + playerPlayIcon, + playerStopFilledIcon, + share, + shareIOS, + shareWindows, + tablerCheckIcon, +} from "../../packages/excalidraw/components/icons"; +import { TextField } from "../../packages/excalidraw/components/TextField"; +import { FilledButton } from "../../packages/excalidraw/components/FilledButton"; +import { activeRoomLinkAtom, CollabAPI } from "../collab/Collab"; +import { atom, useAtom, useAtomValue } from "jotai"; + +import "./ShareDialog.scss"; + +type OnExportToBackend = () => void; +type ShareDialogType = "share" | "collaborationOnly"; + +export const shareDialogStateAtom = atom< + { isOpen: false } | { isOpen: true; type: ShareDialogType } +>({ isOpen: false }); + +const getShareIcon = () => { + const navigator = window.navigator as any; + const isAppleBrowser = /Apple/.test(navigator.vendor); + const isWindowsBrowser = navigator.appVersion.indexOf("Win") !== -1; + + if (isAppleBrowser) { + return shareIOS; + } else if (isWindowsBrowser) { + return shareWindows; + } + + return share; +}; + +export type ShareDialogProps = { + collabAPI: CollabAPI | null; + handleClose: () => void; + onExportToBackend: OnExportToBackend; + type: ShareDialogType; +}; + +const ActiveRoomDialog = ({ + collabAPI, + activeRoomLink, + handleClose, +}: { + collabAPI: CollabAPI; + activeRoomLink: string; + handleClose: () => void; +}) => { + const { t } = useI18n(); + const [justCopied, setJustCopied] = useState(false); + const timerRef = useRef(0); + const ref = useRef(null); + const isShareSupported = "share" in navigator; + + const copyRoomLink = async () => { + try { + await copyTextToSystemClipboard(activeRoomLink); + + setJustCopied(true); + + if (timerRef.current) { + window.clearTimeout(timerRef.current); + } + + timerRef.current = window.setTimeout(() => { + setJustCopied(false); + }, 3000); + } catch (error: any) { + collabAPI.setErrorMessage(error.message); + } + + ref.current?.select(); + }; + + const shareRoomLink = async () => { + try { + await navigator.share({ + title: t("roomDialog.shareTitle"), + text: t("roomDialog.shareTitle"), + url: activeRoomLink, + }); + } catch (error: any) { + // Just ignore. + } + }; + + return ( + <> +

    + {t("labels.liveCollaboration").replace(/\./g, "")} +

    + event.key === KEYS.ENTER && handleClose()} + /> +
    + + {isShareSupported && ( + + )} + + + + + event.preventDefault()} + onCloseAutoFocus={(event) => event.preventDefault()} + className="ShareDialog__popover" + side="top" + align="end" + sideOffset={5.5} + > + {tablerCheckIcon} copied + + +
    +
    +

    + + {t("roomDialog.desc_privacy")} +

    +

    {t("roomDialog.desc_exitSession")}

    +
    + +
    + { + trackEvent("share", "room closed"); + collabAPI.stopCollaboration(); + if (!collabAPI.isCollaborating()) { + handleClose(); + } + }} + /> +
    + + ); +}; + +const ShareDialogPicker = (props: ShareDialogProps) => { + const { t } = useI18n(); + + const { collabAPI } = props; + + const startCollabJSX = collabAPI ? ( + <> +
    + {t("labels.liveCollaboration").replace(/\./g, "")} +
    + +
    +
    {t("roomDialog.desc_intro")}
    + {t("roomDialog.desc_privacy")} +
    + +
    + { + trackEvent("share", "room creation", `ui (${getFrame()})`); + collabAPI.startCollaboration(null); + }} + /> +
    + + {props.type === "share" && ( +
    + {t("shareDialog.or")} +
    + )} + + ) : null; + + return ( + <> + {startCollabJSX} + + {props.type === "share" && ( + <> +
    + {t("exportDialog.link_title")} +
    +
    + {t("exportDialog.link_details")} +
    + +
    + { + await props.onExportToBackend(); + props.handleClose(); + }} + /> +
    + + )} + + ); +}; + +const ShareDialogInner = (props: ShareDialogProps) => { + const activeRoomLink = useAtomValue(activeRoomLinkAtom); + + return ( + +
    + {props.collabAPI && activeRoomLink ? ( + + ) : ( + + )} +
    +
    + ); +}; + +export const ShareDialog = (props: { + collabAPI: CollabAPI | null; + onExportToBackend: OnExportToBackend; +}) => { + const [shareDialogState, setShareDialogState] = useAtom(shareDialogStateAtom); + + if (!shareDialogState.isOpen) { + return null; + } + + return ( + setShareDialogState({ isOpen: false })} + collabAPI={props.collabAPI} + onExportToBackend={props.onExportToBackend} + type={shareDialogState.type} + > + ); +}; diff --git a/packages/excalidraw/assets/lock.svg b/packages/excalidraw/assets/lock.svg deleted file mode 100644 index aa9dbf170..000000000 --- a/packages/excalidraw/assets/lock.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/packages/excalidraw/components/Button.tsx b/packages/excalidraw/components/Button.tsx index 43b6de9e1..779cee582 100644 --- a/packages/excalidraw/components/Button.tsx +++ b/packages/excalidraw/components/Button.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import React from "react"; import { composeEventHandlers } from "../utils"; import "./Button.scss"; diff --git a/packages/excalidraw/components/FilledButton.scss b/packages/excalidraw/components/FilledButton.scss index 5891698e8..70f75cbbb 100644 --- a/packages/excalidraw/components/FilledButton.scss +++ b/packages/excalidraw/components/FilledButton.scss @@ -24,6 +24,7 @@ } } + &, &__contents { display: flex; justify-content: center; diff --git a/packages/excalidraw/components/JSONExportDialog.tsx b/packages/excalidraw/components/JSONExportDialog.tsx index b5cea4af6..95f4117fc 100644 --- a/packages/excalidraw/components/JSONExportDialog.tsx +++ b/packages/excalidraw/components/JSONExportDialog.tsx @@ -78,7 +78,7 @@ const JSONExportModal = ({ onClick={async () => { try { trackEvent("export", "link", `ui (${getFrame()})`); - await onExportToBackend(elements, appState, files, canvas); + await onExportToBackend(elements, appState, files); onCloseRequest(); } catch (error: any) { setAppState({ errorMessage: error.message }); diff --git a/packages/excalidraw/components/ShareableLinkDialog.scss b/packages/excalidraw/components/ShareableLinkDialog.scss index 2b89f09d6..2429d50ca 100644 --- a/packages/excalidraw/components/ShareableLinkDialog.scss +++ b/packages/excalidraw/components/ShareableLinkDialog.scss @@ -22,7 +22,7 @@ } &__popover { - @keyframes RoomDialog__popover__scaleIn { + @keyframes ShareableLinkDialog__popover__scaleIn { from { opacity: 0; } @@ -61,7 +61,7 @@ } transform-origin: var(--radix-popover-content-transform-origin); - animation: RoomDialog__popover__scaleIn 150ms ease-out; + animation: ShareableLinkDialog__popover__scaleIn 150ms ease-out; } &__linkRow { diff --git a/packages/excalidraw/components/TextField.tsx b/packages/excalidraw/components/TextField.tsx index 10b3d9b53..44a7c25ff 100644 --- a/packages/excalidraw/components/TextField.tsx +++ b/packages/excalidraw/components/TextField.tsx @@ -13,8 +13,6 @@ import { Button } from "./Button"; import { eyeIcon, eyeClosedIcon } from "./icons"; type TextFieldProps = { - value?: string; - onChange?: (value: string) => void; onClick?: () => void; onKeyDown?: (event: KeyboardEvent) => void; @@ -26,12 +24,11 @@ type TextFieldProps = { label?: string; placeholder?: string; isRedacted?: boolean; -}; +} & ({ value: string } | { defaultValue: string }); export const TextField = forwardRef( ( { - value, onChange, label, fullWidth, @@ -40,6 +37,7 @@ export const TextField = forwardRef( selectOnRender, onKeyDown, isRedacted = false, + ...rest }, ref, ) => { @@ -73,10 +71,17 @@ export const TextField = forwardRef( > onChange?.(event.target.value)} diff --git a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss index edbcf198f..573fbccce 100644 --- a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss +++ b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.scss @@ -3,7 +3,7 @@ .excalidraw { .collab-button { --button-bg: var(--color-primary); - --button-color: white; + --button-color: var(--color-surface-lowest); --button-border: var(--color-primary); --button-width: var(--lg-button-size); @@ -35,12 +35,6 @@ } } - &.theme--dark { - .collab-button { - color: var(--color-gray-90); - } - } - .CollabButton.is-collaborating { background-color: var(--button-special-active-bg-color); diff --git a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx index 3111680cb..a22bc523a 100644 --- a/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx +++ b/packages/excalidraw/components/live-collaboration/LiveCollaborationTrigger.tsx @@ -1,5 +1,5 @@ import { t } from "../../i18n"; -import { usersIcon } from "../icons"; +import { share } from "../icons"; import { Button } from "../Button"; import clsx from "clsx"; @@ -17,16 +17,18 @@ const LiveCollaborationTrigger = ({ } & React.ButtonHTMLAttributes) => { const appState = useUIAppState(); + const showIconOnly = appState.width < 830; + return ( . Please include information below by copying and pasting into the GitHub issue.", "sceneContent": "Scene content:" }, + "shareDialog": { + "or": "Or" + }, "roomDialog": { - "desc_intro": "You can invite people to your current scene to collaborate with you.", - "desc_privacy": "Don't worry, the session uses end-to-end encryption, so whatever you draw will stay private. Not even our server will be able to see what you come up with.", + "desc_intro": "Invite people to collaborate on your drawing.", + "desc_privacy": "Don't worry, the session is end-to-end encrypted, and fully private. Not even our server can see what you draw.", "button_startSession": "Start session", "button_stopSession": "Stop session", "desc_inProgressIntro": "Live-collaboration session is now in progress.", diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index e29bb9f89..89b121b2f 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -495,7 +495,6 @@ export type ExportOpts = { exportedElements: readonly NonDeletedExcalidrawElement[], appState: UIAppState, files: BinaryFiles, - canvas: HTMLCanvasElement, ) => void; renderCustomUI?: ( exportedElements: readonly NonDeletedExcalidrawElement[], diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index 47fa52311..525652e6b 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -845,7 +845,7 @@ export const composeEventHandlers = ( if ( !checkForDefaultPrevented || - !(event as unknown as Event).defaultPrevented + !(event as unknown as Event)?.defaultPrevented ) { return ourEventHandler?.(event); } From def1df2c6811a26aa438b2aedd2a459b2fec13cd Mon Sep 17 00:00:00 2001 From: "YuBin, Hsu" <31545456+yubinTW@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:53:10 +0800 Subject: [PATCH 77/79] fix: keep customData when converting to ExcalidrawElement (#7656) * feat: keep customData when converting to ExcalidrawElement (#7654) * docs: add changelog for keeping customData when converting to ExcalidrawElement --- packages/excalidraw/CHANGELOG.md | 4 + .../data/__snapshots__/transform.test.ts.snap | 50 +++ packages/excalidraw/data/transform.test.ts | 18 ++ packages/excalidraw/element/newElement.ts | 2 + .../__snapshots__/contextmenu.test.tsx.snap | 93 ++++++ .../__snapshots__/dragCreate.test.tsx.snap | 5 + .../tests/__snapshots__/move.test.tsx.snap | 6 + .../multiPointCreate.test.tsx.snap | 2 + .../regressionTests.test.tsx.snap | 288 ++++++++++++++++++ .../__snapshots__/selection.test.tsx.snap | 5 + .../data/__snapshots__/restore.test.ts.snap | 9 + 11 files changed, 482 insertions(+) diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index d2c40c25e..34ad056fa 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -19,6 +19,10 @@ Please add the latest change on the top under the correct section. - Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450) +### Fixes + +- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656) + ### Breaking Changes - `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539) diff --git a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap index dcd48f8b5..450fce7de 100644 --- a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap +++ b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap @@ -14,6 +14,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -49,6 +50,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -79,6 +81,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": { "elementId": "ellipse-1", @@ -132,6 +135,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": { "elementId": "ellipse-1", @@ -190,6 +194,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -227,6 +232,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t }, ], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -271,6 +277,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t }, ], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -313,6 +320,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": { "elementId": "text-2", @@ -368,6 +376,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t "baseline": 0, "boundElements": null, "containerId": "id48", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -410,6 +419,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": { "elementId": "id40", @@ -465,6 +475,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "baseline": 0, "boundElements": null, "containerId": "id37", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -507,6 +518,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -542,6 +554,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -577,6 +590,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": { "elementId": "id44", @@ -632,6 +646,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when "baseline": 0, "boundElements": null, "containerId": "id41", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -676,6 +691,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when }, ], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -720,6 +736,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when }, ], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -757,6 +774,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -787,6 +805,7 @@ exports[`Test Transform > should transform linear elements 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -832,6 +851,7 @@ exports[`Test Transform > should transform linear elements 2`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "triangle", "endBinding": null, "fillStyle": "solid", @@ -877,6 +897,7 @@ exports[`Test Transform > should transform linear elements 3`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -922,6 +943,7 @@ exports[`Test Transform > should transform linear elements 4`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -967,6 +989,7 @@ exports[`Test Transform > should transform regular shapes 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -997,6 +1020,7 @@ exports[`Test Transform > should transform regular shapes 2`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1027,6 +1051,7 @@ exports[`Test Transform > should transform regular shapes 3`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1057,6 +1082,7 @@ exports[`Test Transform > should transform regular shapes 4`] = ` "angle": 0, "backgroundColor": "#c0eb75", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1087,6 +1113,7 @@ exports[`Test Transform > should transform regular shapes 5`] = ` "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1117,6 +1144,7 @@ exports[`Test Transform > should transform regular shapes 6`] = ` "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -1149,6 +1177,7 @@ exports[`Test Transform > should transform text element 1`] = ` "baseline": 0, "boundElements": null, "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1188,6 +1217,7 @@ exports[`Test Transform > should transform text element 2`] = ` "baseline": 0, "boundElements": null, "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1230,6 +1260,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -1280,6 +1311,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -1330,6 +1362,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -1380,6 +1413,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "type": "text", }, ], + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -1427,6 +1461,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "baseline": 0, "boundElements": null, "containerId": "id25", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1466,6 +1501,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "baseline": 0, "boundElements": null, "containerId": "id26", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1505,6 +1541,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "baseline": 0, "boundElements": null, "containerId": "id27", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1545,6 +1582,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide "baseline": 0, "boundElements": null, "containerId": "id28", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1588,6 +1626,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1623,6 +1662,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1658,6 +1698,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1693,6 +1734,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1728,6 +1770,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1763,6 +1806,7 @@ exports[`Test Transform > should transform to text containers when label provide "type": "text", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1795,6 +1839,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id13", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1834,6 +1879,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id14", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1874,6 +1920,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id15", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1916,6 +1963,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id16", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1956,6 +2004,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id17", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, @@ -1997,6 +2046,7 @@ exports[`Test Transform > should transform to text containers when label provide "baseline": 0, "boundElements": null, "containerId": "id18", + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 20, diff --git a/packages/excalidraw/data/transform.test.ts b/packages/excalidraw/data/transform.test.ts index 7c71f33f8..239cd2f4c 100644 --- a/packages/excalidraw/data/transform.test.ts +++ b/packages/excalidraw/data/transform.test.ts @@ -822,4 +822,22 @@ describe("Test Transform", () => { "Duplicate id found for rect-1", ); }); + + it("should contains customData if provided", () => { + const rawData = [ + { + type: "rectangle", + x: 100, + y: 100, + customData: { createdBy: "user01" }, + }, + ]; + const convertedElements = convertToExcalidrawElements( + rawData as ExcalidrawElementSkeleton[], + opts, + ); + expect(convertedElements[0].customData).toStrictEqual({ + createdBy: "user01", + }); + }); }); diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index 447a07993..f1e0d8093 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -68,6 +68,7 @@ export type ElementConstructorOpts = MarkOptional< | "roundness" | "locked" | "opacity" + | "customData" >; const _newElementBase = ( @@ -121,6 +122,7 @@ const _newElementBase = ( updated: getUpdatedTimestamp(), link, locked, + customData: rest.customData, }; return element; }; diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index b14000c2c..682af4bfe 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -387,6 +387,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -421,6 +422,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -584,6 +586,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -643,6 +646,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -786,6 +790,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -818,6 +823,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -877,6 +883,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -920,6 +927,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -949,6 +957,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -992,6 +1001,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1021,6 +1031,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1164,6 +1175,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1196,6 +1208,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1255,6 +1268,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1298,6 +1312,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1327,6 +1342,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1370,6 +1386,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1399,6 +1416,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1544,6 +1562,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1603,6 +1622,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1744,6 +1764,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1803,6 +1824,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1844,6 +1866,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1987,6 +2010,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2019,6 +2043,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2078,6 +2103,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2121,6 +2147,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2150,6 +2177,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2298,6 +2326,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2332,6 +2361,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2393,6 +2423,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2436,6 +2467,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2465,6 +2497,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2511,6 +2544,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2542,6 +2576,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2689,6 +2724,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -2721,6 +2757,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -2780,6 +2817,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2823,6 +2861,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2852,6 +2891,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2895,6 +2935,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2924,6 +2965,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2967,6 +3009,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2996,6 +3039,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3039,6 +3083,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3068,6 +3113,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3111,6 +3157,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3140,6 +3187,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3183,6 +3231,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3212,6 +3261,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3255,6 +3305,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3284,6 +3335,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3327,6 +3379,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3356,6 +3409,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "angle": 0, "backgroundColor": "#a5d8ff", "boundElements": null, + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [], @@ -3499,6 +3553,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3531,6 +3586,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3590,6 +3646,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3633,6 +3690,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3662,6 +3720,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3705,6 +3764,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3734,6 +3794,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3877,6 +3938,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3909,6 +3971,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3968,6 +4031,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4011,6 +4075,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4040,6 +4105,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4083,6 +4149,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4112,6 +4179,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4258,6 +4326,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4290,6 +4359,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4349,6 +4419,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4392,6 +4463,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4421,6 +4493,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4467,6 +4540,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -4498,6 +4572,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -4544,6 +4619,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4573,6 +4649,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4992,6 +5069,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5024,6 +5102,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5083,6 +5162,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5126,6 +5206,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5155,6 +5236,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5576,6 +5658,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5610,6 +5693,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5671,6 +5755,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5714,6 +5799,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5743,6 +5829,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5789,6 +5876,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5820,6 +5908,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -6872,6 +6961,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6904,6 +6994,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6936,6 +7027,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6995,6 +7087,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], diff --git a/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap index 5c986f44b..91203eefb 100644 --- a/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap @@ -7,6 +7,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -56,6 +57,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -90,6 +92,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -122,6 +125,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -171,6 +175,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index f348d0501..f287e547b 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -5,6 +5,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -37,6 +38,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -69,6 +71,7 @@ exports[`move element > rectangle 5`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -106,6 +109,7 @@ exports[`move element > rectangles with binding arrow 5`] = ` "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -143,6 +147,7 @@ exports[`move element > rectangles with binding arrow 6`] = ` "type": "arrow", }, ], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -175,6 +180,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": { "elementId": "id1", diff --git a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap index 12e7e61ed..3697f91b1 100644 --- a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap @@ -5,6 +5,7 @@ exports[`multi point mode in linear elements > arrow 3`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -59,6 +60,7 @@ exports[`multi point mode in linear elements > line 3`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index 65fa16899..de1166432 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -142,6 +142,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -185,6 +186,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -214,6 +216,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -257,6 +260,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -286,6 +290,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -315,6 +320,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -361,6 +367,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -390,6 +397,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -421,6 +429,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -602,6 +611,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -645,6 +655,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -674,6 +685,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -717,6 +729,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -746,6 +759,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -775,6 +789,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -821,6 +836,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -850,6 +866,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -881,6 +898,7 @@ exports[`given element A and group of elements B and given both are selected whe "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1053,6 +1071,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1096,6 +1115,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1125,6 +1145,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1171,6 +1192,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1202,6 +1224,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1247,6 +1270,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1278,6 +1302,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1323,6 +1348,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1354,6 +1380,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1385,6 +1412,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1432,6 +1460,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1464,6 +1493,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1496,6 +1526,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1541,6 +1572,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1573,6 +1605,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1605,6 +1638,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1650,6 +1684,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1682,6 +1717,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1714,6 +1750,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -1888,6 +1925,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -1931,6 +1969,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2106,6 +2145,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2149,6 +2189,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2178,6 +2219,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2221,6 +2263,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2250,6 +2293,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2279,6 +2323,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2325,6 +2370,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2354,6 +2400,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2385,6 +2432,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -2559,6 +2607,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] histo "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2602,6 +2651,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] histo "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2631,6 +2681,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] histo "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2801,6 +2852,7 @@ exports[`regression tests > arrow keys > [end of test] history 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -2973,6 +3025,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3016,6 +3069,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3045,6 +3099,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3088,6 +3143,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3117,6 +3173,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3146,6 +3203,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3189,6 +3247,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3218,6 +3277,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3247,6 +3307,7 @@ exports[`regression tests > can drag element that covers another element, while "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3417,6 +3478,7 @@ exports[`regression tests > change the properties of a shape > [end of test] his "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3460,6 +3522,7 @@ exports[`regression tests > change the properties of a shape > [end of test] his "angle": 0, "backgroundColor": "#ffec99", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3503,6 +3566,7 @@ exports[`regression tests > change the properties of a shape > [end of test] his "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3546,6 +3610,7 @@ exports[`regression tests > change the properties of a shape > [end of test] his "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3691,6 +3756,7 @@ exports[`regression tests > click on an element and drag it > [dragged] element "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3750,6 +3816,7 @@ exports[`regression tests > click on an element and drag it > [dragged] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3793,6 +3860,7 @@ exports[`regression tests > click on an element and drag it > [dragged] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -3965,6 +4033,7 @@ exports[`regression tests > click on an element and drag it > [end of test] hist "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4008,6 +4077,7 @@ exports[`regression tests > click on an element and drag it > [end of test] hist "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4051,6 +4121,7 @@ exports[`regression tests > click on an element and drag it > [end of test] hist "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4223,6 +4294,7 @@ exports[`regression tests > click to select a shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4266,6 +4338,7 @@ exports[`regression tests > click to select a shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4295,6 +4368,7 @@ exports[`regression tests > click to select a shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4468,6 +4542,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4511,6 +4586,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4540,6 +4616,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4583,6 +4660,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4612,6 +4690,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4641,6 +4720,7 @@ exports[`regression tests > click-drag to select a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4813,6 +4893,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4856,6 +4937,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4885,6 +4967,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -4931,6 +5014,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -4962,6 +5046,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5007,6 +5092,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5038,6 +5124,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5085,6 +5172,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5116,6 +5204,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -5184,6 +5273,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5268,6 +5358,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5345,6 +5436,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5388,6 +5480,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5417,6 +5510,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5483,6 +5577,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5616,6 +5711,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5659,6 +5755,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5688,6 +5785,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5754,6 +5852,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5837,6 +5936,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -5914,6 +6014,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6084,6 +6185,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6254,6 +6356,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6297,6 +6400,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6326,6 +6430,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6369,6 +6474,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6398,6 +6504,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6427,6 +6534,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6474,6 +6582,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -6505,6 +6614,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -6536,6 +6646,7 @@ exports[`regression tests > double click to edit a group > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -6712,6 +6823,7 @@ exports[`regression tests > drags selected elements from point inside common bou "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6755,6 +6867,7 @@ exports[`regression tests > drags selected elements from point inside common bou "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6784,6 +6897,7 @@ exports[`regression tests > drags selected elements from point inside common bou "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6828,6 +6942,7 @@ exports[`regression tests > drags selected elements from point inside common bou "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -6857,6 +6972,7 @@ exports[`regression tests > drags selected elements from point inside common bou "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7025,6 +7141,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7068,6 +7185,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7097,6 +7215,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7140,6 +7259,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7169,6 +7289,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7198,6 +7319,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7241,6 +7363,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7270,6 +7393,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7299,6 +7423,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7328,6 +7453,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -7386,6 +7512,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7415,6 +7542,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7444,6 +7572,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7473,6 +7602,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -7517,6 +7647,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -7575,6 +7706,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7604,6 +7736,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7633,6 +7766,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7662,6 +7796,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -7706,6 +7841,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -7750,6 +7886,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -7811,6 +7948,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7840,6 +7978,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7869,6 +8008,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -7898,6 +8038,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -7942,6 +8083,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -7986,6 +8128,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8051,6 +8194,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8080,6 +8224,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8109,6 +8254,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8138,6 +8284,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8182,6 +8329,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8226,6 +8374,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8277,6 +8426,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8338,6 +8488,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8367,6 +8518,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8396,6 +8548,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8425,6 +8578,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8469,6 +8623,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8513,6 +8668,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8564,6 +8720,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8627,6 +8784,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8656,6 +8814,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8685,6 +8844,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -8714,6 +8874,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8758,6 +8919,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8802,6 +8964,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -8853,6 +9016,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -8904,6 +9068,7 @@ exports[`regression tests > draw every type of shape > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9099,6 +9264,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9142,6 +9308,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9171,6 +9338,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9214,6 +9382,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9243,6 +9412,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9272,6 +9442,7 @@ exports[`regression tests > given a group of selected elements with an element t "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9445,6 +9616,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9488,6 +9660,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9517,6 +9690,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "angle": 0, "backgroundColor": "#ffc9c9", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9689,6 +9863,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9718,6 +9893,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9890,6 +10066,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9919,6 +10096,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9962,6 +10140,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -9991,6 +10170,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "angle": 0, "backgroundColor": "red", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -10161,6 +10341,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -10331,6 +10512,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] history 1 "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -10501,6 +10683,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] history 1 "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -10694,6 +10877,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -10902,6 +11086,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -11083,6 +11268,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -11298,6 +11484,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -11483,6 +11670,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] history 1 "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -11676,6 +11864,7 @@ exports[`regression tests > key l selects line tool > [end of test] history 1`] "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -11861,6 +12050,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] history 1 "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12027,6 +12217,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12219,6 +12410,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12397,6 +12589,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12440,6 +12633,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12469,6 +12663,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12512,6 +12707,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12541,6 +12737,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12570,6 +12767,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -12617,6 +12815,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12648,6 +12847,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12679,6 +12879,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12728,6 +12929,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12759,6 +12961,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12790,6 +12993,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12821,6 +13025,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12852,6 +13057,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -12883,6 +13089,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] histor "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -13057,6 +13264,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13100,6 +13308,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13129,6 +13338,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13424,6 +13634,7 @@ exports[`regression tests > shift click on selected element should deselect it o "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13598,6 +13809,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13641,6 +13853,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13670,6 +13883,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13714,6 +13928,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13743,6 +13958,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13919,6 +14135,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13962,6 +14179,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -13991,6 +14209,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14034,6 +14253,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14063,6 +14283,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14092,6 +14313,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14139,6 +14361,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14170,6 +14393,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14201,6 +14425,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14248,6 +14473,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14277,6 +14503,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14306,6 +14533,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14486,6 +14714,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14529,6 +14758,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14558,6 +14788,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14604,6 +14835,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14635,6 +14867,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14680,6 +14913,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14711,6 +14945,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14742,6 +14977,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14785,6 +15021,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14816,6 +15053,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14847,6 +15085,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14876,6 +15115,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -14922,6 +15162,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14953,6 +15194,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -14984,6 +15226,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15015,6 +15258,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15065,6 +15309,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15097,6 +15342,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15129,6 +15375,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15161,6 +15408,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15464,6 +15712,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15507,6 +15756,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15536,6 +15786,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15579,6 +15830,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15608,6 +15860,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15637,6 +15890,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -15684,6 +15938,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15715,6 +15970,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15746,6 +16002,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15792,6 +16049,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15823,6 +16081,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15854,6 +16113,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15902,6 +16162,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15933,6 +16194,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -15965,6 +16227,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -16015,6 +16278,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -16046,6 +16310,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -16078,6 +16343,7 @@ exports[`regression tests > supports nested groups > [end of test] history 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [ @@ -16147,6 +16413,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16233,6 +16500,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16310,6 +16578,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16353,6 +16622,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16382,6 +16652,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16425,6 +16696,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16454,6 +16726,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16483,6 +16756,7 @@ exports[`regression tests > switches from group of selected elements to another "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16549,6 +16823,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16634,6 +16909,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16711,6 +16987,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16754,6 +17031,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -16783,6 +17061,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17066,6 +17345,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17095,6 +17375,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17124,6 +17405,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -17189,6 +17471,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17218,6 +17501,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17247,6 +17531,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -17321,6 +17606,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17364,6 +17650,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -17393,6 +17680,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] history "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], diff --git a/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap index 92ebee631..9b0ebecc6 100644 --- a/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap @@ -5,6 +5,7 @@ exports[`select single element on the scene > arrow 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": "arrow", "endBinding": null, "fillStyle": "solid", @@ -52,6 +53,7 @@ exports[`select single element on the scene > arrow escape 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -99,6 +101,7 @@ exports[`select single element on the scene > diamond 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -131,6 +134,7 @@ exports[`select single element on the scene > ellipse 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -163,6 +167,7 @@ exports[`select single element on the scene > rectangle 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": null, + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], diff --git a/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap b/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap index 0c06b65f1..457ed4f14 100644 --- a/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap +++ b/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap @@ -5,6 +5,7 @@ exports[`restoreElements > should restore arrow element correctly 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": [], + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -52,6 +53,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and "angle": 0, "backgroundColor": "blue", "boundElements": [], + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [ @@ -88,6 +90,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and "angle": 0, "backgroundColor": "blue", "boundElements": [], + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [ @@ -124,6 +127,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and "angle": 0, "backgroundColor": "blue", "boundElements": [], + "customData": undefined, "fillStyle": "cross-hatch", "frameId": null, "groupIds": [ @@ -160,6 +164,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = ` "angle": 0, "backgroundColor": "transparent", "boundElements": [], + "customData": undefined, "fillStyle": "solid", "frameId": null, "groupIds": [], @@ -196,6 +201,7 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] = "angle": 0, "backgroundColor": "transparent", "boundElements": [], + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -243,6 +249,7 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] = "angle": 0, "backgroundColor": "transparent", "boundElements": [], + "customData": undefined, "endArrowhead": null, "endBinding": null, "fillStyle": "solid", @@ -292,6 +299,7 @@ exports[`restoreElements > should restore text element correctly passing value f "baseline": 0, "boundElements": [], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 14, @@ -333,6 +341,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo "baseline": 0, "boundElements": [], "containerId": null, + "customData": undefined, "fillStyle": "solid", "fontFamily": 1, "fontSize": 10, From adc4c9f4847c6d701fb05c6e57758194a297b160 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:50:50 +0100 Subject: [PATCH 78/79] fix: prevent panning to trigger history on macos chrome (#7671) --- packages/excalidraw/components/App.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index f965a7679..7b9cc8cc2 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1410,6 +1410,13 @@ class App extends React.Component { }); }; + private toggleOverscrollBehavior(event: React.PointerEvent) { + // when pointer inside editor, disable overscroll behavior to prevent + // panning to trigger history back/forward on MacOS Chrome + document.documentElement.style.overscrollBehaviorX = + event.type === "pointerenter" ? "none" : "auto"; + } + public render() { const selectedElements = this.scene.getSelectedElements(this.state); const { renderTopRightUI, renderCustomStats } = this.props; @@ -1463,6 +1470,8 @@ class App extends React.Component { onKeyDown={ this.props.handleKeyboardGlobally ? undefined : this.onKeyDown } + onPointerEnter={this.toggleOverscrollBehavior} + onPointerLeave={this.toggleOverscrollBehavior} > @@ -2455,6 +2464,7 @@ class App extends React.Component { isSomeElementSelected.clearCache(); selectGroupsForSelectedElements.clearCache(); touchTimeout = 0; + document.documentElement.style.overscrollBehaviorX = ""; } private onResize = withBatchedUpdates(() => { From 48c3465b19f10ec755b3eb84e21a01a468e96e43 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Fri, 9 Feb 2024 19:29:50 +0530 Subject: [PATCH 79/79] docs: release patch v0.17.3 (#7673) * docs: release patch v0.17.3 * update cl --- packages/excalidraw/CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/excalidraw/CHANGELOG.md b/packages/excalidraw/CHANGELOG.md index 34ad056fa..3759b44c4 100644 --- a/packages/excalidraw/CHANGELOG.md +++ b/packages/excalidraw/CHANGELOG.md @@ -61,10 +61,12 @@ Please add the latest change on the top under the correct section. - `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336) -## 0.17.1 (2023-11-28) +## 0.17.3 (2024-02-09) ### Fixes +- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656) + - Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean. ``` @@ -73,6 +75,10 @@ define: { } ``` +- Disable caching bounds for arrow labels [#7343](https://github.com/excalidraw/excalidraw/pull/7343) + +- Bounds cached prematurely resulting in incorrectly rendered labels [#7339](https://github.com/excalidraw/excalidraw/pull/7339) + ## Excalidraw Library ### Fixes

    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�?qd(5Hh*uAIxK&qTYu3V< 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 PfG>3wZRJu$%i#Y78=43z literal 0 HcmV?d00001 diff --git a/examples/excalidraw/with-script-in-browser/public/images/excalibot.png b/examples/excalidraw/with-script-in-browser/public/images/excalibot.png new file mode 100644 index 0000000000000000000000000000000000000000..7928ec325b9ab27d04a434ff69a3a60e068a727d GIT binary patch literal 30330 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 literal 0 HcmV?d00001 diff --git a/examples/excalidraw/with-script-in-browser/public/images/pika.jpeg b/examples/excalidraw/with-script-in-browser/public/images/pika.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..455ed52a638a0b060fba54892835e8923d671bfd GIT binary patch literal 6250 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 Vod0WA{QK4aPuTdMX5h>5{{x3Dqs0IK literal 0 HcmV?d00001 diff --git a/examples/excalidraw/with-script-in-browser/public/images/rocket.jpeg b/examples/excalidraw/with-script-in-browser/public/images/rocket.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f17a74bd65c0705097ac832a6f63c6f3f6ca4e82 GIT binary patch literal 40368 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