Compare commits

...

389 Commits

Author SHA1 Message Date
0200fa9a82 build(deps): bump firebase from 8.3.3 to 10.9.0
Bumps [firebase](https://github.com/firebase/firebase-js-sdk) from 8.3.3 to 10.9.0.
- [Release notes](https://github.com/firebase/firebase-js-sdk/releases)
- [Changelog](https://github.com/firebase/firebase-js-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/firebase/firebase-js-sdk/compare/firebase@8.3.3...firebase@10.9.0)

---
updated-dependencies:
- dependency-name: firebase
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 22:23:33 +00:00
57cf577376 fix: cleanup scripts and support upto node 22 (#8794) 2024-11-11 23:56:00 +05:30
6e0ee89ee4 fix: usage of node12 which is deprecated (#8791) 2024-11-11 12:05:55 +01:00
35f778a734 build: set PWA flag in dev to false (#8788) 2024-11-09 21:04:50 +01:00
ee091d0dbd build: add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment (#8784)
* build: add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment

* fix

* set VITE_ENABLE_PWA to false in .env.development
2024-11-09 21:45:37 +05:30
ef9ea14a75 fix: remove manifest.json (#8783)
* fix: remove manifest.json

* disable pwa in dev
2024-11-09 13:42:11 +05:30
df168a6883 fix: load env vars correctly and set debug and linter flags to false explicitly in prod mode (#8770)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-11-07 21:14:49 +00:00
798f5f4dfb feat: update blog url (#8767) 2024-11-06 16:41:41 +00:00
d9ad7c039b feat: export scene to e+ on workspace creation/redemption (#8514)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-11-04 22:35:45 +00:00
7c0239e693 fix: Console error in dev mode due to missing font path in non-prod (#8756)
Fix console error due to missing font path in dev mode reported by Firefox.
2024-11-04 17:54:00 +01:00
da33481fa3 chore: support upto node 22.x.x (#8755) 2024-11-04 18:57:40 +05:30
70e0e8dc29 fix: text pushes UI due to padding (#8745) 2024-11-01 23:43:48 +02:00
2734e646ca chore: simplify line-break regexes, separate text wrapping (#8715) 2024-10-30 14:24:12 +01:00
dfaaff4432 fix: fix trailing line whitespaces layout shift (#8714) 2024-10-30 14:11:13 +01:00
03028eaa8c fix: load font faces in Safari manually (#8693) 2024-10-30 12:40:24 +01:00
79b181bcdc fix: restore svg image DataURL dimensions (#8730) 2024-10-29 22:40:24 +01:00
f9815b8b4f fix: image cropping svg + compat mode (#8710)
Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
2024-10-28 10:08:05 +01:00
96ed8a4331 chore: remove duplicated meta tag (#8718)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-10-25 16:29:48 +00:00
33b01d4e80 fix: usage of node12 which is deprecated (#8709) 2024-10-25 10:39:44 +05:30
7d52176fea feat: added sitemap & fixed robot txt (#8699) 2024-10-24 12:49:33 +02:00
958e03fcc6 fix: image render perf (#8697) 2024-10-23 11:58:43 +02:00
4cedf3d966 feat: do not strip unknown element properties on restore (#8682) 2024-10-21 22:56:22 +02:00
e957c8e9ee feat: image cropping (#8613)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-10-21 22:26:52 +02:00
eb09b48ae6 fix: undo/redo action for international keyboard layouts (#8649)
Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2024-10-21 17:08:39 +02:00
61623bbeba fix: Comic Shanns issues, new fonts structure (#8641) 2024-10-21 00:11:53 +02:00
15ca182333 fix: remove export-to-clip-as-svg shortcut for now (#8660) 2024-10-17 20:47:05 +02:00
b479f3bd65 feat: add first-class support for CJK (#8530) 2024-10-17 20:14:17 +02:00
21815fb930 fix: text disappearing on edit (#8558) (#8624) 2024-10-17 13:11:48 +02:00
47ee8a0094 refactor: point() -> pointFrom() to fix compiler issue (#8578) 2024-10-01 21:27:17 +02:00
a977dd1bf5 feat: Added reddit links as embeddable (#8099)
feat: #8063 Added reddit links as embeddable

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2024-09-28 11:49:18 +05:30
3fe1883f3f feat: prefer user defined coords and dimensions over calculated for for frame (#8517)
* feat: prefer user defined coords and dimensions over calculated for frame

* update changelog

* lint

* show the info only in dev mode and when children present
2024-09-24 21:09:15 +05:30
a80cb5896a feat: self-hosting existing google fonts (#8540) 2024-09-24 17:30:21 +02:00
6dfa18414a test: decrease min coverage thresholds (#8541) 2024-09-24 12:01:28 +00:00
8ca4cf3260 feat: flip arrowheads if only arrow(s) selected (#8525)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2024-09-19 15:46:36 +02:00
f3f0ab7c83 fix: Elbow arrow fixedpoint flipping now properly flips on inverted resize and flip action (#8324)
* Flipping action now properly mirrors selections with elbow arrows
* Flipping action now re-centers the selection to the original center to avoid "walking" selections on repeated flipping
2024-09-19 08:47:23 +02:00
44a1c8d857 fix: svg and png frame clipping cases (#8515) 2024-09-18 00:20:22 +02:00
e0a22edfbd fix: Re-route elbow arrows when pasted (#8448)
Re-route elbow arrows when pasted
2024-09-17 12:20:40 +02:00
c07f5a0c80 feat: Common elbow mid segments (#8440)
Common start or end segment length for elbow arrows regardless of arrowhead is present
2024-09-17 10:11:07 +02:00
508f16dc04 refactor: rename example App.tsx -> ExampleApp.tsx (#8501) 2024-09-13 16:56:32 +02:00
c1b310c56b fix: Buffer dependency (#8474)
* fix Buffer dependency

* moved to encode.ts

* move base64 parsing out

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-12 15:48:47 +02:00
d4900e8f19 fix: Linear element complete button disabled (#8492) 2024-09-12 14:59:38 +02:00
caf2db934c fix: aspect ratio of distorted images are not preserved in SVG exports (#8061) 2024-09-12 14:11:08 +02:00
60e3801691 fix: WYSIWYG editor padding is not normalized with zoom.value (#8481) 2024-09-12 13:42:39 +02:00
80f3b75d42 chore: revert vite 5.4.2 -> 5.0.12 (#8499) 2024-09-12 12:56:45 +02:00
dc812bee19 docs: replace dead link (#8494)
* docs update dead link on main-menu page

* doc: fix dead link
2024-09-12 13:20:18 +05:30
01e83cc9a5 fix: default sidebar triggers & behavior (#8498) 2024-09-11 20:19:14 +02:00
813f9b702e feat: merge search sidebar back to default sidebar (#8497) 2024-09-11 19:26:01 +02:00
fd39712ba6 fix: improve canvas search scroll behavior further (#8491) 2024-09-11 18:01:18 +02:00
b46ca0192b fix: addFiles clears the whole image cache when each file is added - regression from #8471 (#8490)
Update App.tsx
2024-09-11 07:57:41 +02:00
72b7c937b1 feat: smarter zooming when scrolling to match & only match on search/switch (#8488) 2024-09-10 21:26:01 +02:00
d107215564 fix: select instead of focus search input (#8483) 2024-09-09 19:57:22 +02:00
6959a363f0 feat: canvas search (#8438)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-09 17:12:07 +02:00
5a11c70714 fix: image rendering issue when passed in initialData (#8471) 2024-09-08 23:56:00 +02:00
6ff56c36e3 fix: add partial mocking (#8473)
* fix: add partial mocking

* lint

* Update packages/utils/export.test.ts
2024-09-06 16:41:37 +05:30
51ea184938 build: upgrade vite to 5.4.x, vitest to 2.x and related vite packages (#8459)
* build: upgrade vite to 5.x, vitest to 2.x and related vite packages

* upgrade vitest-ui and coverage

* pass empty set to fix type error and update snap

* set ignoreEmptyLines to false

* update threshold

* update coverage threshold

* downgrade vite-plugin-pwa as its better to push separately with testing

* add package resolutions for strip-ansi, string-width and wrap-ansi

* disable pwa

* only add resolution for strip-ansi
2024-09-05 18:35:36 +05:30
21fff26d31 build(deps): bump micromatch from 4.0.5 to 4.0.8 in /dev-docs (#8458)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-04 15:46:37 +05:30
f4dd23fc31 chore: Unify math types, utils and functions (#8389)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-02 22:23:38 +00:00
e3d1dee9d0 build(deps): bump micromatch from 4.0.7 to 4.0.8 (#8450)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 00:37:49 +02:00
e3f31df747 docs : update dead link on main-menu page (#8454) 2024-09-01 22:31:13 +00:00
60e75406e0 refactor: remove unused env variable (#8457) 2024-09-02 00:25:12 +02:00
b396e07b90 fix: PropertiesPopover maxWidth changing fixed units to relative units (#8456) 2024-09-01 18:18:37 +02:00
2d1d84a47b fix: View mode wheel zooming does not work (#8452)
* Update App.tsx

* fix: prevent zooming browser over DOM

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-31 12:06:38 +02:00
ee30225062 feat: subset font glyphs for SVG export (#8384)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-30 22:26:06 +02:00
16cae4fc07 feat: reset copyStatus on export dialog settings change (#8443) 2024-08-29 15:10:08 +02:00
576bc0dbe5 feat: tweak copy button success animation (#8441) 2024-08-29 00:43:01 +02:00
00af35c692 feat: enable panning/zoom while in wysiwyg (#8437) 2024-08-29 00:42:46 +02:00
ea7c702cfc feat: Visual debugger (#8344)
Add visual debugger to the Excalidraw app (only).
2024-08-27 19:46:00 +02:00
26d2296578 fix: fixed copy to clipboard button (#8426)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-27 00:27:44 +02:00
afb68a6467 feat: improve elbow arrow keyboard move (#8392) 2024-08-26 15:58:54 +02:00
b459e5cfd2 fix: context menu does not work after after dragging on StatsDragInput (#8386)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-26 15:58:46 +02:00
5facc0d6da fix: perf regression in getCommonBounds (#8429) 2024-08-26 15:57:28 +02:00
824ad603e1 docs: Update function call in @excalidraw/mermaid-to-excalidraw (#8420) 2024-08-26 13:20:29 +02:00
5e1ff7cafe perf: improve new element drawing (#8340)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-23 20:27:57 +02:00
b5d7f5b4ba feat: rewrite d2c to not require token (#8269) 2024-08-20 18:06:22 +02:00
fb4bb29aa5 fix: object snapping not working (#8381) 2024-08-15 18:48:25 +02:00
3cfcc7b489 feat: split gridSize from enabled state & support custom gridStep (#8364) 2024-08-14 14:59:14 +02:00
4320a3cf41 feat: improve zoom-to-content when creating flowchart (#8368) 2024-08-12 20:42:08 +02:00
8420e1aa13 fix: Reimplement rectangle intersection (#8367) 2024-08-12 19:19:16 +02:00
5daf1a1b4e fix: Round coordinates and sizes for rectangle intersection (#8366)
Round coordinates and sizes for rectangle intersection
2024-08-12 11:07:58 +02:00
97981804d7 feat: Stats popup style tweaks (#8361) 2024-08-11 19:33:44 +02:00
f7b3befd0a fix: text content with tab characters act different in view/edit (#8336)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-09 20:20:36 +00:00
7b2bee9746 chore: remove unused parameter (#8355) 2024-08-09 21:39:40 +02:00
88014ace4a fix: drawing from 0-dimension canvas (#8356) 2024-08-09 21:36:04 +02:00
87a9430809 fix: disable flowchart keybindings inside inputs (#8353) 2024-08-09 18:44:17 +02:00
99b91c46f7 fix: Yet more patching of intersect code (#8352)
* Yet more patching of intersect code
2024-08-09 17:33:12 +02:00
1ea5b26f25 fix: missing act() in flowchart tests (#8354) 2024-08-09 17:27:02 +02:00
d5f4ee7b3f fix: z-index change by one causes app to freeze (#8314) 2024-08-09 15:26:08 +02:00
261304c1a4 fix: Patch over intersection calculation issue (#8350)
* Patch over intersection calculation issue
2024-08-09 14:40:57 +02:00
84398a7e5c fix: Point duplication in LEE on ALT+click (#8347) 2024-08-09 12:01:32 +02:00
54491d13d4 feat: create flowcharts from a generic element using elbow arrows (#8329)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-08 21:43:15 +02:00
dd1370381d chore: Refactor and remove scene from elbow arrow generation (#8342)
* Refactor and remove scene from elbow arrow generation
2024-08-08 14:06:26 +02:00
72d6ee48fc fix: Do not allow resizing unbound elbow arrows either (#8333)
* Fix resizing of unbound elbow arrows
2024-08-06 17:33:34 +02:00
232242d2e9 test: skip test.yml in PRs (#8330) 2024-08-06 16:04:04 +02:00
f19ce30dfe chore: bump @testing-library/react 12.1.5 -> 16.0.0 (#8322) 2024-08-06 15:17:42 +02:00
3cf14c73a3 refactor: rename draggingElement -> newElement (#8294)
* add newElement to appState

* freedraw should not be an editing element

* do not set editing element for freedraw and generic

* remove ununsed `appState.draggingElement`

* remove setting dragged for new linear element

* decouple selection element from new element

* fix hint for text bindables

* update snapshot

* fixes

* fix frame regressions

* add comments to types

* document `editingElement`

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-06 19:26:06 +08:00
8d530cf102 fix: Docker build in CI (#8312)
* Fix Docker build CI
* Bump nginx-alpine version to 1.27
2024-08-06 13:21:20 +02:00
b87925d253 build: add example apps public and vite dev-dist to eslintignore (#8326) 2024-08-05 23:43:48 +02:00
a6684b09f2 docs: fix typos in props.mdx (#8325) 2024-08-05 21:41:19 +00:00
b427617f66 build: add rm:build, rm:node_modules & clean-install scripts (#8323) 2024-08-05 23:37:33 +02:00
2907fbe34b build(deps): bump @excalidraw/excalidraw from 0.17.0 to 0.17.6 in /dev-docs (#7908)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 09:49:57 +02:00
c67815f7b0 fix: Duplicating arrow without bound elements throws error (#8316)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-08-03 16:37:36 +00:00
c641860cb1 fix: CVE-2023-45133 (#7988)
* Upgrade @babel/* versions to 7.24 to ensure non-vulnerable Babel versions
* Pinning React version to 18.2.0 exactly, avoiding test-utils type version clashes
* Fix warning message on yarn start
* Moving react to peer dependencies
* Moving app dependencies from workspace into app
* Bump vitest to 1.6.0 to fix history.test.tsx breaking

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-08-02 14:44:38 +02:00
84d89b9a8a fix: throttle fractional indices validation (#8306) 2024-08-02 11:55:15 +02:00
e63dd025c9 fix: allow binding elbow arrows to frame children (#8309) 2024-08-01 19:40:54 +02:00
15e019706d feat: Orthogonal (elbow) arrows for diagramming (#8299)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-01 18:39:03 +02:00
a133a70e87 build: update release script to build esm (#8308) 2024-08-01 20:19:37 +05:30
80ea7ca23f fix: skip registering font faces for local fonts (#8303) 2024-08-01 11:32:16 +02:00
e844580b14 feat: remove automatic frame naming (#8302) 2024-07-31 13:56:11 +02:00
5a0771ad9c fix: load fonts for exportToCanvas (#8298) 2024-07-30 17:23:35 +02:00
adcdbe2907 fix: re-add Cascadia Code with ligatures (#8291) 2024-07-30 11:15:11 +02:00
230d0edc44 feat: multiple fonts fallbacks (#8286) 2024-07-30 10:34:40 +02:00
d0a380758e feat: ability to debug the state of fractional indices (#8235) 2024-07-30 10:03:27 +02:00
7b36de0476 fix: linear elements not selected on pointer up from hitting its bound text (#8285)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-27 13:02:00 +00:00
2427e622b0 feat: improve mermaid detection on paste (#8287) 2024-07-27 12:36:54 +02:00
62228e0bbb feat: introduce font picker (#8012)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-25 18:55:55 +02:00
4c5408263c chore: Correct Typos in Code Comments (#8268)
chore: correct typos

Co-authored-by: wuzhiqing <wuzhiqing@linklogis.com>
2024-07-23 14:26:55 +05:30
bd7b778f41 perf: cache the temp canvas created for labeled arrows (#8267)
* perf: cache the temp canvas created for labeled arrows

* use allEleemntsMap so bound text element can be retrieved when editing

* remove logs

* fix rotation

* pass isRotating

* feat: cache `element.angle` instead of relying on `appState.isRotating`

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-23 11:17:32 +05:30
43b2476dfe fix: revert default element canvas padding change (#8266) 2024-07-22 11:47:16 +02:00
df8875a497 fix: freedraw jittering (#8238) 2024-07-14 08:44:47 +00:00
6fbc44fd1f fix: messed up env variable (#8231) 2024-07-11 14:33:35 +02:00
d25a7d365b feat: upgrade mermaid-to-excalidraw to v1.1.0 (#8226)
* feat: upgrade mermaid-to-excalidraw to v1.1.0

* fixes

* upgrade and remove config as its redundant

* lint

* upgrade to v1.1.0
2024-07-10 20:57:43 +05:30
e52c2cd0b6 fix: log allowed events (#8224) 2024-07-09 12:16:14 +02:00
96eeec5119 feat: bump max file size (#8220) 2024-07-08 18:35:13 +02:00
f5221d521b ci: upgrade gh actions checkout and setup-node to v4 (#8168)
fix: usage of `node12 which is deprecated`
2024-07-08 14:26:25 +05:30
db2c235cd4 Fix : exportToCanvas() doc example (#8127) 2024-07-08 08:52:05 +00:00
148b895f46 feat: smarter preferred lang detection (#8205) 2024-07-04 17:55:35 +02:00
d9258a736b chore: Consolidate i18n import in LanguageList component (#8201) 2024-07-04 17:34:16 +02:00
2e1f08c796 fix: memory leak - scene.destroy() and window.launchQueue (#8198) 2024-07-02 22:08:02 +02:00
1d5b41dabb fix: stop updating text versions on init (#8191) 2024-07-01 14:04:58 +02:00
66a2f24296 fix: Add binding update to manual stat changes (#8183)
Manual stats changes now respect previous element bindings.
2024-07-01 09:45:31 +02:00
04668d8263 fix: Binding after duplicating is now applied for both the old and duplicate shapes (#8185)
Using ALT/OPT + drag to clone does not transfer the bindings (or leaves the duplicates in place of the old one , which are also not bound).

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-06-28 15:28:48 +02:00
abbeed3d5f feat: support Stats bound text fontSize editing (#8187) 2024-06-28 13:52:29 +02:00
ba8c09d529 fix: Incorrect point offsetting in LinearElementEditor.movePoints() (#8145)
The LinearElementEditor.movePoints() function incorrectly calculates the offset for local linear element points when multiple targetPoints are provided, one of those target points is index === 0 AND the other points are moved in the negative direction, and ending up with negative local coordinates.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-06-28 12:23:10 +02:00
744b3e5d09 fix: stats state leaking & race conds (#8177) 2024-06-26 23:31:08 +02:00
6ba9bd60e8 feat: allow props.initialData to be a function (#8135) 2024-06-24 11:36:49 +02:00
a1ffa064df fix: only bind arrow (#8152)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-06-19 10:55:35 +02:00
4dc4590f24 fix: repair invalid binding on restore & fix type check (#8133) 2024-06-13 19:42:08 +02:00
d2f67e619f feat: editable element stats (#6382)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-06-12 19:49:46 +02:00
22b39277f5 feat: paste as mermaid if applicable (#8116) 2024-06-11 19:19:22 +02:00
63dee03ef0 docs: remove extra braces in callback JSX (#8087)
Fix: Syantax error
2024-05-31 10:48:31 +00:00
08b13f971d fix: wysiwyg blur-submit on mobile (#8075) 2024-05-28 16:18:02 +02:00
69f4cc70cb feat: stop autoselecting text on text edit on mobile (#8076) 2024-05-28 16:17:52 +02:00
860308eb27 feat: create new text with width (#8038)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-28 15:53:52 +02:00
4eb9463f26 fix: restore linear dimensions from points (#8062) 2024-05-27 10:36:58 +02:00
6ed6131169 build: run tests on master branch (#8072)
* build: run tests on master branch

* lint
2024-05-27 12:44:20 +05:30
1ed98f9c93 fix: lp plus url (#8056) 2024-05-24 09:10:14 +00:00
a71bb63d1f fix: fix twitter og image (#8050) 2024-05-23 11:52:37 +02:00
661d6a4a75 fix: flaky snapshot tests with floating point precision issues (#8049) 2024-05-23 11:51:01 +02:00
defd34923a docs: fix updateScene storeAction default tsdoc & document types (#8048) 2024-05-22 13:40:23 +02:00
c540bd68aa feat: wrap long text when pasting (#8026)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-21 16:56:09 +02:00
eddbe55f50 fix: always re-generate index of defined moved elements (#8040) 2024-05-20 23:23:42 +02:00
2f9526da24 feat: upgrade to mermaid-to-excalidraw v1 🚀 (#8022)
* feat: upgrade to mermaid-to-excalidraw v1 🚀

* upgrade to v1
2024-05-20 11:19:38 +05:30
1b6e3fe05b feat: rerender canvas on focus (#8035) 2024-05-19 22:20:40 +02:00
afe52c89a7 fix: undo/redo when exiting view mode (#8024)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-19 15:54:52 +02:00
be4e127f6c fix: Two finger panning is slow (#7849)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-19 14:23:43 +02:00
ff0b4394b1 feat: add missing type="button" (#8030) 2024-05-18 08:36:08 +00:00
Hey
7d8b7fc14d fix: compatible safari layers button svg (#8020)
Co-authored-by: ysen <ysen.ge@hairobotics.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-15 15:22:05 +02:00
971b4d4ae6 feat: text wrapping (#7999)
* resize single elements from the side

* fix lint

* do not resize texts from the sides (for we want to wrap/unwrap)

* omit side handles for frames too

* upgrade types

* enable resizing from the sides for multiple elements as well

* fix lint

* maintain aspect ratio when elements are not of the same angle

* lint

* always resize proportionally for multiple elements

* increase side resizing padding

* code cleanup

* adaptive handles

* do not resize for linear elements with only two points

* prioritize point dragging over edge resizing

* lint

* allow free resizing for multiple elements at degree 0

* always resize from the sides

* reduce hit threshold

* make small multiple elements movable

* lint

* show side handles on touch screen and mobile devices

* differentiate touchscreens

* keep proportional with text in multi-element resizing

* update snapshot

* update multi elements resizing logic

* lint

* reduce side resizing padding

* bound texts do not scale in normal cases

* lint

* test sides for texts

* wrap text

* do not update text size when changing its alignment

* keep text wrapped/unwrapped when editing

* change wrapped size to auto size from context menu

* fix test

* lint

* increase min width for wrapped texts

* wrap wrapped text in container

* unwrap when binding text to container

* rename `wrapped` to `autoResize`

* fix lint

* revert: use `center` align when wrapping text in container

* update snaps

* fix lint

* simplify logic on autoResize

* lint and test

* snapshots

* remove unnecessary code

* snapshots

* fix: defaults not set correctly

* tests for wrapping texts when resized

* tests for text wrapping when edited

* fix autoResize refactor

* include autoResize flag check

* refactor

* feat: rename action label & change contextmenu position

* fix: update version on `autoResize` action

* fix infinite loop when editing text in a container

* simplify

* always maintain `width` if `!autoResize`

* maintain `x` if `!autoResize`

* maintain `y` pos after fontSize change if `!autoResize`

* refactor

* when editing, do not wrap text in textWysiwyg

* simplify text editor

* make test more readable

* comment

* rename action to match file name

* revert function signature change

* only update  in app

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-15 21:04:53 +08:00
cc4c51996c build: specify packageManager field (#8010) 2024-05-14 10:45:27 +02:00
79257a1923 fix: correctly resolve the package version (#8016)
The property name is `VITE_PKG_VERSION` (not `PKG_VERSION`)

Resolves #7984
2024-05-14 13:31:02 +05:30
dc66261c19 fix: re-introduce wysiwyg width offset (#8014) 2024-05-13 17:38:21 +02:00
273ba803d9 fix: font not rendered correctly on init (#8002) 2024-05-10 16:37:46 +02:00
301e83805d feat: add install-PWA to command palette (#7935) 2024-05-08 22:02:28 +02:00
ed5ce8d3de fix: command palette filter (#7981) 2024-05-08 17:56:05 +02:00
1ed53b153c build: enable consistent type imports eslint rule (#7992)
* build: enable consistent type imports eslint rule

* change to warn

* fix the warning in example and excalidraw-app

* fix packages

* enable type annotations and throw error for the rule
2024-05-08 14:21:50 +05:30
c1926f33bb fix: remove unused param from drawImagePlaceholder (#7991) 2024-05-07 20:22:51 +05:30
6539029d2a fix: docker build of Excalidraw app (#7430)
* fix: docker build of Excalidraw app

Fixes #7403.

* deps: update (container) Nginx to 1.24

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2024-05-07 18:00:58 +05:30
d1f37cc64f feat: tweak a few icons & add line editor button to side panel (#7990) 2024-05-07 13:18:39 +02:00
f0d25e34c3 chore: Add lcov coverage output to vitest (#7987)
chore: Add lcov coverage output to vitest so VSCode coverage gutters extension works

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-05-06 17:35:23 +02:00
d9bbf1eda6 feat: Allow binding only via linear element ends (#7946)
Arrows now only bind to new shapes if their start or end point is dragged close to them. Arrows previously bound to shapes remain bound on move and drag if at the end of the drag/move the points remain in the original shapes' binding area.

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Sammy Lee <sammy.joe.lee@gmail.com>
2024-05-02 08:32:12 +02:00
f79fb9aae2 chore: Bump vitest to 1.5.3 to support VSCode vitest Extension (#7968)
Bump vitest to 1.5.3 to support VSCode vitest Extension

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-05-01 20:47:52 +02:00
275f6fbe24 fix: typo in doc api (#7466) 2024-04-30 16:52:42 +00:00
88812e0386 feat: resize elements from the sides (#7855)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-30 18:05:00 +02:00
6e5aeb112d feat: record freedraw tool selection to history (#7949) 2024-04-25 17:24:05 +00:00
4d83d1c91e fix: use Reflect API instead of Object.hasOwn (#7958) 2024-04-25 15:36:26 +02:00
a04676d423 fix: CTRL/CMD & arrow point drag unbinds both sides (#6459) (#7877) 2024-04-23 00:01:05 +02:00
c851aaaf7b fix: z-index for laser pointer to be able to draw on embeds and such (#7918) 2024-04-22 23:53:55 +02:00
1bd2b1fe55 feat: export reconciliation (#7917) 2024-04-22 17:27:57 +02:00
015b46ab23 feat: expose StoreAction in relation to multiplayer history (#7898)
Improved Store API and improved handling of actions to eliminate potential concurrency issues
2024-04-22 09:22:25 +00:00
530617be90 feat: multiplayer undo / redo (#7348) 2024-04-17 14:01:24 +02:00
5211b003b8 fix: double text rendering on edit (#7904) 2024-04-17 13:48:04 +02:00
bbcca06b94 fix: collision regressions from vector geometry rewrite (#7902) 2024-04-17 13:31:12 +02:00
f92f04c13c fix: Correct unit from 'eg' to 'deg' (#7891) 2024-04-15 11:11:27 +02:00
890ed9f31f feat: add "toggle grid" to command palette (#7887) 2024-04-13 19:12:29 +02:00
da2e507298 fix: allow same origin for all necessary domains (#7889) 2024-04-13 18:51:30 +02:00
f59b4f6af4 fix: always make sure we render bound text above containers (#7880) 2024-04-12 21:50:02 +02:00
afcde542f9 fix: parse embeddable srcdoc urls strictly (#7884) 2024-04-12 20:51:17 +02:00
4689a6b300 fix: hit test for closed sharp curves (#7881) 2024-04-12 12:58:51 +02:00
0ae9b383d6 fix: Gist embed allowing unsafe html (#7883) 2024-04-12 12:57:43 +02:00
f597bd3e01 fix: command palette tweaks and fixes (#7876) 2024-04-11 11:39:19 +02:00
4987cc53d0 fix: include borders when testing insides of a shape (#7865) 2024-04-09 16:07:36 +02:00
d917db438e fix: external link not opening (#7859) 2024-04-09 16:06:49 +02:00
a33a400f01 fix: add safe check for arrow points length in tranformToExcalidrawElements (#7863)
* fix: add safe check for arrow points length in tranformToExcalidrawElements

* add spec

* throw error only for dev mode

* fix lint
2024-04-09 09:56:21 +05:30
8a162a4cb4 fix: import (#7869) 2024-04-08 16:59:03 +02:00
c6a045d092 fix: theme toggle shortcut event.code (#7868) 2024-04-08 16:55:33 +02:00
cd50aa719f feat: add system mode to the theme selector (#7853)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-08 16:46:24 +02:00
92bc08207c fix: remove incorrect check from index.html (#7867) 2024-04-08 16:42:00 +02:00
32df5502ae feat: fractional indexing (#7359)
* Introducing fractional indices as part of `element.index`

* Ensuring invalid fractional indices are always synchronized with the array order

* Simplifying reconciliation based on the fractional indices

* Moving reconciliation inside the `@excalidraw/excalidraw` package

---------

Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-04 13:51:11 +01:00
bbdcd30a73 refactor: update collision from ga to vector geometry (#7636)
* new collision api

* isPointOnShape

* removed redundant code

* new collision methods in app

* curve shape takes starting point

* clean up geometry

* curve rotation

* freedraw

* inside curve

* improve ellipse inside check

* ellipse distance func

* curve inside

* include frame name bounds

* replace previous private methods for getting elements at x,y

* arrow bound text hit detection

* keep iframes on top

* remove dependence on old collision methods from app

* remove old collision functions

* move some hit functions outside of app

* code refactor

* type

* text collision from inside

* fix context menu test

* highest z-index collision

* fix 1px away binding test

* strictly less

* remove unused imports

* lint

* 'ignore' resize flipping test

* more lint fix

* skip 'flips while resizing' test

* more test

* fix merge errors

* fix selection in resize test

* added a bit more comment

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-04 16:31:23 +08:00
3e334a67ed feat: show firefox-compatible command palette shortcut alias (#7825) 2024-03-28 18:12:54 +01:00
1d71f84515 fix: stop using lookbehind for backwards compat (#7824) 2024-03-28 17:32:38 +01:00
550a388b2b feat: command palette (#7804)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-03-28 16:16:32 +00:00
6b523563d8 fix: ejs support in html files (#7822) 2024-03-28 14:58:47 +01:00
65bc500598 fix: excalidrawAPI.toggleSidebar not switching between tabs correctly (#7821) 2024-03-28 14:52:23 +01:00
7949aa1f1c feat: upgrade mermaid-to-excalidraw to 0.3.0 (#7819) 2024-03-28 16:44:29 +05:30
15bfa626b4 feat: support to not render remote cursor & username (#7130) 2024-03-18 10:41:06 +01:00
068895db0e feat: expose more collaborator status icons (#7777) 2024-03-18 10:20:07 +01:00
b7babe554b feat: load old library if migration fails 2024-03-11 09:57:01 +01:00
6a385d6663 feat: change LibraryPersistenceAdapter load() source -> priority
to clarify the semantics
2024-03-11 09:40:51 +01:00
2382fad4f6 feat: store library to IndexedDB & support storage adapters (#7655) 2024-03-08 22:29:19 +01:00
480572f893 fix: correcting Assistant metrics (#7758)
* Changed Assistant metrics to the corrrect ones from OS/2 table

* Adding more information about font metrics

* Adding branded types to avoid future mistakes
2024-03-07 16:54:36 +01:00
68b1fdb20e fix: add missing font metrics for Assistant (#7752) 2024-03-06 10:53:37 +01:00
a38e82f999 feat: close dropdown on escape (#7750) 2024-03-05 23:22:34 +01:00
a07f6e9e3a feat: show ai badge for discovery (#7749) 2024-03-05 23:22:25 +01:00
7e471b55eb feat: text measurements based on font metrics (#7693)
* Introduced vertical offset based on harcoded font metrics 

* Unified usage of alphabetic baseline for both canvas & svg export

* Removed baseline property

* Removed font-size rounding on Safari

* Removed artificial width offset
2024-03-05 19:33:27 +00:00
160440b860 feat: improve collab error notification (#7741)
* identify cause

* toast after dialog for error messages in collab

* remove comment

* shake tooltip instead for repeating collab errors

* clear collab error

* empty commit

* simplify & fix reset race condition

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-03-04 20:43:44 +08:00
f207bd0a1c build: export types for @excalidraw/utils (#7736)
* build: export types for @excalidraw/utils

* fix

* add types
2024-02-29 15:43:04 +05:30
99601baffc build: create ESM build for utils package 🥳 (#7500)
* build: create ESM build for utils package

* add deps, exports and import.meta
2024-02-28 19:33:47 +05:30
af1a3d5b76 fix: export utils from excalidraw package in excalidraw library (#7731)
* fix: export utils from excalidraw package in excalidraw library

* don't export utils utilities

* fix import path

* fix export

* don't export export utilites

* fix export paths

* reexport utils from excalidraw package

* add exports from withinBounds

* fix path
2024-02-28 11:14:57 +05:30
36e56267c9 docs: add missing closing angle bracket in integration.mdx (#7729)
Update integration.mdx: Fix missing closing angle bracket in code sample

 A closing angle bracket was missing in a code sample.

Original code:
<div style={{height:"500px", width:"500px"}}
    <Excalidraw />
</div>

Changes:

<div style={{height:"500px", width:"500px"}}>
    <Excalidraw />
</div>
2024-02-27 07:19:20 +00:00
b09b5cb5f4 fix: split renderScene so that locales aren't imported unnecessarily (#7718)
* fix: split renderScene so that locales aren't imported unnecessarily

* lint

* split export code

* rename renderScene to helpers.ts

* add helpers

* fix typo

* fixes

* move renderElementToSvg to export

* lint

* rename export to staticSvgScene

* fix
2024-02-27 10:37:44 +05:30
dd8529743a docs: type mistake in integration of excalidraw (#7723) 2024-02-26 10:24:27 +01:00
f639d44a95 fix: remove dependency of t in blob.ts (#7717)
* remove dependency of t in blob.ts

* fix
2024-02-23 15:05:46 +05:30
f5ab3e4e12 fix: remove dependency of t from clipboard and image (#7712)
* fix: remove dependency of t from clipboard and image

* pass errorMessage to copyTextToSystemClipboard where needed

* wrap copyTextToSystemClipboard and rethrow translated error in caller

* review fix

* typo
2024-02-21 19:45:33 +05:30
361a9449bb fix: remove scene hack from export.ts & remove pass elementsMap to getContainingFrame (#7713)
* fix: remove scene hack from export.ts as its not needed anymore

* remove

* pass elementsMap to getContainingFrame

* remove unused `mapElementIds` param

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-21 16:34:20 +05:30
2e719ff671 fix: decouple pure functions from hyperlink to prevent mermaid bundling (#7710)
* move hyperlink code into its folder

* move pure js functions to hyperlink/helpers and move actionLink to actions

* fix tests

* fix
2024-02-20 20:59:01 +05:30
79d9dc2f8f fix: make bounds independent of scene (#7679)
* fix: make bounds independent of scene

* pass only elements to getCommonBounds

* lint

* pass elementsMap to getVisibleAndNonSelectedElements
2024-02-19 19:39:14 +05:30
9013c84524 fix: make LinearElementEditor independent of scene (#7670)
* fix: make LinearElementEditor independent of scene

* more fixes

* pass elements and elementsMap to maybeBindBindableElement,getHoveredElementForBinding,bindingBorderTest,getElligibleElementsForBindableElementAndWhere,isLinearElementEligibleForNewBindingByBindable

* replace `ElementsMap` with `NonDeletedSceneElementsMap` & remove unused params

* fix lint

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-19 11:49:01 +05:30
47f87f4ecb fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap (#7663)
* fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap

* lint

* fix

* use non deleted elements where possible

* use non deleted elements map in actions

* pass elementsMap instead of array to elementOverlapsWithFrame

* lint

* fix

* pass elementsMap to getElementsCorners

* pass elementsMap to getEligibleElementsForBinding

* pass elementsMap in bindOrUnbindSelectedElements and unbindLinearElements

* pass elementsMap in elementsAreInFrameBounds,elementOverlapsWithFrame,isCursorInFrame,getElementsInResizingFrame

* pass elementsMap in getElementsWithinSelection, getElementsCompletelyInFrame, isElementContainingFrame, getElementsInNewFrame

* pass elementsMap to getElementWithTransformHandleType

* pass elementsMap to getVisibleGaps, getMaximumGroups,getReferenceSnapPoints,snapDraggedElements

* lint

* pass elementsMap to bindTextToShapeAfterDuplication,bindLinearElementToElement,getTextBindableContainerAtPosition

* revert changes for bindTextToShapeAfterDuplication
2024-02-16 11:35:01 +05:30
73bf50e8a8 fix: remove t from getDefaultAppState and allow name to be nullable (#7666)
* fix: remove t and allow name to be nullable

* pass name as required prop

* remove Unnamed

* pass name to excalidrawPlus as well for better type safe

* render once we have excalidrawAPI to avoid defaulting

* rename `getAppName` -> `getName` (temporary)

* stop preventing editing filenames when `props.name` supplied

* keep `name` as optional param for export functions

* keep `appState.name` on `props.name` state separate

* fix lint

* assertive first

* fix lint

* Add TODO

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-15 11:11:18 +05:30
48c3465b19 docs: release patch v0.17.3 (#7673)
* docs: release patch v0.17.3

* update cl
2024-02-09 19:29:50 +05:30
adc4c9f484 fix: prevent panning to trigger history on macos chrome (#7671) 2024-02-08 19:50:50 +01:00
def1df2c68 fix: keep customData when converting to ExcalidrawElement (#7656)
* feat: keep customData when converting to ExcalidrawElement (#7654)

* docs: add changelog for keeping customData when converting to ExcalidrawElement
2024-02-08 17:23:10 +05:30
0513b647ec feat: change collab trigger & add share dialog (#7647) 2024-02-03 14:04:23 +00:00
a289c42830 feat: add loading state to FilledButton (#7650) 2024-02-03 14:53:31 +01:00
d67eaa8710 fix: file save timing out with big file sizes (#7649) 2024-02-03 11:53:35 +01:00
0c3dffb082 fix: make getEmbedLink independent of t function (#7643)
* fix: make getEmbedLink independent of t function

* rename warning to error and make it type safe
2024-02-01 21:12:10 +05:30
0e0f34edd8 fix: follow mode border for hosts apps (#7642) 2024-02-01 15:03:15 +01:00
4888d9d355 chore: change default port of collab server (#7641) 2024-02-01 14:41:38 +01:00
1c39bd5781 fix: don't bundle react and jotai when importing from scene (#7640)
* don't bundle react and jotai when importing from scene

* fix
2024-02-01 18:24:17 +05:30
90ad885446 feat: support onPointerUp prop (#7638)
* feat: support onPointerUp prop

* update changelog

* Update packages/excalidraw/CHANGELOG.md

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-02-01 12:26:55 +00:00
1741c234a6 fix: decouple container cache logic to containerCache. (#7637) 2024-01-31 21:17:41 +05:30
63b50b3586 fix: don't bundle react-dom when importing from transformHandles (#7634)
* fix: don't bundle react when importing from transfromHandles

* rename to DEFAULT_TRANSFORM_HANDLE_SPACING
2024-01-31 16:50:35 +05:30
e0fefa8025 fix: don't bundle react-dom when importing from element (#7635) 2024-01-31 16:43:37 +05:30
d426cc968d refactor: remove portal as it is no longer needed (#7623)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 16:37:09 +01:00
2409c091ff feat: support roundness for images (#7558)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 15:27:07 +01:00
626fe252ab fix: frame name field (#7457)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 10:57:22 +01:00
10bd08ef19 fix: make getBoundTextElement and related helpers pure (#7601)
* fix: make getBoundTextElement pure

* updating args

* fix

* pass boundTextElement to getBoundTextMaxWidth

* fix labelled arrows

* lint

* pass elementsMap to removeElementsFromFrame

* pass elementsMap to getMaximumGroups, alignElements and distributeElements

* lint

* pass allElementsMap to renderElement

* lint

* feat: make more typesafe

* fix: remove unnecessary assertion

* fix: remove unused params

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-26 11:29:07 +05:30
2789d08154 docs: update the docs for next js integration (#7605)
* docs: update the docs for next js integration

* update

* update

* update docs with tabbed examples

* fix
2024-01-25 20:26:48 +05:30
678bb2b819 build(deps-dev): bump vite from 5.0.6 to 5.0.12 (#7586)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 19:29:50 +05:30
966f9aead9 build(deps-dev): bump vite from 5.0.6 to 5.0.12 in /examples/excalidraw/with-script-in-browser (#7603)
build(deps-dev): bump vite

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 19:28:11 +05:30
4f0a2a9593 docs: add next js with app router example (#7552)
* move the existing example to with-script-in-browser

* Add example with next js app router

* disable ssr for excalidraw client comp

* typo

* update output dir

* don't include nextjs example in tsconfig

* remove meta.json

* lint

* remove example.ts

* port

* move the examples outside packages and use the deps as workspaces in examples

* update gitignore

* fix example

* update path of build dir

* fix

* fix scripts

* try local path

* fix

* update commands

* fix

* fix

* fix script

* skip ts

* disable ts

* add vercel.json

* install

* update tsconfig

* fix lint

* remove console.log

* lets see if this works

* revert

* remove ts nocheck

* add types and some utils in nextjs example

* fix types

* updatw example and remove nextjs dynamic syntax so we don't import excal twice

* move both examples to workspaces and create generic example to be used by browser and next js both

* copy the static assets to nextjs

* fix ts config

* render custom menu items

* fix custom footer

* fix types in browser example

* use regular imports for importing excal and import it using dynamic next js in app router instead

* Add example for pages router

* fix css discrepancies

* fix css

* configure output dir

* fix

* fix css

* rename to with-nextjs

* move components to examples/excalidraw/components
2024-01-24 17:07:54 +05:30
f3f8217125 docs: toggleSidebar api fix (#7575) 2024-01-23 14:50:51 +00:00
89bd6181f2 fix: revert mapElementIds flag removal (#7594) 2024-01-22 17:23:00 +01:00
c6fdac131b ci: add the workspace ignore check to install actions as dependency for auto release (#7593) 2024-01-22 17:01:00 +05:30
0415c616b1 refactor: decoupling global Scene state part-1 (#7577) 2024-01-22 00:23:02 +01:00
740a165452 fix: filter out elements not overlapping frame on paste (#7591) 2024-01-21 20:55:57 +01:00
4997624a3a fix: frame name editing inconvenience (#7437) 2024-01-21 20:55:28 +01:00
b66daae1f3 fix: Truncate collaborator name in dropdown. (#7576) 2024-01-21 20:36:09 +01:00
1e7df58b5b feat: add pasted elements to frame under cursor (#7590) 2024-01-21 14:01:43 +01:00
46da032626 fix: exporting frame-overlapping elements belonging to other frames (#7584) 2024-01-19 14:41:22 +01:00
3b0593baa7 fix: Prevent the library label from being collapsed (#7579) 2024-01-19 14:41:08 +01:00
dd530737a2 docs: fix "canvas actions" link in Props page (#7536)
fix "canvas actions" link in Props page
2024-01-17 16:19:42 +05:30
a4e5e46dd1 fix: move default to last so its compatible with nextjs (#7561) 2024-01-15 14:52:04 +05:30
0fa5f5de4c fix: translating frames containing grouped text containers (#7557) 2024-01-13 21:28:54 +01:00
41cc746885 fix: host font assets from root (#7548) 2024-01-11 21:29:29 +01:00
8ead8559e0 feat: redirect font requests to cdn (#7549) 2024-01-11 21:08:17 +01:00
5245276409 feat: erase groups atomically (#7545) 2024-01-11 17:43:04 +01:00
0c24a7042f feat: remove ExcalidrawEmbeddableElement.validated flag (#7539) 2024-01-11 17:42:51 +01:00
Are
86cfeb714c feat: add eraser tool trail (#7511)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-11 16:10:15 +00:00
872973f145 fix: do not modify elements while erasing (#7531) 2024-01-11 16:00:07 +01:00
3ecf72a507 docs: add changelog for ESM build (#7542)
* docs: add changelog for ESM build

* move to breaking change
2024-01-11 16:40:45 +05:30
1aaa400876 docs: fix extra space in UIOptions/tools (#7537)
fix typo in UIOptions/tools
2024-01-11 11:09:33 +00:00
65047cc2cb fix: decouple react and react-dom imports from utils and make it treeshakeable (#7527)
fix: decouple react and react-dom imports from utils and make it tree-shakeable
2024-01-08 21:01:47 +05:30
8b993d409e feat: render embeds lazily (#7519) 2024-01-04 19:03:04 +01:00
1cb350b2aa feat: update X brand logo & tweak labels (#7518) 2024-01-04 14:57:31 +01:00
43ccc875fb feat: support multi-embed pasting & x.com domain (#7516) 2024-01-04 13:27:52 +01:00
4249b7dec8 chore: add version for excalidraw-app workspace (#7514) 2024-01-04 13:53:19 +05:30
49f15c736b chore: remove unused files (#7509)
chore remove unused files
2024-01-03 16:25:36 +05:30
a8064ba3ee build: Welcome ESM and Bye Bye UMD (#7441)
* build: Welcome ESM and Bye Bye UMD

* remove package

* create unbundled esm build

* update script for example

* fix typo

* dummy commit

* update autorelease script to build esm

* revert dummy commit

* move react, react-dom and testing library to dev dependencies

* remove entry.js, publicPath and yarn install:deps script

* fix

* upgrade esbuild to fix glob import error for locales

* remove webpack chunk names as thats not needed anymore

* marking the code sideeffects free

* make the library tree-shakeable and move fonts to fonts directory

* allow side effects for css, scss files

* remove tree-shaking

* comment code for tree shaking

* move to vite for example

* bye bye webpack

* ignore ts

* separate build and output dir

* use esbuild for creating bundle for example

* update output dir

* lint

* create browser dev build with source maps and prod with minification

* add dev and prod builds for bundler

* lint

* update script

* remove await

* load prod build

* create minified build in dist

* prod and dev builds using export field

* remove import.meta

* dummy

* define import.meta prod and dev

* fix

* export types

* add types field

* typo

* lint

* Update scripts/buildPackage.js

* move types inside export

* newline
2024-01-01 20:18:44 +05:30
e6c3c06c2e feat: support pen erasing (#7496) 2024-01-01 13:27:03 +01:00
d19b51d4f8 fix: drawing-tablet stylus touch events being prevented (#7494) 2023-12-30 15:00:12 +01:00
c72e853c85 refactor: editor events sub/unsub refactor (#7483) 2023-12-30 11:12:38 +01:00
5f40a4cad4 fix: missing cross-env from build:umd in package.json (#7460) 2023-12-19 00:02:03 +01:00
d91c98b82e fix: incorrect types in ActionNavigate (#7462) 2023-12-18 21:14:30 +01:00
57ea4e61d1 fix: mixing clientId & socketId in UserList (#7461) 2023-12-18 18:21:57 +01:00
0808532b49 fix: follow mode collaborator status indicator (#7459) 2023-12-18 16:14:25 +01:00
2a0fe2584e fix: empty snapLines arrays would cause re-render (#7454)
Co-authored-by: Lynda Lin <lynda.lin@optoma.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-18 12:42:17 +00:00
7bd6496854 refactor: Fix Typo (#7445) 2023-12-16 18:23:11 +00:00
537f6e7f68 docs: add steps for local development (#7449)
* docs: add steps for local development #7434

* docs: minor tweaks

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-16 19:18:35 +01:00
6dfa89e846 fix: emitted visible scene bounds not accounting for offsets (#7450) 2023-12-16 17:32:54 +01:00
561e919a2e fix: import Socket as type (#7446) 2023-12-16 11:15:04 +01:00
20e3acf7a6 feat: bump socket.io-client & collab tweaks (#7444) 2023-12-16 00:23:59 +01:00
2c0929e537 fix: follow-mode tweaks (#7443) 2023-12-15 15:16:25 +01:00
aad8ab0123 feat: follow mode (#6848)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-15 00:07:11 +01:00
88a2b286c7 feat: move utils to utils package and make @excalidraw/utils a workspace (#7432)
* feat: move utils to utils package and make @excalidraw/utils a workspace

* remove esm and update types path

* remove esm script

* fix package.json and yarn.lock

* update path

* fix

* fix lint and test
2023-12-13 21:51:27 +05:30
b635b10b59 chore: Update translations from Crowdin (#7176)
* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Arabic)

* New translations en.json (Thai)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* New translations en.json (Catalan)

* Auto commit: Calculate translation coverage

* New translations en.json (Basque)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Arabic)

* New translations en.json (Thai)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Danish)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* New translations en.json (Slovak)

* Auto commit: Calculate translation coverage

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Swedish)

* New translations en.json (Romanian)

* remove packages

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-12-12 16:56:22 +05:30
7ebda02b81 fix: update path of files in docs (#7428)
* fix: update path of files in docs

* remove root yarn install in size limit

* add vercel.json in dev-docs

* update config

* fix lint

* indent

* revert changelog and readme
2023-12-12 14:28:34 +05:30
d6cd8b78f1 build: decouple package deps and introduce yarn workspaces (#7415)
* feat: decouple package deps and introduce yarn workspaces

* update root directory

* fix

* fix scripts

* fix lint

* update path in scripts

* remove yarn.lock files from packages

* ignore workspace

* dummy

* dummy

* remove comment check

* revert workflow changes

* ignore ws when installing gh actions

* remove log

* update path

* fix

* fix typo
2023-12-12 11:32:51 +05:30
b7d7ccc929 fix: env variable for text-to-diagram and use frozen lock file when install deps in excalidraw-app (#7409) 2023-12-07 19:32:19 +05:30
f14ad61bd0 build: move build process and excalidraw-app dependencies in its own package.json (#7021)
* build: move build process and excalidraw-app dependencies in its own package.json

* fix

* fix public path

* move bug-issue-template to excalidraw-app

* make env vars accessible in excalidraw app

* update build script

* install when building

* add ts ignore

* fix build-version script

* update config in vercel.json

* add vercel config for example

* fix vercel config

* update install script in vercel

* update install script in lint.yml

* update install script in test workflows

* push locales to locales folder pwa

* add favicons to manifest

* move react to peer deps in editor

* fix ts

* Enable vite intellisense

* add global.d.ts for excalidraw-app

* remove console.log

* remove react, react-dom and vite from excalidraw-app deps

* increase size limit
2023-12-07 16:39:11 +05:30
8963baf5ad chore: upgrade to vite 5.x and vitest 1.x (#7407)
* chore: upgrade to vite 5.x and vitest 1.x

* fix coverage

* move to ESM for vite config

* use ESM for vitest
2023-12-07 15:30:08 +05:30
557add5bf7 feat: Support Mermaid Class diagrams 🥳 (#7381)
* feat: support mermaid class diagrams

* upgrade mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* add sequence diagrams in supported chart types

* upgrade mermaid-to-excalidraw

* update i18n
2023-12-06 21:31:54 +05:30
b9cfbc2077 feat: add support for more UML arrowheads (#7391) 2023-12-06 16:00:00 +01:00
a04cc707c3 build(deps-dev): bump vite from 4.4.2 to 4.4.12 (#7393)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.2 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 15:31:44 +05:30
72ea8022bf docs: changelog instruction removed from docs (#7395)
changelog instruction removed from docs
2023-12-06 11:36:39 +05:30
4bdeaf999b feat: TTD dialog UI tweaks (#7384) 2023-12-04 17:50:30 +01:00
42d8c5a040 chore: update changelog and package.json for v0.17.1 (#7351) 2023-11-28 19:12:39 +05:30
f299514e44 fix: umd build so it can be used in browser (#7349)
* fix: umd build so it can be used in browser

* fix lint

* increase size limit

* update changelog

* use json.stringify for env preact variable so its accessible as string

* update changelog
2023-11-28 18:11:16 +05:30
dd220bcaea feat: TTD dialog tweaks (#7346)
* tweaks to TTD dialog ~ prepping for settings dialog

* tweaks to ttd parsing & error logging
2023-11-27 16:03:03 +01:00
fe75f29c15 fix: disable caching bounds for arrow labels (#7343) 2023-11-25 23:32:05 +01:00
14845a343b feat: text-to-diagram (#7325)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-25 21:11:03 +00:00
dd8a7d41e2 fix: bounds cached prematurely resulting in incorrectly rendered labels (#7339) 2023-11-24 19:55:56 +01:00
fda5c6fdf7 fix: t2c settings dialog spacing for apps that use CSS resets (#7337) 2023-11-24 16:32:00 +01:00
3d1631f375 feat: d2c tweaks (#7336) 2023-11-24 14:02:11 +01:00
c7ee46e7f8 feat: wireframe-to-code (#7334) 2023-11-23 23:07:53 +01:00
d1e4421823 feat: Expose ActionManager.registerAction through ExcalidrawImperativeAPI (#6995)
* feat: Expose `ActionManager` through `ExcalidrawImperativeAPI`

* Only expose `registerAction` instead of `ActionManager`
2023-11-22 15:22:49 -06:00
7c9cf30909 fix: make zoomToFit fitToViewport account for sidebar (#7298) 2023-11-17 15:56:19 +01:00
1e37dbd60e feat: change frame resizing behavior (#7307) 2023-11-17 14:37:43 +01:00
f8d5c2a1b6 build: allow a range of major node versions (#7306) 2023-11-17 14:23:19 +01:00
23b24ea5c3 build: use caret for specifying node version to avoid major upgrades automatically (#7297) 2023-11-16 16:18:38 +05:30
a528769b68 docs: upgrade to @excalidraw/excalidraw@0.17.0 (#7285) 2023-11-14 20:10:19 +05:30
ddb7585057 docs: Docs for v0.17.0 🚀 (#7248)
* feat: add docs for getCommonBounds

* docs: add docs for frames api support

* docs: update docs for regenerateIds opts in convertToExcalidrawElements

* add docs for ref removal

* add docs for lock support and insertOnCanvasDirectly in setActiveTool

* fix broken links

* update docs for next js support

* update docs for Preact

* add faq

* docs: add `onChange`, `onPointerDown`, `onPointerUp` docs

* docs: update `useDevice` docs

* update docs for disabling image tool

* add docs for withinBounds helpers

* fix lint

* upgrade excal

* add docusaurus2-dotenv for expose env vars

* fix env variable and upgrade excal

* Update dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

* update docs

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

* update docs for process.env

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-14 19:54:34 +05:30
111a48ffb1 docs: release @excalidraw/excalidraw@0.17.0 🎉 (#7284) 2023-11-14 19:53:59 +05:30
54153629c0 chore: update release scripts (#7282)
* chore: update release scripts

* update docs
2023-11-14 16:37:57 +05:30
9c425224c7 feat: support disabling image tool (#6320)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-11-14 10:25:41 +01:00
9d1d45a8ea chore: update changelog (#7279)
* chore: update changelog

* fix

* Update CHANGELOG.md
2023-11-14 13:11:05 +05:30
029c3c48ba fix: image insertion bugs (#7278) 2023-11-13 15:34:59 +01:00
adfd95be33 build: support preact 🥳 (#7255)
* build: support preact

* add log

* Simplify the config and generate prod and dev builds for preact

* update changelog

* remove logs

* use env variable so its available during build time

* update cl

* fix
2023-11-13 16:18:36 +05:30
ceb255e8ee fix: exportToSvg to honor frameRendering also for name not only for frame itself (#7270)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-12 22:34:05 +00:00
ae5b9a4ffd fix: not cloning elements on export polluting Scene mapping (#7276) 2023-11-12 23:32:12 +01:00
3d4ff59f40 fix: Can't toggle penMode off due to missing typecheck in togglePenMode (#7273)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-12 13:24:13 +01:00
7b00089314 chore: bump @excalidraw/random-username (#7272) 2023-11-11 19:23:22 +01:00
af6b81df40 fix: Replace hard coded font family with const value in addFrameLabelsAsTextElements (#7269) 2023-11-11 10:04:02 +01:00
02cc8440c4 feat: allow D&D dice app domain for embeds (#7263)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2023-11-10 15:29:19 +00:00
6363492cee fix: perf issue when ungrouping elements within frame (#7265)
Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
2023-11-10 16:13:08 +01:00
900b317bf3 feat: remove full screen shortcut (#7222) 2023-11-10 14:44:02 +00:00
68179356e6 fix: Fixes the shortcut collision between "toggleHandTool" and "distributeHorizontally" (#7189)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-10 15:33:02 +01:00
3ed15e95da Small typo fix frames.mdx (#7216) 2023-11-10 15:23:43 +01:00
798e1fd858 fix: allow pointer events when editing a linear element (#7238) 2023-11-10 15:21:59 +01:00
f66c93633c feat: make adaptive-roughness less aggressive (#7250) 2023-11-10 13:32:34 +01:00
cee00767df feat: support excalidrawAPI and remove refs support (#7251)
* feat: support excalidrawAPI and remove refs support

* update changelog

* remove ready and readyPromise

* update changelog

* update changelog
2023-11-10 15:33:43 +05:30
864c0b3ea8 feat: render frames on export (#7210) 2023-11-09 17:00:21 +01:00
a9a6f8eafb docs: update the docs with next js dynamic import support (#7252) 2023-11-09 16:03:35 +05:30
3c96943db3 test: fix mermaid test flake (#7249) 2023-11-07 18:06:15 +01:00
9006caff39 fix: make modal use viewport breakpoints (#7246) 2023-11-07 10:10:12 +01:00
ce7a847668 feat: export getCommonBounds util (#7247)
* feat: export getCommonBounds util

* add pr link

* fix
2023-11-07 14:19:13 +05:30
b1037b342d feat: make device breakpoints more specific (#7243) 2023-11-06 16:29:00 +01:00
18a7b97515 chore: Fix typo in comment in LocalData file (#7235) 2023-11-04 18:15:09 +01:00
e8def8da8d feat: Support mermaid flowchart and sequence diagrams to excalidraw diagrams 🥳 (#6920)
* feat: integrate mermaidToExcalidraw

* create mermaid to excal dialog

* allow mermaid syntax and export in preview

* fix

* fix webpack config

* fix markdown error by using named export

* center preview

* set elements as selected when inserted onto canvas

* persist mermaid data to storage

* store canvas data in refs

* load mermaid lazily

* tweak design

* compute width, height correctly for arrows

* fix undefined vertex issue

* add mermaid icon in dropdown

* add a note in dialog

* reset preview when error

* show error in preview when error

* show mermaid error messgae react way

* design tweaks

* add example and docs link

* fix

* tweak design to remove scroll bar

* show a spinner unless mermaid loaded

* regenerate ids when needed via programmatic api, this makes sure for mermaid diagrams ids are regenerated

* tweak

* add option to transform viewport to scene coords in transform api

* make opts optional and use 100% zoom when inserting to canvas

* fix arrow bindings in safari and firefox

* fix elements insert position and viewport centering

* fix: Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.

* defer rendering the preview

* tweak text

* fix tests

* remove only

* make design responsive

* fix: show extra tools dropdown in mobile

* fix mobile css

* width auto

* upgrade mermaid-to-excalidraw

* don't pass appState in deps as its not used

* upgrade mermaid-to-excalidraw to fix firefox issue

* use types from mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* use stable version of mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* fix width of shapes toolbar for smaller screen size and also fix regression of mobile menu

* use i18n

* better api

* enable test coverage in ui

* Add tests

* use common utils to update and get text editor

* updgrade mermaid-to-excalidraw to support sequence diagrams

* fix test

* don't update arrow container height anytime in when redrawing text bounding box

* increase size limit

* increase size limit of vendor to 900kb

* use openDialog for mermaid

* upgrade mermaid-to-excalidraw

* update frame id post generation

* upgrade mermaid-to-excalidraw to add entity codes support

* update size limit

* upgrade mermaid-to-excalidraw package with frame api changes

* upgrade mermaid-to-excalidraw to remove directive and use config

* don't highlight mermaid tool and remove unused api setSelection

* stop using loading state to update text area

* move some styling to scss

* review fixes

* use modifiedTableIcon props and remove stale snap

* css

* dialog css

* fix snap

* use dialog border

* change mermaidToExcalidrawLib to state

* better styling of errors

* make modal bigger

* fix mobile

* update snaps

* fix icon color

* fix dark mode insert button color

* horizontally center spinner

* render canvas conditionally on loaded state

* rd tweaks

* tweak class names

* remove max height

* typo in example

* upgrade mermaid-to-excalidraw

* simplify error state

* fix height & overflow on vertical breakpoint

* fix lint

* show errors in overlay

* set textarea font family

* reduce opacity

* update snap

* upgrade to mermaid  0.1.2

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-11-03 17:41:34 +05:30
a7db41c5ba fix: align input :hover/:focus with spec (#7225) 2023-11-02 16:06:26 +01:00
d8166d9e1d fix: dialog remounting on className updates (#7224) 2023-11-02 16:06:15 +01:00
81c0259041 docs: add npm downloads rate badge to README.md file (#7127) 2023-11-01 13:27:57 +01:00
f5c91c3a0f feat: support frames via programmatic API (#7205)
* update frame id post generation

* support frames via programmatic API

* fix types

* add test for frames

* throw error when element doesn't exist

* naming tweaks

* update the api to use children

* consider max of frame dimensions and calculated bounds of elements

* consider bound elements in frame api
2023-11-01 17:14:04 +05:30
9b8de8a12e test: disable flaky test (#7213) 2023-10-31 12:05:08 +01:00
ea677d4581 feat: make clipboard more robust and reintroduce contextmenu actions (#7198) 2023-10-28 19:29:28 +00:00
ec2de7205f fix: don't update label position when dragging labelled arrows (#6891)
* fix: don't update label position when dragging labelled arrows

* lint

* add test

* don't update coords for label when labelled arrow inside frame

* increase locales bundle size limit
2023-10-27 12:06:11 +05:30
Are
d5e3f436dc feat: add approximate elements in bbox detection (#6727)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-26 23:33:00 +02:00
dcf4592e79 feat: regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping (#7195)
* feat: regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping

* type

* increase limit as some past PR(s) increased the bundle size

* review fixes

* update changelog
2023-10-27 00:43:48 +05:30
d1f8eec174 feat: support giphy.com embed domain (#7192) 2023-10-26 00:00:50 +02:00
0f81c30276 fix: frame add/remove/z-index ordering changes (#7194) 2023-10-25 23:16:02 +02:00
f098789d16 fix: element relative position when dragging multiple elements on grid (#7107)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-25 22:48:03 +02:00
f794b0bb90 fix: freedraw non-solid bg hitbox not working (#7193) 2023-10-25 17:21:01 +02:00
104f64f1dc revert: remove bound-arrows from frames (#7190) 2023-10-25 10:39:19 +02:00
71ad3c5356 fix: Actions panel ux improvement (#6850)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-24 18:36:13 +00:00
afea0df141 feat: renderer tweaks (#6698) 2023-10-20 17:45:37 +02:00
d2a508104e fix: Better fill rendering with latest RoughJS (#7031)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-20 15:08:24 +02:00
3697618266 feat: support props.locked for setActiveTool (#7153)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-10-20 13:16:23 +02:00
e7cc2337ea feat: add onChange, onPointerDown, onPointerUp api subs (#7154) 2023-10-20 13:08:22 +02:00
9eb89f9960 build(deps): bump @babel/traverse from 7.18.9 to 7.23.2 in /dev-docs (#7165)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 11:31:27 +02:00
ab1bcc7615 chore: Update translations from Crowdin (#6695) 2023-10-20 11:29:28 +02:00
b1cac35269 feat: Closing of "Save to.." Dialog on Save To Disk (#7168)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 17:51:50 +00:00
83f86e2b86 fix: Fix for Strange Symbol Appearing on Canvas after Deleting Grouped Graphics (Issue #7116) (#7170)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 12:59:39 +02:00
7e38cab76e build(deps): bump @babel/traverse from 7.21.4 to 7.23.2 (#7171)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 12:33:16 +02:00
2cabb1f1f4 fix: attempt to fix flake in wysiwyg tests (#7173) 2023-10-19 12:32:31 +02:00
63650f82d1 feat: Added Copy/Paste from Google Docs (#7136)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 12:14:23 +02:00
dde3dac931 feat: remove bound-arrows from frames (#7157) 2023-10-17 18:18:20 +02:00
5b94cffc74 fix: ensure ClipboardItem created in the same tick to fix safari (#7066) 2023-10-16 11:38:57 +02:00
aaf73c8ff3 fix: double image dialog shown on insert (#7152) 2023-10-16 00:19:46 +02:00
44d9d5fcac fix: wysiwyg left in undefined state on reload (#7123) 2023-10-13 14:29:54 +02:00
89a3bbddb7 test: add more resizing tests (#7028)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-12 20:59:02 +02:00
b86184a849 fix: ensure relative z-index of elements added to frame is retained (#7134) 2023-10-12 15:00:23 +02:00
b552166924 feat: new dark mode theme & light theme tweaks (#7104)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-12 14:58:33 +02:00
26ff3993bb feat: better laser cursor for dark mode (#7132) 2023-10-11 11:17:27 +02:00
7ad02c359a fix: memoize static canvas on props.renderConfig (#7131) 2023-10-10 23:31:23 +02:00
2523fe82e3 feat: laser pointer improvements (#7128) 2023-10-10 13:55:55 +02:00
4ea079eb85 fix: regression from #6739 preventing redirect link in view mode (#7120)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-09 12:26:49 +02:00
f20ba90ffa perf: improve element in frame check (#7124) 2023-10-09 16:32:27 +08:00
03da9112cf fix: update links to excalidraw-app (#7072) 2023-10-08 19:37:17 -05:00
1093 changed files with 146676 additions and 60994 deletions

View File

@ -4,8 +4,16 @@
!.eslintrc.json
!.npmrc
!.prettierrc
!excalidraw-app/
!package.json
!public/
!src/
!packages/
!scripts/
!tsconfig.json
!yarn.lock
# keep (sub)sub directories at the end to exclude from explicit included
# e.g. ./packages/excalidraw/{dist,node_modules}
**/build
**/dist
**/node_modules

View File

@ -7,23 +7,20 @@ VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfu
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
VITE_APP_WS_SERVER_URL=http://localhost:3002
# set this only if using the collaboration workflow we use on excalidraw.com
VITE_APP_PORTAL_URL=
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
VITE_APP_PLUS_APP=http://localhost:3000
VITE_APP_AI_BACKEND=http://localhost:3015
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
# put these in your .env.local, or make sure you don't commit!
# must be lowercase `true` when turned on
#
# whether to enable Service Workers in development
VITE_APP_DEV_ENABLE_SW=
# whether to disable live reload / HMR. Usuaully what you want to do when
# debugging Service Workers.
VITE_APP_DEV_DISABLE_LIVE_RELOAD=
VITE_APP_DISABLE_TRACKING=true
VITE_APP_ENABLE_TRACKING=true
FAST_REFRESH=false
@ -40,3 +37,14 @@ VITE_APP_COLLAPSE_OVERLAY=true
# Set this flag to false to disable eslint
VITE_APP_ENABLE_ESLINT=true
# Enable PWA in dev server
VITE_APP_ENABLE_PWA=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2g5T+Rub6Kbf1Mf57t0
7r2zeHuVg4dla3r5ryXMswtzz6x767octl6oLThn33mQsPSy3GKglFZoCTXJR4ij
ba8SxB04sL/N8eRrKja7TFWjCVtRwTTfyy771NYYNFVJclkxHyE5qw4m27crHF1y
UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
HQIDAQAB'

View File

@ -4,15 +4,29 @@ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
VITE_APP_PORTAL_URL=https://portal.excalidraw.com
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
# Fill to set socket server URL used for collaboration.
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
VITE_APP_WS_SERVER_URL=
VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
# socket server URL used for collaboration
VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
VITE_APP_DISABLE_TRACKING=
VITE_APP_ENABLE_TRACKING=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApQ0jM9Qz8TdFLzcuAZZX
/WvuKSOJxiw6AR/ZcE3eFQWM/mbFdhQgyK8eHGkKQifKzH1xUZjCxyXcxW6ZO02t
kPOPxhz+nxUrIoWCD/V4NGmUA1lxwHuO21HN1gzKrN3xHg5EGjyouR9vibT9VDGF
gq6+4Ic/kJX+AD2MM7Yre2+FsOdysrmuW2Fu3ahuC1uQE7pOe1j0k7auNP0y1q53
PrB8Ts2LUpepWC1l7zIXFm4ViDULuyWXTEpUcHSsEH8vpd1tckjypxCwkipfZsXx
iPszy0o0Dx2iArPfWMXlFAI9mvyFCyFC3+nSvfyAUb2C4uZgCwAuyFh/ydPF4DEE
PQIDAQAB'
# Set the below flags explicitly to false in production mode since vite loads and merges .env.local vars when running the build command
VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=false
VITE_APP_COLLAPSE_OVERLAY=false
# Enable eslint in dev server
VITE_APP_ENABLE_ESLINT=false

View File

@ -5,4 +5,7 @@ package-lock.json
firebase/
dist/
public/workbox
src/packages/excalidraw/types
packages/excalidraw/types
examples/**/public
dev-dist
coverage

View File

@ -2,6 +2,7 @@
"extends": ["@excalidraw/eslint-config", "react-app"],
"rules": {
"import/no-anonymous-default-export": "off",
"no-restricted-globals": "off"
"no-restricted-globals": "off",
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }]
}
}

View File

@ -23,5 +23,5 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Auto release
run: |
yarn add @actions/core
yarn add @actions/core -W
yarn autorelease

View File

@ -44,7 +44,7 @@ jobs:
- name: Auto release preview
id: "autorelease"
run: |
yarn add @actions/core
yarn add @actions/core -W
yarn autorelease preview ${{ github.event.issue.number }}
- name: Post comment post release
if: always()

View File

@ -16,7 +16,7 @@ jobs:
- name: Install and lint
run: |
yarn --frozen-lockfile
yarn install
yarn test:other
yarn test:code
yarn test:typecheck

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
@ -22,11 +22,11 @@ jobs:
- name: Create report file
run: |
yarn locales-coverage
FILE_CHANGED=$(git diff src/locales/percentages.json)
FILE_CHANGED=$(git diff packages/excalidraw/locales/percentages.json)
if [ ! -z "${FILE_CHANGED}" ]; then
git config --global user.name 'Excalidraw Bot'
git config --global user.email 'bot@excalidraw.com'
git add src/locales/percentages.json
git add packages/excalidraw/locales/percentages.json
git commit -am "Auto commit: Calculate translation coverage"
git push
fi

View File

@ -11,6 +11,6 @@ jobs:
semantic:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.0.0
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -15,16 +15,14 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Install
run: yarn --frozen-lockfile
- name: Install in src/packages/excalidraw
run: yarn --frozen-lockfile
working-directory: src/packages/excalidraw
- name: Install in packages/excalidraw
run: yarn
working-directory: packages/excalidraw
env:
CI: true
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build:umd
build_script: build:esm
skip_step: install
directory: src/packages/excalidraw
directory: packages/excalidraw

View File

@ -16,7 +16,7 @@ jobs:
with:
node-version: "18.x"
- name: "Install Deps"
run: yarn --frozen-lockfile
run: yarn install
- name: "Test Coverage"
run: yarn test:coverage
- name: "Report Coverage"

View File

@ -1,17 +1,19 @@
name: Tests
on: pull_request
on:
push:
branches: master
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- name: Install and test
run: |
yarn --frozen-lockfile
yarn install
yarn test:app

7
.gitignore vendored
View File

@ -21,10 +21,9 @@ npm-debug.log*
package-lock.json
yarn-debug.log*
yarn-error.log*
src/packages/excalidraw/types
src/packages/excalidraw/example/public/bundle.js
src/packages/excalidraw/example/public/excalidraw-assets-dev
src/packages/excalidraw/example/public/excalidraw.development.js
packages/excalidraw/types
coverage
dev-dist
html
examples/**/bundle.*
meta*.json

View File

@ -1,3 +0,0 @@
## 2020-10-13
- Added ability to embed scene source into exported PNG/SVG files so you can import the scene from them (open via `Load` button or drag & drop). #2219

View File

@ -2,16 +2,18 @@ FROM node:18 AS build
WORKDIR /opt/node_app
COPY package.json yarn.lock ./
RUN yarn --ignore-optional --network-timeout 600000
COPY . .
# do not ignore optional dependencies:
# Error: Cannot find module @rollup/rollup-linux-x64-gnu
RUN yarn --network-timeout 600000
ARG NODE_ENV=production
COPY . .
RUN yarn build:app:docker
FROM nginx:1.21-alpine
FROM nginx:1.27-alpine
COPY --from=build /opt/node_app/build /usr/share/nginx/html
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1

View File

@ -25,6 +25,9 @@
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
</a>
<a href="https://www.npmjs.com/package/@excalidraw/excalidraw">
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" />
</a>
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
</a>
@ -82,7 +85,7 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
## Quick start
Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
```
npm install react react-dom @excalidraw/excalidraw
@ -94,7 +97,7 @@ or via yarn
yarn add react react-dom @excalidraw/excalidraw
```
Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details!
## Contributing

View File

@ -1,3 +1,3 @@
files:
- source: /src/locales/en.json
translation: /src/locales/%locale%.json
- source: /packages/excalidraw/locales/en.json
translation: /packages/excalidraw/locales/%locale%.json

View File

@ -34,7 +34,7 @@ Open the `Menu` in the below playground and you will see the `custom footer` ren
```jsx live noInline
const MobileFooter = ({}) => {
const device = useDevice();
if (device.isMobile) {
if (device.editor.isMobile) {
return (
<Footer>
<button

View File

@ -133,7 +133,7 @@ function App() {
}
```
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/src/components/mainMenu/DefaultItems.tsx) of the default items.
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/components/main-menu/DefaultItems.tsx) of the default items.
### MainMenu.Group

View File

@ -80,7 +80,7 @@ A given tab trigger button that switches to a given sidebar tab. It must be rend
| `className` | `string` | No | |
| `style` | `React.CSSProperties` | No | |
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/ref#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
## Example

View File

@ -31,7 +31,7 @@ The welcome screen consists of two main groups of subcomponents:
<img
src={require("@site/static/img/welcome-screen-overview.png").default}
alt="Excalidraw logo: Sketch handrawn like diagrams."
alt="Excalidraw logo: Sketch hand-drawn like diagrams."
/>
### Center

View File

@ -12,7 +12,7 @@ import { FONT_FAMILY } from "@excalidraw/excalidraw";
| Font Family | Description |
| ----------- | ---------------------- |
| `Virgil` | The `handwritten` font |
| `Virgil` | The `Hand-drawn` font |
| `Helvetica` | The `Normal` Font |
| `Cascadia` | The `Code` Font |
@ -37,7 +37,7 @@ Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
### MIME_TYPES
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
**How to use **

View File

@ -2,21 +2,25 @@
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
## convertToExcalidrawElements
**_Signature_**
<pre>
convertToExcalidrawElements(elements:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133">
ExcalidrawElementSkeleton
</a>
)
</pre>
```ts
convertToExcalidrawElements(
elements: ExcalidrawElementSkeleton,
opts?: { regenerateIds: boolean }
): ExcalidrawElement[]
```
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L137) | | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
**_How to use_**
@ -24,13 +28,13 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";
```
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#updatescene) if you are using the Skeleton API
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) if you are using the Skeleton API
## Supported Features
### Rectangle, Ellipse, and Diamond
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional_.
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional\_.
For the Skeleton API to work, `convertToExcalidrawElements` needs to be called before passing it to Excalidraw Component via initialData, updateScene or any such API.
@ -67,7 +71,7 @@ function App() {
}
```
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L27) as well to decorate the shapes.
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L27) as well to decorate the shapes.
:::info
@ -188,7 +192,7 @@ convertToExcalidrawElements([
### Text Containers
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
@ -322,7 +326,7 @@ convertToExcalidrawElements([
### Arrow bindings
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
```js
convertToExcalidrawElements([
@ -427,3 +431,45 @@ convertToExcalidrawElements([
```
![image](https://github.com/excalidraw/excalidraw/assets/11256141/a8b047c8-2eed-4aea-82a2-e1e6bbddb8d4)
### Frames
To create a frame, you need to pass `type`, `children` (list of Excalidraw element ids). The rest of the attributes are optional.
```ts
{
type: "frame";
children: readonly ExcalidrawElement["id"][];
name?: string;
} & Partial<ExcalidrawFrameElement>);
```
```ts
convertToExcalidrawElements([
{
"type": "rectangle",
"x": 10,
"y": 10,
"strokeWidth": 2,
"id": "1"
},
{
"type": "diamond",
"x": 120,
"y": 20,
"backgroundColor": "#fff3bf",
"strokeWidth": 2,
"label": {
"text": "HELLO EXCALIDRAW",
"strokeColor": "#099268",
"fontSize": 30
},
"id": "2"
},
{
"type": "frame",
"children": ["1", "2"],
"name": "My frame"
}]
}
```

View File

@ -1,29 +1,28 @@
# ref
# excalidrawAPI
<pre>
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">
createRef
</a>{" "}
&#124;{" "}
<a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>{" "}
&#124;{" "}
<a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">
callbackRef
</a>{" "}
&#124; <br />
&#123; current: &#123; readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
resolvablePromise
</a> } }
(api:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616">
ExcalidrawAPI
</a>
) => void;
</pre>
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
Once the callback is triggered, you will need to store the api in state to access it later.
```jsx showLineNumbers
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
return <Excalidraw excalidrawAPI={(api)=> setExcalidrawAPI(api)} />;
}
```
You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616). We expose the below APIs :point_down:
| API | Signature | Usage |
| --- | --- | --- |
| ready | `boolean` | This is set to true once Excalidraw is rendered |
| [readyPromise](#readypromise) | `function` | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readypromise) |
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
| [updateLibrary](#updatelibrary) | `function` | updates the library |
| [addFiles](#addfiles) | `function` | add files data to the appState |
| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
@ -38,61 +37,22 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
| [toggleSidebar](#toggleSidebar) | `function` | Toggles specific sidebar on/off |
| [onChange](#onChange) | `function` | Subscribes to change events |
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
## readyPromise
:::info The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the `excalidrawAPI`.
<pre>
const excalidrawRef = &#123; current:&#123; readyPromise:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
&nbsp;resolvablePromise
</a>
&nbsp;&#125; &#125;
</pre>
Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0.
Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves.
```jsx showLineNumbers
const resolvablePromise = () => {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
};
const App = () => {
const excalidrawRef = useMemo(
() => ({
current: {
readyPromise: resolvablePromise(),
},
}),
[],
);
useEffect(() => {
excalidrawRef.current.readyPromise.then((api) => {
console.log("loaded", api);
});
}, [excalidrawRef]);
return (
<div style={{ height: "500px" }}>
<Excalidraw ref={excalidrawRef} />
</div>
);
};
```
:::
## updateScene
<pre>
(scene:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L339">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L339">
sceneData
</a>
) => void
@ -102,10 +62,10 @@ You can use this function to update the scene with the sceneData. It accepts the
| Name | Type | Description |
| --- | --- | --- |
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L38) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/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/src/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
| `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`. |
```jsx live
function App() {
@ -155,7 +115,7 @@ function App() {
<button className="custom-button" onClick={updateScene}>
Update Scene
</button>
<Excalidraw ref={(api) => setExcalidrawAPI(api)} />
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
);
}
@ -165,13 +125,13 @@ function App() {
<pre>
(opts: &#123; <br /> libraryItems:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249">
LibraryItemsSource
</a>
;<br /> merge?: boolean; <br /> prompt?: boolean;
<br /> openLibraryMenu?: boolean;
<br /> defaultStatus?: "unpublished" | "published"; <br /> &#125;) => Promise&lt;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L246">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L246">
LibraryItems
</a>
&gt;
@ -181,7 +141,7 @@ You can use this function to update the library. It accepts the below attributes
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
| `merge` | boolean | `false` | Whether to merge with existing library items. |
| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
@ -228,8 +188,8 @@ function App() {
Update Library
</button>
<Excalidraw
ref={(api) => setExcalidrawAPI(api)}
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/src/initialData.js
excalidrawAPI={(api) => setExcalidrawAPI(api)}
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/packages/excalidraw/initialData.js
initialData={{
libraryItems: initialData.libraryItems,
appState: { openSidebar: "library" },
@ -244,7 +204,7 @@ function App() {
<pre>
(files:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
BinaryFileData
</a>
) => void
@ -264,7 +224,7 @@ Resets the scene. If `resetLoadingState` is passed as true then it will also for
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
ExcalidrawElement[]
</a>
</pre>
@ -275,7 +235,7 @@ Returns all the elements including the deleted in the scene.
<pre>
() => NonDeleted&#60;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
ExcalidrawElement
</a>
[]&#62;
@ -287,7 +247,7 @@ Returns all the elements excluding the deleted in the scene
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>
</pre>
@ -328,7 +288,7 @@ Scroll the nearest element out of the elements supplied to the center of the vie
| Attribute | type | default | Description |
| --- | --- | --- | --- |
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) &#124; [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) &#124; [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
@ -376,7 +336,7 @@ The unique id of the excalidraw component. This can be used to identify the exca
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82">
files
</a>
</pre>
@ -387,14 +347,25 @@ This API can be used to get the files present in the scene. It may contain files
This API has the below signature. It sets the `tool` passed in param as the active tool.
<pre>
(tool: <br /> &#123; type:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">
SHAPES
</a>
[number]["value"]&#124; "eraser" &#125; &#124;
<br /> &#123; type: "custom"; customType: string &#125;) => void
</pre>
```ts
(
tool: (
| (
| { type: Exclude<ToolType, "image"> }
| {
type: Extract<ToolType, "image">;
insertOnCanvasDirectly?: boolean;
}
)
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {};
```
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
## setCursor
@ -421,3 +392,51 @@ This API is especially useful when you render a custom [`<Sidebar/>`](/docs/@exc
```
This API can be used to reset to default mouse cursor.
## onChange
```tsx
(
callback: (
elements: readonly ExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
) => void
) => () => void
```
Subscribes to change events, similar to [`props.onChange`](/docs/@excalidraw/excalidraw/api/props#onchange).
Returns an unsubscribe function.
## onPointerDown
```tsx
(
callback: (
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: React.PointerEvent<HTMLElement>,
) => void,
) => () => void
```
Subscribes to canvas `pointerdown` events.
Returns an unsubscribe function.
## onPointerUp
```tsx
(
callback: (
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: PointerEvent,
) => void,
) => () => void
```
Subscribes to canvas `pointerup` events.
Returns an unsubscribe function.

View File

@ -1,18 +1,18 @@
# initialData
<pre>
&#123; elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> &#125;
&#123; elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> &#125;
</pre>
This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
| Name | Type | Description |
| --- | --- | --- |
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L247) &#124; Promise&lt;[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)&gt; | This library items with which `Excalidraw` should be mounted. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82) | The `files` added to the scene. |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L247) &#124; Promise&lt;[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200)&gt; | This library items with which `Excalidraw` should be mounted. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82) | The `files` added to the scene. |
You might want to use this when you want to load excalidraw with some initial elements and app state.

View File

@ -1,17 +1,17 @@
# Props
All `props` are *optional*.
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. |
| [`ref`](/docs/@excalidraw/excalidraw/api/props/ref) | `object` | _ | `Ref` to be passed to Excalidraw |
| [`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 |
| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down evenets |
| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down events |
| [`onScrollChange`](#onscrollchange) | `function` | _ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | _ | Callback to be triggered if passed when the something is pasted in to the scene |
| [`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. |
| [`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 |
@ -23,21 +23,21 @@ All `props` are *optional*.
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`theme`](#theme) | `"light"` &#124; `"dark"` | `"light"` | The theme of the Excalidraw component |
| [`name`](#name) | `string` | | Name of the drawing |
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
| [`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 |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
### Storing custom data on Excalidraw elements
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L66) and is optional.
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L66) and is optional.
You can use this to add any extra information you need to keep track of.
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/ref#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/ref#updatelibrary) afterwards.
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatelibrary) afterwards.
```js showLineNumbers
{
@ -59,11 +59,11 @@ Every time component updates, this callback if passed will get triggered and has
(excalidrawElements, appState, files) => void;
```
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) in the scene.
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) in the scene.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) which are added to the scene.
Here you can try saving the data to your backend or local storage for example.
@ -79,22 +79,30 @@ This callback is triggered when mouse pointer is updated.
2.`button`: The position of the button. This will be one of `["down", "up"]`
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) map of the scene
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L131) map of the scene
```js
(exportedElements, appState, canvas) => void
```
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L87) which needs to be exported.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L87) which needs to be exported.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
3. `canvas`: The `HTMLCanvasElement` of the scene.
### onPointerDown
This prop if passed will be triggered on pointer down events and has the below signature.
<pre>
(activeTool: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115"> AppState["activeTool"]</a>, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">PointerDownState</a>) => void
(activeTool:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
{" "}
AppState["activeTool"]
</a>
, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L424">
PointerDownState
</a>) => void
</pre>
### onScrollChange
@ -110,7 +118,11 @@ This prop if passed will be triggered when canvas is scrolled and has the below
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
<pre>
(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">ClipboardData</a>, event: ClipboardEvent &#124; null) => boolean
(data:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/clipboard.ts#L18">
ClipboardData
</a>
, event: ClipboardEvent &#124; null) => boolean
</pre>
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
@ -123,7 +135,7 @@ This callback if supplied will get triggered when the library is updated and has
<pre>
(items:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">
LibraryItems
</a>
) => void | Promise&lt;any&gt;
@ -136,8 +148,11 @@ It is invoked with empty items when user clears the library. You can use this ca
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
<pre>
(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>,
event: CustomEvent&lt;&#123; nativeEvent: MouseEvent }&gt;) => void
(element:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
ExcalidrawElement
</a>
, event: CustomEvent&lt;&#123; nativeEvent: MouseEvent }&gt;) => void
</pre>
Example:
@ -167,7 +182,7 @@ const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
### langCode
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
```js
import { defaultLang, languages } from "@excalidraw/excalidraw";
@ -176,34 +191,34 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
| name | type |
| --- | --- |
| `defaultLang` | `string` |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
### viewModeEnabled
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over *intialData.appState.viewModeEnabled*, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over _intialData.appState.viewModeEnabled_, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### zenModeEnabled
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over *intialData.appState.zenModeEnabled*, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over _intialData.appState.zenModeEnabled_, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### gridModeEnabled
This prop indicates whether the shows the grid. When supplied, the value takes precedence over *intialData.appState.gridModeEnabled*, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the shows the grid. When supplied, the value takes precedence over _intialData.appState.gridModeEnabled_, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### libraryReturnUrl
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
Defaults to *window.location.origin + window.location.pathname*. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
### theme
This prop controls Excalidraw's theme. When supplied, the value takes precedence over *intialData.appState.theme*, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless *UIOptions.canvasActions.toggleTheme* is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
This prop controls Excalidraw's theme. When supplied, the value takes precedence over _intialData.appState.theme_, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless _UIOptions.canvasActions.toggleTheme_ is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
### name
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over *intialData.appState.name*, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
### detectScroll
@ -236,4 +251,4 @@ validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) =>
This is an optional property. By default we support a handful of well-known sites. You may allow additional sites or disallow the default ones by supplying a custom validator. If you pass `true`, all URLs will be allowed. You can also supply a list of hostnames, RegExp (or list of RegExp objects), or a function. If the function returns `undefined`, the built-in validator will be used.
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.

View File

@ -4,7 +4,7 @@
<pre>
(isMobile: boolean, appState:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>) => JSX | null
</pre>
@ -66,7 +66,7 @@ function App() {
<pre>
(element: NonDeleted&lt;ExcalidrawEmbeddableElement&gt;, appState:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>
) => JSX.Element | null

View File

@ -1,10 +1,10 @@
# UIOptions
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) and [`welcomeScreen`](#welcmescreen).
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) [`welcomeScreen`](#welcmescreen) and [`tools`](#tools).
<pre>
&#123;
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L372">
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L372">
CanvasActions
</a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
@ -55,7 +55,7 @@ If `UIOptions.canvasActions.export` is `false` the export button will not be ren
## dockedSidebarBreakpoint
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L161).
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L161).
If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown
If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
@ -70,3 +70,12 @@ function App() {
);
}
```
## tools
This `prop` controls the visibility of the tools in the editor.
Currently you can control the visibility of `image` tool via this prop.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| image | boolean | true | Decides whether `image` tool should be visible.

View File

@ -20,16 +20,16 @@ exportToCanvas(&#123;<br/>&nbsp;
getDimensions,<br/>&nbsp;
files,<br/>&nbsp;
exportPadding?: number;<br/>
&#125;: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a>
&#125;: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/utils/export.ts#L24">ExportOpts</a>
</pre>
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to be exported to canvas. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L17) | The app state of the scene. |
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to be exported to canvas. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L17) | The app state of the scene. |
| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to `1`), with which canvas is to be exported. |
| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59) | _ | The files added to the scene. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59) | _ | The files added to the scene. |
| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
@ -90,7 +90,7 @@ function App() {
<img src={canvasUrl} alt="" />
</div>
<div style={{ height: "400px" }}>
<Excalidraw ref={(api) => setExcalidrawAPI(api)}
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)}
/>
</div>
</>
@ -105,7 +105,7 @@ function App() {
<pre>
exportToBlob(<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & &#123;<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L14">ExportOpts</a> & &#123;<br/>&nbsp;
mimeType?: string,<br/>&nbsp;
quality?: number,<br/>&nbsp;
exportPadding?: number;<br/>
@ -134,16 +134,16 @@ Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-
<pre>
exportToSvg(&#123;<br/>&nbsp;
elements:&nbsp;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
ExcalidrawElement[]
</a>,<br/>&nbsp;
appState:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95"> AppState
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95"> AppState
</a>,<br/>&nbsp;
exportPadding: number,<br/>&nbsp;
metadata: string,<br/>&nbsp;
files:&nbsp;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
BinaryFiles
</a>,<br/>
&#125;);
@ -151,10 +151,10 @@ exportToSvg(&#123;<br/>&nbsp;
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to exported as `svg `|
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The `appState` of the scene |
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to exported as `svg `|
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L11) | The `appState` of the scene |
| exportPadding | number | 10 | The `padding` to be added on canvas |
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | undefined | The `files` added to the scene. |
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) | undefined | The `files` added to the scene. |
This function returns a promise which resolves to `svg` of the exported drawing.
@ -164,7 +164,7 @@ This function returns a promise which resolves to `svg` of the exported drawing.
<pre>
exportToClipboard(<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a> & &#123;<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a> & &#123;<br/>&nbsp;
mimeType?: string,<br/>&nbsp;
quality?: number;<br/>&nbsp;
type: 'png' | 'svg' |'json'<br/>

View File

@ -8,7 +8,7 @@ id: "restore"
**_Signature_**
<pre>
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>&nbsp; localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>&nbsp; localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>
</pre>
**_How to use_**
@ -17,7 +17,7 @@ restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob
import { restoreAppState } from "@excalidraw/excalidraw";
```
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) and if any key is missing, it will be set to its `default` value.
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) and if any key is missing, it will be set to its `default` value.
When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.
Use this as a way to not override user's defaults if you persist them.
@ -29,16 +29,16 @@ You can pass `null` / `undefined` if not applicable.
<pre>
restoreElements(
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
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/>
)
</pre>
| Prop | Type | Description |
| ---- | ---- | ---- |
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> &#124; null &#124; undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> &#124; null &#124; undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
#### localElements
@ -70,15 +70,15 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
<pre>
restore(
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp;
localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a><br/>
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/>
)
</pre>
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreElements) about `localElements`.
**_How to use_**
@ -93,7 +93,7 @@ This function makes sure elements and state is set to appropriate values and set
**_Signature_**
<pre>
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>&nbsp;
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>&nbsp;
defaultStatus: "published" | "unpublished")
</pre>

View File

@ -8,7 +8,7 @@ These are pure Javascript functions exported from the @excalidraw/excalidraw [`@
### serializeAsJSON
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L42) source for details).
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/json.ts#L42) source for details).
If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
@ -16,8 +16,8 @@ If you want to overwrite the `source` field in the `JSON` string, you can set `w
<pre>
serializeAsJSON(&#123;<br/>&nbsp;
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>,<br/>
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>,<br/>
}): string
</pre>
@ -37,7 +37,7 @@ If you want to overwrite the source field in the JSON string, you can set `windo
<pre>
serializeLibraryAsJSON(
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems[]</a>)
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems[]</a>)
</pre>
**How to use**
@ -53,7 +53,7 @@ Returns `true` if element is invisibly small (e.g. width & height are zero).
**_Signature_**
<pre>
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>): boolean
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement</a>): boolean
</pre>
**How to use**
@ -80,10 +80,10 @@ excalidrawAPI.updateScene(scene);
<pre>
loadFromBlob(<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
fileHandle?: FileSystemHandle | null <br/>
) => Promise&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L61">RestoredDataState</a>>
) => Promise&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L61">RestoredDataState</a>>
</pre>
### loadLibraryFromBlob
@ -129,11 +129,11 @@ if (contents.type === MIME_TYPES.excalidraw) {
<pre>
loadSceneOrLibraryFromBlob(<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
fileHandle?: FileSystemHandle | null<br/>
) => Promise&lt;&#123; type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L33">ImportedLibraryState</a>}>
) => Promise&lt;&#123; type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L33">ImportedLibraryState</a>}>
</pre>
### getFreeDrawSvgPath
@ -149,7 +149,7 @@ import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
**Signature**
<pre>
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
</pre>
### isLinearElement
@ -164,9 +164,9 @@ import { isLinearElement } from "@excalidraw/excalidraw";
**Signature**
```tsx
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L80">ExcalidrawElement</a>): boolean
```
<pre>
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L80">ExcalidrawElement</a>): boolean
</pre>
### getNonDeletedElements
@ -181,7 +181,7 @@ import { getNonDeletedElements } from "@excalidraw/excalidraw";
**Signature**
<pre>
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
</pre>
### mergeLibraryItems
@ -195,8 +195,10 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw";
**_Signature_**
<pre>
mergeLibraryItems(localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/>&nbsp;
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a>) => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
mergeLibraryItems(<br/>&nbsp;
localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>,<br/>&nbsp;
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems</a><br/>
): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>
</pre>
### parseLibraryTokensFromUrl
@ -237,8 +239,8 @@ export const App = () => {
<pre>
useHandleLibrary(opts: &#123;<br/>&nbsp;
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L494">ExcalidrawAPI</a>,<br/>&nbsp;
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L253">LibraryItemsSource</a><br/>
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L494">ExcalidrawAPI</a>,<br/>&nbsp;
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L253">LibraryItemsSource</a><br/>
});
</pre>
@ -251,7 +253,7 @@ This function returns the current `scene` version.
**_Signature_**
<pre>
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>)
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>)
</pre>
**How to use**
@ -272,7 +274,7 @@ import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
<pre>
sceneCoordsToViewportCoords(&#123; sceneX: number, sceneY: number },<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): &#123; x: number, y: number }
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): &#123; x: number, y: number }
</pre>
### viewportCoordsToSceneCoords
@ -287,7 +289,7 @@ import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
<pre>
viewportCoordsToSceneCoords(&#123; clientX: number, clientY: number },<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): &#123;x: number, y: number}
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): &#123;x: number, y: number}
</pre>
### useDevice
@ -299,7 +301,7 @@ Open the `main menu` in the below example to view the footer.
```jsx live noInline
const MobileFooter = ({}) => {
const device = useDevice();
if (device.isMobile) {
if (device.editor.isMobile) {
return (
<Footer>
<button
@ -331,14 +333,15 @@ const App = () => (
render(<App />);
```
The `device` has the following `attributes`
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
| Name | Type | Description |
| --- | --- | --- |
| `isSmScreen` | `boolean` | Set to `true` when the device small screen is small (Width < `640px` ) |
| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
| `viewport.isMobile` | `boolean` | Set to `true` when viewport is in `mobile` breakpoint |
| `viewport.isLandscape` | `boolean` | Set to `true` when the viewport is in `landscape` mode |
| `editor.canFitSidebar` | `boolean` | Set to `true` if there's enough space to fit the `sidebar` |
| `editor.isMobile` | `boolean` | Set to `true` when editor container is in `mobile` breakpoint |
| `isTouchScreen` | `boolean` | Set to `true` for `touch` when touch event detected |
### i18n
@ -347,8 +350,8 @@ To help with localization, we export the following.
| name | type |
| --- | --- |
| `defaultLang` | `string` |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
```js
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
@ -383,3 +386,94 @@ function App() {
);
}
```
### getCommonBounds
This util can be used to get the common bounds of the passed elements.
**_Signature_**
```ts
getCommonBounds(
elements: readonly ExcalidrawElement[]
): readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
]
```
**_How to use_**
```js
import { getCommonBounds } from "@excalidraw/excalidraw";
```
### elementsOverlappingBBox
To filter `elements` that are inside, overlap, or contain the `bounds` rectangle.
The bounds check is approximate and does not precisely follow the element's shape. You can also supply `errorMargin` which effectively makes the `bounds` larger by that amount.
This API has 3 `type`s of operation: `overlap`, `contain`, and `inside`:
- `overlap` - filters elements that are overlapping or inside bounds.
- `contain` - filters elements that are inside bounds or bounds inside elements.
- `inside` - filters elements that are inside bounds.
**_Signature_**
<pre>
elementsOverlappingBBox(<br/>&nbsp;
elements: readonly NonDeletedExcalidrawElement[];<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a> | ExcalidrawElement;<br/>&nbsp;
errorMargin?: number;<br/>&nbsp;
type: "overlap" | "contain" | "inside";<br/>
): NonDeletedExcalidrawElement[];
</pre>
**_How to use_**
```js
import { elementsOverlappingBBox } from "@excalidraw/excalidraw";
```
### isElementInsideBBox
Lower-level API than `elementsOverlappingBBox` to check if a single `element` is inside `bounds`. If `eitherDirection=true`, returns `true` if `element` is fully inside `bounds` rectangle, or vice versa. When `false`, it returns `true` only for the former case.
**_Signature_**
<pre>
isElementInsideBBox(<br/>&nbsp;
element: NonDeletedExcalidrawElement,<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>&nbsp;
eitherDirection = false,<br/>
): boolean
</pre>
**_How to use_**
```js
import { isElementInsideBBox } from "@excalidraw/excalidraw";
```
### elementPartiallyOverlapsWithOrContainsBBox
Checks if `element` is overlapping the `bounds` rectangle, or is fully inside.
**_Signature_**
<pre>
elementPartiallyOverlapsWithOrContainsBBox(<br/>&nbsp;
element: NonDeletedExcalidrawElement,<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
): boolean
</pre>
**_How to use_**
```js
import { elementPartiallyOverlapsWithOrContainsBBox } from "@excalidraw/excalidraw";
```

View File

@ -21,7 +21,7 @@ Most notably, you can customize the primary colors, by overriding these variable
- `--color-primary-light`
- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/css/theme.scss), though most of them will not make sense to override.
```css showLineNumbers
.custom-styles .excalidraw {

View File

@ -13,7 +13,7 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
1. Install the dependencies
```bash
cd src/packages/excalidraw && yarn
cd packages/excalidraw && yarn
```
2. Start the example app
@ -43,7 +43,7 @@ Once the version is released `@excalibot` will post a comment with the release v
To release the next stable version follow the below steps:
```bash
yarn prerelease version
yarn prerelease:excalidraw
```
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
@ -51,7 +51,7 @@ You need to pass the `version` for which you want to create the release. This wi
The next step is to run the `release` script:
```bash
yarn release
yarn release:excalidraw
```
This will publish the package.

View File

@ -2,7 +2,7 @@
### Does this package support collaboration ?
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
### Turning off Aggressive Anti-Fingerprinting in Brave browser
@ -18,7 +18,7 @@ We strongly recommend turning it off. You can follow the steps below on how to d
2. Once opened, look for **Aggressively Block Fingerprinting**
![Aggresive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
![Aggressive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
3. Switch to **Block Fingerprinting**
@ -31,6 +31,17 @@ We strongly recommend turning it off. You can follow the steps below on how to d
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
### ReferenceError: process is not defined
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
```
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
},
```
## Need help?

View File

@ -30,25 +30,131 @@ function App() {
}
```
### Rendering Excalidraw only on client
### Next.js
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
Since Excalidraw doesn't support `server side rendering` so it should be rendered only on `client`. The way to achieve this in next.js is using `next.js dynamic import`.
The following workflow shows one way how to render Excalidraw on Next.js. We'll add more detailed and alternative Next.js examples, soon.
If you want to only import `Excalidraw` component you can do :point_down:
```jsx showLineNumbers
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
const Excalidraw = dynamic(
async () => (await import("@excalidraw/excalidraw")).Excalidraw,
{
ssr: false,
},
);
export default function App() {
const [Excalidraw, setExcalidraw] = useState(null);
useEffect(() => {
import("@excalidraw/excalidraw").then((comp) => setExcalidraw(comp.Excalidraw));
}, []);
return <>{Excalidraw && <Excalidraw />}</>;
return <Excalidraw />;
}
```
However the above component only works for named component exports. If you want to import some util / constant or something else apart from Excalidraw, then this approach will not work. Instead you can write a wrapper over Excalidraw and import the wrapper dynamically.
If you are using `pages router` then importing the wrapper dynamically would work, where as if you are using `app router` then you will have to also add `useClient` directive on top of the file in addition to dynamically importing the wrapper as shown :point_down:
<Tabs>
<TabItem value="Excalidraw Wrapper" label="Excalidraw Wrapper" >
```jsx showLineNumbers
"use client";
import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
const ExcalidrawWrapper: React.FC = () => {
console.info(convertToExcalidrawElements([{
type: "rectangle",
id: "rect-1",
width: 186.47265625,
height: 141.9765625,
},]));
return (
<div style={{height:"500px", width:"500px"}}>
<Excalidraw />
</div>
);
};
export default ExcalidrawWrapper;
```
</TabItem>
<TabItem value="pages" label="Pages router">
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<ExcalidrawWrapper />
);
}
```
</TabItem>
<TabItem value="app" label="App router">
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<ExcalidrawWrapper />
);
}
```
</TabItem>
</Tabs>
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)
### Preact
Since we support `umd` build ships with `react/jsx-runtime` and `react-dom/client` inlined with the package. This conflicts with `Preact` and hence the build doesn't work directly with `Preact`.
However we have shipped a separate build for `Preact` so if you are using `Preact` you need to set `process.env.IS_PREACT` to `true` to use the `Preact` build.
Once the above `env` variable is set, you will be able to use the package in `Preact` as well.
:::info
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
```
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
},
```
:::
## Browser
To use it in a browser directly:
@ -99,7 +205,7 @@ import TabItem from "@theme/TabItem";
<h1>Excalidraw Embed Example</h1>
<div id="app"></div>
</div>
<script type="text/javascript" src="src/index.js"></script>
<script type="text/javascript" src="packages/excalidraw/index.js"></script>
</body>
</html>
```

View File

@ -14,7 +14,7 @@ This API receives the mermaid syntax as the input, and resolves to skeleton Exca
import { parseMermaidToExcalidraw } from "@excalidraw/mermaid-to-excalidraw";
import { convertToExcalidrawElements} from "@excalidraw/excalidraw"
try {
const { elements, files } = await parseMermaid(mermaidSyntax: string, {
const { elements, files } = await parseMermaidToExcalidraw(mermaidSyntax: string, {
fontSize: number,
});
const excalidrawElements = convertToExcalidrawElements(elements);

View File

@ -38,9 +38,9 @@ Add the diagram type in switch case in [`parseMermaid`](https://github.com/excal
## Writing the Excalidraw Skeleton Convertor
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) format.
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) format.
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133).
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133).
Thats it, you have added the new diagram type 🥳, now lets test it out!

View File

@ -6,7 +6,7 @@ In this section we will be diving into how the [flowchart parser](https://github
![image](https://github.com/excalidraw/excalidraw/assets/11256141/2a097bbb-64bf-49d6-bf7f-21172bdb538d)
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
@ -42,7 +42,7 @@ Considering the same example this is the response from the API
}
}
```
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
The final output from `parseVertex` looks like :point_down:

View File

@ -55,11 +55,11 @@ If you want to understand how flowchart parser works, you can navigate to [Flowc
## Converting to ExcalidrawElementSkeleton
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
![image](https://github.com/excalidraw/excalidraw/assets/11256141/00226e9d-043d-4a08-989a-3ad9d2a574f1)

View File

@ -0,0 +1,22 @@
# Frames
## Ordering
Frames should be ordered where frame children come first, followed by the frame element itself:
```
[
other_element,
frame1_child1,
frame1_child2,
frame1,
other_element,
frame2_child1,
frame2_child2,
frame2,
other_element,
...
]
```
If not ordered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.

View File

@ -43,7 +43,7 @@ When saving an Excalidraw scene locally to a file, the JSON file (`.excalidraw`)
// editor state (canvas config, preferences, ...)
"appState": {
"gridSize": null,
"gridSize": 20,
"viewBackgroundColor": "#ffffff"
},

View File

@ -52,15 +52,6 @@ Make sure the title starts with a semantic prefix:
- **chore**: Other changes that don't modify src or test files
- **revert**: Reverts a previous commit
### Changelog
Add a brief description of your pull request to the changelog located here: [changelog](https://github.com/excalidraw/excalidraw/blob/master/CHANGELOG.md)
Notes:
- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
### Testing
Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.

View File

@ -1,6 +1,11 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
// Set the env variable to false so the excalidraw npm package doesn't throw
// process undefined as docusaurus doesn't expose env variables by default
process.env.IS_PREACT = "false";
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Excalidraw developer docs",
@ -36,10 +41,7 @@ const config = {
showLastUpdateTime: true,
},
theme: {
customCss: [
require.resolve("./src/css/custom.scss"),
require.resolve("../src/packages/excalidraw/example/App.scss"),
],
customCss: [require.resolve("./src/css/custom.scss")],
},
}),
],
@ -139,7 +141,15 @@ const config = {
},
}),
themes: ["@docusaurus/theme-live-codeblock"],
plugins: ["docusaurus-plugin-sass"],
plugins: [
"docusaurus-plugin-sass",
[
"docusaurus2-dotenv",
{
systemvars: true,
},
],
],
};
module.exports = config;

View File

@ -18,18 +18,19 @@
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/theme-live-codeblock": "2.2.0",
"@excalidraw/excalidraw": "0.15.2-eb020d0",
"@excalidraw/excalidraw": "0.17.6",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "0.2.3",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"sass": "1.57.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
"@tsconfig/docusaurus": "^1.0.5",
"docusaurus2-dotenv": "1.4.0",
"typescript": "^4.7.4"
},
"browserslist": {

View File

@ -23,7 +23,11 @@ const sidebars = {
},
items: ["introduction/development", "introduction/contributing"],
},
{ type: "category", label: "Codebase", items: ["codebase/json-schema"] },
{
type: "category",
label: "Codebase",
items: ["codebase/json-schema", "codebase/frames"],
},
{
type: "category",
label: "@excalidraw/excalidraw",
@ -49,7 +53,7 @@ const sidebars = {
},
items: [
"@excalidraw/excalidraw/api/props/initialdata",
"@excalidraw/excalidraw/api/props/ref",
"@excalidraw/excalidraw/api/props/excalidraw-api",
"@excalidraw/excalidraw/api/props/render-props",
"@excalidraw/excalidraw/api/props/ui-options",
],

View File

@ -59,7 +59,7 @@ pre a {
padding: 5px;
background: #70b1ec;
color: white;
font-weight: bold;
font-weight: 700;
border: none;
}

4
dev-docs/vercel.json Normal file
View File

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

View File

@ -145,6 +145,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
@ -202,6 +210,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@ -265,6 +283,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@ -280,6 +303,14 @@
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@ -287,6 +318,13 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-member-expression-to-functions@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
@ -374,11 +412,28 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
@ -412,11 +467,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.12.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8", "@babel/parser@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@ -1147,19 +1216,28 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98"
integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.9"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.9"
"@babel/types" "^7.18.9"
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@ -1171,6 +1249,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@ -1460,7 +1547,7 @@
"@docusaurus/theme-search-algolia" "2.2.0"
"@docusaurus/types" "2.2.0"
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
"@docusaurus/react-loadable@5.5.2":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
@ -1631,10 +1718,10 @@
url-loader "^4.1.1"
webpack "^5.73.0"
"@excalidraw/excalidraw@0.15.2-eb020d0":
version "0.15.2-eb020d0"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2-eb020d0.tgz#25bd61e6f23da7c084fb16a3e0fe0dd9ad8e6533"
integrity sha512-TKGLzpOVqFQcwK1GFKTDXgg1s2U6tc5KE3qXuv87osbzOtftQn3x4+VH61vwdj11l00nEN80SMdXUC43T9uJqQ==
"@excalidraw/excalidraw@0.17.6":
version "0.17.6"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.6.tgz#5fd208ce69d33ca712d1804b50d7d06d5c46ac4d"
integrity sha512-fyCl+zG/Z5yhHDh5Fq2ZGmphcrALmuOdtITm8gN4d8w4ntnaopTXcTfnAAaU3VleDC6LhTkoLOTG6P5kgREiIg==
"@hapi/hoek@^9.0.0":
version "9.3.0"
@ -1670,6 +1757,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@ -1688,6 +1780,19 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17":
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
@ -2684,7 +2789,14 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.2, braces@~3.0.2:
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@ -3462,6 +3574,13 @@ docusaurus-plugin-sass@0.2.3:
dependencies:
sass-loader "^10.1.1"
docusaurus2-dotenv@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/docusaurus2-dotenv/-/docusaurus2-dotenv-1.4.0.tgz#9ab900e29de9081f9f1f28f7224ff63760385641"
integrity sha512-iWqem5fnBAyeBBtX75Fxp71uUAnwFaXzOmade8zAhN4vL3RG9m27sLSRwjJGVVgIkEo3esjGyCcTGTiCjfi+sg==
dependencies:
dotenv-webpack "1.7.0"
dom-converter@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@ -3547,6 +3666,25 @@ dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"
dotenv-defaults@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz#032c024f4b5906d9990eb06d722dc74cc60ec1bd"
integrity sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==
dependencies:
dotenv "^6.2.0"
dotenv-webpack@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-1.7.0.tgz#4384d8c57ee6f405c296278c14a9f9167856d3a1"
integrity sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==
dependencies:
dotenv-defaults "^1.0.2"
dotenv@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
duplexer3@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
@ -3880,6 +4018,13 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
finalhandler@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
@ -5076,11 +5221,11 @@ methods@~1.1.2:
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.2"
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
@ -6059,14 +6204,13 @@ react-dev-utils@^12.0.1:
strip-ansi "^6.0.1"
text-table "^0.2.0"
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
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"
object-assign "^4.1.1"
scheduler "^0.20.2"
scheduler "^0.23.0"
react-error-overlay@^6.0.11:
version "6.0.11"
@ -6129,6 +6273,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
dependencies:
"@babel/runtime" "^7.10.3"
"react-loadable@npm:@docusaurus/react-loadable@5.5.2":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
dependencies:
"@types/react" "*"
prop-types "^15.6.2"
react-router-config@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988"
@ -6179,13 +6331,12 @@ react-textarea-autosize@^8.3.2:
use-composed-ref "^1.3.0"
use-latest "^1.2.1"
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
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"
object-assign "^4.1.1"
readable-stream@^2.0.1:
version "2.3.7"
@ -6533,13 +6684,12 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
scheduler@^0.23.0:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@2.7.0:
version "2.7.0"

View File

@ -1,6 +1,5 @@
import { ExcalidrawImperativeAPI } from "../../../types";
import { MIME_TYPES } from "../entry";
import { Button } from "../../../components/Button";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
const COMMENT_SVG = (
<svg
@ -18,24 +17,28 @@ const COMMENT_SVG = (
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
</svg>
);
const CustomFooter = ({
excalidrawAPI,
excalidrawLib,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
excalidrawLib: typeof TExcalidraw;
}) => {
const { Button, MIME_TYPES } = excalidrawLib;
return (
<>
<Button
onSelect={() => alert("General Kenobi!")}
className="you are a bold one"
style={{ marginLeft: "1rem" }}
style={{ marginLeft: "1rem", width: "auto" }}
title="Hello there!"
>
{COMMENT_SVG}
Hit me
</Button>
<button
<Button
className="custom-element"
onClick={() => {
onSelect={() => {
excalidrawAPI?.setActiveTool({
type: "custom",
customType: "comment",
@ -58,15 +61,10 @@ const CustomFooter = ({
)}`;
excalidrawAPI?.setCursor(`url(${url}), auto`);
}}
title="Comments!"
>
{COMMENT_SVG}
</button>
<button
className="custom-footer"
onClick={() => alert("This is dummy footer")}
>
custom footer
</button>
</Button>
</>
);
};

View File

@ -15,14 +15,23 @@
border-radius: 50%;
}
}
.app-title {
margin-block-start: 0.83em;
margin-block-end: 0.83em;
}
}
.button-wrapper button {
z-index: 1;
height: 40px;
max-width: 200px;
margin: 10px;
padding: 5px;
.button-wrapper {
input[type="checkbox"] {
margin: 5px;
}
button {
z-index: 1;
height: 40px;
max-width: 200px;
margin: 10px;
padding: 5px;
}
}
.excalidraw .App-menu_top .buttonList {

View File

@ -1,23 +1,31 @@
import { useEffect, useState, useRef, useCallback } from "react";
import React, {
useEffect,
useState,
useRef,
useCallback,
Children,
cloneElement,
} from "react";
import ExampleSidebar from "./sidebar/ExampleSidebar";
import type * as TExcalidraw from "../index";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import "./App.scss";
import initialData from "./initialData";
import { nanoid } from "nanoid";
import type { ResolvablePromise } from "../utils";
import {
resolvablePromise,
ResolvablePromise,
distance2d,
fileOpen,
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../../../utils";
import { EVENT, ROUNDNESS } from "../../../constants";
import { distance2d } from "../../../math";
import { fileOpen } from "../../../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils";
import {
} from "../utils";
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import initialData from "../initialData";
import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
@ -25,18 +33,14 @@ import {
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "../../../types";
import { NonDeletedExcalidrawElement, Theme } from "../../../element/types";
import { ImportedLibraryData } from "../../../data/types";
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import { KEYS } from "../../../keys";
} from "@excalidraw/excalidraw/dist/excalidraw/types";
import type {
NonDeletedExcalidrawElement,
Theme,
} from "@excalidraw/excalidraw/dist/excalidraw/element/types";
import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types";
declare global {
interface Window {
ExcalidrawLib: typeof TExcalidraw;
}
}
import "./ExampleApp.scss";
type Comment = {
x: number;
@ -57,27 +61,6 @@ type PointerDownState = {
};
};
// This is so that we use the bundled excalidraw.development.js file instead
// of the actual source code
const {
exportToCanvas,
exportToSvg,
exportToBlob,
exportToClipboard,
Excalidraw,
useHandleLibrary,
MIME_TYPES,
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
Footer,
WelcomeScreen,
MainMenu,
LiveCollaborationTrigger,
convertToExcalidrawElements,
} = window.ExcalidrawLib;
const COMMENT_ICON_DIMENSION = 32;
const COMMENT_INPUT_HEIGHT = 50;
const COMMENT_INPUT_WIDTH = 150;
@ -86,9 +69,38 @@ export interface AppProps {
appTitle: string;
useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
customArgs?: any[];
children: React.ReactNode;
excalidrawLib: typeof TExcalidraw;
}
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
export default function ExampleApp({
appTitle,
useCustom,
customArgs,
children,
excalidrawLib,
}: AppProps) {
const {
exportToCanvas,
exportToSvg,
exportToBlob,
exportToClipboard,
useHandleLibrary,
MIME_TYPES,
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
Footer,
WelcomeScreen,
MainMenu,
LiveCollaborationTrigger,
convertToExcalidrawElements,
TTDDialog,
TTDDialogTrigger,
ROUNDNESS,
loadSceneOrLibraryFromBlob,
} = excalidrawLib;
const appRef = useRef<any>(null);
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
@ -98,6 +110,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
const [exportEmbedScene, setExportEmbedScene] = useState(false);
const [theme, setTheme] = useState<Theme>("light");
const [disableImageTool, setDisableImageTool] = useState(false);
const [isCollaborating, setIsCollaborating] = useState(false);
const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
{},
@ -149,8 +162,105 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
};
};
fetchData();
}, [excalidrawAPI]);
}, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]);
const renderExcalidraw = (children: React.ReactNode) => {
const Excalidraw: any = Children.toArray(children).find(
(child) =>
React.isValidElement(child) &&
typeof child.type !== "string" &&
//@ts-ignore
child.type.displayName === "Excalidraw",
);
if (!Excalidraw) {
return;
}
const newElement = cloneElement(
Excalidraw,
{
excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api),
initialData: initialStatePromiseRef.current.promise,
onChange: (
elements: NonDeletedExcalidrawElement[],
state: AppState,
) => {
console.info("Elements :", elements, "State : ", state);
},
onPointerUpdate: (payload: {
pointer: { x: number; y: number };
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => setPointerData(payload),
viewModeEnabled,
zenModeEnabled,
gridModeEnabled,
theme,
name: "Custom name of drawing",
UIOptions: {
canvasActions: {
loadScene: false,
},
tools: { image: !disableImageTool },
},
renderTopRightUI,
onLinkOpen,
onPointerDown,
onScrollChange: rerenderCommentIcons,
validateEmbeddable: true,
},
<>
{excalidrawAPI && (
<Footer>
<CustomFooter
excalidrawAPI={excalidrawAPI}
excalidrawLib={excalidrawLib}
/>
</Footer>
)}
<WelcomeScreen />
<Sidebar name="custom">
<Sidebar.Tabs>
<Sidebar.Header />
<Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
<Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
</Sidebar.Tabs>
</Sidebar>
<Sidebar.Trigger
name="custom"
tab="one"
style={{
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
bottom: "20px",
zIndex: 9999999999999999,
}}
>
Toggle Custom Sidebar
</Sidebar.Trigger>
{renderMenu()}
{excalidrawAPI && (
<TTDDialogTrigger icon={<span>😀</span>}>
Text to diagram
</TTDDialogTrigger>
)}
<TTDDialog
onTextSubmit={async (_) => {
console.info("submit");
// sleep for 2s
await new Promise((resolve) => setTimeout(resolve, 2000));
throw new Error("error, go away now");
// return "dummy";
}}
/>
</>,
);
return newElement;
};
const renderTopRightUI = (isMobile: boolean) => {
return (
<>
@ -334,8 +444,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
pointerDownState: PointerDownState,
) => {
return withBatchedUpdates((event) => {
window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove);
window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp);
window.removeEventListener("pointermove", pointerDownState.onMove);
window.removeEventListener("pointerup", pointerDownState.onUp);
excalidrawAPI?.setActiveTool({ type: "selection" });
const distance = distance2d(
pointerDownState.x,
@ -399,8 +509,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
onPointerMoveFromPointerDownHandler(pointerDownState);
const onPointerUp =
onPointerUpFromPointerDownHandler(pointerDownState);
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
pointerDownState.onMove = onPointerMove;
pointerDownState.onUp = onPointerUp;
@ -492,7 +602,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
}}
onBlur={saveComment}
onKeyDown={(event) => {
if (!event.shiftKey && event.key === KEYS.ENTER) {
if (!event.shiftKey && event.key === "Enter") {
event.preventDefault();
saveComment();
}
@ -525,7 +635,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.Help />
{excalidrawAPI && <MobileFooter excalidrawAPI={excalidrawAPI} />}
{excalidrawAPI && (
<MobileFooter
excalidrawLib={excalidrawLib}
excalidrawAPI={excalidrawAPI}
/>
)}
</MainMenu>
);
};
@ -606,6 +721,16 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
/>
Switch to Dark Theme
</label>
<label>
<input
type="checkbox"
checked={disableImageTool === true}
onChange={() => {
setDisableImageTool(!disableImageTool);
}}
/>
Disable Image Tool
</label>
<label>
<input
type="checkbox"
@ -664,66 +789,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
</div>
</div>
<div className="excalidraw-wrapper">
<Excalidraw
ref={(api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api)}
initialData={initialStatePromiseRef.current.promise}
onChange={(elements, state) => {
console.info("Elements :", elements, "State : ", state);
}}
onPointerUpdate={(payload: {
pointer: { x: number; y: number };
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => setPointerData(payload)}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
theme={theme}
name="Custom name of drawing"
UIOptions={{
canvasActions: {
loadScene: false,
},
}}
renderTopRightUI={renderTopRightUI}
onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown}
onScrollChange={rerenderCommentIcons}
// allow all urls
validateEmbeddable={true}
>
{excalidrawAPI && (
<Footer>
<CustomFooter excalidrawAPI={excalidrawAPI} />
</Footer>
)}
<WelcomeScreen />
<Sidebar name="custom">
<Sidebar.Tabs>
<Sidebar.Header />
<Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
<Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
</Sidebar.Tabs>
</Sidebar>
<Sidebar.Trigger
name="custom"
tab="one"
style={{
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
bottom: "20px",
zIndex: 9999999999999999,
}}
>
Toggle Custom Sidebar
</Sidebar.Trigger>
{renderMenu()}
</Excalidraw>
{renderExcalidraw(children)}
{Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
{comment && renderComment()}
</div>
@ -806,7 +872,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
files: excalidrawAPI.getFiles(),
});
const ctx = canvas.getContext("2d")!;
ctx.font = "30px Virgil";
ctx.font = "30px Excalifont";
ctx.strokeText("My custom text", 50, 60);
setCanvasUrl(canvas.toDataURL());
}}
@ -827,7 +893,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
files: excalidrawAPI.getFiles(),
});
const ctx = canvas.getContext("2d")!;
ctx.font = "30px Virgil";
ctx.font = "30px Excalifont";
ctx.strokeText("My custom text", 50, 60);
setCanvasUrl(canvas.toDataURL());
}}

View File

@ -0,0 +1,27 @@
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
import CustomFooter from "./CustomFooter";
import type * as TExcalidraw from "@excalidraw/excalidraw";
const MobileFooter = ({
excalidrawAPI,
excalidrawLib,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
excalidrawLib: typeof TExcalidraw;
}) => {
const { useDevice, Footer } = excalidrawLib;
const device = useDevice();
if (device.editor.isMobile) {
return (
<Footer>
<CustomFooter
excalidrawAPI={excalidrawAPI}
excalidrawLib={excalidrawLib}
/>
</Footer>
);
}
return null;
};
export default MobileFooter;

View File

@ -1,5 +1,6 @@
import React, { useState } from "react";
import { useState } from "react";
import "./ExampleSidebar.scss";
export default function Sidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);

View File

@ -1,5 +1,5 @@
import { ExcalidrawElementSkeleton } from "../../../data/transform";
import { FileId } from "../../../element/types";
import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/data/transform";
import type { FileId } from "@excalidraw/excalidraw/element/types";
const elements: ExcalidrawElementSkeleton[] = [
{
@ -7,6 +7,7 @@ const elements: ExcalidrawElementSkeleton[] = [
x: 10,
y: 10,
strokeWidth: 2,
id: "1",
},
{
type: "diamond",
@ -19,6 +20,7 @@ const elements: ExcalidrawElementSkeleton[] = [
strokeColor: "#099268",
fontSize: 30,
},
id: "2",
},
{
type: "arrow",
@ -36,10 +38,15 @@ const elements: ExcalidrawElementSkeleton[] = [
height: 230,
fileId: "rocket" as FileId,
},
{
type: "frame",
children: ["1", "2"],
name: "My frame",
},
];
export default {
elements,
appState: { viewBackgroundColor: "#AFEEEE", currentItemFontFamily: 1 },
appState: { viewBackgroundColor: "#AFEEEE", currentItemFontFamily: 5 },
scrollToContent: true,
libraryItems: [
[

View File

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

View File

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

View File

@ -0,0 +1,146 @@
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";
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
const INPUT_CHANGE_INTERVAL_MS = 500;
export type ResolvablePromise<T> = Promise<T> & {
resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void;
reject: (error: Error) => void;
};
export const resolvablePromise = <T>() => {
let resolve!: any;
let reject!: any;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
(promise as any).resolve = resolve;
(promise as any).reject = reject;
return promise as ResolvablePromise<T>;
};
export const distance2d = (x1: number, y1: number, x2: number, y2: number) => {
const xd = x2 - x1;
const yd = y2 - y1;
return Math.hypot(xd, yd);
};
export const fileOpen = <M extends boolean | undefined = false>(opts: {
extensions?: FILE_EXTENSION[];
description: string;
multiple?: M;
}): Promise<M extends false | undefined ? File : File[]> => {
// an unsafe TS hack, alas not much we can do AFAIK
type RetType = M extends false | undefined ? File : File[];
const mimeTypes = opts.extensions?.reduce((mimeTypes, type) => {
mimeTypes.push(MIME_TYPES[type]);
return mimeTypes;
}, [] as string[]);
const extensions = opts.extensions?.reduce((acc, ext) => {
if (ext === "jpg") {
return acc.concat(".jpg", ".jpeg");
}
return acc.concat(`.${ext}`);
}, [] as string[]);
return _fileOpen({
description: opts.description,
extensions,
mimeTypes,
multiple: opts.multiple ?? false,
legacySetup: (resolve, reject, input) => {
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
const focusHandler = () => {
checkForFile();
document.addEventListener("keyup", scheduleRejection);
document.addEventListener("pointerup", scheduleRejection);
scheduleRejection();
};
const checkForFile = () => {
// this hack might not work when expecting multiple files
if (input.files?.length) {
const ret = opts.multiple ? [...input.files] : input.files[0];
resolve(ret as RetType);
}
};
requestAnimationFrame(() => {
window.addEventListener("focus", focusHandler);
});
const interval = window.setInterval(() => {
checkForFile();
}, INPUT_CHANGE_INTERVAL_MS);
return (rejectPromise) => {
clearInterval(interval);
scheduleRejection.cancel();
window.removeEventListener("focus", focusHandler);
document.removeEventListener("keyup", scheduleRejection);
document.removeEventListener("pointerup", scheduleRejection);
if (rejectPromise) {
// so that something is shown in console if we need to debug this
console.warn("Opening the file was canceled (legacy-fs).");
rejectPromise(new AbortError());
}
};
},
}) as Promise<RetType>;
};
export const debounce = <T extends any[]>(
fn: (...args: T) => void,
timeout: number,
) => {
let handle = 0;
let lastArgs: T | null = null;
const ret = (...args: T) => {
lastArgs = args;
clearTimeout(handle);
handle = window.setTimeout(() => {
lastArgs = null;
fn(...args);
}, timeout);
};
ret.flush = () => {
clearTimeout(handle);
if (lastArgs) {
const _lastArgs = lastArgs;
lastArgs = null;
fn(..._lastArgs);
}
};
ret.cancel = () => {
lastArgs = null;
clearTimeout(handle);
};
return ret;
};
export const withBatchedUpdates = <
TFunction extends ((event: any) => void) | (() => void),
>(
func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,
) =>
((event) => {
unstable_batchedUpdates(func as TFunction, event);
}) as TFunction;
/**
* barches React state updates and throttles the calls to a single call per
* animation frame
*/
export const withBatchedUpdatesThrottled = <
TFunction extends ((event: any) => void) | (() => void),
>(
func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,
) => {
// @ts-ignore
return throttleRAF<Parameters<TFunction>>(((event) => {
unstable_batchedUpdates(func, event);
}) as TFunction);
};

View File

@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# copied assets
public/**/*.woff2

View File

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3005) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
distDir: "build",
typescript: {
// The ts config doesn't work with `jsx: preserve" and if updated to `react-jsx` it gets ovewritten by next js throwing ts errors hence I am ignoring build errors until this is fixed.
ignoreBuildErrors: true,
},
// This is needed as in pages router the code for importing types throws error as its outside next js app
transpilePackages: ["../"],
};
module.exports = nextConfig;

View File

@ -0,0 +1,26 @@
{
"name": "with-nextjs",
"version": "0.1.0",
"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",
"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"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@ -0,0 +1,26 @@
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
// with ssr false
const ExcalidrawWithClientOnly = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<>
<a href="/excalidraw-in-pages">Switch to Pages router</a>
<h1 className="page-title">App Router</h1>
<Script id="load-env-variables" strategy="beforeInteractive">
{`window["EXCALIDRAW_ASSET_PATH"] = window.origin;`}
</Script>
{/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
<ExcalidrawWithClientOnly />
</>
);
}

View File

@ -0,0 +1,15 @@
* {
box-sizing: border-box;
font-family: sans-serif;
}
a {
color: #1c7ed6;
font-size: 20px;
text-decoration: none;
font-weight: 500;
}
.page-title {
text-align: center;
}

View File

@ -0,0 +1,22 @@
"use client";
import * as excalidrawLib from "@excalidraw/excalidraw";
import { Excalidraw } from "@excalidraw/excalidraw";
import App from "../../components/ExampleApp";
import "@excalidraw/excalidraw/index.css";
const ExcalidrawWrapper: React.FC = () => {
return (
<>
<App
appTitle={"Excalidraw with Nextjs Example"}
useCustom={(api: any, args?: any[]) => {}}
excalidrawLib={excalidrawLib}
>
<Excalidraw />
</App>
</>
);
};
export default ExcalidrawWrapper;

View File

@ -0,0 +1,22 @@
import dynamic from "next/dynamic";
import "../common.scss";
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
// with ssr false
const Excalidraw = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<>
<a href="/">Switch to App router</a>
<h1 className="page-title">Pages Router</h1>
{/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
<Excalidraw />
</>
);
}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
},
"forceConsistentCasingInFileNames": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "build/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,3 @@
{
"outputDirectory": "build"
}

View File

@ -0,0 +1,252 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@excalidraw/excalidraw@workspace:^":
version "0.17.2"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.2.tgz#9a636a1e6bb3c88c5883347d3a7e75e9cce8ab96"
integrity sha512-7pqUWD8+mPjDhF4XxG3gw4rvE2JGaLW3Vss5UZfTbITPxAtFaGEc1K081bncitnaYhUwN9ENJE0i87QB3poDwQ==
"@next/env@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a"
integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==
"@next/swc-darwin-arm64@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618"
integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==
"@next/swc-darwin-x64@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b"
integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==
"@next/swc-linux-arm64-gnu@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21"
integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==
"@next/swc-linux-arm64-musl@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd"
integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==
"@next/swc-linux-x64-gnu@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32"
integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==
"@next/swc-linux-x64-musl@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247"
integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==
"@next/swc-win32-arm64-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3"
integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==
"@next/swc-win32-ia32-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600"
integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==
"@next/swc-win32-x64-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1"
integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==
"@swc/helpers@0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
dependencies:
tslib "^2.4.0"
"@types/node@^20":
version "20.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
dependencies:
undici-types "~5.26.4"
"@types/prop-types@*":
version "15.7.11"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
"@types/react-dom@^18":
version "18.2.18"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18":
version "18.2.47"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40"
integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.8"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
busboy@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
dependencies:
streamsearch "^1.1.0"
caniuse-lite@^1.0.30001406:
version "1.0.30001576"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4"
integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
graceful-fs@^4.1.2, graceful-fs@^4.2.11:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
nanoid@^3.3.6:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
next@14.0.4:
version "14.0.4"
resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc"
integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==
dependencies:
"@next/env" "14.0.4"
"@swc/helpers" "0.5.2"
busboy "1.6.0"
caniuse-lite "^1.0.30001406"
graceful-fs "^4.2.11"
postcss "8.4.31"
styled-jsx "5.1.1"
watchpack "2.4.0"
optionalDependencies:
"@next/swc-darwin-arm64" "14.0.4"
"@next/swc-darwin-x64" "14.0.4"
"@next/swc-linux-arm64-gnu" "14.0.4"
"@next/swc-linux-arm64-musl" "14.0.4"
"@next/swc-linux-x64-gnu" "14.0.4"
"@next/swc-linux-x64-musl" "14.0.4"
"@next/swc-win32-arm64-msvc" "14.0.4"
"@next/swc-win32-ia32-msvc" "14.0.4"
"@next/swc-win32-x64-msvc" "14.0.4"
path2d-polyfill@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391"
integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@8.4.31:
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
react-dom@^18:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react@^18:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
styled-jsx@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
dependencies:
client-only "0.0.1"
tslib@^2.4.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
typescript@^5:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
watchpack@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"

View File

@ -0,0 +1,2 @@
# copied assets
public/**/*.woff2

View File

@ -11,19 +11,23 @@
<title>React App</title>
<script>
window.name = "codesandbox";
window.EXCALIDRAW_ASSET_PATH = window.origin;
</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>
<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>
<!-- This is so that we use the bundled excalidraw.development.js file instead
of the actual source code -->
<script src="./excalidraw.development.js"></script>
<script type="module">
import * as ExcalidrawLib from "@excalidraw/excalidraw";
<script src="./bundle.js"></script>
console.log(ExcalidrawLib);
window.ExcalidrawLib = ExcalidrawLib;
</script>
<script type="module" src="index.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,28 @@
import App from "../components/ExampleApp";
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
declare global {
interface Window {
ExcalidrawLib: typeof TExcalidraw;
}
}
const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement);
const { Excalidraw } = window.ExcalidrawLib;
root.render(
<StrictMode>
<App
appTitle={"Excalidraw Example"}
useCustom={(api: any, args?: any[]) => {}}
excalidrawLib={window.ExcalidrawLib}
>
<Excalidraw />
</App>
</StrictMode>,
);

View File

@ -0,0 +1,21 @@
{
"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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

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

View File

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

View File

@ -0,0 +1,313 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/aix-ppc64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3"
integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==
"@esbuild/android-arm64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220"
integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==
"@esbuild/android-arm@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c"
integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==
"@esbuild/android-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2"
integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==
"@esbuild/darwin-arm64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf"
integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==
"@esbuild/darwin-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e"
integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==
"@esbuild/freebsd-arm64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a"
integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==
"@esbuild/freebsd-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2"
integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==
"@esbuild/linux-arm64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545"
integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==
"@esbuild/linux-arm@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3"
integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==
"@esbuild/linux-ia32@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4"
integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==
"@esbuild/linux-loong64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121"
integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==
"@esbuild/linux-mips64el@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9"
integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==
"@esbuild/linux-ppc64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912"
integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==
"@esbuild/linux-riscv64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916"
integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==
"@esbuild/linux-s390x@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8"
integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==
"@esbuild/linux-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766"
integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==
"@esbuild/netbsd-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d"
integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==
"@esbuild/openbsd-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2"
integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==
"@esbuild/sunos-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767"
integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==
"@esbuild/win32-arm64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee"
integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==
"@esbuild/win32-ia32@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c"
integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==
"@esbuild/win32-x64@0.19.11":
version "0.19.11"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04"
integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==
"@rollup/rollup-android-arm-eabi@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz#b752b6c88a14ccfcbdf3f48c577ccc3a7f0e66b9"
integrity sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==
"@rollup/rollup-android-arm64@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz#33757c3a448b9ef77b6f6292d8b0ec45c87e9c1a"
integrity sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==
"@rollup/rollup-darwin-arm64@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz#5234ba62665a3f443143bc8bcea9df2cc58f55fb"
integrity sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==
"@rollup/rollup-darwin-x64@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz#981256c054d3247b83313724938d606798a919d1"
integrity sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==
"@rollup/rollup-linux-arm-gnueabihf@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz#120678a5a2b3a283a548dbb4d337f9187a793560"
integrity sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==
"@rollup/rollup-linux-arm64-gnu@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz#c99d857e2372ece544b6f60b85058ad259f64114"
integrity sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==
"@rollup/rollup-linux-arm64-musl@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz#3064060f568a5718c2a06858cd6e6d24f2ff8632"
integrity sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==
"@rollup/rollup-linux-riscv64-gnu@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz#987d30b5d2b992fff07d055015991a57ff55fbad"
integrity sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==
"@rollup/rollup-linux-x64-gnu@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz#85946ee4d068bd12197aeeec2c6f679c94978a49"
integrity sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==
"@rollup/rollup-linux-x64-musl@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz#fe0b20f9749a60eb1df43d20effa96c756ddcbd4"
integrity sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==
"@rollup/rollup-win32-arm64-msvc@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz#422661ef0e16699a234465d15b2c1089ef963b2a"
integrity sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==
"@rollup/rollup-win32-ia32-msvc@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz#7b73a145891c202fbcc08759248983667a035d85"
integrity sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==
"@rollup/rollup-win32-x64-msvc@4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602"
integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==
"@types/estree@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
esbuild@^0.19.3:
version "0.19.11"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96"
integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==
optionalDependencies:
"@esbuild/aix-ppc64" "0.19.11"
"@esbuild/android-arm" "0.19.11"
"@esbuild/android-arm64" "0.19.11"
"@esbuild/android-x64" "0.19.11"
"@esbuild/darwin-arm64" "0.19.11"
"@esbuild/darwin-x64" "0.19.11"
"@esbuild/freebsd-arm64" "0.19.11"
"@esbuild/freebsd-x64" "0.19.11"
"@esbuild/linux-arm" "0.19.11"
"@esbuild/linux-arm64" "0.19.11"
"@esbuild/linux-ia32" "0.19.11"
"@esbuild/linux-loong64" "0.19.11"
"@esbuild/linux-mips64el" "0.19.11"
"@esbuild/linux-ppc64" "0.19.11"
"@esbuild/linux-riscv64" "0.19.11"
"@esbuild/linux-s390x" "0.19.11"
"@esbuild/linux-x64" "0.19.11"
"@esbuild/netbsd-x64" "0.19.11"
"@esbuild/openbsd-x64" "0.19.11"
"@esbuild/sunos-x64" "0.19.11"
"@esbuild/win32-arm64" "0.19.11"
"@esbuild/win32-ia32" "0.19.11"
"@esbuild/win32-x64" "0.19.11"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@^8.4.32:
version "8.4.33"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.0.2"
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
rollup@^4.2.0:
version "4.9.5"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.5.tgz#62999462c90f4c8b5d7c38fc7161e63b29101b05"
integrity sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==
dependencies:
"@types/estree" "1.0.5"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.9.5"
"@rollup/rollup-android-arm64" "4.9.5"
"@rollup/rollup-darwin-arm64" "4.9.5"
"@rollup/rollup-darwin-x64" "4.9.5"
"@rollup/rollup-linux-arm-gnueabihf" "4.9.5"
"@rollup/rollup-linux-arm64-gnu" "4.9.5"
"@rollup/rollup-linux-arm64-musl" "4.9.5"
"@rollup/rollup-linux-riscv64-gnu" "4.9.5"
"@rollup/rollup-linux-x64-gnu" "4.9.5"
"@rollup/rollup-linux-x64-musl" "4.9.5"
"@rollup/rollup-win32-arm64-msvc" "4.9.5"
"@rollup/rollup-win32-ia32-msvc" "4.9.5"
"@rollup/rollup-win32-x64-msvc" "4.9.5"
fsevents "~2.3.2"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
vite@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c"
integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ==
dependencies:
esbuild "^0.19.3"
postcss "^8.4.32"
rollup "^4.2.0"
optionalDependencies:
fsevents "~2.3.3"

1144
excalidraw-app/App.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,15 @@
import { useEffect, useState } from "react";
import { debounce, getVersion, nFormatter } from "../src/utils";
import { debounce, getVersion, nFormatter } from "../packages/excalidraw/utils";
import {
getElementsStorageSize,
getTotalStorageSize,
} from "./data/localStorage";
import { DEFAULT_VERSION } from "../src/constants";
import { t } from "../src/i18n";
import { copyTextToSystemClipboard } from "../src/clipboard";
import { NonDeletedExcalidrawElement } from "../src/element/types";
import { UIAppState } from "../src/types";
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 };
@ -51,39 +52,33 @@ const CustomStats = (props: Props) => {
}
return (
<>
<tr>
<th colSpan={2}>{t("stats.storage")}</th>
</tr>
<tr>
<td>{t("stats.scene")}</td>
<td>{nFormatter(storageSizes.scene, 1)}</td>
</tr>
<tr>
<td>{t("stats.total")}</td>
<td>{nFormatter(storageSizes.total, 1)}</td>
</tr>
<tr>
<th colSpan={2}>{t("stats.version")}</th>
</tr>
<tr>
<td
colSpan={2}
style={{ textAlign: "center", cursor: "pointer" }}
onClick={async () => {
try {
await copyTextToSystemClipboard(getVersion());
props.setToast(t("toast.copyToClipboard"));
} catch {}
}}
title={t("stats.versionCopy")}
>
{timestamp}
<br />
{hash}
</td>
</tr>
</>
<Stats.StatsRows order={-1}>
<Stats.StatsRow heading>{t("stats.version")}</Stats.StatsRow>
<Stats.StatsRow
style={{ textAlign: "center", cursor: "pointer" }}
onClick={async () => {
try {
await copyTextToSystemClipboard(getVersion());
props.setToast(t("toast.copyToClipboard"));
} catch {}
}}
title={t("stats.versionCopy")}
>
{timestamp}
<br />
{hash}
</Stats.StatsRow>
<Stats.StatsRow heading>{t("stats.storage")}</Stats.StatsRow>
<Stats.StatsRow columns={2}>
<div>{t("stats.scene")}</div>
<div>{nFormatter(storageSizes.scene, 1)}</div>
</Stats.StatsRow>
<Stats.StatsRow columns={2}>
<div>{t("stats.total")}</div>
<div>{nFormatter(storageSizes.total, 1)}</div>
</Stats.StatsRow>
</Stats.StatsRows>
);
};

View File

@ -0,0 +1,222 @@
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";
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
const EXCALIDRAW_PLUS_ORIGIN = import.meta.env.VITE_APP_PLUS_APP;
// -----------------------------------------------------------------------------
// outgoing message
// -----------------------------------------------------------------------------
type MESSAGE_REQUEST_SCENE = {
type: "REQUEST_SCENE";
jwt: string;
};
type MESSAGE_FROM_PLUS = MESSAGE_REQUEST_SCENE;
// incoming messages
// -----------------------------------------------------------------------------
type MESSAGE_READY = { type: "READY" };
type MESSAGE_ERROR = { type: "ERROR"; message: string };
type MESSAGE_SCENE_DATA = {
type: "SCENE_DATA";
elements: OrderedExcalidrawElement[];
appState: Pick<AppState, "viewBackgroundColor">;
files: { loadedFiles: BinaryFileData[]; erroredFiles: Map<FileId, true> };
};
type MESSAGE_FROM_EDITOR = MESSAGE_ERROR | MESSAGE_SCENE_DATA | MESSAGE_READY;
// -----------------------------------------------------------------------------
const parseSceneData = async ({
rawElementsString,
rawAppStateString,
}: {
rawElementsString: string | null;
rawAppStateString: string | null;
}): Promise<MESSAGE_SCENE_DATA> => {
if (!rawElementsString || !rawAppStateString) {
throw new ExcalidrawError("Elements or appstate is missing.");
}
try {
const elements = JSON.parse(
rawElementsString,
) as OrderedExcalidrawElement[];
if (!elements.length) {
throw new ExcalidrawError("Scene is empty, nothing to export.");
}
const appState = JSON.parse(rawAppStateString) as Pick<
AppState,
"viewBackgroundColor"
>;
const fileIds = elements.reduce((acc, el) => {
if ("fileId" in el && el.fileId) {
acc.push(el.fileId);
}
return acc;
}, [] as FileId[]);
const files = await LocalData.fileStorage.getFiles(fileIds);
return {
type: "SCENE_DATA",
elements,
appState,
files,
};
} catch (error: any) {
throw error instanceof ExcalidrawError
? error
: new ExcalidrawError("Failed to parse scene data.");
}
};
const verifyJWT = async ({
token,
publicKey,
}: {
token: string;
publicKey: string;
}) => {
try {
if (!publicKey) {
throw new ExcalidrawError("Public key is undefined");
}
const [header, payload, signature] = token.split(".");
if (!header || !payload || !signature) {
throw new ExcalidrawError("Invalid JWT format");
}
// JWT is using Base64URL encoding
const decodedPayload = base64urlToString(payload);
const decodedSignature = base64urlToString(signature);
const data = `${header}.${payload}`;
const signatureArrayBuffer = Uint8Array.from(decodedSignature, (c) =>
c.charCodeAt(0),
);
const keyData = publicKey.replace(/-----\w+ PUBLIC KEY-----/g, "");
const keyArrayBuffer = Uint8Array.from(atob(keyData), (c) =>
c.charCodeAt(0),
);
const key = await crypto.subtle.importKey(
"spki",
keyArrayBuffer,
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
true,
["verify"],
);
const isValid = await crypto.subtle.verify(
"RSASSA-PKCS1-v1_5",
key,
signatureArrayBuffer,
new TextEncoder().encode(data),
);
if (!isValid) {
throw new Error("Invalid JWT");
}
const parsedPayload = JSON.parse(decodedPayload);
// Check for expiration
const currentTime = Math.floor(Date.now() / 1000);
if (parsedPayload.exp && parsedPayload.exp < currentTime) {
throw new Error("JWT has expired");
}
} catch (error) {
console.error("Failed to verify JWT:", error);
throw new Error(error instanceof Error ? error.message : "Invalid JWT");
}
};
export const ExcalidrawPlusIframeExport = () => {
const readyRef = useRef(false);
useLayoutEffect(() => {
const handleMessage = async (event: MessageEvent<MESSAGE_FROM_PLUS>) => {
if (event.origin !== EXCALIDRAW_PLUS_ORIGIN) {
throw new ExcalidrawError("Invalid origin");
}
if (event.data.type === EVENT_REQUEST_SCENE) {
if (!event.data.jwt) {
throw new ExcalidrawError("JWT is missing");
}
try {
try {
await verifyJWT({
token: event.data.jwt,
publicKey: import.meta.env.VITE_APP_PLUS_EXPORT_PUBLIC_KEY,
});
} catch (error: any) {
console.error(`Failed to verify JWT: ${error.message}`);
throw new ExcalidrawError("Failed to verify JWT");
}
const parsedSceneData: MESSAGE_SCENE_DATA = await parseSceneData({
rawAppStateString: localStorage.getItem(
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
),
rawElementsString: localStorage.getItem(
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
),
});
event.source!.postMessage(parsedSceneData, {
targetOrigin: EXCALIDRAW_PLUS_ORIGIN,
});
} catch (error) {
const responseData: MESSAGE_ERROR = {
type: "ERROR",
message:
error instanceof ExcalidrawError
? error.message
: "Failed to export scene data",
};
event.source!.postMessage(responseData, {
targetOrigin: EXCALIDRAW_PLUS_ORIGIN,
});
}
}
};
window.addEventListener("message", handleMessage);
// so we don't send twice in StrictMode
if (!readyRef.current) {
readyRef.current = true;
const message: MESSAGE_FROM_EDITOR = { type: "READY" };
window.parent.postMessage(message, EXCALIDRAW_PLUS_ORIGIN);
}
return () => {
window.removeEventListener("message", handleMessage);
};
}, []);
// Since this component is expected to run in a hidden iframe on Excaildraw+,
// it doesn't need to render anything. All the data we need is available in
// LocalStorage and IndexedDB. It only needs to handle the messaging between
// the parent window and the iframe with the relevant data.
return null;
};

View File

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

View File

@ -0,0 +1,25 @@
import LanguageDetector from "i18next-browser-languagedetector";
import { defaultLang, languages } from "../../packages/excalidraw";
export const languageDetector = new LanguageDetector();
languageDetector.init({
languageUtils: {},
});
export const getPreferredLanguage = () => {
const detectedLanguages = languageDetector.detect();
const detectedLanguage = Array.isArray(detectedLanguages)
? detectedLanguages[0]
: detectedLanguages;
const initialLanguage =
(detectedLanguage
? // region code may not be defined if user uses generic preferred language
// (e.g. chinese vs instead of chinese-simplified)
languages.find((lang) => lang.code.startsWith(detectedLanguage))?.code
: null) || defaultLang.code;
return initialLanguage;
};

View File

@ -0,0 +1,15 @@
import { atom, useAtom } from "jotai";
import { useEffect } from "react";
import { getPreferredLanguage, languageDetector } from "./language-detector";
export const appLangCodeAtom = atom(getPreferredLanguage());
export const useAppLangCode = () => {
const [langCode, setLangCode] = useAtom(appLangCodeAtom);
useEffect(() => {
languageDetector.cacheUserLanguage(langCode);
}, [langCode]);
return [langCode, setLangCode] as const;
};

View File

@ -15,11 +15,17 @@ export const FILE_CACHE_MAX_AGE_SEC = 31536000;
export const WS_EVENTS = {
SERVER_VOLATILE: "server-volatile-broadcast",
SERVER: "server-broadcast",
};
USER_FOLLOW_CHANGE: "user-follow",
USER_FOLLOW_ROOM_CHANGE: "user-follow-room-change",
} as const;
export enum WS_SCENE_EVENT_TYPES {
export enum WS_SUBTYPES {
INVALID_RESPONSE = "INVALID_RESPONSE",
INIT = "SCENE_INIT",
UPDATE = "SCENE_UPDATE",
MOUSE_LOCATION = "MOUSE_LOCATION",
IDLE_STATUS = "IDLE_STATUS",
USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS",
}
export const FIREBASE_STORAGE_PREFIXES = {
@ -33,10 +39,15 @@ export const STORAGE_KEYS = {
LOCAL_STORAGE_ELEMENTS: "excalidraw",
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
LOCAL_STORAGE_THEME: "excalidraw-theme",
LOCAL_STORAGE_DEBUG: "excalidraw-debug",
VERSION_DATA_STATE: "version-dataState",
VERSION_FILES: "version-files",
IDB_LIBRARY: "excalidraw-library",
// do not use apart from migrations
__LEGACY_LOCAL_STORAGE_LIBRARY: "excalidraw-library",
} as const;
export const COOKIES = {

View File

@ -1,39 +1,51 @@
import throttle from "lodash.throttle";
import { PureComponent } from "react";
import { ExcalidrawImperativeAPI } from "../../src/types";
import { ErrorDialog } from "../../src/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../src/constants";
import { ImportedDataState } from "../../src/data/types";
import {
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";
import type {
ExcalidrawElement,
FileId,
InitializedExcalidrawImageElement,
} from "../../src/element/types";
OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import {
StoreAction,
getSceneVersion,
restoreElements,
} from "../../src/packages/excalidraw/index";
import { Collaborator, Gesture } from "../../src/types";
zoomToFitBounds,
reconcileElements,
} from "../../packages/excalidraw";
import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
import {
assertNever,
preventUnload,
resolvablePromise,
withBatchedUpdates,
} from "../../src/utils";
throttleRAF,
} from "../../packages/excalidraw/utils";
import {
CURSOR_SYNC_TIMEOUT,
FILE_UPLOAD_MAX_BYTES,
FIREBASE_STORAGE_PREFIXES,
INITIAL_SCENE_UPDATE_TIMEOUT,
LOAD_IMAGES_TIMEOUT,
WS_SCENE_EVENT_TYPES,
WS_SUBTYPES,
SYNC_FULL_SCENE_INTERVAL_MS,
WS_EVENTS,
} from "../app_constants";
import type {
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
import {
generateCollaborationLinkData,
getCollaborationLink,
getCollabServer,
getSyncableElements,
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
import {
isSavedToFirebase,
@ -47,42 +59,51 @@ import {
saveUsernameToLocalStorage,
} from "../data/localStorage";
import Portal from "./Portal";
import RoomDialog from "./RoomDialog";
import { t } from "../../src/i18n";
import { UserIdleState } from "../../src/types";
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../src/constants";
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 "../../src/errors";
import { AbortError } from "../../packages/excalidraw/errors";
import {
isImageElement,
isInitializedImageElement,
} from "../../src/element/typeChecks";
import { newElementWith } from "../../src/element/mutateElement";
import {
ReconciledElements,
reconcileElements as _reconcileElements,
} from "./reconciliation";
import { decryptData } from "../../src/data/encryption";
} 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 { atom, useAtom } from "jotai";
import { atom } from "jotai";
import { appJotaiStore } 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 type {
ReconciledExcalidrawElement,
RemoteExcalidrawElement,
} from "../../packages/excalidraw/data/reconcile";
export const collabAPIAtom = atom<CollabAPI | null>(null);
export const collabDialogShownAtom = atom(false);
export const isCollaboratingAtom = atom(false);
export const isOfflineAtom = atom(false);
interface CollabState {
errorMessage: string;
errorMessage: string | null;
/** errors related to saving */
dialogNotifiedErrors: Record<string, boolean>;
username: string;
activeRoomLink: string;
activeRoomLink: string | null;
}
export const activeRoomLinkAtom = atom<string | null>(null);
type CollabInstance = InstanceType<typeof Collab>;
export interface CollabAPI {
@ -93,32 +114,34 @@ export interface CollabAPI {
stopCollaboration: CollabInstance["stopCollaboration"];
syncElements: CollabInstance["syncElements"];
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
setUsername: (username: string) => void;
setUsername: CollabInstance["setUsername"];
getUsername: CollabInstance["getUsername"];
getActiveRoomLink: CollabInstance["getActiveRoomLink"];
setCollabError: CollabInstance["setErrorDialog"];
}
interface PublicProps {
interface CollabProps {
excalidrawAPI: ExcalidrawImperativeAPI;
}
type Props = PublicProps & { modalIsShown: boolean };
class Collab extends PureComponent<Props, CollabState> {
class Collab extends PureComponent<CollabProps, CollabState> {
portal: Portal;
fileManager: FileManager;
excalidrawAPI: Props["excalidrawAPI"];
excalidrawAPI: CollabProps["excalidrawAPI"];
activeIntervalId: number | null;
idleTimeoutId: number | null;
private socketInitializationTimer?: number;
private lastBroadcastedOrReceivedSceneVersion: number = -1;
private collaborators = new Map<string, Collaborator>();
private collaborators = new Map<SocketId, Collaborator>();
constructor(props: Props) {
constructor(props: CollabProps) {
super(props);
this.state = {
errorMessage: "",
errorMessage: null,
dialogNotifiedErrors: {},
username: importUsernameFromLocalStorage() || "",
activeRoomLink: "",
activeRoomLink: null,
};
this.portal = new Portal(this);
this.fileManager = new FileManager({
@ -136,7 +159,7 @@ class Collab extends PureComponent<Props, CollabState> {
throw new AbortError();
}
return saveFilesToFirebase({
const { savedFiles, erroredFiles } = await saveFilesToFirebase({
prefix: `${FIREBASE_STORAGE_PREFIXES.collabFiles}/${roomId}`,
files: await encodeFilesForUpload({
files: addedFiles,
@ -144,6 +167,29 @@ class Collab extends PureComponent<Props, CollabState> {
maxBytes: FILE_UPLOAD_MAX_BYTES,
}),
});
return {
savedFiles: savedFiles.reduce(
(acc: Map<FileId, BinaryFileData>, id) => {
const fileData = addedFiles.get(id);
if (fileData) {
acc.set(id, fileData);
}
return acc;
},
new Map(),
),
erroredFiles: erroredFiles.reduce(
(acc: Map<FileId, BinaryFileData>, id) => {
const fileData = addedFiles.get(id);
if (fileData) {
acc.set(id, fileData);
}
return acc;
},
new Map(),
),
};
},
});
this.excalidrawAPI = props.excalidrawAPI;
@ -151,12 +197,28 @@ class Collab extends PureComponent<Props, CollabState> {
this.idleTimeoutId = null;
}
private onUmmount: (() => void) | null = null;
componentDidMount() {
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
window.addEventListener("online", this.onOfflineStatusToggle);
window.addEventListener("offline", this.onOfflineStatusToggle);
window.addEventListener(EVENT.UNLOAD, this.onUnload);
const unsubOnUserFollow = this.excalidrawAPI.onUserFollow((payload) => {
this.portal.socket && this.portal.broadcastUserFollowed(payload);
});
const throttledRelayUserViewportBounds = throttleRAF(
this.relayVisibleSceneBounds,
);
const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() =>
throttledRelayUserViewportBounds(),
);
this.onUmmount = () => {
unsubOnUserFollow();
unsubOnScrollChange();
};
this.onOfflineStatusToggle();
const collabAPI: CollabAPI = {
@ -167,6 +229,9 @@ class Collab extends PureComponent<Props, CollabState> {
fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
stopCollaboration: this.stopCollaboration,
setUsername: this.setUsername,
getUsername: this.getUsername,
getActiveRoomLink: this.getActiveRoomLink,
setCollabError: this.setErrorDialog,
};
appJotaiStore.set(collabAPIAtom, collabAPI);
@ -204,6 +269,7 @@ class Collab extends PureComponent<Props, CollabState> {
window.clearTimeout(this.idleTimeoutId);
this.idleTimeoutId = null;
}
this.onUmmount?.();
}
isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
@ -238,24 +304,39 @@ class Collab extends PureComponent<Props, CollabState> {
syncableElements: readonly SyncableExcalidrawElement[],
) => {
try {
const savedData = await saveToFirebase(
const storedElements = await saveToFirebase(
this.portal,
syncableElements,
this.excalidrawAPI.getAppState(),
);
if (this.isCollaborating() && savedData && savedData.reconciledElements) {
this.handleRemoteSceneUpdate(
this.reconcileElements(savedData.reconciledElements),
);
this.resetErrorIndicator();
if (this.isCollaborating() && storedElements) {
this.handleRemoteSceneUpdate(this._reconcileElements(storedElements));
}
} catch (error: any) {
this.setState({
// firestore doesn't return a specific error code when size exceeded
errorMessage: /is longer than.*?bytes/.test(error.message)
? t("errors.collabSaveFailed_sizeExceeded")
: t("errors.collabSaveFailed"),
});
const errorMessage = /is longer than.*?bytes/.test(error.message)
? t("errors.collabSaveFailed_sizeExceeded")
: t("errors.collabSaveFailed");
if (
!this.state.dialogNotifiedErrors[errorMessage] ||
!this.isCollaborating()
) {
this.setErrorDialog(errorMessage);
this.setState({
dialogNotifiedErrors: {
...this.state.dialogNotifiedErrors,
[errorMessage]: true,
},
});
}
if (this.isCollaborating()) {
this.setErrorIndicator(errorMessage);
}
console.error(error);
}
};
@ -264,6 +345,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.queueBroadcastAllElements.cancel();
this.queueSaveToFirebase.cancel();
this.loadImageFiles.cancel();
this.resetErrorIndicator(true);
this.saveCollabRoomToFirebase(
getSyncableElements(
@ -302,7 +384,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.excalidrawAPI.updateScene({
elements,
commitToHistory: false,
storeAction: StoreAction.UPDATE,
});
}
};
@ -313,9 +395,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.fileManager.reset();
if (!opts?.isUnload) {
this.setIsCollaborating(false);
this.setState({
activeRoomLink: "",
});
this.setActiveRoomLink(null);
this.collaborators = new Map();
this.excalidrawAPI.updateScene({
collaborators: this.collaborators,
@ -339,7 +419,7 @@ class Collab extends PureComponent<Props, CollabState> {
.filter((element) => {
return (
isInitializedImageElement(element) &&
!this.fileManager.isFileHandled(element.fileId) &&
!this.fileManager.isFileTracked(element.fileId) &&
!element.isDeleted &&
(opts.forceFetchFiles
? element.status !== "pending" ||
@ -356,7 +436,7 @@ class Collab extends PureComponent<Props, CollabState> {
iv: Uint8Array,
encryptedData: ArrayBuffer,
decryptionKey: string,
) => {
): Promise<ValueOf<SocketUpdateDataSource>> => {
try {
const decrypted = await decryptData(iv, encryptedData, decryptionKey);
@ -368,7 +448,7 @@ class Collab extends PureComponent<Props, CollabState> {
window.alert(t("alerts.decryptFailed"));
console.error(error);
return {
type: "INVALID_RESPONSE",
type: WS_SUBTYPES.INVALID_RESPONSE,
};
}
};
@ -377,11 +457,11 @@ class Collab extends PureComponent<Props, CollabState> {
startCollaboration = async (
existingRoomLinkData: null | { roomId: string; roomKey: string },
): Promise<ImportedDataState | null> => {
) => {
if (!this.state.username) {
import("@excalidraw/random-username").then(({ getRandomUsername }) => {
const username = getRandomUsername();
this.onUsernameChange(username);
this.setUsername(username);
});
}
@ -403,7 +483,11 @@ class Collab extends PureComponent<Props, CollabState> {
);
}
const scenePromise = resolvablePromise<ImportedDataState | null>();
// TODO: `ImportedDataState` type here seems abused
const scenePromise = resolvablePromise<
| (ImportedDataState & { elements: readonly OrderedExcalidrawElement[] })
| null
>();
this.setIsCollaborating(true);
LocalData.pauseSave("collaboration");
@ -423,13 +507,9 @@ class Collab extends PureComponent<Props, CollabState> {
this.fallbackInitializationHandler = fallbackInitializationHandler;
try {
const socketServerData = await getCollabServer();
this.portal.socket = this.portal.open(
socketIOClient(socketServerData.url, {
transports: socketServerData.polling
? ["websocket", "polling"]
: ["websocket"],
socketIOClient(import.meta.env.VITE_APP_WS_SERVER_URL, {
transports: ["websocket", "polling"],
}),
roomId,
roomKey,
@ -438,7 +518,7 @@ class Collab extends PureComponent<Props, CollabState> {
this.portal.socket.once("connect_error", fallbackInitializationHandler);
} catch (error: any) {
console.error(error);
this.setState({ errorMessage: error.message });
this.setErrorDialog(error.message);
return null;
}
@ -449,14 +529,13 @@ class Collab extends PureComponent<Props, CollabState> {
}
return element;
});
// remove deleted elements from elements array & history to ensure we don't
// remove deleted elements from elements array to ensure we don't
// expose potentially sensitive user data in case user manually deletes
// existing elements (or clears scene), which would otherwise be persisted
// to database even if deleted before creating the room.
this.excalidrawAPI.history.clear();
this.excalidrawAPI.updateScene({
elements,
commitToHistory: true,
storeAction: StoreAction.UPDATE,
});
this.saveCollabRoomToFirebase(getSyncableElements(elements));
@ -484,16 +563,15 @@ class Collab extends PureComponent<Props, CollabState> {
);
switch (decryptedData.type) {
case "INVALID_RESPONSE":
case WS_SUBTYPES.INVALID_RESPONSE:
return;
case WS_SCENE_EVENT_TYPES.INIT: {
case WS_SUBTYPES.INIT: {
if (!this.portal.socketInitialized) {
this.initializeRoom({ fetchScene: false });
const remoteElements = decryptedData.payload.elements;
const reconciledElements = this.reconcileElements(remoteElements);
this.handleRemoteSceneUpdate(reconciledElements, {
init: true,
});
const reconciledElements =
this._reconcileElements(remoteElements);
this.handleRemoteSceneUpdate(reconciledElements);
// noop if already resolved via init from firebase
scenePromise.resolve({
elements: reconciledElements,
@ -502,41 +580,75 @@ class Collab extends PureComponent<Props, CollabState> {
}
break;
}
case WS_SCENE_EVENT_TYPES.UPDATE:
case WS_SUBTYPES.UPDATE:
this.handleRemoteSceneUpdate(
this.reconcileElements(decryptedData.payload.elements),
this._reconcileElements(decryptedData.payload.elements),
);
break;
case "MOUSE_LOCATION": {
case WS_SUBTYPES.MOUSE_LOCATION: {
const { pointer, button, username, selectedElementIds } =
decryptedData.payload;
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
decryptedData.payload.socketId ||
// @ts-ignore legacy, see #2094 (#2097)
decryptedData.payload.socketID;
const collaborators = new Map(this.collaborators);
const user = collaborators.get(socketId) || {}!;
user.pointer = pointer;
user.button = button;
user.selectedElementIds = selectedElementIds;
user.username = username;
collaborators.set(socketId, user);
this.updateCollaborator(socketId, {
pointer,
button,
selectedElementIds,
username,
});
break;
}
case WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS: {
const { sceneBounds, socketId } = decryptedData.payload;
const appState = this.excalidrawAPI.getAppState();
// we're not following the user
// (shouldn't happen, but could be late message or bug upstream)
if (appState.userToFollow?.socketId !== socketId) {
console.warn(
`receiving remote client's (from ${socketId}) viewport bounds even though we're not subscribed to it!`,
);
return;
}
// cross-follow case, ignore updates in this case
if (
appState.userToFollow &&
appState.followedBy.has(appState.userToFollow.socketId)
) {
return;
}
this.excalidrawAPI.updateScene({
collaborators,
appState: zoomToFitBounds({
appState,
bounds: sceneBounds,
fitToViewport: true,
viewportZoomFactor: 1,
}).appState,
});
break;
}
case WS_SUBTYPES.IDLE_STATUS: {
const { userState, socketId, username } = decryptedData.payload;
this.updateCollaborator(socketId, {
userState,
username,
});
break;
}
case "IDLE_STATUS": {
const { userState, socketId, username } = decryptedData.payload;
const collaborators = new Map(this.collaborators);
const user = collaborators.get(socketId) || {}!;
user.userState = userState;
user.username = username;
this.excalidrawAPI.updateScene({
collaborators,
});
break;
default: {
assertNever(decryptedData, null);
}
}
},
@ -553,11 +665,20 @@ class Collab extends PureComponent<Props, CollabState> {
scenePromise.resolve(sceneData);
});
this.portal.socket.on(
WS_EVENTS.USER_FOLLOW_ROOM_CHANGE,
(followedBy: SocketId[]) => {
this.excalidrawAPI.updateScene({
appState: { followedBy: new Set(followedBy) },
});
this.relayVisibleSceneBounds({ force: true });
},
);
this.initializeIdleDetector();
this.setState({
activeRoomLink: window.location.href,
});
this.setActiveRoomLink(window.location.href);
return scenePromise;
};
@ -609,17 +730,15 @@ class Collab extends PureComponent<Props, CollabState> {
return null;
};
private reconcileElements = (
private _reconcileElements = (
remoteElements: readonly ExcalidrawElement[],
): ReconciledElements => {
): ReconciledExcalidrawElement[] => {
const localElements = this.getSceneElementsIncludingDeleted();
const appState = this.excalidrawAPI.getAppState();
remoteElements = restoreElements(remoteElements, null);
const reconciledElements = _reconcileElements(
const restoredRemoteElements = restoreElements(remoteElements, null);
const reconciledElements = reconcileElements(
localElements,
remoteElements,
restoredRemoteElements as RemoteExcalidrawElement[],
appState,
);
@ -650,20 +769,13 @@ class Collab extends PureComponent<Props, CollabState> {
}, LOAD_IMAGES_TIMEOUT);
private handleRemoteSceneUpdate = (
elements: ReconciledElements,
{ init = false }: { init?: boolean } = {},
elements: ReconciledExcalidrawElement[],
) => {
this.excalidrawAPI.updateScene({
elements,
commitToHistory: !!init,
storeAction: StoreAction.UPDATE,
});
// We haven't yet implemented multiplayer undo functionality, so we clear the undo stack
// when we receive any messages from another peer. This UX can be pretty rough -- if you
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
// right now we think this is the right tradeoff.
this.excalidrawAPI.history.clear();
this.loadImageFiles();
};
@ -721,20 +833,39 @@ class Collab extends PureComponent<Props, CollabState> {
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
};
setCollaborators(sockets: string[]) {
setCollaborators(sockets: SocketId[]) {
const collaborators: InstanceType<typeof Collab>["collaborators"] =
new Map();
for (const socketId of sockets) {
if (this.collaborators.has(socketId)) {
collaborators.set(socketId, this.collaborators.get(socketId)!);
} else {
collaborators.set(socketId, {});
}
collaborators.set(
socketId,
Object.assign({}, this.collaborators.get(socketId), {
isCurrentUser: socketId === this.portal.socket?.id,
}),
);
}
this.collaborators = collaborators;
this.excalidrawAPI.updateScene({ collaborators });
}
updateCollaborator = (socketId: SocketId, updates: Partial<Collaborator>) => {
const collaborators = new Map(this.collaborators);
const user: Mutable<Collaborator> = Object.assign(
{},
collaborators.get(socketId),
updates,
{
isCurrentUser: socketId === this.portal.socket?.id,
},
);
collaborators.set(socketId, user);
this.collaborators = collaborators;
this.excalidrawAPI.updateScene({
collaborators,
});
};
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
this.lastBroadcastedOrReceivedSceneVersion = version;
};
@ -760,29 +891,42 @@ class Collab extends PureComponent<Props, CollabState> {
CURSOR_SYNC_TIMEOUT,
);
relayVisibleSceneBounds = (props?: { force: boolean }) => {
const appState = this.excalidrawAPI.getAppState();
if (this.portal.socket && (appState.followedBy.size > 0 || props?.force)) {
this.portal.broadcastVisibleSceneBounds(
{
sceneBounds: getVisibleSceneBounds(appState),
},
`follow@${this.portal.socket.id}`,
);
}
};
onIdleStateChange = (userState: UserIdleState) => {
this.portal.broadcastIdleChange(userState);
};
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
broadcastElements = (elements: readonly OrderedExcalidrawElement[]) => {
if (
getSceneVersion(elements) >
this.getLastBroadcastedOrReceivedSceneVersion()
) {
this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements, false);
this.portal.broadcastScene(WS_SUBTYPES.UPDATE, elements, false);
this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements);
this.queueBroadcastAllElements();
}
};
syncElements = (elements: readonly ExcalidrawElement[]) => {
syncElements = (elements: readonly OrderedExcalidrawElement[]) => {
this.broadcastElements(elements);
this.queueSaveToFirebase();
};
queueBroadcastAllElements = throttle(() => {
this.portal.broadcastScene(
WS_SCENE_EVENT_TYPES.UPDATE,
WS_SUBTYPES.UPDATE,
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
true,
);
@ -808,41 +952,49 @@ class Collab extends PureComponent<Props, CollabState> {
{ leading: false },
);
handleClose = () => {
appJotaiStore.set(collabDialogShownAtom, false);
};
setUsername = (username: string) => {
this.setState({ username });
};
onUsernameChange = (username: string) => {
this.setUsername(username);
saveUsernameToLocalStorage(username);
};
render() {
const { username, errorMessage, activeRoomLink } = this.state;
getUsername = () => this.state.username;
const { modalIsShown } = this.props;
setActiveRoomLink = (activeRoomLink: string | null) => {
this.setState({ activeRoomLink });
appJotaiStore.set(activeRoomLinkAtom, activeRoomLink);
};
getActiveRoomLink = () => this.state.activeRoomLink;
setErrorIndicator = (errorMessage: string | null) => {
appJotaiStore.set(collabErrorIndicatorAtom, {
message: errorMessage,
nonce: Date.now(),
});
};
resetErrorIndicator = (resetDialogNotifiedErrors = false) => {
appJotaiStore.set(collabErrorIndicatorAtom, { message: null, nonce: 0 });
if (resetDialogNotifiedErrors) {
this.setState({
dialogNotifiedErrors: {},
});
}
};
setErrorDialog = (errorMessage: string | null) => {
this.setState({
errorMessage,
});
};
render() {
const { errorMessage } = this.state;
return (
<>
{modalIsShown && (
<RoomDialog
handleClose={this.handleClose}
activeRoomLink={activeRoomLink}
username={username}
onUsernameChange={this.onUsernameChange}
onRoomCreate={() => this.startCollaboration(null)}
onRoomDestroy={this.stopCollaboration}
setErrorMessage={(errorMessage) => {
this.setState({ errorMessage });
}}
/>
)}
{errorMessage && (
<ErrorDialog onClose={() => this.setState({ errorMessage: "" })}>
{errorMessage != null && (
<ErrorDialog onClose={() => this.setErrorDialog(null)}>
{errorMessage}
</ErrorDialog>
)}
@ -861,11 +1013,6 @@ if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
window.collab = window.collab || ({} as Window["collab"]);
}
const _Collab: React.FC<PublicProps> = (props) => {
const [collabDialogShown] = useAtom(collabDialogShownAtom);
return <Collab {...props} modalIsShown={collabDialogShown} />;
};
export default _Collab;
export default Collab;
export type TCollabClass = Collab;

View File

@ -0,0 +1,35 @@
@import "../../packages/excalidraw/css/variables.module.scss";
.excalidraw {
.collab-errors-button {
width: 26px;
height: 26px;
margin-inline-end: 1rem;
color: var(--color-danger);
flex-shrink: 0;
}
.collab-errors-button-shake {
animation: strong-shake 0.15s 6;
}
@keyframes strong-shake {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(10deg);
}
50% {
transform: rotate(0deg);
}
75% {
transform: rotate(-10deg);
}
100% {
transform: rotate(0deg);
}
}
}

View File

@ -0,0 +1,54 @@
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
import { warning } from "../../packages/excalidraw/components/icons";
import clsx from "clsx";
import { useEffect, useRef, useState } from "react";
import "./CollabError.scss";
import { atom } from "jotai";
type ErrorIndicator = {
message: string | null;
/** used to rerun the useEffect responsible for animation */
nonce: number;
};
export const collabErrorIndicatorAtom = atom<ErrorIndicator>({
message: null,
nonce: 0,
});
const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
const [isAnimating, setIsAnimating] = useState(false);
const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
useEffect(() => {
setIsAnimating(true);
clearAnimationRef.current = setTimeout(() => {
setIsAnimating(false);
}, 1000);
return () => {
clearTimeout(clearAnimationRef.current);
};
}, [collabError.message, collabError.nonce]);
if (!collabError.message) {
return null;
}
return (
<Tooltip label={collabError.message} long={true}>
<div
className={clsx("collab-errors-button", {
"collab-errors-button-shake": isAnimating,
})}
>
{warning}
</div>
</Tooltip>
);
};
CollabError.displayName = "CollabError";
export default CollabError;

View File

@ -1,28 +1,29 @@
import {
isSyncableElement,
import type {
SocketUpdateData,
SocketUpdateDataSource,
SyncableExcalidrawElement,
} from "../data";
import { isSyncableElement } from "../data";
import { TCollabClass } from "./Collab";
import type { TCollabClass } from "./Collab";
import { ExcalidrawElement } from "../../src/element/types";
import {
WS_EVENTS,
FILE_UPLOAD_TIMEOUT,
WS_SCENE_EVENT_TYPES,
} from "../app_constants";
import { UserIdleState } from "../../src/types";
import { trackEvent } from "../../src/analytics";
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 "../../src/element/mutateElement";
import { BroadcastedExcalidrawElement } from "./reconciliation";
import { encryptData } from "../../src/data/encryption";
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
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;
socket: SocketIOClient.Socket | null = null;
socket: Socket | null = null;
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
roomId: string | null = null;
roomKey: string | null = null;
@ -32,7 +33,7 @@ class Portal {
this.collab = collab;
}
open(socket: SocketIOClient.Socket, id: string, key: string) {
open(socket: Socket, id: string, key: string) {
this.socket = socket;
this.roomId = id;
this.roomKey = key;
@ -46,12 +47,12 @@ class Portal {
});
this.socket.on("new-user", async (_socketId: string) => {
this.broadcastScene(
WS_SCENE_EVENT_TYPES.INIT,
WS_SUBTYPES.INIT,
this.collab.getSceneElementsIncludingDeleted(),
/* syncAll */ true,
);
});
this.socket.on("room-user-change", (clients: string[]) => {
this.socket.on("room-user-change", (clients: SocketId[]) => {
this.collab.setCollaborators(clients);
});
@ -83,6 +84,7 @@ class Portal {
async _broadcastSocketData(
data: SocketUpdateData,
volatile: boolean = false,
roomId?: string,
) {
if (this.isOpen()) {
const json = JSON.stringify(data);
@ -91,7 +93,7 @@ class Portal {
this.socket?.emit(
volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER,
this.roomId,
roomId ?? this.roomId,
encryptedBuffer,
iv,
);
@ -114,52 +116,51 @@ class Portal {
}
}
this.collab.excalidrawAPI.updateScene({
elements: this.collab.excalidrawAPI
.getSceneElementsIncludingDeleted()
.map((element) => {
if (this.collab.fileManager.shouldUpdateImageElementStatus(element)) {
// this will signal collaborators to pull image data from server
// (using mutation instead of newElementWith otherwise it'd break
// in-progress dragging)
return newElementWith(element, { status: "saved" });
}
return element;
}),
});
let isChanged = false;
const newElements = this.collab.excalidrawAPI
.getSceneElementsIncludingDeleted()
.map((element) => {
if (this.collab.fileManager.shouldUpdateImageElementStatus(element)) {
isChanged = true;
// this will signal collaborators to pull image data from server
// (using mutation instead of newElementWith otherwise it'd break
// in-progress dragging)
return newElementWith(element, { status: "saved" });
}
return element;
});
if (isChanged) {
this.collab.excalidrawAPI.updateScene({
elements: newElements,
storeAction: StoreAction.UPDATE,
});
}
}, FILE_UPLOAD_TIMEOUT);
broadcastScene = async (
updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE,
allElements: readonly ExcalidrawElement[],
updateType: WS_SUBTYPES.INIT | WS_SUBTYPES.UPDATE,
elements: readonly OrderedExcalidrawElement[],
syncAll: boolean,
) => {
if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) {
if (updateType === WS_SUBTYPES.INIT && !syncAll) {
throw new Error("syncAll must be true when sending SCENE.INIT");
}
// sync out only the elements we think we need to to save bandwidth.
// periodically we'll resync the whole thing to make sure no one diverges
// due to a dropped message (server goes down etc).
const syncableElements = allElements.reduce(
(acc, element: BroadcastedExcalidrawElement, idx, elements) => {
if (
(syncAll ||
!this.broadcastedElementVersions.has(element.id) ||
element.version >
this.broadcastedElementVersions.get(element.id)!) &&
isSyncableElement(element)
) {
acc.push({
...element,
// z-index info for the reconciler
[PRECEDING_ELEMENT_KEY]: idx === 0 ? "^" : elements[idx - 1]?.id,
});
}
return acc;
},
[] as BroadcastedExcalidrawElement[],
);
const syncableElements = elements.reduce((acc, element) => {
if (
(syncAll ||
!this.broadcastedElementVersions.has(element.id) ||
element.version > this.broadcastedElementVersions.get(element.id)!) &&
isSyncableElement(element)
) {
acc.push(element);
}
return acc;
}, [] as SyncableExcalidrawElement[]);
const data: SocketUpdateDataSource[typeof updateType] = {
type: updateType,
@ -183,9 +184,9 @@ class Portal {
broadcastIdleChange = (userState: UserIdleState) => {
if (this.socket?.id) {
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
type: "IDLE_STATUS",
type: WS_SUBTYPES.IDLE_STATUS,
payload: {
socketId: this.socket.id,
socketId: this.socket.id as SocketId,
userState,
username: this.collab.state.username,
},
@ -203,9 +204,9 @@ class Portal {
}) => {
if (this.socket?.id) {
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
type: "MOUSE_LOCATION",
type: WS_SUBTYPES.MOUSE_LOCATION,
payload: {
socketId: this.socket.id,
socketId: this.socket.id as SocketId,
pointer: payload.pointer,
button: payload.button || "up",
selectedElementIds:
@ -213,12 +214,43 @@ class Portal {
username: this.collab.state.username,
},
};
return this._broadcastSocketData(
data as SocketUpdateData,
true, // volatile
);
}
};
broadcastVisibleSceneBounds = (
payload: {
sceneBounds: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"]["payload"]["sceneBounds"];
},
roomId: string,
) => {
if (this.socket?.id) {
const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
payload: {
socketId: this.socket.id as SocketId,
username: this.collab.state.username,
sceneBounds: payload.sceneBounds,
},
};
return this._broadcastSocketData(
data as SocketUpdateData,
true, // volatile
roomId,
);
}
};
broadcastUserFollowed = (payload: OnUserFollowedPayload) => {
if (this.socket?.id) {
this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload);
}
};
}
export default Portal;

View File

@ -1,219 +0,0 @@
import { useRef, useState } from "react";
import * as Popover from "@radix-ui/react-popover";
import { copyTextToSystemClipboard } from "../../src/clipboard";
import { trackEvent } from "../../src/analytics";
import { getFrame } from "../../src/utils";
import { useI18n } from "../../src/i18n";
import { KEYS } from "../../src/keys";
import { Dialog } from "../../src/components/Dialog";
import {
copyIcon,
playerPlayIcon,
playerStopFilledIcon,
share,
shareIOS,
shareWindows,
tablerCheckIcon,
} from "../../src/components/icons";
import { TextField } from "../../src/components/TextField";
import { FilledButton } from "../../src/components/FilledButton";
import { ReactComponent as CollabImage } from "../../src/assets/lock.svg";
import "./RoomDialog.scss";
const getShareIcon = () => {
const navigator = window.navigator as any;
const isAppleBrowser = /Apple/.test(navigator.vendor);
const isWindowsBrowser = navigator.appVersion.indexOf("Win") !== -1;
if (isAppleBrowser) {
return shareIOS;
} else if (isWindowsBrowser) {
return shareWindows;
}
return share;
};
export type RoomModalProps = {
handleClose: () => void;
activeRoomLink: string;
username: string;
onUsernameChange: (username: string) => void;
onRoomCreate: () => void;
onRoomDestroy: () => void;
setErrorMessage: (message: string) => void;
};
export const RoomModal = ({
activeRoomLink,
onRoomCreate,
onRoomDestroy,
setErrorMessage,
username,
onUsernameChange,
handleClose,
}: RoomModalProps) => {
const { t } = useI18n();
const [justCopied, setJustCopied] = useState(false);
const timerRef = useRef<number>(0);
const ref = useRef<HTMLInputElement>(null);
const isShareSupported = "share" in navigator;
const copyRoomLink = async () => {
try {
await copyTextToSystemClipboard(activeRoomLink);
setJustCopied(true);
if (timerRef.current) {
window.clearTimeout(timerRef.current);
}
timerRef.current = window.setTimeout(() => {
setJustCopied(false);
}, 3000);
} catch (error: any) {
setErrorMessage(error.message);
}
ref.current?.select();
};
const shareRoomLink = async () => {
try {
await navigator.share({
title: t("roomDialog.shareTitle"),
text: t("roomDialog.shareTitle"),
url: activeRoomLink,
});
} catch (error: any) {
// Just ignore.
}
};
if (activeRoomLink) {
return (
<>
<h3 className="RoomDialog__active__header">
{t("labels.liveCollaboration")}
</h3>
<TextField
value={username}
placeholder="Your name"
label="Your name"
onChange={onUsernameChange}
onKeyDown={(event) => event.key === KEYS.ENTER && handleClose()}
/>
<div className="RoomDialog__active__linkRow">
<TextField
ref={ref}
label="Link"
readonly
fullWidth
value={activeRoomLink}
/>
{isShareSupported && (
<FilledButton
size="large"
variant="icon"
label="Share"
startIcon={getShareIcon()}
className="RoomDialog__active__share"
onClick={shareRoomLink}
/>
)}
<Popover.Root open={justCopied}>
<Popover.Trigger asChild>
<FilledButton
size="large"
label="Copy link"
startIcon={copyIcon}
onClick={copyRoomLink}
/>
</Popover.Trigger>
<Popover.Content
onOpenAutoFocus={(event) => event.preventDefault()}
onCloseAutoFocus={(event) => event.preventDefault()}
className="RoomDialog__popover"
side="top"
align="end"
sideOffset={5.5}
>
{tablerCheckIcon} copied
</Popover.Content>
</Popover.Root>
</div>
<div className="RoomDialog__active__description">
<p>
<span
role="img"
aria-hidden="true"
className="RoomDialog__active__description__emoji"
>
🔒{" "}
</span>
{t("roomDialog.desc_privacy")}
</p>
<p>{t("roomDialog.desc_exitSession")}</p>
</div>
<div className="RoomDialog__active__actions">
<FilledButton
size="large"
variant="outlined"
color="danger"
label={t("roomDialog.button_stopSession")}
startIcon={playerStopFilledIcon}
onClick={() => {
trackEvent("share", "room closed");
onRoomDestroy();
}}
/>
</div>
</>
);
}
return (
<>
<div className="RoomDialog__inactive__illustration">
<CollabImage />
</div>
<div className="RoomDialog__inactive__header">
{t("labels.liveCollaboration")}
</div>
<div className="RoomDialog__inactive__description">
<strong>{t("roomDialog.desc_intro")}</strong>
{t("roomDialog.desc_privacy")}
</div>
<div className="RoomDialog__inactive__start_session">
<FilledButton
size="large"
label={t("roomDialog.button_startSession")}
startIcon={playerPlayIcon}
onClick={() => {
trackEvent("share", "room creation", `ui (${getFrame()})`);
onRoomCreate();
}}
/>
</div>
</>
);
};
const RoomDialog = (props: RoomModalProps) => {
return (
<Dialog size="small" onCloseRequest={props.handleClose} title={false}>
<div className="RoomDialog">
<RoomModal {...props} />
</div>
</Dialog>
);
};
export default RoomDialog;

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