Compare commits

...

88 Commits

Author SHA1 Message Date
5639bb8e87 Updates to point to excalidraw-storage-backend 2025-05-25 22:22:50 -04:00
Ryan Di
712f267519
feat: better unlock (#9546)
* change lock label

* feat: add unlock logic for single units on pointer up

* feat: add unlock popup

* fix: linting errors

* style: padding tweaks

* style: remove redundant line

* feat: lock multiple units together

* style: tweak color & position

* feat: add highlight for locked elements

* feat: select groups correctly after unlocking

* test: update snapshots

* fix: lasso from selecting locked elements

* fix: should prevent selecting unlocked elements and setting locked id at the same time

* fix: reset hit locked id immediately when appropriate

* feat: capture locked units in delta

* test: update locking test

* feat: show lock highlight when locking (including undo/redo)

* feat: make locked highlighting consistent

* feat: show correct cursor type when moving over locked elements

* fix history

* remove `lockedUnits.singleUnits`

* tweak button

* do not render UnlockPopup if not locked element selected

* tweak actions

* refactor: simplify type

* refactor: rename type

* refactor: simplify hit element setting & checking

* fix: prefer locked over link

* rename to `activeLockedId`

* refactor: getElementAtPosition takes an optional hitelments array

* fix: avoid setting active locked id after resizing

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-21 21:57:12 +10:00
Márk Tolmács
41a7613dff
fix: Elbow arrow conversion labels mixed up (#9547) 2025-05-19 20:35:48 +02:00
David Luzar
95d89a751a
refactor: decouple radio button selection from .buttonList wrapper (#9528)
* refactor: decouple radio button selection from `.buttonList`

* fix
2025-05-15 13:22:26 +02:00
Marcel Mraz
6b5fb30d69
fix: unify line height across default fonts (#9513) 2025-05-14 16:02:01 +02:00
Marcel Mraz
d92a849038
fix: issues when importing package outside of browser (#9525) 2025-05-14 16:01:43 +02:00
David Luzar
0a534f1bc6
fix: never show snap lines when lasso tool active (#9523) 2025-05-14 22:04:40 +10:00
Ryan Di
4ca5f53b1f
fix: alt + ctrl lasso selected elements not always kept (#9522)
* fix: alt + ctrl lasso selected elements not always kept

* Update packages/excalidraw/components/App.tsx

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-05-14 22:04:03 +10:00
zsviczian
f7dcc893ea
feat: transparent link background, scale link icon when zooming to below 100% (#9520)
* Do not set link background color, dynamically scale down link icon size with zoom.

* removed unnecessary change

* use canvas bg color & reduce size and stroke width

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-14 13:38:18 +02:00
zsviczian
4dfb8a3f8e
feat: allow forms.microsoft.com domain for embeddables (#9519)
* Update embeddable.ts

* no need for same origin

* The form does not load without allow same origin

* automatically add embed=true to link if not present

* fix link check
2025-05-13 19:48:26 +02:00
David Luzar
298812e1d0
fix: improve ctrl+alt lasso selecting (#9514) 2025-05-12 18:09:37 +02:00
Ryan Di
35bb449a4b
fix: update cached segments when visible area changes (#9512) 2025-05-12 15:55:36 +02:00
David Luzar
c4c064982f
feat: show empty active color if no common color (#9506) 2025-05-11 15:07:57 +02:00
David Luzar
51dbd4831b
refactor: make element type conversion more generic (#9504)
* feat: add `reduceToCommonValue()` & improve opacity slider

* feat: generalize and simplify type conversion cache

* refactor: change cache from atoms to Map

* feat: always attempt to reuse original fontSize when converting generic types
2025-05-10 20:06:16 +02:00
Marcel Mraz
7e41026812
refactor: export everything from @excalidraw/element, don't import from subpaths (#9466)
* Don't import from subpaths

* Fix tests, move related tests to element
2025-05-09 23:01:33 +02:00
shindi-renuo
a8ebe514da
Replace tongue emoji with globe emoji (#9489) 2025-05-09 16:59:06 +00:00
Ryan Di
a30e1b25c6
feat: include frame names in canvas searches (#9484)
* fix frame name clipping on zooming

* include assistant font

* default frame name

* extend search to frame names

* add a simple test

* collpase search match items

* id check out of loop

* fix frame name check

* include focusedId for small perf improvement

* optionally show and hide collapse icon

* update section title

* fix tests

* rename `serverSide` -> `private`

* revert: do not reset zoom on zoom change

* feat: do not close menu on repeated ctrl+f

* remove collapsible

* tweak results CSS

* remove redundant check

* set `appState.searchMatches` to null if empty

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-09 18:32:16 +02:00
David Luzar
ff2ed5d26a
refactor: change movePoints pointUpdates type (#9499) 2025-05-08 16:47:13 +02:00
Narek Malkhasyan
e058a08b33
fix: use rimraf instead of rm -rf (#9460) 2025-05-07 14:13:27 +02:00
Narek Malkhasyan
a306a909a0
fix: don't scroll page when TTDDialog is opened (#9455) 2025-05-07 13:33:18 +02:00
Marcel Mraz
3dc54a724a
feat: add onIncrement API (#9450) 2025-05-06 19:23:02 +02:00
David Luzar
a7c61319dd
fix: do not translate bound elements twice (#9486) 2025-05-06 13:09:00 +02:00
Narek Malkhasyan
cec5232a7a
fix: when resizing element, update bound elements after final size of element is determined (#9475) 2025-05-05 12:15:42 +02:00
Márk Tolmács
d4f70e9f31
feat: Quarter snap points for diamonds (#9387) 2025-05-05 11:34:40 +02:00
Márk Tolmács
e19fd1332a
feat: Precise highlights for bindings (#9472) 2025-05-05 09:51:20 +02:00
Hazem Krimi
6e655cdb24
fix: When moving a frame through the stats inputs or drags move along its children (#9433)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-05-02 17:07:17 +02:00
Gowtham Selvaraj
192c4e7658
docs: added shape cycling shortcut in helper dialog (#9465)
* docs: added shape cycling shortcut in helper dialog

- Document Tab and Shift+Tab usage for shape cycling

* docs: added shape cycling shortcut in helper dialog

* Update packages/excalidraw/components/HelpDialog.tsx

* Update packages/excalidraw/locales/en.json

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-05-01 12:12:45 +02:00
Ryan Di
195a743874
feat: switch between basic shapes (#9270)
* feat: switch between basic shapes

* add tab for testing

* style tweaks

* only show hint when a new node is created

* fix panel state

* refactor

* combine captures into one

* keep original font size

* switch multi

* switch different types altogether

* use tab only

* fix font size atom

* do not switch from active tool change

* prefer generic when mixed

* provide an optional direction when shape switching

* adjust panel bg & shadow

* redraw to correctly position text

* remove redundant code

* only tab to switch if focusing on app container

* limit which linear elements can be switched

* add shape switch to command palette

* remove hint

* cache initial panel position

* bend line to elbow if needed

* remove debug logic

* clean switch of arrows using app state

* safe conversion between line, sharp, curved, and elbow

* cache linear when panel shows up

* type safe element conversion

* rename type

* respect initial type when switching between linears

* fix elbow segment indexing

* use latest linear

* merge converted elbow points if too close

* focus on panel after click

* set roudness to null to fix drag points offset for elbows

* remove Mutable

* add arrowBoundToElement check

* make it dependent on one signle state

* unmount when not showing

* simpler types, tidy up code

* can change linear when it's linear + non-generic

* fix popup component lifecycle

* move constant to CLASSES

* DRY out type detection

* file & variable renaming

* refactor

* throw in not-prod instead

* simplify

* semi-fix bindings on `generic` type conversion

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-30 18:07:31 +02:00
David Luzar
4a60fe3d22
fix: remove noreferrer on internal links (#9452)
* fix: remove `noreferrer` on internal links

* fix snaps

* fix lint
2025-04-29 18:45:17 +02:00
Narek Malkhasyan
2a0d15799c
fix: when dragging arrow endpoint, update binding only on the dragged side (#9367) 2025-04-25 10:46:58 +02:00
CharitSinghChauhan
a18b139a60
fix: laser pointer trail disappearing on pointerup (#9413) (#9427)
* Fix laser pointer trail disappearing on pointerup (#9413)

Previously, the laser pointer trail would disappear as soon as the pointerup event was triggered. This fix delays the trail removal to ensure it persists for a smoother visual experience.

Fixes #9413.

* Remove extra blank lines

Minor formatting cleanup. No functional changes.
2025-04-24 10:05:08 +10:00
Marcel Mraz
1913599594
refactor: remove dependency on the (static) Scene (#9389) 2025-04-23 13:45:08 +02:00
Vedant Mishra
debf2ad608
docs: Fix missing verb in Footer component documentation (#9393) 2025-04-20 12:35:38 +02:00
David Luzar
8fb2f70414
fix: scrollbar rendering and improve dragging (#9417)
* fix: scrollbar rendering and improve dragging

* tweak offsets
2025-04-20 12:28:41 +02:00
Jack Walsh
5fc13e4309
feat: add props.renderScrollbars (#9399)
* Expose renderScrollbars to AppState

* fix: scrollbar rendering should use al renderable elements

* remove `appState.renderScrollbars`

* clean unused

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-19 21:50:44 +00:00
David Luzar
b5d60973b7
fix: duplication tests pointer state leaking between tests (#9414)
* fix: duplication tests pointer state leaking between tests

* fix snapshots
2025-04-18 11:11:12 +02:00
David Luzar
a5d6939826
fix: keep orig elem in place on alt-duplication (#9403)
* fix: keep orig elem in place on alt-duplication

* clarify comment

* fix: incorrect selection on duplicating labeled containers

* fix: duplicating within group outside frame should remove from group
2025-04-17 16:08:07 +02:00
David Luzar
0cf36d6b30
fix: erasing locked elements (#9400)
* fix: erasing locked elements

* signature tweaks
2025-04-16 10:28:56 +02:00
Ryan Di
58f7d33d80
perf: make eraser great again (#9352)
* perf: make eraser great again

* lint

* refactor and improve perf

* lint
2025-04-15 16:58:45 +02:00
Rubén Norte
6fe7de8020
fix: Add DOCTYPE and XML preamble in exported SVG documents (#9386)
* Add DOCTYPE and XML preamble in exported SVG documents

* Update packages/excalidraw/data/index.ts

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-04-14 21:25:18 +02:00
Márk Tolmács
01304aac49
feat: Keep text label horizontal (#9364)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-13 21:21:49 +02:00
jhanma17dev
dff69e9191
chore: Element center point util (#9298) 2025-04-09 17:04:51 +02:00
Ryan Di
6fc85022ae
fix: lasso selection issues (#9353)
* revert stroke slicing hack for knot

* fix incorrect closing of path

* nonzero enclosure

* lint
2025-04-08 00:50:52 +10:00
Márk Tolmács
e48b63a0ae
fix: Rounded diamond edge elbow arrow U route (#9349) 2025-04-07 10:43:07 +02:00
David Luzar
c2caf78e95
fix: deselected hit element being duplicated + incorrect re-seeding (#9333)
* fix: deselected hit element being duplicated + incorrect re-seeding

* snapshots

* Fix alt-drag binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Add alt-drag bound arrow test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-04-07 10:41:31 +02:00
Ryan Di
ce267aa0d3
feat: lasso selection (#9169)
* lasso without 'real' shape detection

* select a single linear el

* improve ux

* feed segments to worker

* simplify path threshold adaptive to zoom

* add a tiny threshold for checks

* refactor code

* lasso tests

* fix: ts

* do not capture lasso tool

* try worker-loader in next config

* update config

* refactor

* lint

* feat: show active tool when using "more tools"

* keep lasso if selected from toolbar

* fix incorrect checks for resetting to selection

* shift for additive selection

* bound text related fixes

* lint

* keep alt toggled lasso selection if shift pressed

* fix regression

* fix 'dead' lassos

* lint

* use workerpool and polyfill

* fix worker bundled with window related code

* refactor

* add file extension for worker constructor error

* another attempt at constructor error

* attempt at build issue

* attempt with dynamic import

* test not importing from math

* narrow down imports

* Reusing existing workers infrastructure (fallback to the main thread, type-safety)

* Points on curve inside the shared chunk

* Give up on experimental code splitting

* Remove potentially unnecessary optimisation

* Removing workers as the complexit is much worse, while perf. does not seem to be much better

* fix selecting text containers and containing frames together

* render fill directly from animated trail

* do not re-render static when setting selected element ids in lasso

* remove unnecessary property

* tweak trail animation

* slice points to remove notch

* always start alt-lasso from initial point

* revert build & worker changes (unused)

* remove `lasso` from `hasStrokeColor`

* label change

* remove unused props

* remove unsafe optimization

* snaps

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2025-04-07 16:44:25 +10:00
Narek Malkhasyan
6e47fadb59
feat: add container to multiple text elements (#9348) 2025-04-07 00:57:27 +02:00
Márk Tolmács
b3d5ba0567
fix: Linear element is not normalized (#9347)
* Fix #9292
2025-04-06 13:41:11 +02:00
Panagiotis Papadopoulos
c79e892e55
chore: bump @radix-ui/react-tabs version to 1.1.3 (#9329)
* chore: bump @radix-ui/react-tabs version to 1.1.3

bumped the version to latest stable that includes
react ^19 as peerDepenecy.
This fixes peerDependency issues, as reported in #9253

* redeploy

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-02 16:23:15 +02:00
David Luzar
57a9e301d4
feat: tweak color swatch, and button bgs (#9330)
* feat: tweak color swatch, and button bgs

* snapshots
2025-04-02 14:36:13 +02:00
David Luzar
7c58477382
feat: tweak properties panel styling (#9322) 2025-03-30 19:20:13 +02:00
David Luzar
83fac6d0db
feat: tweak stats panel input styles (#9321) 2025-03-30 19:00:31 +02:00
David Luzar
f2e8404c7b
feat: allow to disable preventUnload in dev (#9319)
* feat: allow to disable preventUnload in dev

* add template
2025-03-29 19:42:33 +01:00
David Luzar
d797c2e210
fix: strip legacy attrs on element restore (#9318) 2025-03-29 19:31:16 +01:00
Marcel Mraz
0cd5a259ae
fix: incorrect type imports (#9308) 2025-03-27 12:00:12 +01:00
Marcel Mraz
432a46ef9e
refactor: separate elements logic into a standalone package (#9285) 2025-03-26 15:24:59 +01:00
Márk Tolmács
a18f059188
fix: Reduce allocations in collision detection (#9299)
Reduce allocations
2025-03-26 15:10:43 +01:00
KODIFY
ab89d4c16f
feat: add keyboard shortcut to save file in text (#9295)
Co-authored-by: Aviral Sharma <aviralsharma954@gmail.com>
2025-03-25 22:18:55 +01:00
Mubaraq Wahab
6c3a434f2a
docs: Fix table rendering and broken links in Props page (#9293)
* Fix table rendering and broken links

---------

Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2025-03-25 14:32:15 +01:00
Mursaleen Nisar
e1bb59fb8f
chore: Use isDevEnv() and isTestEnv() (#9264)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-24 19:44:00 +01:00
Márk Tolmács
77aca48c84
fix: Refactor and merge duplication and binding (#9246) 2025-03-23 18:39:33 +01:00
WalterMitty
58990b41ae
fix: 'Rotate' spell error (#9288) 2025-03-22 09:06:23 +00:00
David Luzar
99d8bff175
fix: elements offset incorrectly when action-duplicated during drag (#9275)
* fix: elements offset incorrectly when action-duplicated during drag

* prevent duplicate action during drag altogether
2025-03-15 20:05:42 +01:00
Márk Tolmács
30983d801a
fix: Arrow conversion regression (#9241)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-15 12:31:25 +01:00
Marcel Mraz
21ffaf4d76
refactor: auto ordered imports (#9163) 2025-03-12 15:23:31 +01:00
Marcel Mraz
82b9a6b464
docs: CHANGELOG typos 🙏 (#9250) 2025-03-11 23:18:15 +01:00
Marcel Mraz
817d8c553c
docs: update CHANGELOG (#9243) 2025-03-11 13:44:10 +01:00
Marcel Mraz
69bc5bdaab
chore: post publish docs & examples changes (#9217) 2025-03-11 13:02:59 +01:00
Márk Tolmács
d587b8a3de
fix: Do not rebind undragged elbow arrow endpoint (#9191) 2025-03-10 16:25:33 +01:00
Márk Tolmács
4ec812bc18
fix: Bound elbow arrow on duplication does not route correctly (#9236) 2025-03-08 12:39:54 +01:00
Márk Tolmács
a9e2d2348b
chore: Logging and fixing extremely large scenes (#9225) 2025-03-05 23:06:01 +01:00
David Luzar
70c3e921bb
fix: package env vars (#9221) 2025-03-04 21:45:48 +01:00
David Luzar
d92384b77d
revert: vite@6 -> vite@5 (#9220) 2025-03-04 17:45:52 +00:00
Ritobroto Kalita
c5d3bb0b6a
fix: #8475 Arrow updated on both sides (#8593) 2025-03-04 17:24:39 +01:00
David Luzar
d21c6a1bc6
chore: bump vite@6.x (#9210) 2025-03-04 14:00:13 +01:00
David Luzar
d1112bbf4f
fix: docked sidebar width (#9213) 2025-03-02 18:24:20 +01:00
Márk Tolmács
2523624f15
fix: React 18 element.ref was accessed error (#9208)
Bump @radix-ui/react-popover to fix element.ref React 19 error

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-02 14:24:50 +01:00
David Luzar
68578556ff
fix: cleanup legacy element.rawText (obsidian) (#9203) 2025-03-01 11:03:02 +01:00
Marcel Mraz
ecef5d12f4
chore: release @excalidraw/excalidraw@18.0.0 🎉 (#9127) 2025-02-28 16:49:09 +01:00
Márk Tolmács
392118bf26
fix: Fix elbow arrow fixed binding on restore (#9197) 2025-02-28 15:36:41 +01:00
Márk Tolmács
0ffeaeaecf
feat: Remove GA code from binding (#9042)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-02-25 22:52:06 +01:00
David Luzar
31e8476c78
chore: upgrade to react@19 (#9182) 2025-02-25 19:18:42 +01:00
Abhinav Pant
9ee0b8ffcb
Enhancement: grouped together Undo and Redo buttons on mobile (#9109)
* bugfix: put the redo and undo button under the same div so that they look grouped together

* fixed the position of the redo and undo buttons to the right
2025-02-13 13:07:44 +00:00
David Luzar
16b86d7d16
chore: update firebase@8 to @11 (#9136) 2025-02-13 13:57:14 +01:00
Márk Tolmács
f12b92ce9d
chore: Upgrade Sentry to latest and update debug messages (#9134)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-02-13 12:47:27 +01:00
Márk Tolmács
77dc055d81
chore: Revert aspect ratio fix with element size limits and chk (#9131) 2025-02-12 15:02:35 +01:00
David Luzar
26f02bebea
fix: stop using structuredClone (#9128)
fix: stop using `structuredClone`
2025-02-12 13:02:53 +01:00
Marcel Mraz
e3060dfb8f
feat: custom text metrics provider (#9121) 2025-02-11 14:23:08 +01:00
604 changed files with 45704 additions and 35874 deletions

View File

@ -48,3 +48,6 @@ UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
HQIDAQAB'
# set to true in .env.development.local to disable the prevent unload dialog
VITE_APP_DISABLE_PREVENT_UNLOAD=

View File

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

View File

@ -1,6 +1,21 @@
{
"extends": ["@excalidraw/eslint-config", "react-app"],
"rules": {
"import/order": [
"warn",
{
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
"pathGroups": [
{
"pattern": "@excalidraw/**",
"group": "external",
"position": "after"
}
],
"newlines-between": "always-and-inside-groups",
"warnOnUnassignedImports": true
}
],
"import/no-anonymous-default-export": "off",
"no-restricted-globals": "off",
"@typescript-eslint/consistent-type-imports": [
@ -17,6 +32,12 @@
"name": "jotai",
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
}
],
"react/jsx-no-target-blank": [
"error",
{
"allowReferrer": true
}
]
}
}

1
.gitignore vendored
View File

@ -25,5 +25,4 @@ packages/excalidraw/types
coverage
dev-dist
html
examples/**/bundle.*
meta*.json

View File

@ -63,7 +63,7 @@ The Excalidraw editor (npm package) supports:
- 🏗️&nbsp;Customizable.
- 📷&nbsp;Image support.
- 😀&nbsp;Shape libraries support.
- 👅&nbsp;Localization (i18n) support.
- 🌐&nbsp;Localization (i18n) support.
- 🖼️&nbsp;Export to PNG, SVG & clipboard.
- 💾&nbsp;Open format - export drawings as an `.excalidraw` json file.
- ⚒️&nbsp;Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
@ -87,13 +87,11 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
**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).
```
Use `npm` or `yarn` to install the package.
```bash
npm install react react-dom @excalidraw/excalidraw
```
or via yarn
```
# or
yarn add react react-dom @excalidraw/excalidraw
```

View File

@ -2,7 +2,7 @@
Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node.
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should be a valid React Node.
**Usage**
@ -25,7 +25,7 @@ function App() {
}
```
This will only for `Desktop` devices.
This will only work for `Desktop` devices.
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
@ -65,4 +65,4 @@ const App = () => (
// Need to render when code is span across multiple components
// in Live Code blocks editor
render(<App />);
```
```

View File

@ -8,15 +8,15 @@
import { FONT_FAMILY } from "@excalidraw/excalidraw";
```
`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
`FONT_FAMILY` contains all the font families used in `Excalidraw`. The default families are the following:
| Font Family | Description |
| ----------- | ---------------------- |
| `Virgil` | The `Hand-drawn` font |
| `Helvetica` | The `Normal` Font |
| `Cascadia` | The `Code` Font |
| `Excalifont` | The `Hand-drawn` font |
| `Nunito` | The `Normal` Font |
| `Comic Shanns` | The `Code` Font |
Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
Pre-selected family is `FONT_FAMILY.Excalifont`, unless it's overriden with `initialData.appState.currentItemFontFamily`.
### THEME

View File

@ -65,7 +65,7 @@ You can use this function to update the scene with the sceneData. It accepts the
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L38) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L39) | The `appState` to be updated in the scene. |
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
| `commitToStore` | `boolean` | Implies if the change should be captured and commited to the `store`. Commited changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `false`. |
| `captureUpdate` | [`CaptureUpdateAction`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/store.ts#L40) | Controls which updates should be captured by the `Store`. Captured updates are emmitted and listened to by other components, such as `History` for undo / redo purposes. |
```jsx live
function App() {
@ -105,6 +105,7 @@ function App() {
appState: {
viewBackgroundColor: "#edf2ff",
},
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
excalidrawAPI.updateScene(sceneData);
};
@ -121,6 +122,18 @@ function App() {
}
```
#### captureUpdate
You can use the `captureUpdate` to influence undo / redo behaviour.
> **NOTE**: Some updates are not observed by the store / history - i.e. updates to `collaborators` object or parts of `AppState` which are not observed (not `ObservedAppState`). Such updates will never make it to the undo / redo stacks, regardless of the passed `captureUpdate` value.
| | `captureUpdate` value | Notes |
| --- | --- | --- |
| _Immediately undoable_ | `CaptureUpdateAction.IMMEDIATELY` | Use for updates which should be captured. Should be used for most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
| _Eventually undoable_ | `CaptureUpdateAction.EVENTUALLY` | Use for updates which should not be captured immediately - likely exceptions which are part of some async multi-step process. Otherwise, all such updates would end up being captured with the next `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` or internally by the editor. These updates will _eventually_ make it to the local undo / redo stacks. |
| _Never undoable_ | `CaptureUpdateAction.NEVER` | Use for updates which should never be recorded, such as remote updates or scene initialization. These updates will _never_ make it to the local undo / redo stacks. |
### updateLibrary
<pre>

View File

@ -3,7 +3,7 @@
All `props` are _optional_.
| Name | Type | Default | Description |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- |
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` &#124; `null` &#124; <code>Promise<object &#124; null></code> | `null` | The initial data with which app loads. |
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | \_ | Callback triggered with the excalidraw api once rendered |
| [`isCollaborating`](#iscollaborating) | `boolean` | \_ | This indicates if the app is in `collaboration` mode |
@ -13,7 +13,7 @@ All `props` are _optional_.
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | \_ | Callback to be triggered if passed when something is pasted into the scene |
| [`onLibraryChange`](#onlibrarychange) | `function` | \_ | The callback if supplied is triggered when the library is updated and receives the library items. |
| [`generateLinkForSelection`](#generateLinkForSelection) | `function` | \_ | Allows you to override `url` generation when linking to Excalidraw elements. |
| [`generateLinkForSelection`](#generatelinkforselection) | `function` | \_ | Allows you to override `url` generation when linking to Excalidraw elements. |
| [`onLinkOpen`](#onlinkopen) | `function` | \_ | The callback if supplied is triggered when any link is opened. |
| [`langCode`](#langcode) | `string` | `en` | Language code string to be used in Excalidraw |
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | \_ | Render function that renders custom UI in top right corner |
@ -29,8 +29,9 @@ All `props` are _optional_.
| [`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 |
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateEmbeddable) | string[] | `boolean | RegExp | RegExp[] | ((link: string) => boolean | undefined)` | \_ | use for custom src url validation |
| [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| <code>((link: string) => boolean &#124; undefined)</code> | \_ | use for custom src url validation |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
| [`renderScrollbars`] | `boolean`| | `false` | Indicates whether scrollbars will be shown
### Storing custom data on Excalidraw elements

View File

@ -31,7 +31,7 @@ You can pass `null` / `undefined` if not applicable.
restoreElements(
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean }<br/>
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
)
</pre>
@ -51,8 +51,9 @@ The extra optional parameter to configure restored elements. It has the followin
| Prop | Type | Description|
| --- | --- | ------|
| `refreshDimensions` | `boolean` | Indicates whether we should also `recalculate` text element dimensions. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration. |
| `repairBindings` |`boolean` | Indicates whether the `bindings` for the elements should be repaired. This is to make sure there are no containers with non existent bound text element id and no bound text elements with non existent container id. |
| `refreshDimensions` | `boolean` | Indicates whether we should also _recalculate_ text element dimensions. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration. |
| `repairBindings` |`boolean` | Indicates whether the _bindings_ for the elements should be repaired. This is to make sure there are no containers with non existent bound text element id and no bound text elements with non existent container id. |
| `normalizeIndices` |`boolean` | Indicates whether _fractional indices_ for the elements should be normalized. This is to prevent possible issues caused by using stale (too old, too long) indices. |
**_How to use_**
@ -73,7 +74,7 @@ restore(
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp;
localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L4">DataState</a><br/>
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean }<br/>
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
)
</pre>

View File

@ -13,18 +13,18 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
1. Install the dependencies
```bash
cd packages/excalidraw && yarn
yarn
```
2. Start the example app
```bash
yarn start
yarn start:example
```
[http://localhost:3001](http://localhost:3001) will open in your default browser.
The example is same as the [codesandbox example](https://ehlz3.csb.app/)
This is the same example as the [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
## Releasing

View File

@ -17,11 +17,9 @@ We strongly recommend turning it off. You can follow the steps below on how to d
<div style={{width:'30rem'}}>
2. Once opened, look for **Aggressively Block Fingerprinting**
![Aggressive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
3. Switch to **Block Fingerprinting**
![Block filtering](../../assets/block-fingerprint.png)
4. Thats all. All text elements should be fixed now 🎉

View File

@ -1,16 +1,12 @@
# Installation
**Excalidraw** is published to npm as a component you can directly embed in your projects.
**Excalidraw** is exported as a component to be directly embedded in your project.
Using `npm`:
Use `npm` or `yarn` to install the package.
```bash
npm install react react-dom @excalidraw/excalidraw
```
or `yarn`:
```bash
# or
yarn add react react-dom @excalidraw/excalidraw
```
@ -20,24 +16,40 @@ yarn add react react-dom @excalidraw/excalidraw
:::
### Static assets
### Self-hosting fonts
Excalidraw depends on assets such as localization files (if you opt to use them), fonts, and others.
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
By default these assets are loaded from a public CDN [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist), so you don't need to do anything on your end.
However, if you want to host these files yourself, you can find them in your `node_modules/@excalidraw/excalidraw/dist` directory, in folders `excalidraw-assets` (for production) and `excalidraw-assets-dev` (for development).
Copy these folders to your static assets directory, and add a `window.EXCALIDRAW_ASSET_PATH` variable in your `index.html` or `index.js` entry file pointing to your public assets path (relative). For example, if you serve your assets from the root of your hostname, you would do:
For self-hosting purposes, you'll have to copy the content of the folder `node_modules/@excalidraw/excalidraw/dist/prod/fonts` to the path where your assets should be served from (i.e. `public/` directory in your project). In that case, you should also set `window.EXCALIDRAW_ASSET_PATH` to the very same path, i.e. `/` in case it's in the root:
```js
window.EXCALIDRAW_ASSET_PATH = "/";
```
or, if you serve your assets from the root of your CDN, you would do:
```js
// Vanilla
<head>
<script>
window.EXCALIDRAW_ASSET_PATH = "https://my.cdn.com/assets/";
</script>
</head>
```
or, if you prefer the path to be dynamicly set based on the `location.origin`, you could do the following:
```jsx
// Next.js
<Script id="load-env-variables" strategy="beforeInteractive" >
{ `window["EXCALIDRAW_ASSET_PATH"] = location.origin;` } // or use just "/"!
</Script>
```
### Dimensions of Excalidraw
Excalidraw takes _100%_ of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
### Demo
## Demo
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.

View File

@ -128,11 +128,10 @@ If you are using `pages router` then importing the wrapper dynamically would wor
</TabItem>
</Tabs>
{/* Link should be updated to point to the latest! */}
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs.vercel.app/).
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/).
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
The `types` are available at `@excalidraw/excalidraw/types`, check [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example for details.
### Preact
@ -157,27 +156,9 @@ Since Vite removes env variables by default, you can update the vite config to e
## Browser
To use it in a browser directly:
To use it Excalidraw in a browser directly, use the following setup :point_down:
For development use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
```
For production use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"
></script>
```
You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
> **Note**: We rely on import maps to de-duplicate `react`, `react-dom` and `react/jsx-runtime` versions.
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
@ -191,13 +172,23 @@ import TabItem from "@theme/TabItem";
<head>
<title>Excalidraw in browser</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
<link
rel="stylesheet"
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
/>
<link rel="stylesheet" href="./index.css" />
<script>
window.EXCALIDRAW_ASSET_PATH = "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
</script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.0.0",
"react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.0.0"
}
}
</script>
</head>
<body>
@ -214,6 +205,14 @@ import TabItem from "@theme/TabItem";
<TabItem value="js" label="Javascript">
```js showLineNumbers
// See https://www.npmjs.com/package/@excalidraw/excalidraw documentation.
import * as ExcalidrawLib from 'https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.js?external=react,react-dom';
import React from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0"
window.ExcalidrawLib = ExcalidrawLib;
console.log("Excalidraw library", ExcalidrawLib);
const App = () => {
return React.createElement(
React.Fragment,
@ -236,4 +235,4 @@ root.render(React.createElement(App));
</TabItem>
</Tabs>
You can try it out [here](https://codesandbox.io/p/sandbox/excalidraw-in-browser-tlqom?file=%2Findex.html%3A1%2C10).
You can try it out [here](https://jsfiddle.net/vfn6dm14/3/).

View File

@ -149,6 +149,29 @@ const config = {
systemvars: true,
},
],
function () {
return {
name: "disable-fully-specified-error",
configureWebpack() {
return {
module: {
rules: [
{
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
},
],
},
optimization: {
// disable terser minification
minimize: false,
},
};
},
};
},
],
};

View File

@ -18,7 +18,7 @@
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/theme-live-codeblock": "2.2.0",
"@excalidraw/excalidraw": "0.17.6",
"@excalidraw/excalidraw": "0.18.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "0.2.3",

View File

@ -1,5 +1,6 @@
import React from "react";
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
const FeatureList = [

View File

@ -1,5 +1,6 @@
import React from "react";
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
type FeatureItem = {

View File

@ -1,10 +1,11 @@
import React from "react";
import clsx from "clsx";
import Layout from "@theme/Layout";
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import styles from "./index.module.css";
import HomepageFeatures from "@site/src/components/Homepage";
import Layout from "@theme/Layout";
import clsx from "clsx";
import React from "react";
import styles from "./index.module.css";
function HomepageHeader() {
const { siteConfig } = useDocusaurusContext();

View File

@ -1,6 +1,6 @@
// Import the original mapper
import MDXComponents from "@theme-original/MDXComponents";
import Highlight from "@site/src/components/Highlight";
import MDXComponents from "@theme-original/MDXComponents";
export default {
// Re-use the default mapping

View File

@ -3,11 +3,18 @@ import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
import initialData from "@site/src/initialData";
import { useColorMode } from "@docusaurus/theme-common";
import "@excalidraw/excalidraw/index.css";
let ExcalidrawComp = {};
if (ExecutionEnvironment.canUseDOM) {
ExcalidrawComp = require("@excalidraw/excalidraw");
}
const Excalidraw = React.forwardRef((props, ref) => {
if (!window.EXCALIDRAW_ASSET_PATH) {
window.EXCALIDRAW_ASSET_PATH =
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
}
const { colorMode } = useColorMode();
return <ExcalidrawComp.Excalidraw theme={colorMode} {...props} ref={ref} />;
});

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ services:
- ./:/opt/node_app/app:delegated
- ./package.json:/opt/node_app/package.json
- ./yarn.lock:/opt/node_app/yarn.lock
- notused:/opt/node_app/app/node_modules
# - notused:/opt/node_app/app/node_modules
volumes:
notused:
# volumes:
# notused:

View File

@ -1,13 +0,0 @@
{
"name": "examples",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"@excalidraw/excalidraw": "*"
},
"devDependencies": {
"typescript": "^5"
}
}

View File

@ -1,3 +0,0 @@
{
"extends": "../../tsconfig"
}

View File

@ -1,21 +0,0 @@
{
"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.12",
"typescript": "^5"
},
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"start": "yarn build:workspace && vite",
"build": "yarn build:workspace && vite build",
"build:preview": "yarn build && vite preview --port 5002"
}
}

View File

@ -1,4 +0,0 @@
{
"outputDirectory": "dist",
"installCommand": "yarn install"
}

View File

@ -1,11 +0,0 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 3001,
// open the browser
open: true,
},
publicDir: "public",
});

View File

@ -1,313 +0,0 @@
# 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"

View File

@ -4,22 +4,21 @@
"private": true,
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
"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.2.0",
"react-dom": "18.2.0"
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"path2d-polyfill": "2.0.1",
"typescript": "^5"
}

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,5 +1,6 @@
import dynamic from "next/dynamic";
import Script from "next/script";
import "../common.scss";
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically

View File

@ -1,10 +1,11 @@
"use client";
import * as excalidrawLib from "@excalidraw/excalidraw";
import { Excalidraw } from "@excalidraw/excalidraw";
import App from "../../components/ExampleApp";
import "@excalidraw/excalidraw/index.css";
import App from "../../with-script-in-browser/components/ExampleApp";
const ExcalidrawWrapper: React.FC = () => {
return (
<>

View File

@ -1,4 +1,5 @@
import dynamic from "next/dynamic";
import "../common.scss";
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically

View File

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

View File

@ -0,0 +1,35 @@
{
// These tasks will run in order when initializing your CodeSandbox project.
"setupTasks": [
{
"name": "Install Dependencies",
"command": "yarn install"
}
],
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
"tasks": {
"build": {
"name": "Build",
"command": "yarn build",
"runAtStart": false
},
"start": {
"name": "Start Example",
"command": "yarn start",
"runAtStart": true,
"preview": {
"port": 3001
}
},
"install-deps": {
"name": "Install Dependencies",
"command": "yarn install",
"restartOn": {
"files": ["yarn.lock"],
"branch": false,
"resume": false
}
}
}
}

View File

@ -1,5 +1,7 @@
import React from "react";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
const COMMENT_SVG = (
<svg

View File

@ -52,7 +52,7 @@
transform: none;
}
.excalidraw .panelColumn {
.excalidraw .selected-shape-actions {
text-align: left;
}

View File

@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import React, {
useEffect,
useState,
@ -6,13 +7,24 @@ import React, {
Children,
cloneElement,
} from "react";
import ExampleSidebar from "./sidebar/ExampleSidebar";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
import type {
NonDeletedExcalidrawElement,
Theme,
} from "@excalidraw/excalidraw/element/types";
import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
ExcalidrawInitialDataState,
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "@excalidraw/excalidraw/types";
import { nanoid } from "nanoid";
import type { ResolvablePromise } from "../utils";
import initialData from "../initialData";
import {
resolvablePromise,
distance2d,
@ -23,25 +35,12 @@ import {
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import initialData from "../initialData";
import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
ExcalidrawInitialDataState,
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} 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";
import ExampleSidebar from "./sidebar/ExampleSidebar";
import "./ExampleApp.scss";
import type { ResolvablePromise } from "../utils";
type Comment = {
x: number;
y: number;
@ -105,6 +104,7 @@ export default function ExampleApp({
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
const [gridModeEnabled, setGridModeEnabled] = useState(false);
const [renderScrollbars, setRenderScrollbars] = useState(false);
const [blobUrl, setBlobUrl] = useState<string>("");
const [canvasUrl, setCanvasUrl] = useState<string>("");
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
@ -193,6 +193,7 @@ export default function ExampleApp({
}) => setPointerData(payload),
viewModeEnabled,
zenModeEnabled,
renderScrollbars,
gridModeEnabled,
theme,
name: "Custom name of drawing",
@ -711,6 +712,14 @@ export default function ExampleApp({
/>
Grid mode
</label>
<label>
<input
type="checkbox"
checked={renderScrollbars}
onChange={() => setRenderScrollbars(!renderScrollbars)}
/>
Render scrollbars
</label>
<label>
<input
type="checkbox"

View File

@ -1,6 +1,9 @@
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
import CustomFooter from "./CustomFooter";
import React from "react";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import CustomFooter from "./CustomFooter";
const MobileFooter = ({
excalidrawAPI,

View File

@ -1,4 +1,5 @@
import { useState } from "react";
import React, { useState } from "react";
import "./ExampleSidebar.scss";
export default function Sidebar({ children }: { children: React.ReactNode }) {

View File

@ -11,17 +11,15 @@
<title>React App</title>
<script>
window.name = "codesandbox";
window.EXCALIDRAW_ASSET_PATH = window.origin;
window.EXCALIDRAW_ASSET_PATH =
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
</script>
<link rel="stylesheet" href="/dist/browser/dev/index.css" />
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<!-- This is so that we use the bundled excalidraw.development.js file instead
of the actual source code -->
<script type="module">
import * as ExcalidrawLib from "@excalidraw/excalidraw";

View File

@ -1,10 +1,11 @@
import App from "../components/ExampleApp";
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "@excalidraw/excalidraw/index.css";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
import App from "./components/ExampleApp";
declare global {
interface Window {

View File

@ -0,0 +1,22 @@
{
"name": "with-script-in-browser",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0",
"@excalidraw/excalidraw": "*",
"browser-fs-access": "0.29.1"
},
"devDependencies": {
"vite": "5.0.12",
"typescript": "^5"
},
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview --port 5002",
"build:preview": "yarn build && yarn preview",
"build:package": "yarn workspace @excalidraw/excalidraw run build:esm"
}
}

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "ES2022",
"moduleResolution": "Bundler",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"skipLibCheck": true
}
}

View File

@ -1,7 +1,6 @@
import { unstable_batchedUpdates } from "react-dom";
import { fileOpen as _fileOpen } from "browser-fs-access";
import { MIME_TYPES } from "@excalidraw/excalidraw";
import { AbortError } from "../../packages/excalidraw/errors";
import { fileOpen as _fileOpen } from "browser-fs-access";
import { unstable_batchedUpdates } from "react-dom";
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
@ -85,7 +84,7 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
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());
rejectPromise(new Error("Request Aborted"));
}
};
},

View File

@ -0,0 +1,5 @@
{
"outputDirectory": "dist",
"installCommand": "yarn install",
"buildCommand": "yarn build:package && yarn build"
}

View File

@ -0,0 +1,19 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 3001,
// open the browser
open: true,
},
publicDir: "public",
optimizeDeps: {
esbuildOptions: {
// Bumping to 2022 due to "Arbitrary module namespace identifier names" not being
// supported in Vite's default browser target https://github.com/vitejs/vite/issues/13556
target: "es2022",
treeShaking: true,
},
},
});

View File

@ -1,40 +1,27 @@
import polyfill from "../packages/excalidraw/polyfill";
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";
import { TopErrorBoundary } from "./components/TopErrorBoundary";
import {
Excalidraw,
LiveCollaborationTrigger,
TTDDialogTrigger,
CaptureUpdateAction,
reconcileElements,
} from "@excalidraw/excalidraw";
import { trackEvent } from "@excalidraw/excalidraw/analytics";
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
import {
CommandPalette,
DEFAULT_CATEGORIES,
} from "@excalidraw/excalidraw/components/CommandPalette/CommandPalette";
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
import { OverwriteConfirmDialog } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
import { openConfirmModal } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
import { ShareableLinkDialog } from "@excalidraw/excalidraw/components/ShareableLinkDialog";
import Trans from "@excalidraw/excalidraw/components/Trans";
import {
APP_NAME,
EVENT,
THEME,
TITLE_TIMEOUT,
VERSION_TIMEOUT,
} from "../packages/excalidraw/constants";
import { loadFromBlob } from "../packages/excalidraw/data/blob";
import type {
FileId,
NonDeletedExcalidrawElement,
OrderedExcalidrawElement,
} from "../packages/excalidraw/element/types";
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
import { t } from "../packages/excalidraw/i18n";
import {
Excalidraw,
LiveCollaborationTrigger,
TTDDialogTrigger,
StoreAction,
reconcileElements,
} from "../packages/excalidraw";
import type {
AppState,
ExcalidrawImperativeAPI,
BinaryFiles,
ExcalidrawInitialDataState,
UIAppState,
} from "../packages/excalidraw/types";
import type { ResolvablePromise } from "../packages/excalidraw/utils";
import {
debounce,
getVersion,
getFrame,
@ -42,75 +29,14 @@ import {
preventUnload,
resolvablePromise,
isRunningInIframe,
} from "../packages/excalidraw/utils";
import {
FIREBASE_STORAGE_PREFIXES,
isExcalidrawPlusSignedUser,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
import type { CollabAPI } from "./collab/Collab";
import Collab, {
collabAPIAtom,
isCollaboratingAtom,
isOfflineAtom,
} from "./collab/Collab";
import {
exportToBackend,
getCollaborationLinkData,
isCollaborationLink,
loadScene,
} from "./data";
import {
importFromLocalStorage,
importUsernameFromLocalStorage,
} from "./data/localStorage";
import CustomStats from "./CustomStats";
import type { RestoredDataState } from "../packages/excalidraw/data/restore";
import { restore, restoreAppState } from "../packages/excalidraw/data/restore";
import {
ExportToExcalidrawPlus,
exportToExcalidrawPlus,
} from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../packages/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "../packages/excalidraw/element/typeChecks";
import { loadFilesFromFirebase } from "./data/firebase";
import {
LibraryIndexedDBAdapter,
LibraryLocalStorageMigrationAdapter,
LocalData,
} from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync";
import clsx from "clsx";
import {
parseLibraryTokensFromUrl,
useHandleLibrary,
} from "../packages/excalidraw/data/library";
import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter";
import {
Provider,
useAtom,
useAtomValue,
useAtomWithInitialValue,
appJotaiStore,
} from "./app-jotai";
isDevEnv,
} from "@excalidraw/common";
import polyfill from "@excalidraw/excalidraw/polyfill";
import { useCallback, useEffect, useRef, useState } from "react";
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
import { t } from "@excalidraw/excalidraw/i18n";
import "./index.scss";
import type { ResolutionType } from "../packages/excalidraw/utility-types";
import { ShareableLinkDialog } from "../packages/excalidraw/components/ShareableLinkDialog";
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";
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
import type { RemoteExcalidrawElement } from "../packages/excalidraw/data/reconcile";
import {
CommandPalette,
DEFAULT_CATEGORIES,
} from "../packages/excalidraw/components/CommandPalette/CommandPalette";
import {
GithubIcon,
XBrandIcon,
@ -120,7 +46,84 @@ import {
exportToPlus,
share,
youtubeIcon,
} from "../packages/excalidraw/components/icons";
} from "@excalidraw/excalidraw/components/icons";
import { isElementLink } from "@excalidraw/element";
import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
import { newElementWith } from "@excalidraw/element";
import { isInitializedImageElement } from "@excalidraw/element";
import clsx from "clsx";
import {
parseLibraryTokensFromUrl,
useHandleLibrary,
} from "@excalidraw/excalidraw/data/library";
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
import type { RestoredDataState } from "@excalidraw/excalidraw/data/restore";
import type {
FileId,
NonDeletedExcalidrawElement,
OrderedExcalidrawElement,
} from "@excalidraw/element/types";
import type {
AppState,
ExcalidrawImperativeAPI,
BinaryFiles,
ExcalidrawInitialDataState,
UIAppState,
} from "@excalidraw/excalidraw/types";
import type { ResolutionType } from "@excalidraw/common/utility-types";
import type { ResolvablePromise } from "@excalidraw/common/utils";
import CustomStats from "./CustomStats";
import {
Provider,
useAtom,
useAtomValue,
useAtomWithInitialValue,
appJotaiStore,
} from "./app-jotai";
import {
FIREBASE_STORAGE_PREFIXES,
isExcalidrawPlusSignedUser,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
import Collab, {
collabAPIAtom,
isCollaboratingAtom,
isOfflineAtom,
} from "./collab/Collab";
import { AppFooter } from "./components/AppFooter";
import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import {
ExportToExcalidrawPlus,
exportToExcalidrawPlus,
} from "./components/ExportToExcalidrawPlus";
import { TopErrorBoundary } from "./components/TopErrorBoundary";
import {
exportToBackend,
getCollaborationLinkData,
isCollaborationLink,
loadScene,
} from "./data";
import { updateStaleImageStatuses } from "./data/FileManager";
import {
importFromLocalStorage,
importUsernameFromLocalStorage,
} from "./data/localStorage";
import { loadFilesFromFirebase } from "./data/firebase";
import {
LibraryIndexedDBAdapter,
LibraryLocalStorageMigrationAdapter,
LocalData,
} from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync";
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
import { useHandleAppTheme } from "./useHandleAppTheme";
import { getPreferredLanguage } from "./app-language/language-detector";
import { useAppLangCode } from "./app-language/language-state";
@ -131,7 +134,10 @@ import DebugCanvas, {
} from "./components/DebugCanvas";
import { AIComponents } from "./components/AI";
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
import { isElementLink } from "../packages/excalidraw/element/elementLink";
import "./index.scss";
import type { CollabAPI } from "./collab/Collab";
polyfill();
@ -377,7 +383,7 @@ const ExcalidrawWrapper = () => {
const [, forceRefresh] = useState(false);
useEffect(() => {
if (import.meta.env.DEV) {
if (isDevEnv()) {
const debugState = loadSavedDebugState();
if (debugState.enabled && !window.visualDebug) {
@ -486,7 +492,7 @@ const ExcalidrawWrapper = () => {
excalidrawAPI.updateScene({
...data.scene,
...restore(data.scene, null, null, { repairBindings: true }),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
});
@ -513,7 +519,7 @@ const ExcalidrawWrapper = () => {
setLangCode(getPreferredLanguage());
excalidrawAPI.updateScene({
...localDataState,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
LibraryIndexedDBAdapter.load().then((data) => {
if (data) {
@ -602,7 +608,13 @@ const ExcalidrawWrapper = () => {
excalidrawAPI.getSceneElements(),
)
) {
preventUnload(event);
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
preventUnload(event);
} else {
console.warn(
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
);
}
}
};
window.addEventListener(EVENT.BEFORE_UNLOAD, unloadHandler);
@ -645,7 +657,7 @@ const ExcalidrawWrapper = () => {
if (didChange) {
excalidrawAPI.updateScene({
elements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
}
}
@ -914,16 +926,21 @@ const ExcalidrawWrapper = () => {
<ShareDialog
collabAPI={collabAPI}
onExportToBackend={async () => {
if (excalidrawAPI) {
try {
await onExportToBackend(
excalidrawAPI.getSceneElements(),
excalidrawAPI.getAppState(),
excalidrawAPI.getFiles(),
);
} catch (error: any) {
setErrorMessage(error.message);
if (!excalidrawAPI) {
return;
}
try {
const { url, errorMessage } = await exportToBackend(
excalidrawAPI.getSceneElements(),
excalidrawAPI.getAppState(),
excalidrawAPI.getFiles(),
);
if (errorMessage) {
throw new Error(errorMessage);
}
setLatestShareableLink(url);
} catch (error: any) {
setErrorMessage(error.message);
}
}}
/>

View File

@ -1,15 +1,21 @@
import { Stats } from "@excalidraw/excalidraw";
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
import {
DEFAULT_VERSION,
debounce,
getVersion,
nFormatter,
} from "@excalidraw/common";
import { t } from "@excalidraw/excalidraw/i18n";
import { useEffect, useState } from "react";
import { debounce, getVersion, nFormatter } from "../packages/excalidraw/utils";
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
import type { UIAppState } from "@excalidraw/excalidraw/types";
import {
getElementsStorageSize,
getTotalStorageSize,
} from "./data/localStorage";
import { DEFAULT_VERSION } from "../packages/excalidraw/constants";
import { t } from "../packages/excalidraw/i18n";
import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
import type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
import type { UIAppState } from "../packages/excalidraw/types";
import { Stats } from "../packages/excalidraw";
type StorageSizes = { scene: number; total: number };

View File

@ -1,13 +1,15 @@
import { base64urlToString } from "@excalidraw/excalidraw/data/encode";
import { ExcalidrawError } from "@excalidraw/excalidraw/errors";
import { useLayoutEffect, useRef } from "react";
import { STORAGE_KEYS } from "./app_constants";
import { LocalData } from "./data/LocalData";
import type {
FileId,
OrderedExcalidrawElement,
} from "../packages/excalidraw/element/types";
import type { AppState, BinaryFileData } from "../packages/excalidraw/types";
import { ExcalidrawError } from "../packages/excalidraw/errors";
import { base64urlToString } from "../packages/excalidraw/data/encode";
} from "@excalidraw/element/types";
import type { AppState, BinaryFileData } from "@excalidraw/excalidraw/types";
import { STORAGE_KEYS } from "./app_constants";
import { LocalData } from "./data/LocalData";
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";

View File

@ -1,6 +1,8 @@
import { useI18n, languages } from "@excalidraw/excalidraw/i18n";
import React from "react";
import { useI18n, languages } from "../../packages/excalidraw/i18n";
import { useSetAtom } from "../app-jotai";
import { appLangCodeAtom } from "./language-state";
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {

View File

@ -1,5 +1,5 @@
import { defaultLang, languages } from "@excalidraw/excalidraw";
import LanguageDetector from "i18next-browser-languagedetector";
import { defaultLang, languages } from "../../packages/excalidraw";
export const languageDetector = new LanguageDetector();

View File

@ -1,5 +1,7 @@
import { useEffect } from "react";
import { atom, useAtom } from "../app-jotai";
import { getPreferredLanguage, languageDetector } from "./language-detector";
export const appLangCodeAtom = atom(getPreferredLanguage());

View File

@ -1,33 +1,55 @@
import {
CaptureUpdateAction,
getSceneVersion,
restoreElements,
zoomToFitBounds,
reconcileElements,
} from "@excalidraw/excalidraw";
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
import { APP_NAME, EVENT } from "@excalidraw/common";
import {
IDLE_THRESHOLD,
ACTIVE_THRESHOLD,
UserIdleState,
assertNever,
isDevEnv,
isTestEnv,
preventUnload,
resolvablePromise,
throttleRAF,
} from "@excalidraw/common";
import { decryptData } from "@excalidraw/excalidraw/data/encryption";
import { getVisibleSceneBounds } from "@excalidraw/element";
import { newElementWith } from "@excalidraw/element";
import { isImageElement, isInitializedImageElement } from "@excalidraw/element";
import { AbortError } from "@excalidraw/excalidraw/errors";
import { t } from "@excalidraw/excalidraw/i18n";
import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
import throttle from "lodash.throttle";
import { PureComponent } from "react";
import type {
BinaryFileData,
ExcalidrawImperativeAPI,
SocketId,
} from "../../packages/excalidraw/types";
import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
ReconciledExcalidrawElement,
RemoteExcalidrawElement,
} from "@excalidraw/excalidraw/data/reconcile";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
import type {
ExcalidrawElement,
FileId,
InitializedExcalidrawImageElement,
OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import {
StoreAction,
getSceneVersion,
restoreElements,
zoomToFitBounds,
reconcileElements,
} from "../../packages/excalidraw";
import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
import {
assertNever,
preventUnload,
resolvablePromise,
throttleRAF,
} from "../../packages/excalidraw/utils";
} from "@excalidraw/element/types";
import type {
BinaryFileData,
ExcalidrawImperativeAPI,
SocketId,
Collaborator,
Gesture,
} from "@excalidraw/excalidraw/types";
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
import { appJotaiStore, atom } from "../app-jotai";
import {
CURSOR_SYNC_TIMEOUT,
FILE_UPLOAD_MAX_BYTES,
@ -38,15 +60,17 @@ import {
SYNC_FULL_SCENE_INTERVAL_MS,
WS_EVENTS,
} from "../app_constants";
import type {
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
import {
generateCollaborationLinkData,
getCollaborationLink,
getSyncableElements,
} from "../data";
import {
encodeFilesForUpload,
FileManager,
updateStaleImageStatuses,
} from "../data/FileManager";
import { LocalData } from "../data/LocalData";
import {
isSavedToFirebase,
loadFilesFromFirebase,
@ -58,36 +82,15 @@ import {
importUsernameFromLocalStorage,
saveUsernameToLocalStorage,
} from "../data/localStorage";
import Portal from "./Portal";
import { t } from "../../packages/excalidraw/i18n";
import { UserIdleState } from "../../packages/excalidraw/types";
import {
IDLE_THRESHOLD,
ACTIVE_THRESHOLD,
} from "../../packages/excalidraw/constants";
import {
encodeFilesForUpload,
FileManager,
updateStaleImageStatuses,
} from "../data/FileManager";
import { AbortError } from "../../packages/excalidraw/errors";
import {
isImageElement,
isInitializedImageElement,
} from "../../packages/excalidraw/element/typeChecks";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { decryptData } from "../../packages/excalidraw/data/encryption";
import { resetBrowserStateVersions } from "../data/tabSync";
import { LocalData } from "../data/LocalData";
import { appJotaiStore, atom } from "../app-jotai";
import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
import { collabErrorIndicatorAtom } from "./CollabError";
import Portal from "./Portal";
import type {
ReconciledExcalidrawElement,
RemoteExcalidrawElement,
} from "../../packages/excalidraw/data/reconcile";
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
export const collabAPIAtom = atom<CollabAPI | null>(null);
export const isCollaboratingAtom = atom(false);
@ -235,7 +238,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
appJotaiStore.set(collabAPIAtom, collabAPI);
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
if (isTestEnv() || isDevEnv()) {
window.collab = window.collab || ({} as Window["collab"]);
Object.defineProperties(window, {
collab: {
@ -295,7 +298,13 @@ class Collab extends PureComponent<CollabProps, CollabState> {
// the purpose is to run in immediately after user decides to stay
this.saveCollabRoomToFirebase(syncableElements);
preventUnload(event);
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
preventUnload(event);
} else {
console.warn(
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
);
}
}
});
@ -383,7 +392,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
this.excalidrawAPI.updateScene({
elements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
}
};
@ -534,7 +543,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
// to database even if deleted before creating the room.
this.excalidrawAPI.updateScene({
elements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
this.saveCollabRoomToFirebase(getSyncableElements(elements));
@ -772,7 +781,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
) => {
this.excalidrawAPI.updateScene({
elements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
this.loadImageFiles();
@ -1008,7 +1017,7 @@ declare global {
}
}
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
if (isTestEnv() || isDevEnv()) {
window.collab = window.collab || ({} as Window["collab"]);
}

View File

@ -1,7 +1,8 @@
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
import { warning } from "../../packages/excalidraw/components/icons";
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
import { warning } from "@excalidraw/excalidraw/components/icons";
import clsx from "clsx";
import { useEffect, useRef, useState } from "react";
import { atom } from "../app-jotai";
import "./CollabError.scss";
@ -19,16 +20,16 @@ export const collabErrorIndicatorAtom = atom<ErrorIndicator>({
const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
const [isAnimating, setIsAnimating] = useState(false);
const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
const clearAnimationRef = useRef<string | number>(0);
useEffect(() => {
setIsAnimating(true);
clearAnimationRef.current = setTimeout(() => {
clearAnimationRef.current = window.setTimeout(() => {
setIsAnimating(false);
}, 1000);
return () => {
clearTimeout(clearAnimationRef.current);
window.clearTimeout(clearAnimationRef.current);
};
}, [collabError.message, collabError.nonce]);

View File

@ -1,25 +1,26 @@
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
import { trackEvent } from "@excalidraw/excalidraw/analytics";
import { encryptData } from "@excalidraw/excalidraw/data/encryption";
import { newElementWith } from "@excalidraw/element";
import throttle from "lodash.throttle";
import type { UserIdleState } from "@excalidraw/common";
import type { OrderedExcalidrawElement } from "@excalidraw/element/types";
import type {
OnUserFollowedPayload,
SocketId,
} from "@excalidraw/excalidraw/types";
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
import { isSyncableElement } from "../data";
import type {
SocketUpdateData,
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
import { isSyncableElement } from "../data";
import type { TCollabClass } from "./Collab";
import type { OrderedExcalidrawElement } from "../../packages/excalidraw/element/types";
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
import type {
OnUserFollowedPayload,
SocketId,
UserIdleState,
} from "../../packages/excalidraw/types";
import { trackEvent } from "../../packages/excalidraw/analytics";
import throttle from "lodash.throttle";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { encryptData } from "../../packages/excalidraw/data/encryption";
import type { Socket } from "socket.io-client";
import { StoreAction } from "../../packages/excalidraw";
class Portal {
collab: TCollabClass;
@ -133,7 +134,7 @@ class Portal {
if (isChanged) {
this.collab.excalidrawAPI.updateScene({
elements: newElements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
}
}, FILE_UPLOAD_TIMEOUT);

View File

@ -1,13 +1,14 @@
import type { ExcalidrawImperativeAPI } from "../../packages/excalidraw/types";
import {
DiagramToCodePlugin,
exportToBlob,
getTextFromElements,
MIME_TYPES,
TTDDialog,
} from "../../packages/excalidraw";
import { getDataURL } from "../../packages/excalidraw/data/blob";
import { safelyParseJSON } from "../../packages/excalidraw/utils";
} from "@excalidraw/excalidraw";
import { getDataURL } from "@excalidraw/excalidraw/data/blob";
import { safelyParseJSON } from "@excalidraw/common";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
export const AIComponents = ({
excalidrawAPI,
@ -72,7 +73,7 @@ export const AIComponents = ({
</br>
<div>You can also try <a href="${
import.meta.env.VITE_APP_PLUS_LP
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noreferrer noopener">Excalidraw+</a> to get more requests.</div>
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noopener">Excalidraw+</a> to get more requests.</div>
</div>
</body>
</html>`,

View File

@ -1,9 +1,11 @@
import { Footer } from "@excalidraw/excalidraw/index";
import React from "react";
import { Footer } from "../../packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants";
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
import { EncryptedIcon } from "./EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
import { isExcalidrawPlusSignedUser } from "../app_constants";
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
export const AppFooter = React.memo(
({ onChange }: { onChange: () => void }) => {

View File

@ -1,13 +1,18 @@
import React from "react";
import {
loginIcon,
ExcalLogo,
eyeIcon,
} from "../../packages/excalidraw/components/icons";
import type { Theme } from "../../packages/excalidraw/element/types";
import { MainMenu } from "../../packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants";
} from "@excalidraw/excalidraw/components/icons";
import { MainMenu } from "@excalidraw/excalidraw/index";
import React from "react";
import { isDevEnv } from "@excalidraw/common";
import type { Theme } from "@excalidraw/element/types";
import { LanguageList } from "../app-language/LanguageList";
import { isExcalidrawPlusSignedUser } from "../app_constants";
import { saveDebugState } from "./DebugCanvas";
export const AppMainMenu: React.FC<{
@ -54,7 +59,7 @@ export const AppMainMenu: React.FC<{
>
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
</MainMenu.ItemLink>
{import.meta.env.DEV && (
{isDevEnv() && (
<MainMenu.Item
icon={eyeIcon}
onClick={() => {

View File

@ -1,9 +1,10 @@
import { loginIcon } from "@excalidraw/excalidraw/components/icons";
import { POINTER_EVENTS } from "@excalidraw/common";
import { useI18n } from "@excalidraw/excalidraw/i18n";
import { WelcomeScreen } from "@excalidraw/excalidraw/index";
import React from "react";
import { loginIcon } from "../../packages/excalidraw/components/icons";
import { useI18n } from "../../packages/excalidraw/i18n";
import { WelcomeScreen } from "../../packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants";
import { POINTER_EVENTS } from "../../packages/excalidraw/constants";
export const AppWelcomeScreen: React.FC<{
onCollabDialogOpen: () => any;

View File

@ -1,22 +1,28 @@
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
import { type AppState } from "../../packages/excalidraw/types";
import { throttleRAF } from "../../packages/excalidraw/utils";
import {
bootstrapCanvas,
getNormalizedCanvasDimensions,
} from "../../packages/excalidraw/renderer/helpers";
import type { DebugElement } from "../../packages/excalidraw/visualdebug";
import {
ArrowheadArrowIcon,
CloseIcon,
TrashIcon,
} from "../../packages/excalidraw/components/icons";
import { STORAGE_KEYS } from "../app_constants";
} from "@excalidraw/excalidraw/components/icons";
import {
bootstrapCanvas,
getNormalizedCanvasDimensions,
} from "@excalidraw/excalidraw/renderer/helpers";
import { type AppState } from "@excalidraw/excalidraw/types";
import { throttleRAF } from "@excalidraw/common";
import { useCallback, useImperativeHandle, useRef } from "react";
import {
isLineSegment,
type GlobalPoint,
type LineSegment,
} from "../../packages/math";
} from "@excalidraw/math";
import { isCurve } from "@excalidraw/math/curve";
import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
import type { Curve } from "@excalidraw/math";
import { STORAGE_KEYS } from "../app_constants";
const renderLine = (
context: CanvasRenderingContext2D,
@ -33,6 +39,28 @@ const renderLine = (
context.restore();
};
const renderCubicBezier = (
context: CanvasRenderingContext2D,
zoom: number,
[start, control1, control2, end]: Curve<GlobalPoint>,
color: string,
) => {
context.save();
context.strokeStyle = color;
context.beginPath();
context.moveTo(start[0] * zoom, start[1] * zoom);
context.bezierCurveTo(
control1[0] * zoom,
control1[1] * zoom,
control2[0] * zoom,
control2[1] * zoom,
end[0] * zoom,
end[1] * zoom,
);
context.stroke();
context.restore();
};
const renderOrigin = (context: CanvasRenderingContext2D, zoom: number) => {
context.strokeStyle = "#888";
context.save();
@ -60,6 +88,16 @@ const render = (
el.color,
);
break;
case isCurve(el.data):
renderCubicBezier(
context,
appState.zoom.value,
el.data as Curve<GlobalPoint>,
el.color,
);
break;
default:
throw new Error(`Unknown element type ${JSON.stringify(el)}`);
}
});
};
@ -276,36 +314,35 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
interface DebugCanvasProps {
appState: AppState;
scale: number;
ref?: React.Ref<HTMLCanvasElement>;
}
const DebugCanvas = forwardRef<HTMLCanvasElement, DebugCanvasProps>(
({ appState, scale }, ref) => {
const { width, height } = appState;
const DebugCanvas = ({ appState, scale, ref }: DebugCanvasProps) => {
const { width, height } = appState;
const canvasRef = useRef<HTMLCanvasElement>(null);
useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
ref,
() => canvasRef.current,
[canvasRef],
);
const canvasRef = useRef<HTMLCanvasElement>(null);
useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
ref,
() => canvasRef.current,
[canvasRef],
);
return (
<canvas
style={{
width,
height,
position: "absolute",
zIndex: 2,
pointerEvents: "none",
}}
width={width * scale}
height={height * scale}
ref={canvasRef}
>
Debug Canvas
</canvas>
);
},
);
return (
<canvas
style={{
width,
height,
position: "absolute",
zIndex: 2,
pointerEvents: "none",
}}
width={width * scale}
height={height * scale}
ref={canvasRef}
>
Debug Canvas
</canvas>
);
};
export default DebugCanvas;

View File

@ -1,6 +1,6 @@
import { shield } from "../../packages/excalidraw/components/icons";
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
import { useI18n } from "../../packages/excalidraw/i18n";
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
import { shield } from "@excalidraw/excalidraw/components/icons";
import { useI18n } from "@excalidraw/excalidraw/i18n";
export const EncryptedIcon = () => {
const { t } = useI18n();
@ -10,7 +10,7 @@ export const EncryptedIcon = () => {
className="encrypted-icon tooltip"
href="https://plus.excalidraw.com/blog/end-to-end-encryption"
target="_blank"
rel="noopener noreferrer"
rel="noopener"
aria-label={t("encrypted.link")}
>
<Tooltip label={t("encrypted.tooltip")} long={true}>

View File

@ -10,7 +10,7 @@ export const ExcalidrawPlusAppLink = () => {
import.meta.env.VITE_APP_PLUS_APP
}?utm_source=excalidraw&utm_medium=app&utm_content=signedInUserRedirectButton#excalidraw-redirect`}
target="_blank"
rel="noreferrer"
rel="noopener"
className="plus-button"
>
Go to Excalidraw+

View File

@ -1,30 +1,33 @@
import React from "react";
import { Card } from "../../packages/excalidraw/components/Card";
import { ToolButton } from "../../packages/excalidraw/components/ToolButton";
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
import { uploadBytes, ref } from "firebase/storage";
import { nanoid } from "nanoid";
import { trackEvent } from "@excalidraw/excalidraw/analytics";
import { Card } from "@excalidraw/excalidraw/components/Card";
import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";
import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton";
import { MIME_TYPES, getFrame } from "@excalidraw/common";
import {
encryptData,
generateEncryptionKey,
} from "@excalidraw/excalidraw/data/encryption";
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
import { isInitializedImageElement } from "@excalidraw/element";
import { useI18n } from "@excalidraw/excalidraw/i18n";
import type {
FileId,
NonDeletedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
} from "@excalidraw/element/types";
import type {
AppState,
BinaryFileData,
BinaryFiles,
} from "../../packages/excalidraw/types";
import { nanoid } from "nanoid";
import { useI18n } from "../../packages/excalidraw/i18n";
import {
encryptData,
generateEncryptionKey,
} from "../../packages/excalidraw/data/encryption";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
} from "@excalidraw/excalidraw/types";
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
import { encodeFilesForUpload } from "../data/FileManager";
import { MIME_TYPES } from "../../packages/excalidraw/constants";
import { trackEvent } from "../../packages/excalidraw/analytics";
import { getFrame } from "../../packages/excalidraw/utils";
import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
export const exportToExcalidrawPlus = async (
elements: readonly NonDeletedExcalidrawElement[],
@ -32,7 +35,7 @@ export const exportToExcalidrawPlus = async (
files: BinaryFiles,
name: string,
) => {
const firebase = await loadFirebaseStorage();
const storage = await loadFirebaseStorage();
const id = `${nanoid(12)}`;
@ -49,15 +52,13 @@ export const exportToExcalidrawPlus = async (
},
);
await firebase
.storage()
.ref(`/migrations/scenes/${id}`)
.put(blob, {
customMetadata: {
data: JSON.stringify({ version: 2, name }),
created: Date.now().toString(),
},
});
const storageRef = ref(storage, `/migrations/scenes/${id}`);
await uploadBytes(storageRef, blob, {
customMetadata: {
data: JSON.stringify({ version: 2, name }),
created: Date.now().toString(),
},
});
const filesMap = new Map<FileId, BinaryFileData>();
for (const element of elements) {

View File

@ -1,7 +1,8 @@
import { THEME } from "@excalidraw/common";
import oc from "open-color";
import React from "react";
import { THEME } from "../../packages/excalidraw/constants";
import type { Theme } from "../../packages/excalidraw/element/types";
import type { Theme } from "@excalidraw/element/types";
// https://github.com/tholman/github-corners
export const GitHubCorner = React.memo(

View File

@ -1,7 +1,7 @@
import React from "react";
import Trans from "@excalidraw/excalidraw/components/Trans";
import { t } from "@excalidraw/excalidraw/i18n";
import * as Sentry from "@sentry/browser";
import { t } from "../../packages/excalidraw/i18n";
import Trans from "../../packages/excalidraw/components/Trans";
import React from "react";
interface TopErrorBoundaryState {
hasError: boolean;

View File

@ -1,20 +1,21 @@
import { StoreAction } from "../../packages/excalidraw";
import { compressData } from "../../packages/excalidraw/data/encode";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
import { compressData } from "@excalidraw/excalidraw/data/encode";
import { newElementWith } from "@excalidraw/element";
import { isInitializedImageElement } from "@excalidraw/element";
import { t } from "@excalidraw/excalidraw/i18n";
import type {
ExcalidrawElement,
ExcalidrawImageElement,
FileId,
InitializedExcalidrawImageElement,
} from "../../packages/excalidraw/element/types";
import { t } from "../../packages/excalidraw/i18n";
} from "@excalidraw/element/types";
import type {
BinaryFileData,
BinaryFileMetadata,
ExcalidrawImperativeAPI,
BinaryFiles,
} from "../../packages/excalidraw/types";
} from "@excalidraw/excalidraw/types";
type FileVersion = Required<BinaryFileData>["version"];
@ -268,6 +269,6 @@ export const updateStaleImageStatuses = (params: {
}
return element;
}),
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
};

View File

@ -10,6 +10,13 @@
* (localStorage, indexedDB).
*/
import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
import {
CANVAS_SEARCH_TAB,
DEFAULT_SIDEBAR,
debounce,
} from "@excalidraw/common";
import { clearElementsForLocalStorage } from "@excalidraw/element";
import {
createStore,
entries,
@ -19,26 +26,19 @@ import {
setMany,
get,
} from "idb-keyval";
import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
import {
CANVAS_SEARCH_TAB,
DEFAULT_SIDEBAR,
} from "../../packages/excalidraw/constants";
import type { LibraryPersistedData } from "../../packages/excalidraw/data/library";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
import type {
ExcalidrawElement,
FileId,
} from "../../packages/excalidraw/element/types";
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
import type {
AppState,
BinaryFileData,
BinaryFiles,
} from "../../packages/excalidraw/types";
import type { MaybePromise } from "../../packages/excalidraw/utility-types";
import { debounce } from "../../packages/excalidraw/utils";
} from "@excalidraw/excalidraw/types";
import type { MaybePromise } from "@excalidraw/common/utility-types";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
import { FileManager } from "./FileManager";
import { Locker } from "./Locker";
import { updateBrowserStateVersion } from "./tabSync";

View File

@ -1,30 +1,42 @@
import { reconcileElements } from "../../packages/excalidraw";
import { reconcileElements } from "@excalidraw/excalidraw";
import { MIME_TYPES } from "@excalidraw/common";
import { decompressData } from "@excalidraw/excalidraw/data/encode";
import {
encryptData,
decryptData,
} from "@excalidraw/excalidraw/data/encryption";
import { restoreElements } from "@excalidraw/excalidraw/data/restore";
import { getSceneVersion } from "@excalidraw/element";
import { initializeApp } from "firebase/app";
import {
getFirestore,
doc,
getDoc,
runTransaction,
Bytes,
} from "firebase/firestore";
import { getStorage, ref, uploadBytes } from "firebase/storage";
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
import type {
ExcalidrawElement,
FileId,
OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import { getSceneVersion } from "../../packages/excalidraw/element";
import type Portal from "../collab/Portal";
import { restoreElements } from "../../packages/excalidraw/data/restore";
} from "@excalidraw/element/types";
import type {
AppState,
BinaryFileData,
BinaryFileMetadata,
DataURL,
} from "../../packages/excalidraw/types";
} from "@excalidraw/excalidraw/types";
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
import { decompressData } from "../../packages/excalidraw/data/encode";
import {
encryptData,
decryptData,
} from "../../packages/excalidraw/data/encryption";
import { MIME_TYPES } from "../../packages/excalidraw/constants";
import type { SyncableExcalidrawElement } from ".";
import { getSyncableElements } from ".";
import type { ResolutionType } from "../../packages/excalidraw/utility-types";
import type { SyncableExcalidrawElement } from ".";
import type Portal from "../collab/Portal";
import type { Socket } from "socket.io-client";
import type { RemoteExcalidrawElement } from "../../packages/excalidraw/data/reconcile";
// private
// -----------------------------------------------------------------------------
@ -41,80 +53,42 @@ try {
FIREBASE_CONFIG = {};
}
let firebasePromise: Promise<typeof import("firebase/app").default> | null =
null;
let firestorePromise: Promise<any> | null | true = null;
let firebaseStoragePromise: Promise<any> | null | true = null;
let firebaseApp: ReturnType<typeof initializeApp> | null = null;
let firestore: ReturnType<typeof getFirestore> | null = null;
let firebaseStorage: ReturnType<typeof getStorage> | null = null;
let isFirebaseInitialized = false;
const _loadFirebase = async () => {
const firebase = (
await import(/* webpackChunkName: "firebase" */ "firebase/app")
).default;
if (!isFirebaseInitialized) {
try {
firebase.initializeApp(FIREBASE_CONFIG);
} catch (error: any) {
// trying initialize again throws. Usually this is harmless, and happens
// mainly in dev (HMR)
if (error.code === "app/duplicate-app") {
console.warn(error.name, error.code);
} else {
throw error;
}
}
isFirebaseInitialized = true;
const _initializeFirebase = () => {
if (!firebaseApp) {
firebaseApp = initializeApp(FIREBASE_CONFIG);
}
return firebase;
return firebaseApp;
};
const _getFirebase = async (): Promise<
typeof import("firebase/app").default
> => {
if (!firebasePromise) {
firebasePromise = _loadFirebase();
const _getFirestore = () => {
if (!firestore) {
firestore = getFirestore(_initializeFirebase());
}
return firebasePromise;
return firestore;
};
const _getStorage = () => {
if (!firebaseStorage) {
firebaseStorage = getStorage(_initializeFirebase());
}
return firebaseStorage;
};
// -----------------------------------------------------------------------------
const loadFirestore = async () => {
const firebase = await _getFirebase();
if (!firestorePromise) {
firestorePromise = import(
/* webpackChunkName: "firestore" */ "firebase/firestore"
);
}
if (firestorePromise !== true) {
await firestorePromise;
firestorePromise = true;
}
return firebase;
};
export const loadFirebaseStorage = async () => {
const firebase = await _getFirebase();
if (!firebaseStoragePromise) {
firebaseStoragePromise = import(
/* webpackChunkName: "storage" */ "firebase/storage"
);
}
if (firebaseStoragePromise !== true) {
await firebaseStoragePromise;
firebaseStoragePromise = true;
}
return firebase;
return _getStorage();
};
interface FirebaseStoredScene {
type FirebaseStoredScene = {
sceneVersion: number;
iv: firebase.default.firestore.Blob;
ciphertext: firebase.default.firestore.Blob;
}
iv: Bytes;
ciphertext: Bytes;
};
const encryptElements = async (
key: string,
@ -175,7 +149,7 @@ export const saveFilesToFirebase = async ({
prefix: string;
files: { id: FileId; buffer: Uint8Array }[];
}) => {
const firebase = await loadFirebaseStorage();
const storage = await loadFirebaseStorage();
const erroredFiles: FileId[] = [];
const savedFiles: FileId[] = [];
@ -183,17 +157,10 @@ export const saveFilesToFirebase = async ({
await Promise.all(
files.map(async ({ id, buffer }) => {
try {
await firebase
.storage()
.ref(`${prefix}/${id}`)
.put(
new Blob([buffer], {
type: MIME_TYPES.binary,
}),
{
cacheControl: `public, max-age=${FILE_CACHE_MAX_AGE_SEC}`,
},
);
const storageRef = ref(storage, `${prefix}/${id}`);
await uploadBytes(storageRef, buffer, {
cacheControl: `public, max-age=${FILE_CACHE_MAX_AGE_SEC}`,
});
savedFiles.push(id);
} catch (error: any) {
erroredFiles.push(id);
@ -205,7 +172,6 @@ export const saveFilesToFirebase = async ({
};
const createFirebaseSceneDocument = async (
firebase: ResolutionType<typeof loadFirestore>,
elements: readonly SyncableExcalidrawElement[],
roomKey: string,
) => {
@ -213,10 +179,8 @@ const createFirebaseSceneDocument = async (
const { ciphertext, iv } = await encryptElements(roomKey, elements);
return {
sceneVersion,
ciphertext: firebase.firestore.Blob.fromUint8Array(
new Uint8Array(ciphertext),
),
iv: firebase.firestore.Blob.fromUint8Array(iv),
ciphertext: Bytes.fromUint8Array(new Uint8Array(ciphertext)),
iv: Bytes.fromUint8Array(iv),
} as FirebaseStoredScene;
};
@ -236,20 +200,14 @@ export const saveToFirebase = async (
return null;
}
const firebase = await loadFirestore();
const firestore = firebase.firestore();
const firestore = _getFirestore();
const docRef = doc(firestore, "scenes", roomId);
const docRef = firestore.collection("scenes").doc(roomId);
const storedScene = await firestore.runTransaction(async (transaction) => {
const storedScene = await runTransaction(firestore, async (transaction) => {
const snapshot = await transaction.get(docRef);
if (!snapshot.exists) {
const storedScene = await createFirebaseSceneDocument(
firebase,
elements,
roomKey,
);
if (!snapshot.exists()) {
const storedScene = await createFirebaseSceneDocument(elements, roomKey);
transaction.set(docRef, storedScene);
@ -269,7 +227,6 @@ export const saveToFirebase = async (
);
const storedScene = await createFirebaseSceneDocument(
firebase,
reconciledElements,
roomKey,
);
@ -294,15 +251,13 @@ export const loadFromFirebase = async (
roomKey: string,
socket: Socket | null,
): Promise<readonly SyncableExcalidrawElement[] | null> => {
const firebase = await loadFirestore();
const db = firebase.firestore();
const docRef = db.collection("scenes").doc(roomId);
const doc = await docRef.get();
if (!doc.exists) {
const firestore = _getFirestore();
const docRef = doc(firestore, "scenes", roomId);
const docSnap = await getDoc(docRef);
if (!docSnap.exists()) {
return null;
}
const storedScene = doc.data() as FirebaseStoredScene;
const storedScene = docSnap.data() as FirebaseStoredScene;
const elements = getSyncableElements(
restoreElements(await decryptElements(storedScene, roomKey), null),
);

View File

@ -1,42 +1,46 @@
import {
compressData,
decompressData,
} from "../../packages/excalidraw/data/encode";
} from "@excalidraw/excalidraw/data/encode";
import {
decryptData,
generateEncryptionKey,
IV_LENGTH_BYTES,
} from "../../packages/excalidraw/data/encryption";
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
import { restore } from "../../packages/excalidraw/data/restore";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import type { SceneBounds } from "../../packages/excalidraw/element/bounds";
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
} from "@excalidraw/excalidraw/data/encryption";
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
import { restore } from "@excalidraw/excalidraw/data/restore";
import { isInvisiblySmallElement } from "@excalidraw/element";
import { isInitializedImageElement } from "@excalidraw/element";
import { t } from "@excalidraw/excalidraw/i18n";
import { bytesToHexString } from "@excalidraw/common";
import type { UserIdleState } from "@excalidraw/common";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
import type { SceneBounds } from "@excalidraw/element";
import type {
ExcalidrawElement,
FileId,
OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import { t } from "../../packages/excalidraw/i18n";
} from "@excalidraw/element/types";
import type {
AppState,
BinaryFileData,
BinaryFiles,
SocketId,
UserIdleState,
} from "../../packages/excalidraw/types";
import type { MakeBrand } from "../../packages/excalidraw/utility-types";
import { bytesToHexString } from "../../packages/excalidraw/utils";
import type { WS_SUBTYPES } from "../app_constants";
} from "@excalidraw/excalidraw/types";
import type { MakeBrand } from "@excalidraw/common/utility-types";
import {
DELETED_ELEMENT_TIMEOUT,
FILE_UPLOAD_MAX_BYTES,
ROOM_ID_BYTES,
} from "../app_constants";
import { encodeFilesForUpload } from "./FileManager";
import { saveFilesToFirebase } from "./firebase";
import type { WS_SUBTYPES } from "../app_constants";
export type SyncableExcalidrawElement = OrderedExcalidrawElement &
MakeBrand<"SyncableExcalidrawElement">;

View File

@ -1,10 +1,12 @@
import type { ExcalidrawElement } from "../../packages/excalidraw/element/types";
import type { AppState } from "../../packages/excalidraw/types";
import {
clearAppStateForLocalStorage,
getDefaultAppState,
} from "../../packages/excalidraw/appState";
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
} from "@excalidraw/excalidraw/appState";
import { clearElementsForLocalStorage } from "@excalidraw/element";
import type { ExcalidrawElement } from "@excalidraw/element/types";
import type { AppState } from "@excalidraw/excalidraw/types";
import { STORAGE_KEYS } from "../app_constants";
export const saveUsernameToLocalStorage = (username: string) => {

View File

@ -1,3 +1,6 @@
import "@excalidraw/excalidraw/global";
import "@excalidraw/excalidraw/css";
interface Window {
__EXCALIDRAW_SHA__: string | undefined;
}

View File

@ -1,9 +1,11 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import ExcalidrawApp from "./App";
import { registerSW } from "virtual:pwa-register";
import "../excalidraw-app/sentry";
import ExcalidrawApp from "./App";
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement);

View File

@ -27,22 +27,22 @@
},
"dependencies": {
"@excalidraw/random-username": "1.0.0",
"@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5",
"firebase": "8.3.3",
"@sentry/browser": "9.0.1",
"callsites": "4.2.0",
"firebase": "11.3.1",
"i18next-browser-languagedetector": "6.1.4",
"idb-keyval": "6.0.3",
"jotai": "2.11.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"socket.io-client": "4.7.2",
"vite-plugin-html": "3.2.2"
},
"prettier": "@excalidraw/prettier-config",
"scripts": {
"build-node": "node ./scripts/build-node.js",
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true vite build",
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA cross-env VITE_APP_ENABLE_TRACKING=true vite build",
"build:app:docker": "vite build",
"build:app": "vite build",
"build:version": "node ../scripts/build-version.js",
"build": "yarn build:app && yarn build:version",
"start": "yarn && vite",

View File

@ -1,8 +1,9 @@
import * as Sentry from "@sentry/browser";
import * as SentryIntegrations from "@sentry/integrations";
import callsites from "callsites";
const SentryEnvHostnameMap: { [key: string]: string } = {
"excalidraw.com": "production",
"staging.excalidraw.com": "staging",
"vercel.app": "staging",
};
@ -23,9 +24,13 @@ Sentry.init({
release: import.meta.env.VITE_APP_GIT_SHA,
ignoreErrors: [
"undefined is not an object (evaluating 'window.__pad.performLoop')", // Only happens on Safari, but spams our servers. Doesn't break anything
"InvalidStateError: Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.", // Not much we can do about the IndexedDB closing error
/(Failed to fetch|(fetch|loading) dynamically imported module)/i, // This is happening when a service worker tries to load an old asset
/QuotaExceededError: (The quota has been exceeded|.*setItem.*Storage)/i, // localStorage quota exceeded
"Internal error opening backing store for indexedDB.open", // Private mode and disabled indexedDB
],
integrations: [
new SentryIntegrations.CaptureConsole({
Sentry.captureConsoleIntegration({
levels: ["error"],
}),
],
@ -33,6 +38,44 @@ Sentry.init({
if (event.request?.url) {
event.request.url = event.request.url.replace(/#.*$/, "");
}
if (!event.exception) {
event.exception = {
values: [
{
type: "ConsoleError",
value: event.message ?? "Unknown error",
stacktrace: {
frames: callsites()
.slice(1)
.filter(
(frame) =>
frame.getFileName() &&
!frame.getFileName()?.includes("@sentry_browser.js"),
)
.map((frame) => ({
filename: frame.getFileName() ?? undefined,
function: frame.getFunctionName() ?? undefined,
in_app: !(
frame.getFileName()?.includes("node_modules") ?? false
),
lineno: frame.getLineNumber() ?? undefined,
colno: frame.getColumnNumber() ?? undefined,
})),
},
mechanism: {
type: "instrument",
handled: true,
data: {
function: "console.error",
handler: "Sentry.beforeSend",
},
},
},
],
};
}
return event;
},
});

View File

@ -1,10 +1,8 @@
import { useEffect, useRef, useState } from "react";
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 { trackEvent } from "@excalidraw/excalidraw/analytics";
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
import { Dialog } from "@excalidraw/excalidraw/components/Dialog";
import { FilledButton } from "@excalidraw/excalidraw/components/FilledButton";
import { TextField } from "@excalidraw/excalidraw/components/TextField";
import {
copyIcon,
LinkIcon,
@ -13,17 +11,20 @@ import {
share,
shareIOS,
shareWindows,
} from "../../packages/excalidraw/components/icons";
import { TextField } from "../../packages/excalidraw/components/TextField";
import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
import type { CollabAPI } from "../collab/Collab";
import { activeRoomLinkAtom } from "../collab/Collab";
import { useUIAppState } from "../../packages/excalidraw/context/ui-appState";
import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator";
} from "@excalidraw/excalidraw/components/icons";
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
import { useCopyStatus } from "@excalidraw/excalidraw/hooks/useCopiedIndicator";
import { useI18n } from "@excalidraw/excalidraw/i18n";
import { KEYS, getFrame } from "@excalidraw/common";
import { useEffect, useRef, useState } from "react";
import { atom, useAtom, useAtomValue } from "../app-jotai";
import { activeRoomLinkAtom } from "../collab/Collab";
import "./ShareDialog.scss";
import type { CollabAPI } from "../collab/Collab";
type OnExportToBackend = () => void;
type ShareDialogType = "share" | "collaborationOnly";

View File

@ -1,11 +1,11 @@
import { defaultLang } from "../../packages/excalidraw/i18n";
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
import { defaultLang } from "@excalidraw/excalidraw/i18n";
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
import {
screen,
fireEvent,
waitFor,
render,
} from "../../packages/excalidraw/tests/test-utils";
} from "@excalidraw/excalidraw/tests/test-utils";
import ExcalidrawApp from "../App";

View File

@ -1,11 +1,11 @@
import ExcalidrawApp from "../App";
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
import {
mockBoundingClientRect,
render,
restoreOriginalGetBoundingClientRect,
} from "../../packages/excalidraw/tests/test-utils";
} from "@excalidraw/excalidraw/tests/test-utils";
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
import ExcalidrawApp from "../App";
describe("Test MobileMenu", () => {
const { h } = window;

View File

@ -198,7 +198,7 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
<a
class="welcome-screen-menu-item "
href="undefined/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
rel="noreferrer"
rel="noopener"
target="_blank"
>
<div

View File

@ -1,17 +1,18 @@
import { vi } from "vitest";
import {
act,
render,
waitFor,
} from "../../packages/excalidraw/tests/test-utils";
import ExcalidrawApp from "../App";
import { API } from "../../packages/excalidraw/tests/helpers/api";
import { syncInvalidIndices } from "../../packages/excalidraw/fractionalIndex";
import { CaptureUpdateAction, newElementWith } from "@excalidraw/excalidraw";
import {
createRedoAction,
createUndoAction,
} from "../../packages/excalidraw/actions/actionHistory";
import { StoreAction, newElementWith } from "../../packages/excalidraw";
} from "@excalidraw/excalidraw/actions/actionHistory";
import { syncInvalidIndices } from "@excalidraw/element";
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
import { vi } from "vitest";
import { StoreIncrement } from "@excalidraw/element";
import type { DurableIncrement, EphemeralIncrement } from "@excalidraw/element";
import ExcalidrawApp from "../App";
const { h } = window;
@ -68,6 +69,79 @@ vi.mock("socket.io-client", () => {
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
*/
describe("collaboration", () => {
it("should emit two ephemeral increments even though updates get batched", async () => {
const durableIncrements: DurableIncrement[] = [];
const ephemeralIncrements: EphemeralIncrement[] = [];
await render(<ExcalidrawApp />);
h.store.onStoreIncrementEmitter.on((increment) => {
if (StoreIncrement.isDurable(increment)) {
durableIncrements.push(increment);
} else {
ephemeralIncrements.push(increment);
}
});
// eslint-disable-next-line dot-notation
expect(h.store["scheduledMicroActions"].length).toBe(0);
expect(durableIncrements.length).toBe(0);
expect(ephemeralIncrements.length).toBe(0);
const rectProps = {
type: "rectangle",
id: "A",
height: 200,
width: 100,
x: 0,
y: 0,
} as const;
const rect = API.createElement({ ...rectProps });
API.updateScene({
elements: [rect],
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
await waitFor(() => {
// expect(commitSpy).toHaveBeenCalledTimes(1);
expect(durableIncrements.length).toBe(1);
});
// simulate two batched remote updates
act(() => {
h.app.updateScene({
elements: [newElementWith(h.elements[0], { x: 100 })],
captureUpdate: CaptureUpdateAction.NEVER,
});
h.app.updateScene({
elements: [newElementWith(h.elements[0], { x: 200 })],
captureUpdate: CaptureUpdateAction.NEVER,
});
// we scheduled two micro actions,
// which confirms they are going to be executed as part of one batched component update
// eslint-disable-next-line dot-notation
expect(h.store["scheduledMicroActions"].length).toBe(2);
});
await waitFor(() => {
// altough the updates get batched,
// we expect two ephemeral increments for each update,
// and each such update should have the expected change
expect(ephemeralIncrements.length).toBe(2);
expect(ephemeralIncrements[0].change.elements.A).toEqual(
expect.objectContaining({ x: 100 }),
);
expect(ephemeralIncrements[1].change.elements.A).toEqual(
expect.objectContaining({ x: 200 }),
);
// eslint-disable-next-line dot-notation
expect(h.store["scheduledMicroActions"].length).toBe(0);
});
});
it("should allow to undo / redo even on force-deleted elements", async () => {
await render(<ExcalidrawApp />);
const rect1Props = {
@ -89,7 +163,7 @@ describe("collaboration", () => {
API.updateScene({
elements: syncInvalidIndices([rect1, rect2]),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
API.updateScene({
@ -97,7 +171,7 @@ describe("collaboration", () => {
rect1,
newElementWith(h.elements[1], { isDeleted: true }),
]),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
await waitFor(() => {
@ -125,7 +199,7 @@ describe("collaboration", () => {
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
});
const undoAction = createUndoAction(h.history, h.store);
const undoAction = createUndoAction(h.history);
act(() => h.app.actionManager.executeAction(undoAction));
// with explicit undo (as addition) we expect our item to be restored from the snapshot!
@ -144,7 +218,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
await waitFor(() => {
@ -157,7 +231,7 @@ describe("collaboration", () => {
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
});
const redoAction = createRedoAction(h.history, h.store);
const redoAction = createRedoAction(h.history);
act(() => h.app.actionManager.executeAction(redoAction));
// with explicit redo (as removal) we again restore the element from the snapshot!
@ -182,7 +256,7 @@ describe("collaboration", () => {
h.elements[0],
newElementWith(h.elements[1], { x: 100 }),
]),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
await waitFor(() => {
@ -217,7 +291,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
// snapshot was correctly updated and marked the element as deleted

Some files were not shown because too many files have changed in this diff Show More