Compare commits

..
Author SHA1 Message Date
Aakansha Doshi c447c4e245 fix: text element should be rendered correctly when pasted to a version before measurements change 2023-03-17 16:14:46 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7e330c8ee1 build(deps-dev): bump webpack from 5.73.0 to 5.76.0 in /src/packages/utils (#6354)
build(deps-dev): bump webpack in /src/packages/utils

Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.76.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.76.0)

---
updated-dependencies:
- dependency-name: webpack
  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-03-15 11:04:13 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7d21747644 build(deps-dev): bump webpack from 5.73.0 to 5.76.0 in /src/packages/excalidraw (#6355)
build(deps-dev): bump webpack in /src/packages/excalidraw

Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.76.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.76.0)

---
updated-dependencies:
- dependency-name: webpack
  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-03-15 11:04:01 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e718136aea build(deps): bump webpack from 5.74.0 to 5.76.1 in /dev-docs (#6356)
Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.76.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 11:03:40 +05:30
Salah Eddine DaciandGitHub fe83e2922d build: move TS and types to devDependencies (#6346) 2023-03-14 12:06:57 +00:00
David LuzarandGitHub 20edddcd4e fix: ensure export preview is centered (#6337) 2023-03-14 13:03:55 +01:00
Aakansha DoshiandGitHub f6e8be399e fix: hide text align for labelled arrows (#6339)
* fix: hide text align for labelled arrows

* lintttt

* since we fetch seledcted Elements including the bound text hence this block can be removed

* fix
2023-03-14 17:21:46 +05:30
Aakansha DoshiandGitHub ab49cad6b1 perf: break early if the line width <= max width of the container (#6347)
* fix: break early if the line width <= max width of the container

* Remove dead code

* remove dead code

* lint

* remove
2023-03-14 17:18:16 +05:30
Aakansha DoshiandGitHub 6aeb18b784 fix: refresh dimensions when elements loaded from shareable link and blob (#6333)
* fix: refresh dimensions when elements loaded from shareable link

* refresh text dimensions when loading from file

* remove log
2023-03-14 17:08:23 +05:30
023313e92f fix: show error message when measureText API breaks in brave (#6336)
* fix: show error message when measureText API breaks in brave

* Add docs

* Add assets

* tweak message

* fix

* tweak message

* add translations

* lint

* fix

* fix

* lint

* lint

* lint please work now

* tweak doc

* fix

* split error component to new file

* add specs

* tweaks

Co-authored-by: David Luzar <luzar.david@gmail.com>

* wrap in div with a width of 30rem

* fix spec

* fix

* Fix typo

---------

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Daniel J. Geiger <1852529+DanielJGeiger@users.noreply.github.com>
2023-03-13 19:46:09 +05:30
David LuzarandGitHub 1eee488dab feat: add thai lang support (#6314) 2023-03-11 22:15:52 +01:00
Aakansha DoshiandGitHub dd4c333925 fix: add an offset of 0.5px for text editor in containers (#6328)
* fix: add an offset of 0.5px for text editor in containers

* fix specs and lint
2023-03-09 13:07:36 +05:30
David LuzarandGitHub 8542c95a7a fix: move utility types out of .d.ts file to fix exported declaration files (#6315) 2023-03-04 19:21:57 +01:00
David LuzarandGitHub cef6094d4c fix: more jotai scopes missing (#6313) 2023-03-03 16:19:02 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3322f0fa6f build(deps): bump @sideway/formula from 3.0.0 to 3.0.1 in /dev-docs (#6309)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-03 14:45:29 +01:00
34a7d48b95 fix: provide HelpButton title prop (#6209)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-03-03 12:50:18 +00:00
Aakansha DoshiandGitHub 5c0b15ce2b fix: respect text align when wrapping in a container (#6310)
* fix: respect text align when wrapping in a container

* fix
2023-03-03 18:07:26 +05:30
Aakansha DoshiandGitHub 9f9666110e chore: Add debug flag to enable text container bounding box (#6296)
* debug: Add debug flag to enable text container bounding box

* newline

* fix
2023-03-03 18:01:55 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
05ffce62ef build(deps): bump dns-packet from 5.3.1 to 5.4.0 in /src/packages/excalidraw (#6305)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-03 13:20:53 +01:00
Aakansha DoshiandGitHub 0f06fa3851 feat: create bound container from text (#6301)
* feat: create container from text

* fix lint and spec

* fix

* round off dims

* ceil

* review fixes

* fix

* Add specs

* fix

* fix z-index and type

* consider group

* consider linear bindings

* lint
2023-03-03 17:40:42 +05:30
Aakansha DoshiandGitHub 1ce933d2f5 fix: compute bounding box correctly for text element when multiple element resizing (#6307) 2023-03-03 17:34:11 +05:30
David LuzarandGitHub 15655acb5a fix: use jotai scope for editor-specific atoms (#6308) 2023-03-03 11:58:36 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d5b264c2d2 build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /dev-docs (#6192)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 14:51:36 +05:30
Aakansha DoshiandGitHub bd4424bbe3 fix: consider arrow for bound text element (#6297)
* fix: consider arrow for bound text element

* add spec
2023-02-28 19:53:30 +05:30
Aakansha DoshiandGitHub 38fc51b4e3 fix: text never goes beyond max width for unbound text elements (#6288) 2023-02-27 16:54:29 +05:30
David LuzarandGitHub e1dc748aef fix: svg text baseline (#6285
* fix: svg text baseline

* fix for multiline
2023-02-26 12:51:44 +01:00
Aakansha DoshiandGitHub 0e95e2b386 fix: compute container height from bound text correctly (#6273)
* fix: compute container height from bound text correctly

* fix specs

* Add tests
2023-02-23 17:39:02 +05:30
Aakansha DoshiandGitHub 9659254fd6 feat: improve text measurements in bound containers (#6187)
* feat: move to canvas measureText

* calcualte height with better heuristic

* improve heuristic more

* remove vertical offset as its not needed

* lint

* calculate width of individual char and ceil to calculate width and remove adjustment factor

* push the word if equal to max width

* update height when text overflows for vertical alignment top/bottom

* remove the hack of updating height when line mismatch as its not needed

* remove scroll height and calculate the height instead

* remove unused code

* fix

* remove

* use math.ceil for whole width instead of individual chars

* fix tests

* fix

* fix

* redraw text bounding box instead when font loaded to fix alignment as well

* fix

* fix

* fix

* Add a 0.05px extra only for firefox

* Add spec

* stop taking ceil and increase firefox editor width by 0.05px

* Ad 0.05px in safari too

* lint

* lint

* remove baseline from measureFontSizeFromWH

* don't redraw on font load

* lint

* refactor name and signature
2023-02-23 16:33:10 +05:30
39b96cb011 fix: fit mobile toolbar and make scrollable (#6270)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-23 11:24:04 +01:00
David LuzarandGitHub 04a8c22f39 fix: rerender i18n in host components on lang change (#6224) 2023-02-22 14:01:23 +00:00
Excalidraw BotandGitHub e4506be3e8 chore: Update translations from Crowdin (#6191) 2023-02-22 11:23:10 +00:00
Hikaru YoshinoandGitHub 1e816e87bf fix: indenting via tab clashing with IME compositor (#6258) 2023-02-22 12:10:29 +01:00
Aakansha DoshiandGitHub 5368ddef74 fix: improve text wrapping inside rhombus and more fixes (#6265)
* fix: improve text wrapping inside rhombus

* Add comments

* specs

* fix: shift resize and multiple element regression for ellipse and rhombus

* use container width for scaling font size

* fix

* fix multiple resize

* lint

* redraw on submit

* redraw only newly pasted elements

* no padding when center

* fix tests

* fix

* dont add padding in rhombus when aligning

* refactor

* fix

* move getMaxContainerHeight and getMaxContainerWidth to textElement.ts

* Add specs
2023-02-22 16:28:12 +05:30
Aakansha DoshiandGitHub 88ff32e9b3 fix: improve text wrapping in ellipse and alignment (#6172)
* fix: improve text wrapping in ellipse

* compute height when font properties updated

* fix alignment

* fix alignment when resizing

* fix

* ad padding

* always compute height when redrawing bounding box and refactor

* lint

* fix specs

* fix

* redraw text bounding box when pasted or refreshed

* fix

* Add specs

* fix

* restore on font load

* add comments
2023-02-21 12:36:43 +05:30
Jan KlassandGitHub 0fcbddda8e docs: Fix outdated link in README.md (#6263) 2023-02-20 09:44:25 +00:00
Aakansha DoshiandGitHub b9ba407f96 feat: Bind text to container if double clicked on filled shape or stroke (#6250)
* feat: bind text to container when clicked on filled shape or element stroke

* Bind if double clicked on stroke as well

* remove

* specs

* remove

* shuffle

* fix

* back to normal
2023-02-16 20:46:51 +05:30
Aakansha DoshiandGitHub 5acb99777a docs: fix typo (#6252) 2023-02-16 19:45:41 +05:30
David LuzarandGitHub b107c9af2a docs: fix next.js example (#6241) 2023-02-15 15:14:15 +01:00
c587b85b4e docs: new readme (#6240)
Co-authored-by: David Luzar <luzar.david@gmail.com>
2023-02-15 14:45:06 +01:00
Luka HietalaandGitHub 9686141113 docs: Fixed broken codesandbox link in the dev-docs (#6229)
fixed broken link
2023-02-15 05:31:07 +00:00
Aakansha DoshiandGitHub 0d7ee891e0 feat: Make repair and refreshDimensions configurable in restoreElements (#6238)
* fix: don't repair during reconcilation

* Add opts to restoreElement and enable refreshDimensions and repair via config

* remove

* update changelog

* fix tests

* rename to repairBindings
2023-02-15 10:41:11 +05:30
Aakansha DoshiandGitHub 71fb60394a docs: enable Algolia for search (#6230) 2023-02-13 17:39:11 +05:30
Aakansha DoshiandGitHub c9d18ecab6 fix: don't allow blank space in collab name (#6211)
* don't allow blank space in collab name

* add spec

* prevent blank
2023-02-09 15:51:49 +05:30
DanielJGeigerandGitHub 8c1168ef33 refactor: Make the example React app reusable without duplication (#6188) 2023-02-07 12:41:20 +05:30
Dejavu MoeandGitHub c3c45a8c37 fix: docker build architecture:linux/amd64 error occur on linux/arm64 instance (#6197)
fix docker build
when in linux/arm64 use docker buildx plugin to build linux/amd64 image, a build error will occur causing the build to break
2023-02-07 11:44:31 +05:30
Matthieu RossignonandGitHub a8e6028c33 feat: show error message when not connected to internet while collabo… (#6165)
Co-authored-by: dwelle <luzar.david@gmail.com>
Resolves https://github.com/excalidraw/excalidraw/issues/5994
2023-02-04 15:03:39 +01:00
Jang MinandGitHub 11e2f90ca1 feat: shortcut for clearCanvas confirmDialog (#6114)
Co-authored-by: dwelle <luzar.david@gmail.com>
resolve https://github.com/excalidraw/excalidraw/issues/5818
2023-02-04 13:33:40 +01:00
Excalidraw BotandGitHub 4db87a0b6a chore: Update translations from Crowdin (#6150) 2023-02-04 10:04:15 +01:00
4414069617 feat: disable canvas smoothing (antialiasing) for right-angled elements (#6186)Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
* feat: disable canvas smoothing for text and other types

* disable smoothing for all right-angled elements

* Update src/renderer/renderElement.ts

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>

* Update src/renderer/renderElement.ts

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>

* fix lint

* always enable smoothing while zooming

---------

Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
2023-02-03 17:07:14 +01:00
a9c5bdb878 fix: sort bound text elements to fix text duplication z-index error (#5130)
* fix: sort bound text elements to fix text duplication z-index error

* improve & sort groups & add tests

* fix backtracking and discontiguous groups

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-02 16:23:39 +08:00
Aakansha DoshiandGitHub 5a0334f37f fix: hide welcome screen on mobile once user interacts (#6185)
* fix: hide welcome screen on mobile once started drawing

* Add specs
2023-02-02 12:58:45 +05:30
Aakansha DoshiandGitHub d8a4ca6911 docs: show last updated time and author (#6183)
docs:show last updated time and author
2023-02-01 21:09:23 +05:30
Aakansha DoshiandGitHub eb9eeefc63 fix: edit link in docs (#6182) 2023-02-01 20:27:31 +05:30
f23cdc47ee docs: migrating dev docs to docusaurus :) (#6073)
* docs: migrating existing docs to docosaraus :)

* log broken links

* lint :p

* fix

* divide the doc into diff categories

* fix

* order sidebars and more

* fix lint

* point to installation

* making docs better :)

* fix

* renaming git

* renaming git

* fix links

* fix

* update readme

* fix

* resolve duplicate url and make /docs as base url

* fix

* move main docs as well

* making docs better

* support mdx

* update og

* fix title

* upgrade docusarus to stable version

* use draculla theme

* fix

* make entire sidebar collapsable

* live editor for footer wohoo

* render excalidraw only on client to fix the prod build

* migrate MainMenu to live editor too :)

* lint :p

* cleanup integration and use live editor and tabs

* fix

* Add welcome screen doc

* Live Collaboration comp docs

* Add collaborator example

* Add example

* add more

* remove isCollaborating

* Rewrite ref and move to sidebar

* change color of links inside pre

* add initial data

* fix lint

* Add styling

* fix lint

* Add example for customizing styles

* fix lint

* fix

* fix lint

* Add link to livecollabtrigger

* fix

* rewrite UIOptions to sidebar

* move initialdata to sidebar

* move render props to sidebar and rewrite renderTopRightUI and renderCustomStats

* rewrite renderSidebar

* update og

* update url for testing

* fix url

* update readme

* fix style

* tweaks

* Add highlight comp to highlight text

* Add bash syntax highlight

* fix

* tweaks

* fix

* rewrite export utilities

* fix restore

* rewrite utils

* move constants to sidebar

* update readme

* add copyright

* fix links style

* Add linkedin

* tweaks

* rename package to @excalidraw/excalidraw

* enable algolia with dummy creds

* tweaks to integration doc

* tweak WelcomeScreen docs to reflect upcoming API changes

* tweak components intro

* tweak nomenclature

* fix admonition

* rename `components` sidebar item and change order of components list

* uncollapse package section in sidebar

* show level 4 haeadings in TOC

* remove algolia

* remove unused assets

* capitalize C

* tweak

* rename components to App

* rename components -> children-components in the routes

* move notable used tools to intro

* update MainMenu docs with `onSelect` preventDefault behavior

* change sidebar label for children components

* use code

* tweak README & docs intro

* tweak package development doc

* make scrollbar gutter stable

* tweak api intro

* add admonition for export utils

* use next

* wip

* wip

* make excalidraw examples use current color theme & prefer system

* fix welcomescreen docs

* use latest temp release

* fix component order

* revert wip changes

* use next

* tweak

* increase height to fix welcome screen hint

* tweak editor height

* update excal version

* wrap Excal with forwardRef to fix refs

* migrate contributing.md

* fix broken links

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-02-01 19:57:54 +05:30
Aakansha DoshiandGitHub f68f4cb9e0 docs: release @excalidraw/excalidraw@0.14.2 🎉 (#6181) 2023-02-01 19:08:56 +05:30
Aakansha DoshiandGitHub 71fb573750 fix: horizontal padding when aligning bound text containers (#6180)
* fix: horizontal padding when aligning bound text containers

* Add specs

* fix
2023-02-01 18:54:08 +05:30
David LuzarandGitHub 7562d9b533 fix: make tunnels work in multi-instance scenarios (#6178)
* fix: make tunnels work in multi-instance scenarios

* factor tunnels out

* use tunnel-rat fork until upsteam updated
2023-02-01 10:46:17 +05:30
e6de1fe4a4 feat: rewrite public UI component rendering using tunnels (#6117)
* feat: rewrite public UI component rendering using tunnels

* factor out into components

* comments

* fix variable naming

* fix not hiding welcomeScreen

* factor out AppFooter and memoize components

* remove `UIOptions.welcomeScreen` and render only from host app

* factor out tunnels into own file

* update changelog. Keep `UIOptions.welcomeScreen` as deprecated

* update changelog

* lint

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-01-31 13:53:20 +01:00
Aakansha DoshiandGitHub 3a141ca77a fix: add 1px width to the container to calculate more accurately (#6174)
* fix: add 1px width to the container to calculate accurately

* fix tests
2023-01-30 18:52:56 +05:30
JUNYI OUandGitHub 5ae39c9292 fix: quick typo fix (#6167) 2023-01-29 14:22:25 +01:00
Aakansha DoshiandGitHub e41ea9562b fix: set the width correctly using measureText in editor (#6162) 2023-01-28 12:09:53 +01:00
b52c8943e4 fix: 🐛 broken emojis when wrap text (#6153)
* fix: 🐛 broken emojis when wrap text

* refactor: Delete unnecessary "else" (reduce indentation)

* fix: remove code block that causes the emojis to disappear

* Apply suggestions from code review

Co-authored-by: David Luzar <luzar.david@gmail.com>

* fix: 🚑 possibly undefined value

* Add spec

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-01-26 11:49:21 +05:30
Aakansha DoshiandGitHub cf38c0f933 fix: declare css variable for font in excalidraw so its available in host (#6160)
declar css variable for font in excalidraw so its available in host
2023-01-25 15:44:20 +05:30
David LuzarandGitHub 1db078a3dc feat: close MainMenu and Library dropdown on item select (#6152) 2023-01-23 16:54:35 +01:00
David LuzarandGitHub d4afd66268 feat: add hand/panning tool (#6141)
* feat: add hand/panning tool

* move hand tool right of tool lock separator

* tweak i18n

* rename `panning` -> `hand`

* toggle between last tool and hand on `H` shortcut

* hide properties sidebar when `hand` active

* revert to rendering HandButton manually due to mobile toolbar
2023-01-23 16:12:28 +01:00
David LuzarandGitHub 849e6a0c86 fix: button background and svg sizes (#6155)
* fix: button background color fallback

* fix svg width/height
2023-01-23 16:10:04 +01:00
f03f5c948d style: change in ExportButton style (#6147) (#6148)
Co-authored-by: David Luzar <luzar.david@gmail.com>
2023-01-22 12:37:18 +00:00
David LuzarandGitHub d2b698093c feat: show copy-as-png export button on firefox and show steps how to enable it (#6125)
* feat: hide copy-as-png shortcut from help dialog if not supported

* fix: support firefox if clipboard.write supported

* show shrotcut in firefox and instead show error message how to enable the flag support

* widen to TypeError because minification

* show copy-as-png on firefox even if it will throw
2023-01-22 12:33:15 +01:00
Excalidraw BotandGitHub 0f1720be61 chore: Update translations from Crowdin (#6077) 2023-01-22 12:19:21 +01:00
David LuzarandGitHub d0b33d35db build: temporarily disable pre-commit (#6132) 2023-01-19 13:50:42 +01:00
Aakansha DoshiandGitHub d6a5ef1936 docs: release @excalidraw/excalidraw@0.14.1 🎉 (#6112) 2023-01-16 16:08:03 +05:30
Aakansha DoshiandGitHub c7a11f5cd2 docs: release @excalidraw/excalidraw@0.14.0 🎉 (#6109) 2023-01-13 16:08:29 +05:30
Aakansha DoshiandGitHub 893c487add fix: remove overflow hidden from button (#6110)
remove overflow hidden from button
2023-01-13 15:44:33 +05:30
Aakansha DoshiandGitHub 99fdffdab7 fix: mobile tools positioning (#6107)
* fix: mobile tools positioning

* add var for padding

* use css var

* new line

* stupid mistake

* lint
2023-01-13 00:57:25 +05:30
faad8a65f1 feat: new Live Collaboration Component API (#6104)
* feat: new Live Collaboration Component API

* namespace export icons into `icons` dictionary and lowercase

* update readme and changelog

* review fixes

* fix

* fix

* update docs

* remove

* allow button rest props

* update docs

* docs

* add `WelcomeScreen.Center.MenuItemLiveCollaborationTrigger`

* fix lint

* update changelog

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-12 23:28:57 +05:30
Aakansha DoshiandGitHub 9d04479f98 fix: renamed folder MainMenu->main-menu and support rest props (#6103)
* renamed folder MainMenu -> main-menu

* rename ariaLabel -> aria-label and dataTestId -> data-testid

* allow rest props

* fix

* lint

* add ts check

* ts for div

* fix

* fix

* fix
2023-01-12 20:40:09 +05:30
David LuzarandGitHub 599a8f3c6f feat: support WelcomeScreen customization API (#6048) 2023-01-12 15:49:28 +01:00
David LuzarandGitHub 0982da38fe feat: render unknown supplied children to UI (#6096) 2023-01-12 15:20:16 +01:00
699897f71b feat: generic button export (#6092)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-12 13:06:00 +01:00
Aakansha DoshiandGitHub 328ff6c32d fix: use position absolute for mobile misc tools (#6099) 2023-01-11 19:47:40 +05:30
David LuzarandGitHub 618442299f fix: React.memo resolvers not accounting for all props (#6042) 2023-01-09 10:24:17 +01:00
Antonio Della FortunaandGitHub 06b45e0cfc fix: image horizontal flip fix + improved tests (#5799)
Co-authored-by: Antonio Della Fortuna <a.dellafortuna00@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
fixes https://github.com/excalidraw/excalidraw/issues/5784
2023-01-08 16:19:13 +00:00
David LuzarandGitHub 809d5ba17f fix: png-exporting does not preserve angles correctly for flipped images (#6085)
* fix: png-exporting does not preserve angles correctly for flipped images

* refactor related code

* simplify further and comment
2023-01-08 16:22:04 +01:00
David LuzarandGitHub 40d53d9231 fix: stale appState of MainMenu defaultItems rendered from Actions (#6074) 2023-01-06 14:32:55 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9803a85381 build(deps): bump loader-utils from 2.0.3 to 2.0.4 in /src/packages/excalidraw (#5892)
build(deps): bump loader-utils in /src/packages/excalidraw

Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:31:06 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
72784f9d29 build(deps): bump loader-utils from 2.0.3 to 2.0.4 (#5905)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:30:43 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e3249f930c build(deps): bump json5 from 1.0.1 to 1.0.2 (#6076)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 06:59:44 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cbe0d34f1a build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#5963)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:26:23 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bed8093e47 build(deps): bump json5 from 2.2.1 to 2.2.3 in /dev-docs (#6060)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:48 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1255ca2e84 build(deps): bump json5 from 2.2.1 to 2.2.3 in /src/packages/utils (#6061)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:35 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
14d02dcaea build(deps): bump json5 from 2.2.1 to 2.2.3 in /src/packages/excalidraw (#6062)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 12:25:20 +05:30
Excalidraw BotandGitHub 9747223705 chore: Update translations from Crowdin (#6052)
* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Hindi)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Galician)

* 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 (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 (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)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* New translations en.json (Chinese Traditional)

* New translations en.json (Norwegian Bokmal)

* Auto commit: Calculate translation coverage

* New translations en.json (Latvian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Spanish)

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Vietnamese)

* Auto commit: Calculate translation coverage

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Dutch)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* Auto commit: Calculate translation coverage

* New translations en.json (Latvian)

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Portuguese, Brazilian)

* Auto commit: Calculate translation coverage

* New translations en.json (Japanese)

* Auto commit: Calculate translation coverage
2023-01-06 12:23:14 +05:30
EternalWill43andGitHub 0f11f7da15 fix: HelpDialog (#6072) 2023-01-05 17:43:19 +01:00
8420aecb34 feat: new Menu Component API (#6034)
* feat: new Menu Component API

* allow valid children types

* introduce menu group to group items

* Add lang footer

* use display name

* displayName

* define types inside

* fix default menu

* add json export to menu

* fix

* simplify expression

* put open menu into own compo to optimize perf

So that we don't rerun `useOutsideClickHook` (and rebind event listeners
all the time)

* naming tweaks

* rename MenuComponents->MenuDefaultItems and export default items from Menu.Items

* import Menu.scss in Menu.tsx

* move menu scss to excal app

* Don't filter children inside menu group

* move E+ out of socials

* support style prop for MenuItem and MenuGroup

* Support header in menu group and add Excalidraw links header for default items in social section

* rename header to title

* fix padding for lang

* render menu in mobile

* review fixes

* tweaks

* Export collaborators and show in mobile menu

* revert .env

* lint :p

* again lint

* show correct actions in view mode for mobile

* Whitelist Collaborators Comp

* mobile styling

* padding

* don't show nerds when menu open in mobile

* lint :(

* hide shortcuts

* refactor userlist to support mobile and keep a wrapper comp for excal app

* use only UserList

* render only on mobile for default items

* remove unused hooks

* Show collab button in menu when onCollabButtonClick present and hide export when UIOptions.canvasActions.export is false

* fix tests

* lint

* inject userlist inside menu on mobile

* revert userlist

* move menu socials to default menu

* fix collab

* use meny in library

* Make Menu generic and create hamburgemenu for public excal menu and use menu in library as well

* use appState.openMenu for mobile

* fix tests

* styling fixes and support style and class name in menu content

* fix test

* rename MenuDefaultItems->DefaultItems

* move footer css to its own comp

* rename HamburgerMenu -> MainMenu

* rename menu -> dropdownMenu and update classes, onClick->onToggle

* close main menu when dialog closes

* by bye filtering

* update docs

* fix lint

* update example, docs for useDevice and footer in mobile, rename menu ->DropDownMenu everywhere

* spec

* remove isMenuOpenAtom and set openMenu as canvas for main menu, render decreases in specs :)

* [temp] remove cyclic depenedency to fix build

* hack- update appstate to sync lang change

* Add more specs

* wip: rewrite MainMenu footer

* fix margin

* fix snaps

* not needed as lang list no more imported

* simplify custom footer rendering

* Add DropdownMenuItemLink and DropdownMenuItemCustom and update API, docs

* fix `MainMenu.ItemCustom`

* naming

* use onSelect and base class for custom items

* fix lint

* fix snap

* use custom item for lang

* update docs

* fix

* properly use `MainMenu.ItemCustom` for `LanguageList`

* add margin top to custom items

* flex

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-01-05 22:04:23 +05:30
David LuzarandGitHub 08afb857c3 fix: show error message on collab save failure (#6063)
* fix: show error message on collab save failure

* comment
2023-01-02 23:53:51 +01:00
David LuzarandGitHub 9230c8f4d2 fix: remove ga from docker build (#6059)
* fix: remove ga from docker build

* lint

* fix debug
2023-01-02 21:21:57 +01:00
dba8f812f1 chore: Update translations from Crowdin (#5807)
Co-authored-by: David Luzar <luzar.david@gmail.com>
2023-01-01 17:01:46 +00:00
fdd8552637 feat: Scroll using PageUp and PageDown (#6038)
* feat: Scroll using PageUp and PageDown

* support x-axis via `shift` & enable in viewMode

* tweak test

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-31 15:54:37 -06:00
Aakansha DoshiandGitHub c8370b394c fix: use displayName since name gets stripped off when uglifying/minifiyng in production (#6036)
fix: use displayName since name gets stripped off when uglifying/minifiy in production
2022-12-27 15:17:13 +05:30
5fcf6a4845 fix: remove background from wysiwyg when editing arrow label (#6033)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2022-12-23 19:40:52 +01:00
Aakansha DoshiandGitHub af3b93c410 fix: use canvas measureText to calculate width in measureText (#6030)
* fix: use canvas measureText to calculate width in measureText

* calculate multiline width correctly using canvas measure text and rename functions

* set correct width when pasting in bound container

* take existing value + new pasted

* remove debugger :p

* fix snaps
2022-12-23 21:45:49 +05:30
David LuzarandGitHub 2595e0de82 fix: restoring deleted bindings (#6029)
* fix: restoring deleted bindings

* add tests

* add one more test

* merge restore tests files
2022-12-23 11:48:14 +01:00
8ec5f7b982 feat: support shrinking text containers to original height when text removed (#6025)
* fix:cache bind text containers height so that it could autoshrink to original height when text deleted

* revert

* rename

* reset cache when resized

* safe check

* restore original containr height when text is unbind

* update cache when redrawing bounding box

* reset cache when unbind

* make type-safe

* add specs

* skip one test

* remoe mock

* fix

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-23 11:57:48 +05:30
David LuzarandGitHub 9086674b27 chore: bump typescript @ 4.9.4 (#6024) 2022-12-22 19:32:21 +01:00
6273d56524 fix: ColorPicker getColor (#5949)
Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-22 12:53:49 +00:00
David LuzarandGitHub 7e135c4e22 feat: move contextMenu into the component tree and control via appState (#6021) 2022-12-21 12:47:09 +01:00
b704705ed8 feat: render footer as a component instead of render prop (#5970)
* feat: render footer as a component instead of render prop

* Export FooterCenter as footer

* remove useDevice export

* revert some changes

* remove

* add spec

* update specs

* parse children into a dictionary

* factor app footer components into a single file

* Add docs

* split app footer components

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-21 14:29:06 +05:30
Aakansha DoshiandGitHub d2e371cdf0 fix: don't push whitespace to next line when exceeding max width during wrapping and make sure to use same width of text editor on DOM when measuring dimensions (#5996)
* fix: don't push whitespace to next line when exceeding max width during wrapping

* add a helper function and never push empty line

* use width same as in text area so dimensions are same

* add tests

* make sure dom element has exact same width as text editor
2022-12-21 12:32:43 +05:30
David LuzarandGitHub 6ab3f0eb74 fix: showing grabbing cursor when holding spacebar (#6015) 2022-12-20 13:22:20 +01:00
David LuzarandGitHub 539505affd fix: resize sometimes throwing on missing null-checks (#6013) 2022-12-18 23:06:01 +01:00
David LuzarandGitHub 95d669390f fix: PWA not working after CRA@5 update (#6012)
* fix: PWA not working after CRA@5 update

* fix: fallback to default locale when fetch fails
2022-12-18 22:23:30 +01:00
David LuzarandGitHub 73a45e1988 fix: not properly restoring element stroke and bg colors (#6002) 2022-12-16 18:19:26 +01:00
David LuzarandGitHub 88c2812949 fix: Avatar outline on safari & center (#5997) 2022-12-16 18:18:34 +01:00
David LuzarandGitHub bdb14723b3 fix: chart pasting not working due to removing tab characters (#5987) 2022-12-16 18:18:27 +01:00
David LuzarandGitHub cc9e764585 feat: allow readonly actions to be used in viewMode (#5982) 2022-12-11 22:57:03 +01:00
8466eb0eef fix: apply the right type of roundness when pasting styles (#5979)
* fix: only paste roundness when target and source elements are of the same type

* apply roundness when pasting across different types

* simplify

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-10 20:12:09 +08:00
Aakansha DoshiandGitHub 0ebe6292a3 chore: add display name to context providers (#5974)
* chore: add display name to context providers

* fix typo
2022-12-08 16:19:44 +00:00
5854ac3eed feat: better default radius sizes for rectangles (#5553)
Co-authored-by: Ryan <diweihao@bytedance.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2022-12-08 16:48:49 +01:00
Aakansha DoshiandGitHub 65d84a5d5a fix: remove editor onpaste handler (#5971) 2022-12-07 23:05:57 +05:30
fennghuangandGitHub 808366d112 fix: remove blank space (#5950) 2022-12-06 17:40:53 +05:30
FerandGitHub 9311c99d3c fix: Galego and Kurdî missing in languages plus two locale typos (#5954) 2022-12-06 17:34:22 +05:30
DanielJGeigerandGitHub d131b31084 fix: ExcalidrawArrowElement rather than ExcalidrawArrowEleement (#5955) 2022-12-06 17:30:03 +05:30
Aakansha DoshiandGitHub 0111ca2050 fix: renderFooter styling (#5962) 2022-12-06 16:42:54 +05:30
Aakansha DoshiandGitHub a1dcd6d984 build: move release scripts to use release branch (#5958) 2022-12-06 16:33:02 +05:30
David LuzarandGitHub fffd4957db fix: repair element bindings on restore (#5956)
* fix: repair element bindings on restore

* fix dropping non-text bound elements

* be more conservative
2022-12-06 00:23:47 +01:00
Aakansha DoshiandGitHub 760fd7b3a6 feat: Support labels for arrow 🔥 (#5723)
* feat: support arrow with text

* render arrow -> clear rect-> render text

* move bound text when linear elements move

* fix centering cursor when linear element rotated

* fix y coord when new line added and container has 3 points

* update text position when 2nd point moved

* support adding label on top of 2nd point when 3 points are present

* change linear element editor shortcut to cmd+enter and fix tests

* scale bound text points when resizing via bounding box

* ohh yeah rotation works :)

* fix coords when updating text properties

* calculate new position after rotation always from original position

* rotate the bound text by same angle as parent

* don't rotate text and make sure dimensions and coords are always calculated from original point

* hardcoding the text width for now

* Move the linear element when bound text hit

* Rotation working yaay

* consider text element angle when editing

* refactor

* update x2 coords if needed when text updated

* simplify

* consider bound text to be part of bounding box when hit

* show bounding box correctly when multiple element selected

* fix typo

* support rotating multiple elements

* support multiple element resizing

* shift bound text to mid point when odd points

* Always render linear element handles inside editor after element rendered so point is visible for bound text

* Delete bound text when point attached to it deleted

* move bound to mid segement mid point when points are even

* shift bound text when points nearby deleted and handle segment deletion

* Resize working :)

* more resize fixes

* don't update cache-its breaking delete points, look for better soln

* update mid point cache for bound elements when updated

* introduce wrapping when resizing

* wrap when resize for 2 pointer linear elements

* support adding text for linear elements with more than 3 points

* export to svg  working :)

* clip from nearest enclosing element with non transparent color if present when exporting and fill with correct color in canvas

* fix snap

* use visible elements

* Make export to svg work with Mask :)

* remove id

* mask canvas linear element area where label is added

* decide the position of bound text during render

* fix coords when editing

* fix multiple resize

* update cache when bound text version changes

* fix masking when rotated

* render text in correct position in preview

* remove unnecessary code

* fix masking when rotating linear element

* fix masking with zoom

* fix mask in preview for export

* fix offsets in export view

* fix coords on svg export

* fix mask when element rotated in svg

* enable double-click to enter text

* fix hint

* Position cursor correctly and text dimensiosn when height of element is negative

* don't allow 2 pointer linear element with bound text width to go beyond min width

* code cleanup

* fix freedraw

* Add padding

* don't show vertical align action for linear element containers

* Add specs for getBoundTextElementPosition

* more specs

* move some utils to linearElementEditor.ts

* remove only :p

* check absoulte coods in test

* Add test to hide vertical align for linear eleemnt with bound text

* improve export preview

* support labels only for arrows

* spec

* fix large texts

* fix tests

* fix zooming

* enter line editor with cmd+double click

* Allow points to move beyond min width/height for 2 pointer arrow with bound text

* fix hint for line editing

* attempt to fix arrow getting deselected

* fix hint and shortcut

* Add padding of 5px when creating bound text and add spec

* Wrap bound text when arrow binding containers moved

* Add spec

* remove

* set boundTextElementVersion to null if not present

* dont use cache when version mismatch

* Add a padding of 5px vertically when creating text

* Add box sizing content box

* Set bound elements when text element created to fix the padding

* fix zooming in editor

* fix zoom in export

* remove globalCompositeOperation and use clearRect instead of fillRect
2022-12-05 21:03:13 +05:30
Aakansha DoshiandGitHub 1933116261 fix: don't allow whitespaces for bound text (#5939)
* fix: don't allow whitespaces for bound text

* fix

* remove

* remove empty else

* fix

* fix

* fix
2022-12-02 16:47:50 +05:30
David LuzarandGitHub 8b33ca3a1a fix: bindings do not survive history serialization (#5942) 2022-12-02 10:36:18 +00:00
Aakansha DoshiandGitHub a86224c797 fix: Dedupe boundElement ids when container duplicated with alt+drag (#5938)
* Dedupe boundElement ids when container duplicated with alt+drag and add spec

* set to null by default
2022-12-01 20:44:33 +05:30
Aakansha DoshiandGitHub 66bbfda460 fix: scale font correctly when using shift (#5935)
* fix: scale font correctly when using shift

* fix

* Empty-Commit

* Add spec

* fix
2022-11-30 15:55:01 +05:30
Aakansha DoshiandGitHub 88b2f4707d refactor: remove unnecessary code (#5933) 2022-11-29 16:41:02 +05:30
25c6056b03 feat: Don't add midpoint until dragged beyond a threshold (#5927)
* Don't add midpoint until dragged beyond a threshold

* remove unnecessary code

* fix tests

* fix

* add spec

* remove isMidpoint

* cleanup

* fix threshold for zoom

* split into shouldAddMidpoint and addMidpoint

* wrap in flushSync for synchronous updates

* remove threshold for line editor and add spec

* [unrelated] fix stack overflow state update

* fix tests

* don't drag arrow when dragging to add mid point

* add specs

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-11-29 00:01:53 +05:30
baf9651d34 feat: changed text copy/paste behaviour (#5786)
Co-authored-by: dwelle <luzar.david@gmail.com>
Co-authored-by: Antonio Della Fortuna <a.dellafortuna00@gmail.com>
2022-11-26 23:44:26 +01:00
d2181847be fix: Always bind to container selected by user (#5880)
* fix: Always bind to container selected by user

* Don't bind to container when using text tool

* adjust z-index for bound text

* fix

* Add spec

* Add test

* Allow double click on transparent container and add spec

* fix spec

* adjust z-index only when binding

* update index

* fix

* add index check

* Update src/scene/Scene.ts

Co-authored-by: dwelle <luzar.david@gmail.com>
2022-11-25 15:45:34 +05:30
David LuzarandGitHub 1f117995d9 fix: fonts not rendered on init if loadingdone not fired (#5923)
* fix: fonts not rendered on init if `loadingdone` not fired

* remove unnecessary check
2022-11-23 21:15:32 +01:00
64 changed files with 973 additions and 357 deletions
+5
View File
@@ -22,3 +22,8 @@ REACT_APP_DEV_ENABLE_SW=
REACT_APP_DEV_DISABLE_LIVE_RELOAD=
FAST_REFRESH=false
#Debug flags
# To enable bounding box for text containers
REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=
+1
View File
@@ -25,3 +25,4 @@ 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
coverage
@@ -4,6 +4,34 @@
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.
### Turning off Aggressive Anti-Fingerprinting in Brave browser
When *Aggressive Anti-Fingerprinting* is turned on, the `measureText` API breaks which in turn breaks the Text Elements in your drawings. Here is more [info](https://github.com/excalidraw/excalidraw/pull/6336) on the same.
We strongly recommend turning it off. You can follow the steps below on how to do so.
1. Open [excalidraw.com](https://excalidraw.com) in Brave and click on the **Shield** button
![Shield button](../../assets/brave-shield.png)
<div style={{width:'30rem'}}>
2. Once opened, look for **Aggressively Block Fingerprinting**
![Aggresive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
3. Switch to **Block Fingerprinting**
![Block filtering](../../assets/block-fingerprint.png)
4. Thats all. All text elements should be fixed now 🎉
</div>
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).
## Need help?
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw).
Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+9 -9
View File
@@ -1785,9 +1785,9 @@
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
@@ -4376,9 +4376,9 @@ htmlparser2@^8.0.1:
entities "^4.3.0"
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
http-deceiver@^1.2.7:
version "1.2.7"
@@ -7542,9 +7542,9 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3:
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.73.0:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
version "5.76.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c"
integrity sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
+7 -7
View File
@@ -25,11 +25,6 @@
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.5",
"@tldraw/vec": "1.7.1",
"@types/jest": "27.4.0",
"@types/pica": "5.1.3",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.29.1",
"clsx": "1.1.1",
"cross-env": "7.0.3",
@@ -57,7 +52,6 @@
"sass": "1.51.0",
"socket.io-client": "2.3.1",
"tunnel-rat": "0.1.0",
"typescript": "4.9.4",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
"workbox-cacheable-response": "^6.5.4",
@@ -75,9 +69,14 @@
"@excalidraw/eslint-config": "1.0.0",
"@excalidraw/prettier-config": "1.0.2",
"@types/chai": "4.3.0",
"@types/jest": "27.4.0",
"@types/lodash.throttle": "4.1.7",
"@types/pako": "1.0.3",
"@types/pica": "5.1.3",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/resize-observer-browser": "0.1.7",
"@types/socket.io-client": "1.4.36",
"chai": "4.3.6",
"dotenv": "16.0.1",
"eslint-config-prettier": "8.5.0",
@@ -88,7 +87,8 @@
"lint-staged": "12.3.7",
"pepjs": "0.5.3",
"prettier": "2.6.2",
"rewire": "6.0.0"
"rewire": "6.0.0",
"typescript": "4.9.4"
},
"engines": {
"node": ">=14.0.0"
+6
View File
@@ -2,6 +2,9 @@ const fs = require("fs");
const THRESSHOLD = 85;
// we're using BCP 47 language tags as keys
// e.g. https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1
const crowdinMap = {
"ar-SA": "en-ar",
"bg-BG": "en-bg",
@@ -52,6 +55,7 @@ const crowdinMap = {
"kk-KZ": "en-kk",
"vi-VN": "en-vi",
"mr-IN": "en-mr",
"th-TH": "en-th",
};
const flags = {
@@ -104,6 +108,7 @@ const flags = {
"eu-ES": "🇪🇦",
"vi-VN": "🇻🇳",
"mr-IN": "🇮🇳",
"th-TH": "🇹🇭",
};
const languages = {
@@ -156,6 +161,7 @@ const languages = {
"zh-TW": "繁體中文",
"vi-VN": "Tiếng Việt",
"mr-IN": "मराठी",
"th-TH": "ภาษาไทย",
};
const percentages = fs.readFileSync(
+148 -12
View File
@@ -1,7 +1,8 @@
import { VERTICAL_ALIGN } from "../constants";
import { getNonDeletedElements, isTextElement } from "../element";
import { BOUND_TEXT_PADDING, ROUNDNESS, VERTICAL_ALIGN } from "../constants";
import { getNonDeletedElements, isTextElement, newElement } from "../element";
import { mutateElement } from "../element/mutateElement";
import {
computeContainerDimensionForBoundText,
getBoundTextElement,
measureText,
redrawTextBoundingBox,
@@ -13,8 +14,11 @@ import {
import {
hasBoundTextElement,
isTextBindableContainer,
isUsingAdaptiveRadius,
} from "../element/typeChecks";
import {
ExcalidrawElement,
ExcalidrawLinearElement,
ExcalidrawTextContainer,
ExcalidrawTextElement,
} from "../element/types";
@@ -129,19 +133,151 @@ export const actionBindText = register({
}),
});
redrawTextBoundingBox(textElement, container);
const updatedElements = elements.slice();
const textElementIndex = updatedElements.findIndex(
(ele) => ele.id === textElement.id,
);
updatedElements.splice(textElementIndex, 1);
const containerIndex = updatedElements.findIndex(
(ele) => ele.id === container.id,
);
updatedElements.splice(containerIndex + 1, 0, textElement);
return {
elements: updatedElements,
elements: pushTextAboveContainer(elements, container, textElement),
appState: { ...appState, selectedElementIds: { [container.id]: true } },
commitToHistory: true,
};
},
});
const pushTextAboveContainer = (
elements: readonly ExcalidrawElement[],
container: ExcalidrawElement,
textElement: ExcalidrawTextElement,
) => {
const updatedElements = elements.slice();
const textElementIndex = updatedElements.findIndex(
(ele) => ele.id === textElement.id,
);
updatedElements.splice(textElementIndex, 1);
const containerIndex = updatedElements.findIndex(
(ele) => ele.id === container.id,
);
updatedElements.splice(containerIndex + 1, 0, textElement);
return updatedElements;
};
const pushContainerBelowText = (
elements: readonly ExcalidrawElement[],
container: ExcalidrawElement,
textElement: ExcalidrawTextElement,
) => {
const updatedElements = elements.slice();
const containerIndex = updatedElements.findIndex(
(ele) => ele.id === container.id,
);
updatedElements.splice(containerIndex, 1);
const textElementIndex = updatedElements.findIndex(
(ele) => ele.id === textElement.id,
);
updatedElements.splice(textElementIndex, 0, container);
return updatedElements;
};
export const actionCreateContainerFromText = register({
name: "createContainerFromText",
contextItemLabel: "labels.createContainerFromText",
trackEvent: { category: "element" },
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
return selectedElements.length === 1 && isTextElement(selectedElements[0]);
},
perform: (elements, appState) => {
const selectedElements = getSelectedElements(
getNonDeletedElements(elements),
appState,
);
const updatedElements = elements.slice();
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
const textElement = selectedElements[0];
const container = newElement({
type: "rectangle",
backgroundColor: appState.currentItemBackgroundColor,
boundElements: [
...(textElement.boundElements || []),
{ id: textElement.id, type: "text" },
],
angle: textElement.angle,
fillStyle: appState.currentItemFillStyle,
strokeColor: appState.currentItemStrokeColor,
roughness: appState.currentItemRoughness,
strokeWidth: appState.currentItemStrokeWidth,
strokeStyle: appState.currentItemStrokeStyle,
roundness:
appState.currentItemRoundness === "round"
? {
type: isUsingAdaptiveRadius("rectangle")
? ROUNDNESS.ADAPTIVE_RADIUS
: ROUNDNESS.PROPORTIONAL_RADIUS,
}
: null,
opacity: 100,
locked: false,
x: textElement.x - BOUND_TEXT_PADDING,
y: textElement.y - BOUND_TEXT_PADDING,
width: computeContainerDimensionForBoundText(
textElement.width,
"rectangle",
),
height: computeContainerDimensionForBoundText(
textElement.height,
"rectangle",
),
groupIds: textElement.groupIds,
});
// update bindings
if (textElement.boundElements?.length) {
const linearElementIds = textElement.boundElements
.filter((ele) => ele.type === "arrow")
.map((el) => el.id);
const linearElements = updatedElements.filter((ele) =>
linearElementIds.includes(ele.id),
) as ExcalidrawLinearElement[];
linearElements.forEach((ele) => {
let startBinding = null;
let endBinding = null;
if (ele.startBinding) {
startBinding = { ...ele.startBinding, elementId: container.id };
}
if (ele.endBinding) {
endBinding = { ...ele.endBinding, elementId: container.id };
}
mutateElement(ele, { startBinding, endBinding });
});
}
mutateElement(textElement, {
containerId: container.id,
verticalAlign: VERTICAL_ALIGN.MIDDLE,
boundElements: null,
});
redrawTextBoundingBox(textElement, container);
return {
elements: pushContainerBelowText(
[...elements, container],
container,
textElement,
),
appState: {
...appState,
selectedElementIds: {
[container.id]: true,
[textElement.id]: false,
},
},
commitToHistory: true,
};
}
return {
elements: updatedElements,
appState,
commitToHistory: true,
};
},
});
+3
View File
@@ -745,16 +745,19 @@ export const actionChangeTextAlign = register({
value: "left",
text: t("labels.left"),
icon: TextAlignLeftIcon,
testId: "align-left",
},
{
value: "center",
text: t("labels.center"),
icon: TextAlignCenterIcon,
testId: "align-horizontal-center",
},
{
value: "right",
text: t("labels.right"),
icon: TextAlignRightIcon,
testId: "align-right",
},
]}
value={getFormValue(
+1
View File
@@ -1,5 +1,6 @@
import { isDarwin } from "../constants";
import { t } from "../i18n";
import { SubtypeOf } from "../utility-types";
import { getShortcutKey } from "../utils";
import { ActionName } from "./types";
+3 -1
View File
@@ -6,6 +6,7 @@ import {
ExcalidrawProps,
BinaryFiles,
} from "../types";
import { MarkOptional } from "../utility-types";
export type ActionSource = "ui" | "keyboard" | "contextMenu" | "api";
@@ -113,7 +114,8 @@ export type ActionName =
| "toggleLock"
| "toggleLinearEditor"
| "toggleEraserTool"
| "toggleHandTool";
| "toggleHandTool"
| "createContainerFromText";
export type PanelComponentProps = {
elements: readonly ExcalidrawElement[];
+6 -2
View File
@@ -30,7 +30,10 @@ import clsx from "clsx";
import { actionToggleZenMode } from "../actions";
import "./Actions.scss";
import { Tooltip } from "./Tooltip";
import { shouldAllowVerticalAlign } from "../element/textElement";
import {
shouldAllowVerticalAlign,
suppportsHorizontalAlign,
} from "../element/textElement";
export const SelectedShapeActions = ({
appState,
@@ -122,7 +125,8 @@ export const SelectedShapeActions = ({
{renderAction("changeFontFamily")}
{renderAction("changeTextAlign")}
{suppportsHorizontalAlign(targetElements) &&
renderAction("changeTextAlign")}
</>
)}
+2
View File
@@ -1,6 +1,7 @@
import { atom, useAtom } from "jotai";
import { actionClearCanvas } from "../actions";
import { t } from "../i18n";
import { jotaiScope } from "../jotai";
import { useExcalidrawActionManager } from "./App";
import ConfirmDialog from "./ConfirmDialog";
@@ -9,6 +10,7 @@ export const activeConfirmDialogAtom = atom<"clearCanvas" | null>(null);
export const ActiveConfirmDialog = () => {
const [activeConfirmDialog, setActiveConfirmDialog] = useAtom(
activeConfirmDialogAtom,
jotaiScope,
);
const actionManager = useExcalidrawActionManager();
+45
View File
@@ -0,0 +1,45 @@
import ReactDOM from "react-dom";
import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random";
import { render, queryByTestId } from "../tests/test-utils";
import ExcalidrawApp from "../excalidraw-app";
const renderScene = jest.spyOn(Renderer, "renderScene");
describe("Test <App/>", () => {
beforeEach(async () => {
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
localStorage.clear();
renderScene.mockClear();
reseed(7);
});
it("should show error modal when using brave and measureText API is not working", async () => {
(global.navigator as any).brave = {
isBrave: {
name: "isBrave",
},
};
const originalContext = global.HTMLCanvasElement.prototype.getContext("2d");
//@ts-ignore
global.HTMLCanvasElement.prototype.getContext = (contextId) => {
return {
...originalContext,
measureText: () => ({
width: 0,
}),
};
};
await render(<ExcalidrawApp />);
expect(
queryByTestId(
document.querySelector(".excalidraw-modal-container")!,
"brave-measure-text-error",
),
).toMatchSnapshot();
});
});
+18 -97
View File
@@ -62,6 +62,7 @@ import {
GRID_SIZE,
IMAGE_RENDER_TIMEOUT,
isAndroid,
isBrave,
LINE_CONFIRM_THRESHOLD,
MAX_ALLOWED_FILE_BYTES,
MIME_TYPES,
@@ -175,10 +176,7 @@ import {
} from "../keys";
import { distance2d, getGridPoint, isPathALoop } from "../math";
import { renderScene } from "../renderer/renderScene";
import {
generateElementWithCanvas,
invalidateShapeForElement,
} from "../renderer/renderElement";
import { invalidateShapeForElement } from "../renderer/renderElement";
import {
calculateScrollCenter,
getElementsAtPosition,
@@ -270,6 +268,7 @@ import {
getContainerDims,
getContainerElement,
getTextBindableContainerAtPosition,
isMeasureTextSupported,
isValidTextContainer,
} from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
@@ -287,6 +286,8 @@ import { actionPaste } from "../actions/actionClipboard";
import { actionToggleHandTool } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import { actionCreateContainerFromText } from "../actions/actionBoundText";
import BraveMeasureTextError from "./BraveMeasureTextError";
const deviceContextInitialValue = {
isSmScreen: false,
@@ -431,7 +432,6 @@ class App extends React.Component<AppProps, AppState> {
};
this.id = nanoid();
this.library = new Library(this);
if (excalidrawRef) {
const readyPromise =
@@ -713,6 +713,8 @@ class App extends React.Component<AppProps, AppState> {
const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
let name = actionResult?.appState?.name ?? this.state.name;
const errorMessage =
actionResult?.appState?.errorMessage ?? this.state.errorMessage;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
}
@@ -728,7 +730,6 @@ class App extends React.Component<AppProps, AppState> {
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
this.setState(
(state) => {
// using Object.assign instead of spread to fool TS 4.2.2+ into
@@ -746,6 +747,7 @@ class App extends React.Component<AppProps, AppState> {
gridSize,
theme,
name,
errorMessage,
});
},
() => {
@@ -874,7 +876,6 @@ class App extends React.Component<AppProps, AppState> {
),
};
}
// FontFaceSet loadingdone event we listen on may not always fire
// (looking at you Safari), so on init we manually load fonts for current
// text elements on canvas, and rerender them once done. This also
@@ -1002,6 +1003,13 @@ class App extends React.Component<AppProps, AppState> {
} else {
this.updateDOMRect(this.initializeScene);
}
// note that this check seems to always pass in localhost
if (isBrave() && !isMeasureTextSupported()) {
this.setState({
errorMessage: <BraveMeasureTextError />,
});
}
}
public componentWillUnmount() {
@@ -1645,7 +1653,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.replaceAllElements(nextElements);
newElements.forEach((newElement) => {
if (isTextElement(newElement) && isBoundToContainer(newElement)) {
if (isTextElement(newElement)) {
const container = getContainerElement(newElement);
redrawTextBoundingBox(newElement, container);
}
@@ -2770,7 +2778,6 @@ class App extends React.Component<AppProps, AppState> {
);
if (container) {
if (
isArrowElement(container) ||
hasBoundTextElement(container) ||
!isTransparent(container.backgroundColor) ||
isHittingElementNotConsideringBoundingBox(container, this.state, [
@@ -6241,6 +6248,7 @@ class App extends React.Component<AppProps, AppState> {
actionGroup,
actionUnbindText,
actionBindText,
actionCreateContainerFromText,
actionUngroup,
CONTEXT_MENU_SEPARATOR,
actionAddToLibrary,
@@ -6370,94 +6378,7 @@ class App extends React.Component<AppProps, AppState> {
private resetShouldCacheIgnoreZoomDebounced = debounce(() => {
if (!this.unmounted) {
const cursorButton: {
[id: string]: string | undefined;
} = {};
const pointerViewportCoords: RenderConfig["remotePointerViewportCoords"] =
{};
const remoteSelectedElementIds: RenderConfig["remoteSelectedElementIds"] =
{};
const pointerUsernames: { [id: string]: string } = {};
const pointerUserStates: { [id: string]: string } = {};
this.state.collaborators.forEach((user, socketId) => {
if (user.selectedElementIds) {
for (const id of Object.keys(user.selectedElementIds)) {
if (!(id in remoteSelectedElementIds)) {
remoteSelectedElementIds[id] = [];
}
remoteSelectedElementIds[id].push(socketId);
}
}
if (!user.pointer) {
return;
}
if (user.username) {
pointerUsernames[socketId] = user.username;
}
if (user.userState) {
pointerUserStates[socketId] = user.userState;
}
pointerViewportCoords[socketId] = sceneCoordsToViewportCoords(
{
sceneX: user.pointer.x,
sceneY: user.pointer.y,
},
this.state,
);
cursorButton[socketId] = user.button;
});
const renderingElements = this.scene
.getNonDeletedElements()
.filter((element) => {
if (isImageElement(element)) {
if (
// not placed on canvas yet (but in elements array)
this.state.pendingImageElementId === element.id
) {
return false;
}
}
// don't render text element that's being currently edited (it's
// rendered on remote only)
return (
!this.state.editingElement ||
this.state.editingElement.type !== "text" ||
element.id !== this.state.editingElement.id
);
});
const selectionColor = getComputedStyle(
document.querySelector(".excalidraw")!,
).getPropertyValue("--color-selection");
const renderConfig: RenderConfig = {
selectionColor,
scrollX: this.state.scrollX,
scrollY: this.state.scrollY,
viewBackgroundColor: this.state.viewBackgroundColor,
zoom: this.state.zoom,
remotePointerViewportCoords: pointerViewportCoords,
remotePointerButton: cursorButton,
remoteSelectedElementIds,
remotePointerUsernames: pointerUsernames,
remotePointerUserStates: pointerUserStates,
shouldCacheIgnoreZoom: false,
theme: this.state.theme,
imageCache: this.imageCache,
isExporting: false,
renderScrollbars: !this.device.isMobile,
};
let i = 0;
for (; i < renderingElements.length; i += 200) {
const chunk = renderingElements.slice(i, i + 200);
setTimeout(
() =>
chunk.forEach((el) => generateElementWithCanvas(el, renderConfig)),
i * 50,
);
}
setTimeout(() => this.setState({ shouldCacheIgnoreZoom: false }), i * 50);
this.setState({ shouldCacheIgnoreZoom: false });
}
}, 300);
+42
View File
@@ -0,0 +1,42 @@
import { t } from "../i18n";
const BraveMeasureTextError = () => {
return (
<div data-testid="brave-measure-text-error">
<p>
{t("errors.brave_measure_text_error.start")} &nbsp;
<span style={{ fontWeight: 600 }}>
{t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
</span>{" "}
{t("errors.brave_measure_text_error.setting_enabled")}.
<br />
<br />
{t("errors.brave_measure_text_error.break")}{" "}
<span style={{ fontWeight: 600 }}>
{t("errors.brave_measure_text_error.text_elements")}
</span>{" "}
{t("errors.brave_measure_text_error.in_your_drawings")}.
</p>
<p>
{t("errors.brave_measure_text_error.strongly_recommend")}{" "}
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
{" "}
{t("errors.brave_measure_text_error.steps")}
</a>{" "}
{t("errors.brave_measure_text_error.how")}.
</p>
<p>
{t("errors.brave_measure_text_error.disable_setting")}{" "}
<a href="https://github.com/excalidraw/excalidraw/issues/new">
{t("errors.brave_measure_text_error.issue")}
</a>{" "}
{t("errors.brave_measure_text_error.write")}{" "}
<a href="https://discord.gg/UexuTaE">
{t("errors.brave_measure_text_error.discord")}
</a>
.
</p>
</div>
);
};
export default BraveMeasureTextError;
+2 -1
View File
@@ -6,6 +6,7 @@ import DialogActionButton from "./DialogActionButton";
import { useSetAtom } from "jotai";
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
import { useExcalidrawSetAppState } from "./App";
import { jotaiScope } from "../jotai";
interface Props extends Omit<DialogProps, "onCloseRequest"> {
onConfirm: () => void;
@@ -24,7 +25,7 @@ const ConfirmDialog = (props: Props) => {
...rest
} = props;
const setAppState = useExcalidrawSetAppState();
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
return (
<Dialog
+2 -1
View File
@@ -16,6 +16,7 @@ import { AppState } from "../types";
import { queryFocusableElements } from "../utils";
import { useSetAtom } from "jotai";
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
import { jotaiScope } from "../jotai";
export interface DialogProps {
children: React.ReactNode;
@@ -72,7 +73,7 @@ export const Dialog = (props: DialogProps) => {
}, [islandNode, props.autofocus]);
const setAppState = useExcalidrawSetAppState();
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
const onClose = () => {
setAppState({ openMenu: null });
+4 -4
View File
@@ -5,13 +5,13 @@ import { Dialog } from "./Dialog";
import { useExcalidrawContainer } from "./App";
export const ErrorDialog = ({
message,
children,
onClose,
}: {
message: string;
children?: React.ReactNode;
onClose?: () => void;
}) => {
const [modalIsShown, setModalIsShown] = useState(!!message);
const [modalIsShown, setModalIsShown] = useState(!!children);
const { container: excalidrawContainer } = useExcalidrawContainer();
const handleClose = React.useCallback(() => {
@@ -32,7 +32,7 @@ export const ErrorDialog = ({
onCloseRequest={handleClose}
title={t("errorDialog.title")}
>
<div style={{ whiteSpace: "pre-wrap" }}>{message}</div>
<div style={{ whiteSpace: "pre-wrap" }}>{children}</div>
</Dialog>
)}
</>
+4
View File
@@ -9,6 +9,10 @@
text-align: center;
padding: var(--preview-padding);
margin-bottom: calc(var(--space-factor) * 3);
display: flex;
justify-content: center;
align-items: center;
}
.ExportDialog__preview canvas {
+3 -3
View File
@@ -1,7 +1,7 @@
import { t } from "../i18n";
import { HelpIcon } from "./icons";
type HelpButtonProps = {
title?: string;
name?: string;
id?: string;
onClick?(): void;
@@ -12,8 +12,8 @@ export const HelpButton = (props: HelpButtonProps) => (
className="help-icon"
onClick={props.onClick}
type="button"
title={`${props.title} — ?`}
aria-label={props.title}
title={`${t("helpDialog.title")} — ?`}
aria-label={t("helpDialog.title")}
>
{HelpIcon}
</button>
+3 -4
View File
@@ -364,10 +364,9 @@ const LayerUI = ({
{appState.isLoading && <LoadingMessage delay={250} />}
{appState.errorMessage && (
<ErrorDialog
message={appState.errorMessage}
onClose={() => setAppState({ errorMessage: null })}
/>
<ErrorDialog onClose={() => setAppState({ errorMessage: null })}>
{appState.errorMessage}
</ErrorDialog>
)}
{appState.openDialog === "help" && (
<HelpDialog
@@ -48,6 +48,7 @@ export const LibraryMenuHeader: React.FC<{
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(
isLibraryMenuOpenAtom,
jotaiScope,
);
const renderRemoveLibAlert = useCallback(() => {
const content = selectedItems.length
@@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test <App/> should show error modal when using brave and measureText API is not working 1`] = `
<div
data-testid="brave-measure-text-error"
>
<p>
Looks like you are using Brave browser with the
 
<span
style="font-weight: 600;"
>
Aggressively Block Fingerprinting
</span>
setting enabled
.
<br />
<br />
This could result in breaking the
<span
style="font-weight: 600;"
>
Text Elements
</span>
in your drawings
.
</p>
<p>
We strongly recommend disabling this setting. You can follow
<a
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
>
these steps
</a>
on how to do so
.
</p>
<p>
If disabling this setting doesn't fix the display of text elements, please open an
<a
href="https://github.com/excalidraw/excalidraw/issues/new"
>
issue
</a>
on our GitHub, or write us on
<a
href="https://discord.gg/UexuTaE"
>
Discord
</a>
.
</p>
</div>
`;
+5 -1
View File
@@ -31,6 +31,7 @@ import "./DefaultItems.scss";
import clsx from "clsx";
import { useSetAtom } from "jotai";
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
import { jotaiScope } from "../../jotai";
export const LoadScene = () => {
const { t } = useI18n();
@@ -113,7 +114,10 @@ Help.displayName = "Help";
export const ClearCanvas = () => {
const { t } = useI18n();
const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
const setActiveConfirmDialog = useSetAtom(
activeConfirmDialogAtom,
jotaiScope,
);
const actionManager = useExcalidrawActionManager();
if (!actionManager.isActionEnabled(actionClearCanvas)) {
+3
View File
@@ -12,6 +12,9 @@ export const isFirefox =
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
export const isSafari =
!isChrome && navigator.userAgent.indexOf("Safari") !== -1;
// keeping function so it can be mocked in test
export const isBrave = () =>
(navigator as any).brave?.isBrave?.name === "isBrave";
export const APP_NAME = "Excalidraw";
+2 -1
View File
@@ -7,6 +7,7 @@ import { CanvasError } from "../errors";
import { t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { AppState, DataURL, LibraryItem } from "../types";
import { ValueOf } from "../utility-types";
import { bytesToHexString } from "../utils";
import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
import { isValidExcalidrawData, isValidLibrary } from "./json";
@@ -156,7 +157,7 @@ export const loadSceneOrLibraryFromBlob = async (
},
localAppState,
localElements,
{ repairBindings: true },
{ repairBindings: true, refreshDimensions: true },
),
};
} else if (isValidLibrary(data)) {
+1
View File
@@ -34,6 +34,7 @@ import { bumpVersion } from "../element/mutateElement";
import { getUpdatedTimestamp, updateActiveTool } from "../utils";
import { arrayToMap } from "../utils";
import oc from "open-color";
import { MarkOptional, Mutable } from "../utility-types";
type RestoredAppState = Omit<
AppState,
+1
View File
@@ -23,6 +23,7 @@ import {
import { rescalePoints } from "../points";
import { getBoundTextElement, getContainerElement } from "./textElement";
import { LinearElementEditor } from "./linearElementEditor";
import { Mutable } from "../utility-types";
// x and y position of top left corner, x and y position of bottom right corner
export type Bounds = readonly [number, number, number, number];
+1
View File
@@ -38,6 +38,7 @@ import { isTextElement } from ".";
import { isTransparent } from "../utils";
import { shouldShowBoundingBox } from "./transformHandles";
import { getBoundTextElement } from "./textElement";
import { Mutable } from "../utility-types";
const isElementDraggableFromInside = (
element: NonDeletedExcalidrawElement,
+1
View File
@@ -41,6 +41,7 @@ import { shouldRotateWithDiscreteAngle } from "../keys";
import { getBoundTextElement, handleBindTextResize } from "./textElement";
import { getShapeForElement } from "../renderer/renderElement";
import { DRAGGING_THRESHOLD } from "../constants";
import { Mutable } from "../utility-types";
const editorMidPointsCache: {
version: number | null;
+1
View File
@@ -5,6 +5,7 @@ import { getSizeFromPoints } from "../points";
import { randomInteger } from "../random";
import { Point } from "../types";
import { getUpdatedTimestamp } from "../utils";
import { Mutable } from "../utility-types";
type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
Partial<TElement>,
+1
View File
@@ -32,6 +32,7 @@ import {
} from "./textElement";
import { VERTICAL_ALIGN } from "../constants";
import { isArrowElement } from "./typeChecks";
import { MarkOptional, Merge, Mutable } from "../utility-types";
type ElementConstructorOpts = MarkOptional<
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
+3 -1
View File
@@ -693,7 +693,9 @@ const resizeMultipleElements = (
};
const fontSize = measureFontSizeFromWidth(
boundTextElement ?? (element.orig as ExcalidrawTextElement),
getMaxContainerWidth(updatedElement),
boundTextElement
? getMaxContainerWidth(updatedElement)
: updatedElement.width,
);
if (!fontSize) {
+26 -10
View File
@@ -1,7 +1,7 @@
import { BOUND_TEXT_PADDING } from "../constants";
import { API } from "../tests/helpers/api";
import {
computeContainerHeightForBoundText,
computeContainerDimensionForBoundText,
getContainerCoords,
getMaxContainerWidth,
getMaxContainerHeight,
@@ -16,7 +16,7 @@ describe("Test wrapText", () => {
const text = "Hello whats up ";
const maxWidth = 200 - BOUND_TEXT_PADDING * 2;
const res = wrapText(text, font, maxWidth);
expect(res).toBe("Hello whats up ");
expect(res).toBe(text);
});
it("should work with emojis", () => {
@@ -26,7 +26,7 @@ describe("Test wrapText", () => {
expect(res).toBe("😀");
});
it("should show the text correctly when min width reached", () => {
it("should show the text correctly when max width reached", () => {
const text = "Hello😀";
const maxWidth = 10;
const res = wrapText(text, font, maxWidth);
@@ -35,10 +35,11 @@ describe("Test wrapText", () => {
describe("When text doesn't contain new lines", () => {
const text = "Hello whats up";
[
{
desc: "break all words when width of each word is less than container width",
width: 90,
width: 80,
res: `Hello
whats
up`,
@@ -62,7 +63,7 @@ p`,
{
desc: "break words as per the width",
width: 150,
width: 140,
res: `Hello whats
up`,
},
@@ -93,7 +94,7 @@ whats up`;
[
{
desc: "break all words when width of each word is less than container width",
width: 90,
width: 80,
res: `Hello
whats
up`,
@@ -135,6 +136,7 @@ whats up`,
});
});
});
describe("When text is long", () => {
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
[
@@ -174,6 +176,14 @@ break it now`,
});
});
});
it("should wrap the text correctly when word length is exactly equal to max width", () => {
const text = "Hello Excalidraw";
// Length of "Excalidraw" is 100 and exacty equal to max width
const res = wrapText(text, font, 100);
expect(res).toEqual(`Hello
Excalidraw`);
});
});
describe("Test measureText", () => {
@@ -214,7 +224,7 @@ describe("Test measureText", () => {
});
});
describe("Test computeContainerHeightForBoundText", () => {
describe("Test computeContainerDimensionForBoundText", () => {
const params = {
width: 178,
height: 194,
@@ -225,7 +235,9 @@ describe("Test measureText", () => {
type: "rectangle",
...params,
});
expect(computeContainerHeightForBoundText(element, 150)).toEqual(160);
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
160,
);
});
it("should compute container height correctly for ellipse", () => {
@@ -233,7 +245,9 @@ describe("Test measureText", () => {
type: "ellipse",
...params,
});
expect(computeContainerHeightForBoundText(element, 150)).toEqual(226);
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
226,
);
});
it("should compute container height correctly for diamond", () => {
@@ -241,7 +255,9 @@ describe("Test measureText", () => {
type: "diamond",
...params,
});
expect(computeContainerHeightForBoundText(element, 150)).toEqual(320);
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
320,
);
});
});
+88 -63
View File
@@ -8,15 +8,17 @@ import {
NonDeletedExcalidrawElement,
} from "./types";
import { mutateElement } from "./mutateElement";
import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
import {
BOUND_TEXT_PADDING,
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
TEXT_ALIGN,
VERTICAL_ALIGN,
} from "../constants";
import { MaybeTransformHandleType } from "./transformHandles";
import Scene from "../scene/Scene";
import { isTextElement } from ".";
import {
isBoundToContainer,
isImageElement,
isArrowElement,
} from "./typeChecks";
import { isBoundToContainer, isArrowElement } from "./typeChecks";
import { LinearElementEditor } from "./linearElementEditor";
import { AppState } from "../types";
import { isTextBindableContainer } from "./typeChecks";
@@ -27,6 +29,7 @@ import {
resetOriginalContainerCache,
updateOriginalContainerCache,
} from "./textWysiwyg";
import { ExtractSetType } from "../utility-types";
export const normalizeText = (text: string) => {
return (
@@ -84,9 +87,9 @@ export const redrawTextBoundingBox = (
let nextHeight = containerDims.height;
if (metrics.height > maxContainerHeight) {
nextHeight = computeContainerHeightForBoundText(
container,
nextHeight = computeContainerDimensionForBoundText(
metrics.height,
container.type,
);
mutateElement(container, { height: nextHeight });
maxContainerHeight = getMaxContainerHeight(container);
@@ -188,9 +191,9 @@ export const handleBindTextResize = (
}
// increase height in case text element height exceeds
if (nextHeight > maxHeight) {
containerHeight = computeContainerHeightForBoundText(
container,
containerHeight = computeContainerDimensionForBoundText(
nextHeight,
container.type,
);
const diff = containerHeight - containerDims.height;
@@ -325,25 +328,37 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
const originalLines = text.split("\n");
const spaceWidth = getLineWidth(" ", font);
let currentLine = "";
let currentLineWidthTillNow = 0;
const push = (str: string) => {
if (str.trim()) {
lines.push(str);
}
};
const resetParams = () => {
currentLine = "";
currentLineWidthTillNow = 0;
};
originalLines.forEach((originalLine) => {
const words = originalLine.split(" ");
// This means its newline so push it
if (words.length === 1 && words[0] === "") {
lines.push(words[0]);
const currentLineWidth = getTextWidth(originalLine, font);
//Push the line if its <= maxWidth
if (currentLineWidth <= maxWidth) {
lines.push(originalLine);
return; // continue
}
let currentLine = "";
let currentLineWidthTillNow = 0;
const words = originalLine.split(" ");
resetParams();
let index = 0;
while (index < words.length) {
const currentWordWidth = getLineWidth(words[index], font);
// This will only happen when single word takes entire width
if (currentWordWidth === maxWidth) {
push(words[index]);
@@ -355,8 +370,8 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
// push current line since the current word exceeds the max width
// so will be appended in next line
push(currentLine);
currentLine = "";
currentLineWidthTillNow = 0;
resetParams();
while (words[index].length > 0) {
const currentChar = String.fromCodePoint(
@@ -367,10 +382,6 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
words[index] = words[index].slice(currentChar.length);
if (currentLineWidthTillNow >= maxWidth) {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
}
push(currentLine);
currentLine = currentChar;
currentLineWidthTillNow = width;
@@ -378,11 +389,11 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
currentLine += currentChar;
}
}
// push current line if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
push(currentLine);
currentLine = "";
currentLineWidthTillNow = 0;
resetParams();
} else {
// space needs to be appended before next word
// as currentLine contains chars which couldn't be appended
@@ -390,7 +401,6 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
currentLine += " ";
currentLineWidthTillNow += spaceWidth;
}
index++;
} else {
// Start appending words in a line till max width reached
@@ -398,10 +408,9 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
const word = words[index];
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
if (currentLineWidthTillNow >= maxWidth) {
if (currentLineWidthTillNow > maxWidth) {
push(currentLine);
currentLineWidthTillNow = 0;
currentLine = "";
resetParams();
break;
}
@@ -412,22 +421,15 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
const word = currentLine.slice(0, -1);
push(word);
currentLine = "";
currentLineWidthTillNow = 0;
resetParams();
break;
}
}
if (currentLineWidthTillNow === maxWidth) {
currentLine = "";
currentLineWidthTillNow = 0;
}
}
}
if (currentLine) {
if (currentLine.slice(-1) === " ") {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
}
currentLine = currentLine.slice(0, -1);
push(currentLine);
}
});
@@ -666,14 +668,24 @@ export const shouldAllowVerticalAlign = (
}
return true;
}
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
if (isArrowElement(element)) {
return false;
});
};
export const suppportsHorizontalAlign = (
selectedElements: NonDeletedExcalidrawElement[],
) => {
return selectedElements.some((element) => {
const hasBoundContainer = isBoundToContainer(element);
if (hasBoundContainer) {
const container = getContainerElement(element);
if (isTextElement(element) && isArrowElement(container)) {
return false;
}
return true;
}
return false;
return isTextElement(element);
});
};
@@ -714,32 +726,34 @@ export const getTextBindableContainerAtPosition = (
return isTextBindableContainer(hitElement, false) ? hitElement : null;
};
export const isValidTextContainer = (element: ExcalidrawElement) => {
return (
element.type === "rectangle" ||
element.type === "ellipse" ||
element.type === "diamond" ||
isImageElement(element) ||
isArrowElement(element)
);
};
const VALID_CONTAINER_TYPES = new Set([
"rectangle",
"ellipse",
"diamond",
"image",
"arrow",
]);
export const computeContainerHeightForBoundText = (
container: NonDeletedExcalidrawElement,
boundTextElementHeight: number,
export const isValidTextContainer = (element: ExcalidrawElement) =>
VALID_CONTAINER_TYPES.has(element.type);
export const computeContainerDimensionForBoundText = (
dimension: number,
containerType: ExtractSetType<typeof VALID_CONTAINER_TYPES>,
) => {
if (container.type === "ellipse") {
return Math.round(
((boundTextElementHeight + BOUND_TEXT_PADDING * 2) / Math.sqrt(2)) * 2,
);
dimension = Math.ceil(dimension);
const padding = BOUND_TEXT_PADDING * 2;
if (containerType === "ellipse") {
return Math.round(((dimension + padding) / Math.sqrt(2)) * 2);
}
if (isArrowElement(container)) {
return boundTextElementHeight + BOUND_TEXT_PADDING * 8 * 2;
if (containerType === "arrow") {
return dimension + padding * 8;
}
if (container.type === "diamond") {
return 2 * (boundTextElementHeight + BOUND_TEXT_PADDING * 2);
if (containerType === "diamond") {
return 2 * (dimension + padding);
}
return boundTextElementHeight + BOUND_TEXT_PADDING * 2;
return dimension + padding;
};
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
@@ -796,3 +810,14 @@ export const getMaxContainerHeight = (container: ExcalidrawElement) => {
}
return height - BOUND_TEXT_PADDING * 2;
};
export const isMeasureTextSupported = () => {
const width = getTextWidth(
DUMMY_TEXT,
getFontString({
fontSize: DEFAULT_FONT_SIZE,
fontFamily: DEFAULT_FONT_FAMILY,
}),
);
return width > 0;
};
+128 -4
View File
@@ -3,10 +3,14 @@ import ExcalidrawApp from "../excalidraw-app";
import { GlobalTestState, render, screen } from "../tests/test-utils";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { CODES, KEYS } from "../keys";
import { fireEvent } from "../tests/test-utils";
import {
fireEvent,
mockBoundingClientRect,
restoreOriginalGetBoundingClientRect,
} from "../tests/test-utils";
import { queryByText } from "@testing-library/react";
import { FONT_FAMILY } from "../constants";
import { FONT_FAMILY, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
import {
ExcalidrawTextElement,
ExcalidrawTextElementWithContainer,
@@ -15,6 +19,7 @@ import { API } from "../tests/helpers/api";
import { mutateElement } from "./mutateElement";
import { resize } from "../tests/utils";
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -221,11 +226,19 @@ describe("textWysiwyg", () => {
describe("Test container-unbound text", () => {
const { h } = window;
const dimensions = { height: 400, width: 800 };
let textarea: HTMLTextAreaElement;
let textElement: ExcalidrawTextElement;
beforeAll(() => {
mockBoundingClientRect(dimensions);
});
beforeEach(async () => {
await render(<ExcalidrawApp />);
//@ts-ignore
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
textElement = UI.createElement("text");
@@ -235,6 +248,10 @@ describe("textWysiwyg", () => {
)!;
});
afterAll(() => {
restoreOriginalGetBoundingClientRect();
});
it("should add a tab at the start of the first line", () => {
const event = new KeyboardEvent("keydown", { key: KEYS.TAB });
textarea.value = "Line#1\nLine#2";
@@ -433,6 +450,27 @@ describe("textWysiwyg", () => {
);
expect(h.state.zoom.value).toBe(1);
});
it("text should never go beyond max width", async () => {
UI.clickTool("text");
mouse.clickAt(750, 300);
textarea = document.querySelector(
".excalidraw-textEditorContainer > textarea",
)!;
fireEvent.change(textarea, {
target: {
value:
"Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
},
});
textarea.dispatchEvent(new Event("input"));
await new Promise((cb) => setTimeout(cb, 0));
textarea.blur();
expect(textarea.style.width).toBe("792px");
expect(h.elements[0].width).toBe(1000);
});
});
describe("Test container-bound text", () => {
@@ -631,11 +669,11 @@ describe("textWysiwyg", () => {
["freedraw", "line"].forEach((type: any) => {
it(`shouldn't create text element when pressing 'Enter' key on ${type} `, async () => {
h.elements = [];
const elemnet = UI.createElement(type, {
const element = UI.createElement(type, {
width: 100,
height: 50,
});
API.setSelectedElements([elemnet]);
API.setSelectedElements([element]);
Keyboard.keyPress(KEYS.ENTER);
expect(h.elements.length).toBe(1);
});
@@ -1270,5 +1308,91 @@ describe("textWysiwyg", () => {
`);
});
});
it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
UI.clickTool("text");
mouse.clickAt(20, 30);
const editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
fireEvent.change(editor, {
target: {
value: "Excalidraw is an opensource virtual collaborative whiteboard",
},
});
editor.dispatchEvent(new Event("input"));
await new Promise((cb) => setTimeout(cb, 0));
editor.select();
fireEvent.click(screen.getByTitle("Left"));
await new Promise((r) => setTimeout(r, 0));
editor.blur();
const textElement = h.elements[1] as ExcalidrawTextElement;
expect(textElement.width).toBe(600);
expect(textElement.height).toBe(24);
expect(textElement.textAlign).toBe(TEXT_ALIGN.LEFT);
expect((textElement as ExcalidrawTextElement).text).toBe(
"Excalidraw is an opensource virtual collaborative whiteboard",
);
API.setSelectedElements([textElement]);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 20,
clientY: 30,
});
const contextMenu = document.querySelector(".context-menu");
fireEvent.click(
queryByText(contextMenu as HTMLElement, "Wrap text in a container")!,
);
expect(h.elements.length).toBe(3);
expect(h.elements[1]).toEqual(
expect.objectContaining({
angle: 0,
backgroundColor: "transparent",
boundElements: [
{
id: h.elements[2].id,
type: "text",
},
],
fillStyle: "hachure",
groupIds: [],
height: 34,
isDeleted: false,
link: null,
locked: false,
opacity: 100,
roughness: 1,
roundness: {
type: 3,
},
strokeColor: "#000000",
strokeStyle: "solid",
strokeWidth: 1,
type: "rectangle",
updated: 1,
version: 1,
width: 610,
x: 15,
y: 25,
}),
);
expect(h.elements[2] as ExcalidrawTextElement).toEqual(
expect.objectContaining({
text: "Excalidraw is an opensource virtual collaborative whiteboard",
verticalAlign: VERTICAL_ALIGN.MIDDLE,
textAlign: TEXT_ALIGN.LEFT,
boundElements: null,
}),
);
});
});
});
+3 -4
View File
@@ -11,7 +11,7 @@ import {
isBoundToContainer,
isTextElement,
} from "./typeChecks";
import { CLASSES, isFirefox, isSafari, VERTICAL_ALIGN } from "../constants";
import { CLASSES, VERTICAL_ALIGN } from "../constants";
import {
ExcalidrawElement,
ExcalidrawLinearElement,
@@ -272,9 +272,8 @@ export const textWysiwyg = ({
: updatedTextElement.height / lines.length;
if (!container) {
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
}
// As firefox, Safari needs little higher dimensions on DOM
if (isFirefox || isSafari) {
textElementWidth = Math.min(textElementWidth, maxWidth);
} else {
textElementWidth += 0.5;
}
// Make sure text editor height doesn't go beyond viewport
+66
View File
@@ -0,0 +1,66 @@
import { API } from "../tests/helpers/api";
import { hasBoundTextElement } from "./typeChecks";
describe("Test TypeChecks", () => {
describe("Test hasBoundTextElement", () => {
it("should return true for text bindable containers with bound text", () => {
expect(
hasBoundTextElement(
API.createElement({
type: "rectangle",
boundElements: [{ type: "text", id: "text-id" }],
}),
),
).toBeTruthy();
expect(
hasBoundTextElement(
API.createElement({
type: "ellipse",
boundElements: [{ type: "text", id: "text-id" }],
}),
),
).toBeTruthy();
expect(
hasBoundTextElement(
API.createElement({
type: "arrow",
boundElements: [{ type: "text", id: "text-id" }],
}),
),
).toBeTruthy();
expect(
hasBoundTextElement(
API.createElement({
type: "image",
boundElements: [{ type: "text", id: "text-id" }],
}),
),
).toBeTruthy();
});
it("should return false for text bindable containers without bound text", () => {
expect(
hasBoundTextElement(
API.createElement({
type: "freedraw",
boundElements: [{ type: "arrow", id: "arrow-id" }],
}),
),
).toBeFalsy();
});
it("should return false for non text bindable containers", () => {
expect(
hasBoundTextElement(
API.createElement({
type: "freedraw",
boundElements: [{ type: "text", id: "text-id" }],
}),
),
).toBeFalsy();
});
});
});
+2 -1
View File
@@ -1,5 +1,6 @@
import { ROUNDNESS } from "../constants";
import { AppState } from "../types";
import { MarkNonNullable } from "../utility-types";
import {
ExcalidrawElement,
ExcalidrawTextElement,
@@ -139,7 +140,7 @@ export const hasBoundTextElement = (
element: ExcalidrawElement | null,
): element is MarkNonNullable<ExcalidrawBindableElement, "boundElements"> => {
return (
isBindableElement(element) &&
isTextBindableContainer(element) &&
!!element.boundElements?.some(({ type }) => type === "text")
);
};
+1
View File
@@ -6,6 +6,7 @@ import {
THEME,
VERTICAL_ALIGN,
} from "../constants";
import { MarkNonNullable, ValueOf } from "../utility-types";
export type ChartType = "bar" | "line";
export type FillStyle = "hachure" | "cross-hatch" | "solid";
+3 -4
View File
@@ -838,10 +838,9 @@ class Collab extends PureComponent<Props, CollabState> {
/>
)}
{errorMessage && (
<ErrorDialog
message={errorMessage}
onClose={() => this.setState({ errorMessage: "" })}
/>
<ErrorDialog onClose={() => this.setState({ errorMessage: "" })}>
{errorMessage}
</ErrorDialog>
)}
</>
);
+1
View File
@@ -14,6 +14,7 @@ import { encryptData, decryptData } from "../../data/encryption";
import { MIME_TYPES } from "../../constants";
import { reconcileElements } from "../collab/reconciliation";
import { getSyncableElements, SyncableExcalidrawElement } from ".";
import { ResolutionType } from "../../utility-types";
// private
// -----------------------------------------------------------------------------
+1 -1
View File
@@ -263,7 +263,7 @@ export const loadScene = async (
await importFromBackend(id, privateKey),
localDataState?.appState,
localDataState?.elements,
{ repairBindings: true },
{ repairBindings: true, refreshDimensions: true },
);
} else {
data = restore(localDataState || null, null, null, {
+4 -4
View File
@@ -85,6 +85,7 @@ import { useAtomWithInitialValue } from "../jotai";
import { appJotaiStore } from "./app-jotai";
import "./index.scss";
import { ResolutionType } from "../utility-types";
polyfill();
@@ -672,10 +673,9 @@ const ExcalidrawWrapper = () => {
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (
<ErrorDialog
message={errorMessage}
onClose={() => setErrorMessage("")}
/>
<ErrorDialog onClose={() => setErrorMessage("")}>
{errorMessage}
</ErrorDialog>
)}
</div>
);
-47
View File
@@ -50,36 +50,6 @@ interface Clipboard extends EventTarget {
write(data: any[]): Promise<void>;
}
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type ValueOf<T> = T[keyof T];
type Merge<M, N> = Omit<M, keyof N> & N;
/** utility type to assert that the second type is a subtype of the first type.
* Returns the subtype. */
type SubtypeOf<Supertype, Subtype extends Supertype> = Subtype;
type ResolutionType<T extends (...args: any) => any> = T extends (
...args: any
) => Promise<infer R>
? R
: any;
// https://github.com/krzkaczor/ts-essentials
type MarkOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> &
Required<Pick<T, RK>>;
type MarkNonNullable<T, K extends keyof T> = {
[P in K]-?: P extends K ? NonNullable<T[P]> : T[P];
} & { [P in keyof T]: T[P] };
type NonOptional<T> = Exclude<T, undefined>;
// PNG encoding/decoding
// -----------------------------------------------------------------------------
type TEXtChunk = { name: "tEXt"; data: Uint8Array };
@@ -101,23 +71,6 @@ declare module "png-chunks-extract" {
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// type getter for interface's callable type
// src: https://stackoverflow.com/a/58658851/927631
// -----------------------------------------------------------------------------
type SignatureType<T> = T extends (...args: infer R) => any ? R : never;
type CallableType<T extends (...args: any[]) => any> = (
...args: SignatureType<T>
) => ReturnType<T>;
// --------------------------------------------------------------------------—
// Type for React.forwardRef --- supply only the first generic argument T
type ForwardRef<T, P = any> = Parameters<
CallableType<React.ForwardRefRenderFunction<T, P>>
>[1];
// --------------------------------------------------------------------------—
interface Blob {
handle?: import("browser-fs-acces").FileSystemHandle;
name?: string;
+1
View File
@@ -2,6 +2,7 @@ import { AppState } from "./types";
import { ExcalidrawElement } from "./element/types";
import { isLinearElement } from "./element/typeChecks";
import { deepCopyElement } from "./element/newElement";
import { Mutable } from "./utility-types";
export interface HistoryEntry {
appState: ReturnType<typeof clearAppStatePropertiesForHistory>;
+17 -2
View File
@@ -110,6 +110,7 @@
"increaseFontSize": "Increase font size",
"unbindText": "Unbind text",
"bindText": "Bind text to the container",
"createContainerFromText": "Wrap text in a container",
"link": {
"edit": "Edit link",
"create": "Create link",
@@ -119,7 +120,6 @@
"edit": "Edit line",
"exit": "Exit line editor"
},
"elementLock": {
"lock": "Lock",
"unlock": "Unlock",
@@ -205,7 +205,22 @@
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
"importLibraryError": "Couldn't load library",
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work."
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
"brave_measure_text_error": {
"start": "Looks like you are using Brave browser with the",
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
"setting_enabled": "setting enabled",
"break": "This could result in breaking the",
"text_elements": "Text Elements",
"in_your_drawings": "in your drawings",
"strongly_recommend": "We strongly recommend disabling this setting. You can follow",
"steps": "these steps",
"how": "on how to do so",
"disable_setting": " If disabling this setting doesn't fix the display of text elements, please open an",
"issue": "issue",
"write": "on our GitHub, or write us on",
"discord": "Discord"
}
},
"toolBar": {
"selection": "Selection",
+1
View File
@@ -12,6 +12,7 @@ import {
} from "./element/types";
import { getShapeForElement } from "./renderer/renderElement";
import { getCurvePathOps } from "./element/bounds";
import { Mutable } from "./utility-types";
export const rotate = (
x1: number,
+1 -1
View File
@@ -64,7 +64,7 @@
"terser-webpack-plugin": "5.3.3",
"ts-loader": "9.3.1",
"typescript": "4.7.4",
"webpack": "5.73.0",
"webpack": "5.76.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.9.3",
+22 -22
View File
@@ -1393,10 +1393,10 @@ acorn-walk@^8.0.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.2.tgz#d4632bfc63fd93d0f15fd05ea0e984ffd3f5a8c3"
integrity sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A==
acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.0.4, acorn@^8.5.0, acorn@^8.7.1:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
ajv-formats@^2.1.1:
version "2.1.1"
@@ -2032,9 +2032,9 @@ dns-equal@^1.0.0:
integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
dns-packet@^5.2.2:
version "5.3.1"
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.3.1.tgz#eb94413789daec0f0ebe2fcc230bdc9d7c91b43d"
integrity sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw==
version "5.4.0"
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b"
integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
@@ -2068,10 +2068,10 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.3:
version "5.10.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.10.0:
version "5.12.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -3751,10 +3751,10 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
watchpack@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25"
integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==
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"
@@ -3858,21 +3858,21 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@5.73.0:
version "5.73.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38"
integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==
webpack@5.76.0:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
acorn "^8.4.1"
acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.9.3"
enhanced-resolve "^5.10.0"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
@@ -3885,7 +3885,7 @@ webpack@5.73.0:
schema-utils "^3.1.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.3.1"
watchpack "^2.4.0"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
+1 -1
View File
@@ -48,7 +48,7 @@
"file-loader": "6.2.0",
"sass-loader": "13.0.2",
"ts-loader": "9.3.1",
"webpack": "5.73.0",
"webpack": "5.76.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.10.0"
},
+20 -46
View File
@@ -1187,10 +1187,10 @@ acorn-walk@^8.0.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.2.tgz#d4632bfc63fd93d0f15fd05ea0e984ffd3f5a8c3"
integrity sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A==
acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.0.4, acorn@^8.5.0, acorn@^8.7.1:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
ajv-keywords@^3.5.2:
version "3.5.2"
@@ -1383,18 +1383,7 @@ braces@^3.0.1:
dependencies:
fill-range "^7.0.1"
browserslist@^4.14.5:
version "4.19.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.3.tgz#29b7caad327ecf2859485f696f9604214bedd383"
integrity sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==
dependencies:
caniuse-lite "^1.0.30001312"
electron-to-chromium "^1.4.71"
escalade "^3.1.1"
node-releases "^2.0.2"
picocolors "^1.0.0"
browserslist@^4.20.2, browserslist@^4.21.2:
browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.2:
version "4.21.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.2.tgz#59a400757465535954946a400b841ed37e2b4ecf"
integrity sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==
@@ -1417,11 +1406,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
caniuse-lite@^1.0.30001312:
version "1.0.30001312"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f"
integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==
caniuse-lite@^1.0.30001366:
version "1.0.30001367"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz#2b97fe472e8fa29c78c5970615d7cd2ee414108a"
@@ -1601,20 +1585,15 @@ electron-to-chromium@^1.4.188:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.195.tgz#139b2d95a42a3f17df217589723a1deac71d1473"
integrity sha512-vefjEh0sk871xNmR5whJf9TEngX+KTKS3hOHpjoMpauKkwlGwtMz1H8IaIjAT/GNnX0TbGwAdmVoXCAzXf+PPg==
electron-to-chromium@^1.4.71:
version "1.4.75"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz#d1ad9bb46f2f1bf432118c2be21d27ffeae82fdd"
integrity sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q==
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.3:
version "5.9.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88"
integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.10.0:
version "5.12.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -2011,11 +1990,6 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-releases@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -2494,10 +2468,10 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
watchpack@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25"
integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==
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"
@@ -2548,21 +2522,21 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@5.73.0:
version "5.73.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38"
integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==
webpack@5.76.0:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
acorn "^8.4.1"
acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.9.3"
enhanced-resolve "^5.10.0"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
@@ -2575,7 +2549,7 @@ webpack@5.73.0:
schema-utils "^3.1.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.3.1"
watchpack "^2.4.0"
webpack-sources "^3.2.3"
which@^2.0.1:
+20 -1
View File
@@ -14,6 +14,7 @@ import {
isFreeDrawElement,
isInitializedImageElement,
isArrowElement,
hasBoundTextElement,
} from "../element/typeChecks";
import {
getDiamondPoints,
@@ -41,7 +42,10 @@ import { getStroke, StrokeOptions } from "perfect-freehand";
import {
getApproxLineHeight,
getBoundTextElement,
getContainerCoords,
getContainerElement,
getMaxContainerHeight,
getMaxContainerWidth,
} from "../element/textElement";
import { LinearElementEditor } from "../element/linearElementEditor";
@@ -655,7 +659,7 @@ const generateElementShape = (
}
};
export const generateElementWithCanvas = (
const generateElementWithCanvas = (
element: NonDeletedExcalidrawElement,
renderConfig: RenderConfig,
) => {
@@ -811,6 +815,21 @@ const drawElementFromCanvas = (
elementWithCanvas.canvas!.width / elementWithCanvas.canvasZoom,
elementWithCanvas.canvas!.height / elementWithCanvas.canvasZoom,
);
if (
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX &&
hasBoundTextElement(element)
) {
const coords = getContainerCoords(element);
context.strokeStyle = "#c92a2a";
context.lineWidth = 3;
context.strokeRect(
(coords.x + renderConfig.scrollX) * window.devicePixelRatio,
(coords.y + renderConfig.scrollY) * window.devicePixelRatio,
getMaxContainerWidth(element) * window.devicePixelRatio,
getMaxContainerHeight(element) * window.devicePixelRatio,
);
}
}
context.restore();
@@ -119,6 +119,15 @@ Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.createContainerFromText",
"name": "createContainerFromText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
@@ -4507,6 +4516,15 @@ Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.createContainerFromText",
"name": "createContainerFromText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
@@ -5048,6 +5066,15 @@ Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.createContainerFromText",
"name": "createContainerFromText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
@@ -5888,6 +5915,15 @@ Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.createContainerFromText",
"name": "createContainerFromText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
@@ -6225,6 +6261,15 @@ Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.createContainerFromText",
"name": "createContainerFromText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
@@ -5,7 +5,7 @@ exports[`Test Linear Elements Test bound text element should match styles for te
class="excalidraw-wysiwyg"
data-type="wysiwyg"
dir="auto"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10px; height: 24px; left: 35px; top: 8px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -8px; font: Emoji 20px 20px; line-height: 24px; font-family: Virgil, Segoe UI Emoji;"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 24px; left: 35px; top: 8px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -8px; font: Emoji 20px 20px; line-height: 24px; font-family: Virgil, Segoe UI Emoji;"
tabindex="0"
wrap="off"
/>
+1
View File
@@ -19,6 +19,7 @@ import { newFreeDrawElement, newImageElement } from "../../element/newElement";
import { Point } from "../../types";
import { getSelectedElements } from "../../scene/selection";
import { isLinearElementType } from "../../element/typeChecks";
import { Mutable } from "../../utility-types";
const readFile = util.promisify(fs.readFile);
+12
View File
@@ -1179,5 +1179,17 @@ describe("Test Linear Elements", () => {
easy"
`);
});
it("should not render horizontal align tool when element selected", () => {
createTwoPointerLinearElement("arrow");
const arrow = h.elements[0] as ExcalidrawLinearElement;
createBoundTextElement(DEFAULT_TEXT, arrow);
API.setSelectedElements([arrow]);
expect(queryByTestId(container, "align-left")).toBeNull();
expect(queryByTestId(container, "align-horizontal-center")).toBeNull();
expect(queryByTestId(container, "align-right")).toBeNull();
});
});
});
+30
View File
@@ -0,0 +1,30 @@
import { KEYS } from "../keys";
import { Excalidraw } from "../packages/excalidraw/entry";
import { API } from "./helpers/api";
import { Keyboard } from "./helpers/ui";
import { fireEvent, render, waitFor } from "./test-utils";
describe("shortcuts", () => {
it("Clear canvas shortcut should display confirm dialog", async () => {
await render(
<Excalidraw
initialData={{ elements: [API.createElement({ type: "rectangle" })] }}
handleKeyboardGlobally
/>,
);
expect(window.h.elements.length).toBe(1);
Keyboard.withModifierKeys({ ctrl: true }, () => {
Keyboard.keyDown(KEYS.DELETE);
});
const confirmDialog = document.querySelector(".confirm-dialog")!;
expect(confirmDialog).not.toBe(null);
fireEvent.click(confirmDialog.querySelector('[aria-label="Confirm"]')!);
await waitFor(() => {
expect(window.h.elements[0].isDeleted).toBe(true);
});
});
});
+3 -1
View File
@@ -31,6 +31,8 @@ import Library from "./data/library";
import type { FileSystemHandle } from "./data/filesystem";
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
import { ContextMenuItems } from "./components/ContextMenu";
import { Merge, ForwardRef } from "./utility-types";
import React from "react";
export type Point = Readonly<RoughPoint>;
@@ -100,7 +102,7 @@ export type AppState = {
} | null;
showWelcomeScreen: boolean;
isLoading: boolean;
errorMessage: string | null;
errorMessage: React.ReactNode;
draggingElement: NonDeletedExcalidrawElement | null;
resizingElement: NonDeletedExcalidrawElement | null;
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
+49
View File
@@ -0,0 +1,49 @@
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
export type ValueOf<T> = T[keyof T];
export type Merge<M, N> = Omit<M, keyof N> & N;
/** utility type to assert that the second type is a subtype of the first type.
* Returns the subtype. */
export type SubtypeOf<Supertype, Subtype extends Supertype> = Subtype;
export type ResolutionType<T extends (...args: any) => any> = T extends (
...args: any
) => Promise<infer R>
? R
: any;
// https://github.com/krzkaczor/ts-essentials
export type MarkOptional<T, K extends keyof T> = Omit<T, K> &
Partial<Pick<T, K>>;
export type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> &
Required<Pick<T, RK>>;
export type MarkNonNullable<T, K extends keyof T> = {
[P in K]-?: P extends K ? NonNullable<T[P]> : T[P];
} & { [P in keyof T]: T[P] };
export type NonOptional<T> = Exclude<T, undefined>;
// -----------------------------------------------------------------------------
// type getter for interface's callable type
// src: https://stackoverflow.com/a/58658851/927631
// -----------------------------------------------------------------------------
export type SignatureType<T> = T extends (...args: infer R) => any ? R : never;
export type CallableType<T extends (...args: any[]) => any> = (
...args: SignatureType<T>
) => ReturnType<T>;
// --------------------------------------------------------------------------—
// Type for React.forwardRef --- supply only the first generic argument T
export type ForwardRef<T, P = any> = Parameters<
CallableType<React.ForwardRefRenderFunction<T, P>>
>[1];
export type ExtractSetType<T extends Set<any>> = T extends Set<infer U>
? U
: never;
+1
View File
@@ -16,6 +16,7 @@ import { AppState, DataURL, LastActiveTool, Zoom } from "./types";
import { unstable_batchedUpdates } from "react-dom";
import { SHAPES } from "./shapes";
import { isEraserActive, isHandToolActive } from "./appState";
import { ResolutionType } from "./utility-types";
let mockDateTime: string | null = null;