diff --git a/.github/workflows/autorelease-excalidraw.yml b/.github/workflows/autorelease-excalidraw.yml
index 24071b3a2..ad0a0a7e9 100644
--- a/.github/workflows/autorelease-excalidraw.yml
+++ b/.github/workflows/autorelease-excalidraw.yml
@@ -2,7 +2,7 @@ name: Auto release excalidraw next
on:
push:
branches:
- - master
+ - release
jobs:
Auto-release-excalidraw-next:
diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index d767a3335..f5f9b45bb 100644
--- a/.github/workflows/build-docker.yml
+++ b/.github/workflows/build-docker.yml
@@ -3,7 +3,7 @@ name: Build Docker image
on:
push:
branches:
- - master
+ - release
jobs:
build-docker:
diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml
index 13f6c73c9..e1ef21665 100644
--- a/.github/workflows/cancel.yml
+++ b/.github/workflows/cancel.yml
@@ -3,7 +3,7 @@ name: Cancel previous runs
on:
push:
branches:
- - master
+ - release
pull_request:
jobs:
diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml
index 1ec35b8b8..a4a8a4c5f 100644
--- a/.github/workflows/publish-docker.yml
+++ b/.github/workflows/publish-docker.yml
@@ -3,7 +3,7 @@ name: Publish Docker
on:
push:
branches:
- - master
+ - release
jobs:
publish-docker:
diff --git a/.github/workflows/sentry-production.yml b/.github/workflows/sentry-production.yml
index 228b6f148..6f53f91eb 100644
--- a/.github/workflows/sentry-production.yml
+++ b/.github/workflows/sentry-production.yml
@@ -3,7 +3,7 @@ name: New Sentry production release
on:
push:
branches:
- - master
+ - release
jobs:
sentry:
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 521508d8e..ab2a5ac5f 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +1,2 @@
#!/bin/sh
-yarn lint-staged
+# yarn lint-staged
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b3a5c325c..509908e5d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,63 +1,3 @@
# Contributing
-## Setup
-
-### Option 1 - Manual
-
-1. Fork and clone the repo
-1. Run `yarn` to install dependencies
-1. Create a branch for your PR with `git checkout -b your-branch-name`
-
-> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
->
-> ```sh
-> git remote add upstream https://github.com/excalidraw/excalidraw.git
-> git fetch upstream
-> git branch --set-upstream-to=upstream/master master
-> ```
-
-### Option 2 - CodeSandbox
-
-1. Go to https://codesandbox.io/s/github/excalidraw/excalidraw
-1. Connect your GitHub account
-1. Go to Git tab on left side
-1. Tap on `Fork Sandbox`
-1. Write your code
-1. Commit and PR automatically
-
-## Pull Request Guidelines
-
-Don't worry if you get any of the below wrong, or if you don't know how. We'll gladly help out.
-
-### Title
-
-Make sure the title starts with a semantic prefix:
-
-- **feat**: A new feature
-- **fix**: A bug fix
-- **docs**: Documentation only changes
-- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
-- **refactor**: A code change that neither fixes a bug nor adds a feature
-- **perf**: A code change that improves performance
-- **test**: Adding missing tests or correcting existing tests
-- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
-- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
-- **chore**: Other changes that don't modify src or test files
-- **revert**: Reverts a previous commit
-
-### Changelog
-
-Add a brief description of your pull request to the changelog located here: [`src/packages/excalidraw/CHANGELOG.md`](src/packages/excalidraw/CHANGELOG.md)
-
-Notes:
-
-- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
-- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
-
-### Testing
-
-Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
-
-It's also a good idea to consider if your change should include additional tests. This is highly recommended for new features or bug-fixes. For example, it's good practice to create a test for each bug you fix which ensures that we don't regress the code in the future.
-
-Finally - always manually test your changes using the convenient staging environment deployed for each pull request. As much as local development attempts to replicate production, there can still be subtle differences in behavior. For larger features consider testing your change in multiple browsers as well.
+Head over to the [docs](https://docs.excalidraw.com/docs/introduction/contributing)
diff --git a/Dockerfile b/Dockerfile
index f295f7f6a..d1fa424e5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ FROM node:14-alpine AS build
WORKDIR /opt/node_app
COPY package.json yarn.lock ./
-RUN yarn --ignore-optional
+RUN yarn --ignore-optional --network-timeout 600000
ARG NODE_ENV=production
diff --git a/README.md b/README.md
index 45fec41ba..31ee567de 100644
--- a/README.md
+++ b/README.md
@@ -1,201 +1,126 @@
-
-
-
-
-
Virtual whiteboard for sketching hand-drawn like diagrams. Collaborative and end-to-end encrypted.
-
-
-
-
-
-
-
-
-
Ask questions or hang out on our discord.gg/UexuTaE .
+
+
+
+
+
+
+
+
+
+
+
+ An open source virtual hand-drawn style whiteboard.
+ Collaborative and end-to-end encrypted.
+
+
-## Try it now
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-Go to [excalidraw.com](https://excalidraw.com) to start sketching.
+
+
+
+
+
+
+
+ Create beautiful hand-drawn like diagrams, wireframes, or whatever you like.
+
+
+
+
-Read the latest news and updates on our [blog](https://blog.excalidraw.com). A good start is to see all the updates of [One Year of Excalidraw](https://blog.excalidraw.com/one-year-of-excalidraw/).
+## Features
-## Supporting Excalidraw
+The Excalidraw editor (npm package) supports:
-If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw).
+- 💯 Free & open-source.
+- 🎨 Infinite, canvas-based whiteboard.
+- ✍️ Hand-drawn like style.
+- 🌓 Dark mode.
+- 🏗️ Customizable.
+- 📷 Image support.
+- 😀 Shape libraries support.
+- 👅 Localization (i18n) support.
+- 🖼️ Export to PNG, SVG & clipboard.
+- 💾 Open format - export drawings as an `.excalidraw` json file.
+- ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
+- ➡️ Arrow-binding & labeled arrows.
+- 🔙 Undo / Redo.
+- 🔍 Zoom and panning support.
-[
](https://opencollective.com/excalidraw/tiers/sponsors/0/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/1/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/2/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/3/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/4/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/5/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/6/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/7/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/8/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/9/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/10/website)
+## Excalidraw.com
+
+The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/master/src/excalidraw-app) is part of this repository as well, and the app features:
+
+- 📡 PWA support (works offline).
+- 🤼 Real-time collaboration.
+- 🔒 End-to-end encryption.
+- 💾 Local-first support (autosaves to the browser).
+- 🔗 Shareable links (export to a readonly link you can share with others).
+
+We'll be adding these features as drop-in plugins for the npm package in the future.
+
+## Quick start
+
+Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
+
+```
+npm install react react-dom @excalidraw/excalidraw
+```
+
+or via yarn
+
+```
+yarn add react react-dom @excalidraw/excalidraw
+```
+
+Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
+
+## Contributing
+
+- Missing something or found a bug? [Report here](https://github.com/excalidraw/excalidraw/issues).
+- Want to contribute? Check out our [contribution guide](https://docs.excalidraw.com/docs/introduction/contributing) or let us know on [Discord](https://discord.gg/UexuTaE).
+- Want to help with translations? See the [translation guide](https://docs.excalidraw.com/docs/introduction/contributing#translating).
+
+## Integrations
+
+- [VScode extension](https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor)
+- [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw)
+
+## Who's integrating Excalidraw
+
+[Google Cloud](https://googlecloudcheatsheet.withgoogle.com/architecture) • [Meta](https://meta.com/) • [CodeSandbox](https://codesandbox.io/) • [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) • [Replit](https://replit.com/) • [Slite](https://slite.com/) • [Notion](https://notion.so/) • [HackerRank](https://www.hackerrank.com/) • and many others
+
+## Sponsors & support
+
+If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw) or use [Excalidraw+](https://plus.excalidraw.com/).
+
+## Thank you for supporting Excalidraw
+
+[
](https://opencollective.com/excalidraw/tiers/sponsors/0/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/1/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/2/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/3/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/4/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/5/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/6/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/7/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/8/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/9/website) [
](https://opencollective.com/excalidraw/tiers/sponsors/10/website)
Last but not least, we're thankful to these companies for offering their services for free:
[](https://vercel.com) [](https://sentry.io) [](https://crowdin.com)
-
-## Who's integrating Excalidraw
-
-[Google Cloud](https://googlecloudcheatsheet.withgoogle.com/architecture) • [Meta](https://meta.com/) • [CodeSandbox](https://codesandbox.io/) • [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) • [Replit](https://replit.com/) • [Slite](https://slite.com/) • [Notion](https://notion.so/) • [HackerRank](https://www.hackerrank.com/) •
-
-## Documentation
-
-### Shortcuts
-
-You can almost do anything with shortcuts. Click on the help icon on the bottom right corner to see them all.
-
-### Curved lines and arrows
-
-Choose line or arrow and click click click instead of drag.
-
-### Charts
-
-You can easily create charts by copy pasting data from Excel or just plain comma separated text.
-
-### Translating
-
-To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
-
-Translations will be available on the app if they exceed a certain threshold of completion (currently 85%).
-
-### Create a collaboration session manually
-
-In order to create a session manually, you just need to generate a link of this form:
-
-```
-https://excalidraw.com/#room=[0-9a-f]{20},[a-zA-Z0-9_-]{22}
-```
-
-#### Example
-
-```
-https://excalidraw.com/#room=91bd46ae3aa84dff9d20,pfLqgEoY1c2ioq8LmGwsFA
-```
-
-The first set of digits is the room. This is visible from the server that’s going to dispatch messages to everyone that knows this number.
-
-The second set of digits is the encryption key. The Excalidraw server doesn’t know about it. This is what all the participants use to encrypt/decrypt the messages.
-
-> Note: Please ensure that the encryption key is 22 characters long.
-
-## Shape libraries
-
-Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
-
-## Embedding Excalidraw in your App?
-
-Try out [`@excalidraw/excalidraw`](https://www.npmjs.com/package/@excalidraw/excalidraw). This package allows you to easily embed Excalidraw as a React component into your apps.
-
-## Development
-
-### Code Sandbox
-
-- Go to https://codesandbox.io/p/github/excalidraw/excalidraw
- - You may need to sign in with GitHub and reload the page
-- You can start coding instantly, and even send PRs from there!
-
-### Local Installation
-
-These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
-
-#### Requirements
-
-- [Node.js](https://nodejs.org/en/)
-- [Yarn](https://yarnpkg.com/getting-started/install) (v1 or v2.4.2+)
-- [Git](https://git-scm.com/downloads)
-
-#### Clone the repo
-
-```bash
-git clone https://github.com/excalidraw/excalidraw.git
-```
-
-#### Install the dependencies
-
-```bash
-yarn
-```
-
-#### Start the server
-
-```bash
-yarn start
-```
-
-Now you can open [http://localhost:3000](http://localhost:3000) and start coding in your favorite code editor.
-
-#### Collaboration
-
-For collaboration, you will need to set up [collab server](https://github.com/excalidraw/excalidraw-room) in local.
-
-#### Commands
-
-##### Install the dependencies
-
-```
-yarn
-```
-
-##### Run the project
-
-```
-yarn start
-```
-
-##### Reformat all files with Prettier
-
-```
-yarn fix
-```
-
-##### Run tests
-
-```
-yarn test
-```
-
-##### Update test snapshots
-
-```
-yarn test:update
-```
-
-##### Test for formatting with Prettier
-
-```
-yarn test:code
-```
-
-#### Docker Compose
-
-You can use docker-compose to work on Excalidraw locally if you don't want to setup a Node.js env.
-
-```sh
-docker-compose up --build -d
-```
-
-### Self-hosting
-
-We publish a Docker image with the Excalidraw client at [excalidraw/excalidraw](https://hub.docker.com/r/excalidraw/excalidraw). You can use it to self-host your own client under your own domain, on Kubernetes, AWS ECS, etc.
-
-```sh
-docker build -t excalidraw/excalidraw .
-docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latest
-```
-
-The Docker image is free of analytics and other tracking libraries.
-
-**At the moment, self-hosting your own instance doesn't support sharing or collaboration features.**
-
-We are working towards providing a full-fledged solution for self-hosting your own Excalidraw.
-
-## Contributing
-
-Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
-
-## Notable used tools
-
-- [Create React App](https://github.com/facebook/create-react-app)
-- [Rough.js](https://roughjs.com)
-- [TypeScript](https://www.typescriptlang.org)
-- [Vercel](https://vercel.com)
-
-And the main source of inspiration for starting the project is the awesome [Zwibbler](https://zwibbler.com/demo/) app.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/api-intro.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/api-intro.mdx
new file mode 100644
index 000000000..aee7f3bfb
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/api-intro.mdx
@@ -0,0 +1,11 @@
+---
+slug: /@excalidraw/excalidraw/api
+---
+
+# API
+
+Currently the **API** is divided into 3 broad categories 👇
+
+- [Props](/docs/@excalidraw/excalidraw/api/props) - The `props` you can pass to the `Excalidraw` component.
+- [Children components](/docs/@excalidraw/excalidraw/api/children-components) - Official components you can use to customize the UI.
+- [Utils](/docs/@excalidraw/excalidraw/api/utils) - Utilities and helpers you can use to export, restore and more.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/children-components-intro.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/children-components-intro.mdx
new file mode 100644
index 000000000..706adb594
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/children-components-intro.mdx
@@ -0,0 +1,21 @@
+---
+sidebar_label: Children Components
+slug: /@excalidraw/excalidraw/api/children-components
+---
+
+# `
` children
+
+We expose several components you can render as children of the `
` component to customize the UI.
+
+:::info
+
+We have only recently started migrating to this type of component API. Some UI components are still using render props, and some UI customization isn't supported yet (such as the toolbar or the element properties panel). Stay tuned for more updates!
+
+:::
+
+Below are the currently supported components:
+
+- [MainMenu](/docs/@excalidraw/excalidraw/api/children-components/main-menu)
+- [WelcomeScreen](/docs/@excalidraw/excalidraw/api/children-components/welcome-screen)
+- [Footer](/docs/@excalidraw/excalidraw/api/children-components/footer)
+- [LiveCollaborationTrigger](/docs/@excalidraw/excalidraw/api/children-components/live-collaboration-trigger)
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx
new file mode 100644
index 000000000..262681895
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx
@@ -0,0 +1,69 @@
+# Footer
+
+Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
+
+You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node.
+
+**Usage**
+
+```jsx live
+function App() {
+ return (
+
+
+
+ alert("This is dummy footer")}
+ >
+ {" "}
+ custom footer
+
+
+
+
+ );
+}
+```
+
+This will only for `Desktop` devices.
+
+For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
+
+Open the `Menu` in the below playground and you will see the `custom footer` rendered.
+
+```jsx live noInline
+const MobileFooter = ({}) => {
+ const device = useDevice();
+ if (device.isMobile) {
+ return (
+
+ alert("This is custom footer in mobile menu")}
+ >
+ custom footer
+
+
+ );
+ }
+ return null;
+};
+
+const App = () => (
+
+
+
+ Item1
+ Item 2
+
+
+
+
+);
+
+// Need to render when code is span across multiple components
+// in Live Code blocks editor
+render(
);
+```
\ No newline at end of file
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/live-collaboration-trigger.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/live-collaboration-trigger.mdx
new file mode 100644
index 000000000..ef74d0e65
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/live-collaboration-trigger.mdx
@@ -0,0 +1,62 @@
+# LiveCollaborationTrigger
+
+If you implement live collaboration support and want to expose the same UI button as on [excalidraw.com](https://excalidraw.com), you can render the `
` component using the [renderTopRightUI](/docs/@excalidraw/excalidraw/api/props#rendertoprightui) prop.
+
+You'll need to supply `onSelect()` to handle opening of your collaboration dialog, but the button will display `appState.collaborators` count provided you have supplied it.
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `onSelect` | `function` | Yes | | Handler called when the user clicks on the button |
+| `isCollaborating` | `boolean` | Yes | false | Whether live collaboration session is in effect. Modifies button style. |
+
+```tsx live
+function App() {
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+ const [isCollaborating, setIsCollaborating] = useState(false);
+ return (
+
+
+ Selecting the checkbox to see the collaborator count
+
+
+ {
+ if (!isCollaborating) {
+ const collaborators = new Map();
+ collaborators.set("id1", {
+ username: "Doremon",
+ avatarUrl: "../../../../img/doremon.png",
+ });
+ collaborators.set("id3", {
+ username: "Pika",
+ avatarUrl: "../../../../img/pika.jpeg",
+ });
+ excalidrawAPI.updateScene({ collaborators });
+ } else {
+ excalidrawAPI.updateScene({
+ collaborators: new Map(),
+ });
+ }
+ setIsCollaborating(!isCollaborating);
+ }}
+ />
+ Show Collaborators
+
+
setExcalidrawAPI(api)}
+ renderTopRightUI={() => (
+ {
+ window.alert("You clicked on collab button");
+ setIsCollaborating(true);
+ }}
+ />
+ )}
+ >
+
+ );
+}
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/main-menu.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/main-menu.mdx
new file mode 100644
index 000000000..8fbf228df
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/main-menu.mdx
@@ -0,0 +1,169 @@
+# MainMenu
+
+By default Excalidraw will render the `MainMenu` with default options. If you want to customise the `MainMenu`, you can pass the `MainMenu` component with the list options.
+
+**Usage**
+
+```jsx live
+function App() {
+ return (
+
+
+
+ window.alert("Item1")}>
+ Item1
+
+ window.alert("Item2")}>
+ {" "}
+ Item 2{" "}
+
+
+
+
+ );
+}
+```
+
+### `
`
+
+This is the `MainMenu` component. If you render it, you will need to populate the menu with your own items as we will not render any ourselves at that point.
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | :-: | :-: | --- |
+| `onSelect` | `function` | No | - | Triggered when any item is selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
+
+### MainMenu.Item
+
+To render an item, its recommended to use `MainMenu.Item`.
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | :-: | :-: | --- |
+| `onSelect` | `function` | Yes | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
+| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
+| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
+| `shortcut` | `string` | No | - | The shortcut to be shown for the menu item |
+
+### MainMenu.ItemLink
+
+To render an item as a link, its recommended to use `MainMenu.ItemLink`.
+
+**Usage**
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+ Google
+
+
+ Excalidraw
+
+
+
+
+ );
+}
+```
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | :-: | :-: | --- |
+| `onSelect` | `function` | No | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
+| `href` | `string` | Yes | - | The `href` attribute to be added to the `anchor` element. |
+| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
+| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
+| `shortcut` | `string` | No | - | The shortcut to be shown for the menu item |
+
+### MainMenu.ItemCustom
+
+To render a custom item, you can use `MainMenu.ItemCustom`.
+
+**Usage**
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+ window.alert("custom menu item")}
+ >
+ {" "}
+ custom item
+
+
+
+
+
+ );
+}
+```
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | :-: | :-: | --- |
+| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
+
+### MainMenu.DefaultItems
+
+For the items which are shown in the menu in [excalidraw.com](https://excalidraw.com), you can use `MainMenu.DefaultItems`
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+
+ window.alert("Item1")}>
+ Item1
+
+ window.alert("Item2")}>
+ Item 2
+
+
+
+
+ );
+}
+```
+
+Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/src/components/mainMenu/DefaultItems.tsx) of the default items.
+
+### MainMenu.Group
+
+To Group item in the main menu, you can use `MainMenu.Group`
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+
+
+
+
+ window.alert("Item1")}>
+ Item1
+
+ window.alert("Item2")}>
+ Item 2
+
+
+
+
+
+ );
+}
+```
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | :-: | :-: | --- |
+| `children ` | `React.ReactNode` | Yes | - | The content of the `Menu Group` |
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/welcome-screen.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/welcome-screen.mdx
new file mode 100644
index 000000000..8150508bf
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/welcome-screen.mdx
@@ -0,0 +1,140 @@
+# WelcomeScreen
+
+When the canvas is empty, Excalidraw can show a welcome _splash_ screen with a logo, a few quick action items, and hints explaining what some of the UI buttons do. Once the user picks a tool, or has created an element on the canvas, the welcome screen will disappear.
+
+You can enable this behavior by rendering a `WelcomeScreen` component like this:
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+You can also customize the welcome screen by passing children to the `WelcomeScreen` component. See below.
+
+##
+
+This is the main component. If you render it without any children, we will render the default welcome screen.
+
+You can customize which welcome screen subcomponents are rendered by passing them as children.
+
+The welcome screen consists of two main groups of subcomponents:
+
+1. [WelcomeScreen.Center](#welcomescreencenterlogo).
+2. [WeelcomeScreen.Hints](#welcomescreenhints).
+
+
+
+### Center
+
+` ` subcomponent is the center piece of the welcome screen, containing the `logo`, `heading`, and `menu`. All three subcomponents are optional, and you can render whatever you wish into the center component.
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+
+
+ Welcome Screen Heading!
+
+
+
+ Excalidraw GitHub
+
+
+
+
+
+
+
+ );
+}
+```
+
+#### Logo
+
+Use the ` ` to render a logo. By default it renders the Excalidraw logo and name. Supply `children` to customize.
+
+#### Heading
+
+Use the ` ` to render a heading below the logo. Supply `children` to change the default message.
+
+#### Menu
+
+` ` is a wrapper component for the menu items. You can build your menu using the `` and `` components, render your own, or render one of the default menu items.
+
+The default menu items are:
+
+- ` ` - opens the help dialog.
+
+- ` ` - open the load file dialog.
+
+- ` ` - intended to open the live collaboration dialog. Works similarly to [``](/docs/@excalidraw/excalidraw/api/children-components/live-collaboration-trigger) and you must supply `onSelect()` handler to integrate with your collaboration implementation.
+
+#### MenuItem
+
+The ` ` component can be used to render a menu item.
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `onSelect` | `function` | Yes | | The handler is triggered when the item is selected. |
+| `children` | `React.ReactNode` | Yes | | The content of the menu item |
+| `icon` | `JSX.Element` | No | | The icon used in the menu item |
+| `shortcut` | `string` | No | | The keyboard shortcut (label-only, does not affect behavior) |
+
+**WelcomeScreen.Center.MenuItemLink**
+
+To render an external link in a menu item, you can use this component.
+
+| Prop | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `href` | `string` | Yes | | The `href` attribute to be added to the `anchor` element. |
+| `children` | `React.ReactNode` | Yes | | The content of the menu item |
+| `icon` | `JSX.Element` | No | | The icon used in the menu item |
+| `shortcut` | `string` | No | | The keyboard shortcut (label-only, does not affect behavior) |
+
+### Hints
+
+These `` subcomponents render the UI hints. Text of each hint can be customized by supplying `children`.
+
+```jsx live
+function App() {
+ return (
+
+
+
+
+ ToolBar Hints
+
+
+
+
+
+
+ );
+}
+```
+
+#### MenuHint
+
+`` hint subcomponent for the main menu. Supply `children` to customize the hint text.
+
+#### ToolbarHint
+
+`` hint subcomponent for the toolbar. Supply `children` to customize the hint text.
+
+#### Help
+
+`` hint subcomponent for the help dialog. Supply `children` to customize the hint text.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/constants.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/constants.mdx
new file mode 100644
index 000000000..a6c95ab2a
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/constants.mdx
@@ -0,0 +1,46 @@
+# Constants
+
+### FONT_FAMILY
+
+**How to use**
+
+```js
+import { FONT_FAMILY } from "@excalidraw/excalidraw";
+```
+
+`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
+
+| Font Family | Description |
+| ----------- | ---------------------- |
+| `Virgil` | The `handwritten` font |
+| `Helvetica` | The `Normal` Font |
+| `Cascadia` | The `Code` Font |
+
+Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
+
+### THEME
+
+**How to use**
+
+```js
+import { THEME } from "@excalidraw/excalidraw";
+```
+
+`THEME` contains all the themes supported by `Excalidraw` as explained below
+
+| Theme | Description |
+| ------- | ----------------- |
+| `LIGHT` | The `light` theme |
+| `DARK` | The `Dark` theme |
+
+Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
+
+### MIME_TYPES
+
+[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
+
+**How to use **
+
+```js
+import { MIME_TYPES } from "@excalidraw/excalidraw";
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/initialdata.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/initialdata.mdx
new file mode 100644
index 000000000..7d79128f0
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/initialdata.mdx
@@ -0,0 +1,55 @@
+# initialData
+
+
+{ elements?: ExcalidrawElement[] , appState?: AppState }
+
+
+This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
+
+| Name | Type | Description |
+| --- | --- | --- |
+| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
+| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
+| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
+| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L247) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> | This library items with which `Excalidraw` should be mounted. |
+| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82) | The `files` added to the scene. |
+
+You might want to use this when you want to load excalidraw with some initial elements and app state.
+
+```jsx live
+function App() {
+ return (
+
+
+
+ );
+}
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
new file mode 100644
index 000000000..a871874c5
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
@@ -0,0 +1,230 @@
+# Props
+
+All `props` are *optional*.
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | Promise
| `null` | The initial data with which app loads. |
+| [`ref`](/docs/@excalidraw/excalidraw/api/props/ref) | `object` | _ | `Ref` to be passed to Excalidraw |
+| [`isCollaborating`](#iscollaborating) | `boolean` | _ | This indicates if the app is in `collaboration` mode |
+| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
+| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
+| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down evenets |
+| [`onScrollChange`](#onscrollchange) | `function` | _ | This prop if passed gets triggered when scrolling the canvas. |
+| [`onPaste`](#onpaste) | `function` | _ | Callback to be triggered if passed when the something is pasted in to the scene |
+| [`onLibraryChange`](#onlibrarychange) | `function` | _ | The callback if supplied is triggered when the library is updated and receives the library items. |
+| [`onLinkOpen`](#onlinkopen) | `function` | _ | The callback if supplied is triggered when any link is opened. |
+| [`langCode`](#langcode) | `string` | `en` | Language code string to be used in Excalidraw |
+| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | _ | Render function that renders custom UI in top right corner |
+| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | _ | Render function that can be used to render custom stats on the stats dialog. |
+| [`renderSidebar`](/docs/@excalidraw/excalidraw/api/props/render-props#rendersidebar) | `function` | _ | Render function that renders custom sidebar. |
+| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | _ | This indicates if the app is in `view` mode. |
+| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | _ | This indicates if the `zen` mode is enabled |
+| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | _ | This indicates if the `grid` mode is enabled |
+| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
+| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
+| [`name`](#name) | `string` | | Name of the drawing |
+| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
+| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
+| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
+| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
+| [`generateIdForFile`](#generateidforfile) | `function` | _ | Allows you to override `id` generation for files added on canvas |
+
+### Storing custom data on Excalidraw elements
+
+Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L66) and is optional.
+
+You can use this to add any extra information you need to keep track of.
+
+You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/ref#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/ref#updatelibrary) afterwards.
+
+```js showLineNumbers
+{
+ type: "rectangle",
+ id: "oDVXy8D6rom3H1-LLH2-f",
+ customData: {customId: '162'},
+}
+```
+
+### isCollaborating
+
+This prop indicates if the app is in `collaboration` mode.
+
+### onChange
+
+Every time component updates, this callback if passed will get triggered and has the below signature.
+
+```js
+(excalidrawElements, appState, files) => void;
+```
+
+1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) in the scene.
+
+2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
+
+3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
+
+Here you can try saving the data to your backend or local storage for example.
+
+### onPointerUpdate
+
+This callback is triggered when mouse pointer is updated.
+
+```js
+({ x, y }, button, pointersMap}) => void;
+```
+
+1.`{x, y}`: Pointer coordinates
+
+2.`button`: The position of the button. This will be one of `["down", "up"]`
+
+3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) map of the scene
+
+```js
+(exportedElements, appState, canvas) => void
+```
+
+1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L87) which needs to be exported.
+2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
+3. `canvas`: The `HTMLCanvasElement` of the scene.
+
+### onPointerDown
+
+This prop if passed will be triggered on pointer down events and has the below signature.
+
+
+(activeTool: AppState["activeTool"] , pointerDownState: PointerDownState ) => void
+
+
+### onScrollChange
+
+This prop if passed will be triggered when canvas is scrolled and has the below signature.
+
+```ts
+(scrollX: number, scrollY: number) => void
+```
+
+### onPaste
+
+This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
+
+
+(data: ClipboardData , event: ClipboardEvent | null) => boolean
+
+
+This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
+
+In case you want to prevent the excalidraw paste action you must return `false`, it will stop the native excalidraw clipboard management flow (nothing will be pasted into the scene).
+
+### onLibraryChange
+
+This callback if supplied will get triggered when the library is updated and has the below signature.
+
+
+ (items:{" "}
+
+ LibraryItems
+
+ ) => void | Promise<any>
+
+
+It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
+
+### onLinkOpen
+
+This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
+
+
+(element: ExcalidrawElement ,
+ event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
+
+
+Example:
+
+```js showLineNumbers
+const history = useHistory();
+
+// open internal links using the app's router, but opens external links in
+// a new tab/window
+const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
+ (element, event) => {
+ const link = element.link;
+ const { nativeEvent } = event.detail;
+ const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
+ const isNewWindow = nativeEvent.shiftKey;
+ const isInternalLink =
+ link.startsWith("/") || link.includes(window.location.origin);
+ if (isInternalLink && !isNewTab && !isNewWindow) {
+ history.push(link.replace(window.location.origin, ""));
+ // signal that we're handling the redirect ourselves
+ event.preventDefault();
+ }
+ },
+ [history],
+);
+```
+
+### langCode
+
+Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
+
+```js
+import { defaultLang, languages } from "@excalidraw/excalidraw";
+```
+
+| name | type |
+| --- | --- |
+| `defaultLang` | `string` |
+| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
+
+### viewModeEnabled
+
+This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over *intialData.appState.viewModeEnabled*, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
+
+### zenModeEnabled
+
+This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over *intialData.appState.zenModeEnabled*, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
+
+### gridModeEnabled
+
+This prop indicates whether the shows the grid. When supplied, the value takes precedence over *intialData.appState.gridModeEnabled*, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
+
+### libraryReturnUrl
+
+If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
+Defaults to *window.location.origin + window.location.pathname*. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
+
+### theme
+
+This prop controls Excalidraw's theme. When supplied, the value takes precedence over *intialData.appState.theme*, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless *UIOptions.canvasActions.toggleTheme* is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
+
+You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
+
+### name
+
+This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over *intialData.appState.name*, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
+
+
+### detectScroll
+
+Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method).
+
+### handleKeyboardGlobally
+
+Indicates whether to bind keyboard events to `document`. Disabled by default, meaning the keyboard events are bound to the Excalidraw component. This allows for multiple Excalidraw components to live on the same page, and ensures that Excalidraw keyboard handling doesn't collide with your app's (or the browser) when the component isn't focused.
+
+Enable this if you want Excalidraw to handle keyboard even if the component isn't focused (e.g. a user is interacting with the navbar, sidebar, or similar).
+
+
+### autoFocus
+
+This prop indicates whether to `focus` the Excalidraw component on page load. Defaults to false.
+
+### generateIdForFile
+
+Allows you to override `id` generation for files added on canvas (images). By default, an SHA-1 digest of the file is used.
+
+```tsx
+(file: File) => string | Promise
+```
+
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx
new file mode 100644
index 000000000..a37843c76
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/ref.mdx
@@ -0,0 +1,391 @@
+# ref
+
+createRef | useRef | callbackRef | { current: { readyPromise: resolvablePromise } }
+
+
+You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
+
+| API | Signature | Usage |
+| --- | --- | --- |
+| ready | `boolean` | This is set to true once Excalidraw is rendered |
+| [readyPromise](#readypromise) | `function` | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readypromise) |
+| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
+| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
+| [addFiles](#addfiles) | `function` | add files data to the appState |
+| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
+| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
+| [getSceneElements](#getsceneelements) | `function` | Returns all the elements excluding the deleted in the scene |
+| [getAppState](#getappstate) | `function` | Returns current appState |
+| [history](#history) | `object` | This is the history API. `history.clear()` will clear the history |
+| [scrollToContent](#scrolltocontent) | `function` | Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene. |
+| [refresh](#refresh) | `function` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). |
+| [setToast](#settoast) | `function` | This API can be used to show the toast with custom message. |
+| [id](#id) | `string` | Unique ID for the excalidraw component. |
+| [getFiles](#getfiles) | `function` | This API can be used to get the files present in the scene. |
+| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
+| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
+| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
+| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
+
+## readyPromise
+
+
+ const excalidrawRef = { current:{ readyPromise:
+
+ resolvablePromise
+
+ } }
+
+
+Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves.
+
+```jsx showLineNumbers
+const resolvablePromise = () => {
+ let resolve;
+ let reject;
+ const promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve;
+ reject = _reject;
+ });
+ promise.resolve = resolve;
+ promise.reject = reject;
+ return promise;
+};
+
+const App = () => {
+ const excalidrawRef = useMemo(
+ () => ({
+ current: {
+ readyPromise: resolvablePromise(),
+ },
+ }),
+ [],
+ );
+
+ useEffect(() => {
+ excalidrawRef.current.readyPromise.then((api) => {
+ console.log("loaded", api);
+ });
+ }, [excalidrawRef]);
+ return (
+
+
+
+ );
+};
+```
+
+## updateScene
+
+
+ (scene:{" "}
+
+ sceneData
+
+ ) => void
+
+
+You can use this function to update the scene with the sceneData. It accepts the below attributes.
+
+| Name | Type | Description |
+| --- | --- | --- |
+| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L38) | The `elements` to be updated in the scene |
+| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L39) | The `appState` to be updated in the scene. |
+| `collaborators` | MapCollaborator>
| The list of collaborators to be updated in the scene. |
+| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
+
+```jsx live
+function App() {
+ const updateScene = () => {
+ const sceneData = {
+ elements: [
+ {
+ type: "rectangle",
+ version: 141,
+ versionNonce: 361174001,
+ isDeleted: false,
+ id: "oDVXy8D6rom3H1-LLH2-f",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: 100.50390625,
+ y: 93.67578125,
+ strokeColor: "#c92a2a",
+ backgroundColor: "transparent",
+ width: 186.47265625,
+ height: 141.9765625,
+ seed: 1968410350,
+ groupIds: [],
+ boundElements: null,
+ locked: false,
+ link: null,
+ updated: 1,
+ roundness: {
+ type: 3,
+ value: 32,
+ },
+ },
+ ],
+ appState: {
+ viewBackgroundColor: "#edf2ff",
+ },
+ };
+ excalidrawAPI.updateScene(sceneData);
+ };
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+ return (
+
+
Click to update the scene
+
Update Scene
+
setExcalidrawAPI(api)} />
+
+ );
+}
+```
+
+### updateLibrary
+
+
+ (opts: { libraryItems:{" "}
+
+ LibraryItemsSource
+
+ ; merge?: boolean; prompt?: boolean;
+ openLibraryMenu?: boolean;
+ defaultStatus?: "unpublished" | "published"; }) => Promise<
+
+ LibraryItems
+
+ >
+
+
+You can use this function to update the library. It accepts the below attributes.
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
+| `merge` | boolean | `false` | Whether to merge with existing library items. |
+| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
+| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
+| `defaultStatus` | "unpublished" | "published"
| `"unpublished"` | Default library item's `status` if not present. |
+
+```tsx live
+function App() {
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+
+ useEffect(() => {
+ if (!excalidrawAPI) {
+ return;
+ }
+ // to open the library sidebar
+ excalidrawAPI.updateScene({ appState: { openSidebar: "library" } });
+ }, [excalidrawAPI]);
+
+ return (
+
+
Click to update the library items
+
{
+ const libraryItems = [
+ {
+ status: "published",
+ id: "1",
+ created: 1,
+ elements: initialData.libraryItems[1],
+ },
+ {
+ status: "unpublished",
+ id: "2",
+ created: 2,
+ elements: initialData.libraryItems[1],
+ },
+ ];
+ excalidrawAPI.updateLibrary({
+ libraryItems,
+ openLibraryMenu: true
+
+ });
+
+ }}
+ >
+ Update Library
+
+
setExcalidrawAPI(api)}
+ // initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/src/initialData.js
+ initialData={{
+ libraryItems: initialData.libraryItems,
+ appState: { openSidebar: "library" },
+ }}
+ />
+
+ );
+}
+```
+
+### addFiles
+
+
+ (files:{" "}
+
+ BinaryFileData
+
+ ) => void
+
+
+Adds supplied files data to the `appState.files` cache on top of existing files present in the cache.
+
+## resetScene
+
+```tsx
+(opts?: { resetLoadingState: boolean }) => void
+```
+
+Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false.
+
+## getSceneElementsIncludingDeleted
+
+
+ () =>{" "}
+
+ ExcalidrawElement[]
+
+
+
+Returns all the elements including the deleted in the scene.
+
+## getSceneElements
+
+
+ () => NonDeleted<
+
+ ExcalidrawElement
+
+ []>
+
+
+Returns all the elements excluding the deleted in the scene
+
+## getAppState
+
+
+ () =>{" "}
+
+ AppState
+
+
+
+Returns current appState.
+
+## history
+
+```tsx
+{
+ clear: () => void
+}
+```
+
+This is the history API. history.clear() will clear the history.
+
+## scrollToContent
+
+
+ (target?:{" "}
+
+ ExcalidrawElement
+ {" "}
+ |{" "}
+
+ ExcalidrawElement
+
+ []) => void
+
+
+Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene.
+
+## refresh
+
+```tsx
+() => void
+```
+
+Updates the `offsets` for the `Excalidraw` component so that the coordinates are computed correctly (for example the cursor position).
+
+You don't have to call this when the position is changed on page scroll or when the excalidraw container resizes (we handle that ourselves).
+
+For any other cases if the position of excalidraw is updated (example due to scroll on parent container and not page scroll) you should call this API.
+
+## setToast
+
+This API can be used to show the toast with custom message.
+
+```tsx
+({ message: string, closable?:boolean,duration?:number
+ } | null) => void
+```
+
+| Attribute | type | Description |
+| --- | --- | --- |
+| message | string | The message to be shown on the toast. |
+| closable | boolean | Indicates whether to show the closable button on toast to dismiss the toast. |
+| duration | number | Determines the duration after which the toast should auto dismiss. To prevent autodimiss you can pass `Infinity`. |
+
+To dismiss an existing toast you can simple pass `null`
+
+```js
+setToast(null);
+```
+
+## id
+
+The unique id of the excalidraw component. This can be used to identify the excalidraw component, for example importing the library items to the excalidraw component from where it was initiated when you have multiple excalidraw components rendered on the same page as shown in [multiple excalidraw demo](https://codesandbox.io/s/multiple-excalidraw-k1xx5).
+
+## getFiles
+
+
+ () =>{" "}
+
+ files
+
+
+
+This API can be used to get the files present in the scene. It may contain files that aren't referenced by any element, so if you're persisting the files to a storage, you should compare them against stored elements.
+
+## setActiveTool
+
+This API has the below signature. It sets the `tool` passed in param as the active tool.
+
+
+
+(tool: { type: SHAPES [number]["value"]| "eraser" } | { type: "custom"; customType: string }) => void
+
+
+## setCursor
+
+This API can be used to customise the mouse cursor on the canvas and has the below signature.
+It sets the mouse cursor to the cursor passed in param.
+
+```tsx
+(cursor: string) => void
+```
+
+## toggleMenu
+
+```tsx
+(type: "library" | "customSidebar", force?: boolean) => boolean;
+```
+
+This API can be used to toggle a specific menu (currently only the sidebars), and returns whether the menu was toggled on or off. If the `force` flag passed, it will force the menu to be toggled either on/off based on the `boolean` passed.
+
+This API is especially useful when you render a custom sidebar using [`renderSidebar`](#rendersidebar) prop, and you want to toggle it from your app based on a user action.
+
+## resetCursor
+
+```tsx
+() => void
+```
+
+This API can be used to reset to default mouse cursor.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/render-props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/render-props.mdx
new file mode 100644
index 000000000..ca329e3e6
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/render-props.mdx
@@ -0,0 +1,126 @@
+# Render Props
+
+## renderTopRightUI
+
+
+ (isMobile: boolean, appState:{" "}
+
+ AppState
+
+ ) => JSX | null
+
+
+A function returning `JSX` to render `custom` UI in the top right corner of the app.
+
+```jsx live
+function App() {
+ return (
+
+ {
+ return (
+ window.alert("This is dummy top right UI")}
+ >
+ {" "}
+ Click me{" "}
+
+ );
+ }}
+ />
+
+ );
+}
+```
+
+## renderCustomStats
+
+A function that can be used to render custom stats (returns JSX) in the `nerd stats` dialog.
+
+
+
+For example you can use this prop to render the size of the elements in the storage as do in [excalidraw.com](https://excalidraw.com).
+
+```jsx live
+function App() {
+ return (
+
+
(
+
+ {" "}
+ Dummy stats will be shown here{" "}
+
+ )}
+ />
+
+ );
+}
+```
+
+## renderSidebar
+
+```tsx
+() => JSX | null;
+```
+
+You can render `custom sidebar` using this prop. This sidebar is the same that the library menu sidebar is using, and can be used for any purposes your app needs.
+
+You need to import the `Sidebar` component from `excalidraw` package and pass your content as its `children`. The function `renderSidebar` should return the `Sidebar` instance.
+
+### Sidebar
+The `` component takes these props (all are optional except `children`):
+
+| Prop | Type | Description |
+| --- | --- | --- |
+| `children` | `React.ReactNode` | Content you want to render inside the `sidebar`. |
+| `onClose` | `function` | Invoked when the component is closed (by user, or the editor). No need to act on this event, as the editor manages the sidebar open state on its own. |
+| `onDock` | `function` | Invoked when the user toggles the `dock` button. The callback recieves a `boolean` parameter `isDocked` which indicates whether the sidebar is `docked` |
+| `docked` | `boolean` | Indicates whether the sidebar is`docked`. By default, the sidebar is `undocked`. If passed, the docking becomes controlled, and you are responsible for updating the `docked` state by listening on `onDock` callback. To decide the breakpoint for docking you can use [UIOptions.dockedSidebarBreakpoint](/docs/@excalidraw/excalidraw/api/props/ui-options#dockedsidebarbreakpoint) for more info on docking. |
+| `dockable` | `boolean` | Indicates whether to show the `dock` button so that user can `dock` the sidebar. If `false`, you can still dock programmatically by passing `docked` as `true`. |
+
+The sidebar will always include a header with `close / dock` buttons (when applicable).
+You can also add custom content to the header, by rendering `` as a child of the `` component. Note that the custom header will still include the default buttons.
+
+
+### Sidebar.Header
+
+| name | type | description |
+| --- | --- | --- |
+| children | `React.ReactNode` | Content you want to render inside the sidebar header as a sibling of `close` / `dock` buttons. |
+
+To control the visibility of the sidebar you can use [`toggleMenu("customSidebar")`](/docs/@excalidraw/excalidraw/api/props/ref#togglemenu) api available via `ref`.
+
+```tsx live
+function App() {
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+
+ return (
+
+
excalidrawAPI.toggleMenu("customSidebar")}>
+ {" "}
+ Toggle Custom Sidebar{" "}
+
+
setExcalidrawAPI(api)}
+ renderSidebar={() => {
+ return (
+
+ Custom Sidebar Header
+ custom Sidebar Content
+
+ );
+ }}
+ />
+
+ );
+}
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx
new file mode 100644
index 000000000..d7551e366
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx
@@ -0,0 +1,72 @@
+# UIOptions
+
+This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) and [`welcomeScreen`](#welcmescreen).
+
+
+ {
+ canvasActions?:
+ CanvasActions
+ , dockedSidebarBreakpoint?: number, welcomeScreen?: boolean
+
+ }
+
+
+## canvasActions
+
+This `prop` controls the visibility of the canvas actions inside the `menu`.
+
+| Prop | Type | Default | Description |
+| --- | --- | --- | --- |
+| `changeViewBackgroundColor` | `boolean` | `true` | Indicates whether to show `Background color picker`. |
+| `clearCanvas` | `boolean` | `true` | Indicates whether to show `Clear canvas` button. |
+| `export` | `false` | [`exportOpts`](#exportopts) | `object` | This prop allows to customize the UI inside the export dialog. By default it shows the `save file to disk`. For more details visit [`exportOpts`](#exportopts). |
+| `loadScene` | `boolean` | `true` | Indicates whether to show `Load` button. |
+| `saveToActiveFile` | `boolean` | `true` | Indicates whether to show `Save` button to save to current file. |
+| `toggleTheme` | `boolean` | `null` | `null` | Indicates whether to show `Theme toggle`. When defined as `boolean`, takes precedence over [`props.theme`](/docs/@excalidraw/excalidraw/api/props#theme) to show `Theme toggle`. |
+| `saveAsImage` | `boolean` | `true` | Indicates whether to show `Save as image` button. |
+
+```tsx live
+function App() {
+ const UIOptions = {
+ canvasActions: {
+ changeViewBackgroundColor: false,
+ clearCanvas: false,
+ loadScene: false,
+ },
+ };
+ return (
+
+
+
+ );
+}
+```
+
+### exportOpts
+
+The below attributes can be set in `UIOptions.canvasActions.export` to customize the export dialog.
+If `UIOptions.canvasActions.export` is `false` the export button will not be rendered.
+
+| Prop | Type | Default | Description |
+| --- | --- | --- | --- |
+| `saveFileToDisk` | `boolean` | `true` | Indicates whether `save file to disk` button should be shown |
+| `onExportToBackend` | `object` | \_ | This callback is triggered when the shareable-link button is clicked in the export dialog. The link button will only be shown if this callback is passed. |
+| `renderCustomUI` | `object` | \_ | This callback should be supplied if you want to render custom UI in the export dialog. |
+
+## dockedSidebarBreakpoint
+
+This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L161).
+If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown
+If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
+
+
+
+```tsx live
+function App() {
+ return (
+
+
+
+ );
+}
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/utils/export.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/utils/export.mdx
new file mode 100644
index 000000000..f5469f01d
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/utils/export.mdx
@@ -0,0 +1,196 @@
+---
+title: Export Utilities
+id: "export"
+---
+
+:::info
+
+We're working on much improved export utilities. Stay tuned!
+
+:::
+
+### exportToCanvas
+
+**_Signature_**
+
+
+exportToCanvas({
+ elements,
+ appState
+ getDimensions,
+ files,
+ exportPadding?: number;
+}: ExportOpts
+
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to be exported to canvas. |
+| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L17) | The app state of the scene. |
+| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to `1`), with which canvas is to be exported. |
+| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
+| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59) | _ | The files added to the scene. |
+| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
+
+
+#### getDimensions
+
+```tsx
+(width: number, height: number) => {
+ width: number,
+ height: number,
+ scale?: number
+}
+```
+A function which returns the `width`, `height`, and optionally `scale` (defaults to `1`), with which canvas is to be exported.
+
+**How to use**
+
+```js
+import { exportToCanvas } from "@excalidraw/excalidraw";
+```
+
+This function returns the canvas with the exported elements, appState and dimensions.
+
+```jsx live
+function App() {
+ const [canvasUrl, setCanvasUrl] = useState("");
+ const [excalidrawAPI, setExcalidrawAPI] = useState(null);
+
+ return (
+ <>
+ {
+ if (!excalidrawAPI) {
+ return
+ }
+ const elements = excalidrawAPI.getSceneElements();
+ if (!elements || !elements.length) {
+ return
+ }
+ const canvas = await exportToCanvas({
+ elements,
+ appState: {
+ ...initialData.appState,
+ exportWithDarkMode: false,
+ },
+ files: excalidrawAPI.getFiles(),
+ getDimensions: () => { return {width: 350, height: 350}}
+ });
+ const ctx = canvas.getContext("2d");
+ ctx.font = "30px Virgil";
+ ctx.strokeText("My custom text", 50, 60);
+ setCanvasUrl(canvas.toDataURL());
+ }}
+ >
+ Export to Canvas
+
+
+
+
+
+ setExcalidrawAPI(api)}
+/>
+
+ >
+ )
+}
+```
+
+
+### exportToBlob
+
+**_Signature_**
+
+
+exportToBlob(
+ opts: ExportOpts & {
+ mimeType?: string,
+ quality?: number,
+ exportPadding?: number;
+})
+
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `opts` | `object` | _ | This param is passed to `exportToCanvas`. You can refer to [`exportToCanvas`](#exporttocanvas) |
+| `mimeType` | `string` | `image/png` | Indicates the image format. |
+| `quality` | `number` | `0.92` | A value between `0` and `1` indicating the [image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#parameters). Applies only to `image/jpeg`/`image/webp` MIME types. |
+| `exportPadding` | `number` | `10` | The padding to be added on canvas. |
+
+**How to use**
+
+```js
+import { exportToBlob } from "@excalidraw/excalidraw";
+```
+
+Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob). It internally uses [canvas.ToBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob).
+
+### exportToSvg
+
+**_Signature_**
+
+
+exportToSvg({
+ elements:
+
+ ExcalidrawElement[]
+ ,
+ appState:
+ AppState
+ ,
+ exportPadding: number,
+ metadata: string,
+ files:
+
+ BinaryFiles
+ ,
+});
+
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to exported as `svg `|
+| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The `appState` of the scene |
+| exportPadding | number | 10 | The `padding` to be added on canvas |
+| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | undefined | The `files` added to the scene. |
+
+This function returns a promise which resolves to `svg` of the exported drawing.
+
+### exportToClipboard
+
+**_Signature_**
+
+
+exportToClipboard(
+ opts: ExportOpts & {
+ mimeType?: string,
+ quality?: number;
+ type: 'png' | 'svg' |'json'
+})
+
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `opts` | | | This param is same as the params passed to `exportToCanvas`. You can refer to [`exportToCanvas`](#exporttocanvas). |
+| `mimeType` | `string` | `image/png` | Indicates the image format, this will be used when exporting as `png`. |
+| `quality` | `number` | `0.92` | A value between `0` and `1` indicating the [image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#parameters). Applies only to `image/jpeg` / `image/webp` MIME types. This will be used when exporting as `png`. |
+| `type` | 'png' | 'svg' | 'json' | _ | This determines the format to which the scene data should be `exported`. |
+
+**How to use**
+
+```js
+import { exportToClipboard } from "@excalidraw/excalidraw";
+```
+
+Copies the scene data in the specified format (determined by `type`) to clipboard.
+
+### Additional attributes of appState for export\* APIs
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `exportBackground` | `boolean` | `true` | Indicates whether `background` should be exported |
+| `viewBackgroundColor` | `string` | `#fff` | The default background color |
+| `exportWithDarkMode` | `boolean` | `false` | Indicates whether to export with `dark` mode |
+| `exportEmbedScene` | `boolean` | `false` | Indicates whether scene data should be embedded in `svg/png`. This will increase the image size. |
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/utils/restore.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/utils/restore.mdx
new file mode 100644
index 000000000..198626eec
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/utils/restore.mdx
@@ -0,0 +1,90 @@
+---
+title: Restore Utilities
+id: "restore"
+---
+
+### restoreAppState
+
+**_Signature_**
+
+
+restoreAppState(appState: ImportedDataState["appState"] , localAppState: Partial<AppState > | null): AppState
+
+
+**_How to use_**
+
+```js
+import { restoreAppState } from "@excalidraw/excalidraw";
+```
+
+This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) and if any key is missing, it will be set to its `default` value.
+
+When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.
+Use this as a way to not override user's defaults if you persist them.
+You can pass `null` / `undefined` if not applicable.
+
+### restoreElements
+
+**_Signature_**
+
+
+restoreElements(
+ elements: ImportedDataState["elements"] ,
+ localElements: ExcalidrawElement[] | null | undefined): ExcalidrawElement[] ,
+ refreshDimensions?: boolean
+)
+
+
+**_How to use_**
+
+```js
+import { restoreElements } from "@excalidraw/excalidraw";
+```
+
+This function will make sure all properties of element is correctly set and if any attribute is missing, it will be set to its default value.
+
+When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`.
+Use this when you import elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the updates.
+
+Parameter `refreshDimensions` indicates whether we should also `recalculate` text element dimensions. Defaults to `false`. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration.
+
+### restore
+
+**_Signature_**
+
+
+restore(
+ data: ImportedDataState ,
+ localAppState: Partial<AppState > | null | undefined,
+ localElements: ExcalidrawElement[] | null | undefined ): DataState
+)
+
+
+See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
+
+**_How to use_**
+
+```js
+import { restore } from "@excalidraw/excalidraw";
+```
+
+This function makes sure elements and state is set to appropriate values and set to default value if not present. It is a combination of [restoreElements](#restoreelements) and [restoreAppState](#restoreappstate).
+
+### restoreLibraryItems
+
+**_Signature_**
+
+
+restoreLibraryItems(libraryItems: ImportedDataState["libraryItems"] ,
+defaultStatus: "published" | "unpublished")
+
+
+**_How to use_**
+
+```js
+import { restoreLibraryItems } from "@excalidraw/excalidraw";
+
+restoreLibraryItems(libraryItems, "unpublished");
+```
+
+This function normalizes library items elements, adding missing values when needed.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md b/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md
new file mode 100644
index 000000000..c21592382
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/utils/utils-intro.md
@@ -0,0 +1,341 @@
+---
+slug: /@excalidraw/excalidraw/api/utils
+---
+
+# Utils
+
+These are pure Javascript functions exported from the @excalidraw/excalidraw [`@excalidraw/excalidraw`](https://npmjs.com/@excalidraw/excalidraw). If you want to export your drawings in different formats eg `png`, `svg` and more you can check out [Export Utilities](/docs/@excalidraw/excalidraw/API/utils/export). If you want to restore your drawings you can check out [Restore Utilities](/docs/@excalidraw/excalidraw/API/utils/restore).
+
+### serializeAsJSON
+
+Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L42) source for details).
+
+If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
+
+**_Signature_**
+
+
+serializeAsJSON({
+ elements: ExcalidrawElement[] ,
+ appState: AppState ,
+}): string
+
+
+**How to use**
+
+```js
+import { serializeAsJSON } from "@excalidraw/excalidraw";
+```
+
+### serializeLibraryAsJSON
+
+Takes the `library` items and returns a `JSON` string.
+
+If you want to overwrite the source field in the JSON string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
+
+**_Signature_**
+
+
+serializeLibraryAsJSON(
+ libraryItems: LibraryItems[] )
+
+
+**How to use**
+
+```js
+import { serializeLibraryAsJSON } from "@excalidraw/excalidraw";
+```
+
+#### isInvisiblySmallElement
+
+Returns `true` if element is invisibly small (e.g. width & height are zero).
+
+**_Signature_**
+
+
+isInvisiblySmallElement(element: ExcalidrawElement ): boolean
+
+
+**How to use**
+
+```js
+import { isInvisiblySmallElement } from "@excalidraw/excalidraw";
+```
+
+### loadFromBlob
+
+This function loads the scene data from the blob (or file). If you pass `localAppState`, `localAppState` value will be preferred over the `appState` derived from `blob`. Throws if blob doesn't contain valid scene data.
+
+**How to use**
+
+```js
+import { loadFromBlob } from "@excalidraw/excalidraw";
+
+const scene = await loadFromBlob(file, null, null);
+excalidrawAPI.updateScene(scene);
+```
+
+**Signature**
+
+
+loadFromBlob(
+ blob: Blob ,
+ localAppState: AppState | null,
+ localElements: ExcalidrawElement[] | null,
+ fileHandle?: FileSystemHandle | null
+) => Promise<RestoredDataState >
+
+
+### loadLibraryFromBlob
+
+This function loads the library from the blob. Additonally takes `defaultStatus` param which sets the default status for library item if not present, defaults to `unpublished`.
+
+**How to use **
+
+```js
+import { loadLibraryFromBlob } from "@excalidraw/excalidraw";
+```
+
+**_Signature_**
+
+
+loadLibraryFromBlob(blob: Blob , defaultStatus: "published" | "unpublished")
+
+
+### loadSceneOrLibraryFromBlob
+
+This function loads either scene or library data from the supplied blob. If the blob contains scene data, and you pass `localAppState`, `localAppState` value will be preferred over the `appState` derived from `blob`.
+
+:::caution
+
+Throws if blob doesn't contain valid `scene` data or `library` data.
+
+:::
+
+**How to use**
+
+```js showLineNumbers
+import { loadSceneOrLibraryFromBlob, MIME_TYPES } from "@excalidraw/excalidraw";
+
+const contents = await loadSceneOrLibraryFromBlob(file, null, null);
+if (contents.type === MIME_TYPES.excalidraw) {
+ excalidrawAPI.updateScene(contents.data);
+} else if (contents.type === MIME_TYPES.excalidrawlib) {
+ excalidrawAPI.updateLibrary(contents.data);
+}
+```
+
+**_Signature_**
+
+
+loadSceneOrLibraryFromBlob(
+ blob: Blob ,
+ localAppState: AppState | null,
+ localElements: ExcalidrawElement[] | null,
+ fileHandle?: FileSystemHandle | null
+) => Promise<{ type: string, data: RestoredDataState | ImportedLibraryState }>
+
+
+### getFreeDrawSvgPath
+
+This function returns the `free draw` svg path for the element.
+
+**How to use**
+
+```js
+import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
+```
+
+**Signature**
+
+
+getFreeDrawSvgPath(element: ExcalidrawFreeDrawElement )
+
+
+### isLinearElement
+
+This function returns true if the element is `linear` type (`arrow` |`line`) else returns `false`.
+
+**How to use**
+
+```js
+import { isLinearElement } from "@excalidraw/excalidraw";
+```
+
+**Signature**
+
+```tsx
+isLinearElement(elementType?: ExcalidrawElement ): boolean
+```
+
+### getNonDeletedElements
+
+This function returns an array of `deleted` elements.
+
+**How to use**
+
+```js
+import { getNonDeletedElements } from "@excalidraw/excalidraw";
+```
+
+**Signature**
+
+
+getNonDeletedElements(elements: readonly ExcalidrawElement[] ): as readonly NonDeletedExcalidrawElement[]
+
+
+### mergeLibraryItems
+
+This function merges two `LibraryItems` arrays, where unique items from `otherItems` are sorted first in the returned array.
+
+```js
+import { mergeLibraryItems } from "@excalidraw/excalidraw";
+```
+
+**_Signature_**
+
+
+mergeLibraryItems(localItems: LibraryItems ,
+ otherItems: LibraryItems ) => LibraryItems
+
+
+### parseLibraryTokensFromUrl
+
+Parses library parameters from URL if present (expects the `#addLibrary` hash key), and returns an object with the `libraryUrl` and `idToken`. Returns `null` if `#addLibrary` hash key not found.
+
+**How to use**
+
+```js
+import { parseLibraryTokensFromUrl } from "@excalidraw/excalidraw";
+```
+
+**Signature**
+
+```tsx
+parseLibraryTokensFromUrl(): {
+ libraryUrl: string;
+ idToken: string | null;
+} | null
+```
+
+### useHandleLibrary
+
+A hook that automatically imports library from url if `#addLibrary` hash key exists on initial load, or when it changes during the editing session (e.g. when a user installs a new library), and handles initial library load if `getInitialLibraryItems` getter is supplied.
+
+**How to use**
+
+```js
+import { useHandleLibrary } from "@excalidraw/excalidraw";
+
+export const App = () => {
+ // ...
+ useHandleLibrary({ excalidrawAPI });
+};
+```
+
+**Signature**
+
+
+useHandleLibrary(opts: {
+ excalidrawAPI: ExcalidrawAPI ,
+ getInitialLibraryItems?: () => LibraryItemsSource
+});
+
+
+In the future, we will be adding support for handling `library` persistence to `browser storage` (or elsewhere).
+
+### getSceneVersion
+
+This function returns the current `scene` version.
+
+**_Signature_**
+
+
+getSceneVersion(elements: ExcalidrawElement[] )
+
+
+**How to use**
+
+```js
+import { getSceneVersion } from "@excalidraw/excalidraw";
+```
+
+### sceneCoordsToViewportCoords
+
+This function returns equivalent `viewport` coords for the provided `scene` coords in params.
+
+```js
+import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
+```
+
+**_Signature_**
+
+
+sceneCoordsToViewportCoords({ sceneX: number, sceneY: number },
+ appState: AppState ): { x: number, y: number }
+
+
+### viewportCoordsToSceneCoords
+
+This function returns equivalent `scene` coords for the provided `viewport` coords in params.
+
+```js
+import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
+```
+
+**_Signature_**
+
+
+viewportCoordsToSceneCoords({ clientX: number, clientY: number },
+ appState: AppState ): {x: number, y: number}
+
+
+### useDevice
+
+This hook can be used to check the type of device which is being used. It can only be used inside the `children` of `Excalidraw` component.
+
+Open the `main menu` in the below example to view the footer.
+
+```jsx live noInline
+const MobileFooter = ({}) => {
+ const device = useDevice();
+ if (device.isMobile) {
+ return (
+
+ alert("This is custom footer in mobile menu")}
+ >
+ custom footer
+
+
+ );
+ }
+ return null;
+};
+const App = () => (
+
+
+
+ Item1
+ Item 2
+
+
+
+
+);
+
+// Need to render when code is span across multiple components
+// in Live Code blocks editor
+render( );
+```
+
+The `device` has the following `attributes`
+
+| Name | Type | Description |
+| --- | --- | --- |
+| `isSmScreen` | `boolean` | Set to `true` when the device small screen is small (Width < `640px` ) |
+| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
+| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
+| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
diff --git a/dev-docs/docs/@excalidraw/excalidraw/customizing-styles.mdx b/dev-docs/docs/@excalidraw/excalidraw/customizing-styles.mdx
new file mode 100644
index 000000000..7a1d423fc
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/customizing-styles.mdx
@@ -0,0 +1,49 @@
+# Customizing Styles
+
+Excalidraw is using CSS variables to style certain components. To override them, you should set your own on the `.excalidraw` and `.excalidraw.theme--dark` (for dark mode variables) selectors.
+
+Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:
+
+```css
+.your-app .excalidraw {
+ --color-primary: red;
+}
+.your-app .excalidraw.theme--dark {
+ --color-primary: pink;
+}
+```
+
+Most notably, you can customize the primary colors, by overriding these variables:
+
+- `--color-primary`
+- `--color-primary-darker`
+- `--color-primary-darkest`
+- `--color-primary-light`
+- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
+
+For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
+
+```css showLineNumbers
+.custom-styles .excalidraw {
+ --color-primary: #fcc6d9;
+ --color-primary-darker: #f783ac;
+ --color-primary-darkest: #e64980;
+ --color-primary-light: #f2a9c4;
+}
+
+.custom-styles .excalidraw.theme--dark {
+ --color-primary: #d494aa;
+ --color-primary-darker: #d64c7e;
+ --color-primary-darkest: #e86e99;
+ --color-primary-light: #dcbec9;
+}
+```
+```tsx live
+function App() {
+ return (
+
+
+
+ );
+}
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/development.mdx b/dev-docs/docs/@excalidraw/excalidraw/development.mdx
new file mode 100644
index 000000000..066e0a24e
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/development.mdx
@@ -0,0 +1,59 @@
+---
+pagination_prev: "@excalidraw/excalidraw/installation"
+---
+
+# Development
+
+This page relates to developing the `@excalidraw/excalidraw` package itself.
+
+## Example app
+
+To start the example app using the `@excalidraw/excalidraw` package, follow the below steps:
+
+1. Install the dependencies
+
+ ```bash
+ cd src/packages/excalidraw && yarn
+ ```
+
+2. Start the example app
+
+ ```bash
+ yarn start
+ ```
+
+ [http://localhost:3001](http://localhost:3001) will open in your default browser.
+
+ The example is same as the [codesandbox example](https://ehlz3.csb.app/)
+
+## Releasing
+
+### Create a test release
+
+You can create a test release by posting the below comment in your pull request:
+
+```bash
+@excalibot trigger release
+```
+
+Once the version is released `@excalibot` will post a comment with the release version.
+
+### Creating a production release
+
+To release the next stable version follow the below steps:
+
+```bash
+yarn prerelease version
+```
+
+You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
+
+The next step is to run the `release` script:
+
+```bash
+yarn release
+```
+
+This will publish the package.
+
+Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/faq.mdx b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx
new file mode 100644
index 000000000..6f0fd30a7
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/faq.mdx
@@ -0,0 +1,9 @@
+# FAQ
+
+### Does this package support collaboration ?
+
+No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
+
+## 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).
diff --git a/dev-docs/docs/@excalidraw/excalidraw/installation.mdx b/dev-docs/docs/@excalidraw/excalidraw/installation.mdx
new file mode 100644
index 000000000..d8ddd59c3
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/installation.mdx
@@ -0,0 +1,43 @@
+# Installation
+
+**Excalidraw** is published to npm as a component you can directly embed in your projects.
+
+Using `npm`:
+
+```bash
+npm install react react-dom @excalidraw/excalidraw
+```
+
+or `yarn`:
+
+```bash
+yarn add react react-dom @excalidraw/excalidraw
+```
+
+:::tip
+
+**If you don't want to wait for the next stable release and try out the unreleased changes you can use `@excalidraw/excalidraw@next`.**
+
+:::
+
+### Static assets
+
+Excalidraw depends on assets such as localization files (if you opt to use them), fonts, and others.
+
+By default these assets are loaded from a public CDN [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist), so you don't need to do anything on your end.
+
+However, if you want to host these files yourself, you can find them in your `node_modules/@excalidraw/excalidraw/dist` directory, in folders `excalidraw-assets` (for production) and `excalidraw-assets-dev` (for development).
+
+Copy these folders to your static assets directory, and add a `window.EXCALIDRAW_ASSET_PATH` variable in your `index.html` or `index.js` entry file pointing to your public assets path (relative). For example, if you serve your assets from the root of your hostname, you would do:
+
+```js
+window.EXCALIDRAW_ASSET_PATH = "/";
+```
+
+### Dimensions of Excalidraw
+
+Excalidraw takes _100%_ of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
+
+### Demo
+
+[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
diff --git a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx
new file mode 100644
index 000000000..7080c32e9
--- /dev/null
+++ b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx
@@ -0,0 +1,131 @@
+# Integration
+
+## Module bundler
+
+If you are using a module bundler (for instance, Webpack), you can import it as an ES6 module as shown below
+
+```js
+import { Excalidraw } from "@excalidraw/excalidraw";
+```
+
+:::info
+
+Throughout the documentation we use live, editable Excalidraw examples like the one shown below.
+
+While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
+For example, we're passing a `theme` prop to it based on the current color theme of the docs you're just reading.
+
+:::
+
+```jsx live
+function App() {
+ return (
+ <>
+ Excalidraw Example
+
+
+
+ >
+ );
+}
+```
+
+### Rendering Excalidraw only on client
+
+Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
+
+The following worfklow shows one way how to render Excalidraw on Next.js. We'll add more detailed and alternative Next.js examples, soon.
+
+```jsx showLineNumbers
+import { useState, useEffect } from "react";
+export default function App() {
+ const [Excalidraw, setExcalidraw] = useState(null);
+ useEffect(() => {
+ import("@excalidraw/excalidraw").then((comp) => setExcalidraw(comp.Excalidraw));
+ }, []);
+ return <>{Excalidraw && }>;
+}
+```
+
+The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
+
+## Browser
+
+To use it in a browser directly:
+
+For development use :point_down:
+
+```js
+
+```
+
+For production use :point_down:
+
+```js
+
+```
+
+You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
+
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+
+
+
+
+```html
+
+
+
+ Excalidraw in browser
+
+
+
+
+
+
+
+
+
+
Excalidraw Embed Example
+
+
+
+
+
+```
+
+
+
+
+```js showLineNumbers
+const App = () => {
+ return React.createElement(
+ React.Fragment,
+ null,
+ React.createElement(
+ "div",
+ {
+ style: { height: "500px" },
+ },
+ React.createElement(ExcalidrawLib.Excalidraw),
+ ),
+ );
+};
+
+const excalidrawWrapper = document.getElementById("app");
+const root = ReactDOM.createRoot(excalidrawWrapper);
+root.render(React.createElement(App));
+```
+
+
+
diff --git a/dev-docs/docs/assets/nerd-stats.png b/dev-docs/docs/assets/nerd-stats.png
new file mode 100644
index 000000000..0c1a525e4
Binary files /dev/null and b/dev-docs/docs/assets/nerd-stats.png differ
diff --git a/dev-docs/docs/codebase/overview.md b/dev-docs/docs/codebase/overview.md
deleted file mode 100644
index 595a74d48..000000000
--- a/dev-docs/docs/codebase/overview.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-sidebar_position: 1
-title: Overview
----
-
-In development. For now, refer to [excalidraw Readme](https://github.com/excalidraw/excalidraw/blob/master/README.md).
diff --git a/dev-docs/docs/get-started.md b/dev-docs/docs/get-started.md
deleted file mode 100644
index 2fb40f1fa..000000000
--- a/dev-docs/docs/get-started.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-sidebar_position: 1
-title: Introduction
----
-
-Want to integrate Excalidraw into your app? Head over to the [package docs](/docs/package/overview).
-
-If you're looking into the Excalidraw codebase itself, start [here](/docs/codebase/overview).
diff --git a/dev-docs/docs/introduction/contributing.mdx b/dev-docs/docs/introduction/contributing.mdx
new file mode 100644
index 000000000..d384c97a1
--- /dev/null
+++ b/dev-docs/docs/introduction/contributing.mdx
@@ -0,0 +1,72 @@
+# Contributing
+
+Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
+
+## Setup
+
+### Option 1 - Manual
+
+1. Fork and clone the repo
+1. Run `yarn` to install dependencies
+1. Create a branch for your PR with `git checkout -b your-branch-name`
+
+> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
+>
+> ```bash
+> git remote add upstream https://github.com/excalidraw/excalidraw.git
+> git fetch upstream
+> git branch --set-upstream-to=upstream/master master
+> ```
+
+### Option 2 - CodeSandbox
+
+1. Go to https://codesandbox.io/p/github/excalidraw/excalidraw
+1. Connect your GitHub account
+1. Go to Git tab on left side
+1. Tap on `Fork Sandbox`
+1. Write your code
+1. Commit and PR automatically
+
+## Pull Request Guidelines
+
+Don't worry if you get any of the below wrong, or if you don't know how. We'll gladly help out.
+
+### Title
+
+Make sure the title starts with a semantic prefix:
+
+- **feat**: A new feature
+- **fix**: A bug fix
+- **docs**: Documentation only changes
+- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
+- **refactor**: A code change that neither fixes a bug nor adds a feature
+- **perf**: A code change that improves performance
+- **test**: Adding missing tests or correcting existing tests
+- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
+- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
+- **chore**: Other changes that don't modify src or test files
+- **revert**: Reverts a previous commit
+
+### Changelog
+
+Add a brief description of your pull request to the changelog located here: [changelog](https://github.com/excalidraw/excalidraw/blob/master/CHANGELOG.md)
+
+Notes:
+
+- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
+- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
+
+### Testing
+
+Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
+
+It's also a good idea to consider if your change should include additional tests. This is highly recommended for new features or bug-fixes. For example, it's good practice to create a test for each bug you fix which ensures that we don't regress the code in the future.
+
+Finally - always manually test your changes using the convenient staging environment deployed for each pull request. As much as local development attempts to replicate production, there can still be subtle differences in behavior. For larger features consider testing your change in multiple browsers as well.
+
+
+## Translating
+
+To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
+
+Translations will be available on the app if they exceed a certain threshold of completion (currently **85%**).
diff --git a/dev-docs/docs/introduction/development.mdx b/dev-docs/docs/introduction/development.mdx
new file mode 100644
index 000000000..63c1875e5
--- /dev/null
+++ b/dev-docs/docs/introduction/development.mdx
@@ -0,0 +1,102 @@
+# Development
+
+## Code Sandbox
+
+- Go to https://codesandbox.io/p/github/excalidraw/excalidraw
+ - You may need to sign in with GitHub and reload the page
+- You can start coding instantly, and even send PRs from there!
+
+## Local Installation
+
+These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
+
+### Requirements
+
+- [Node.js](https://nodejs.org/en/)
+- [Yarn](https://yarnpkg.com/getting-started/install) (v1 or v2.4.2+)
+- [Git](https://git-scm.com/downloads)
+
+### Clone the repo
+
+```bash
+git clone https://github.com/excalidraw/excalidraw.git
+```
+
+### Install the dependencies
+
+```bash
+yarn
+```
+
+### Start the server
+
+```bash
+yarn start
+```
+
+Now you can open [http://localhost:3000](http://localhost:3000) and start coding in your favorite code editor.
+
+## Collaboration
+
+For collaboration, you will need to set up [collab server](https://github.com/excalidraw/excalidraw-room) in local.
+
+## Commands
+
+### Install the dependencies
+
+```bash
+yarn
+```
+
+### Run the project
+
+```bash
+yarn start
+```
+
+### Reformat all files with Prettier
+
+```bash
+yarn fix
+```
+
+### Run tests
+
+```bash
+yarn test
+```
+
+### Update test snapshots
+
+```bash
+yarn test:update
+```
+
+### Test for formatting with Prettier
+
+```bash
+yarn test:code
+```
+
+### Docker Compose
+
+You can use docker-compose to work on Excalidraw locally if you don't want to setup a Node.js env.
+
+```bash
+docker-compose up --build -d
+```
+
+## Self-hosting
+
+We publish a Docker image with the Excalidraw client at [excalidraw/excalidraw](https://hub.docker.com/r/excalidraw/excalidraw). You can use it to self-host your own client under your own domain, on Kubernetes, AWS ECS, etc.
+
+```bash
+docker build -t excalidraw/excalidraw .
+docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latest
+```
+
+The Docker image is free of analytics and other tracking libraries.
+
+**At the moment, self-hosting your own instance doesn't support sharing or collaboration features.**
+
+We are working towards providing a full-fledged solution for self-hosting your own Excalidraw.
diff --git a/dev-docs/docs/introduction/get-started.mdx b/dev-docs/docs/introduction/get-started.mdx
new file mode 100644
index 000000000..f122c9a77
--- /dev/null
+++ b/dev-docs/docs/introduction/get-started.mdx
@@ -0,0 +1,16 @@
+---
+title: Introduction
+slug: ../
+---
+
+## Try now
+
+Go to [excalidraw.com](https://excalidraw.com) to start sketching.
+
+## How are these docs structured
+
+These docs are focused on developers, and structured in the following way:
+
+- [Introduction](/docs/) — development setup and introduction.
+- [@excalidraw/excalidraw](/docs/@excalidraw/excalidraw/installation) — docs for the npm package to help you integrate Excalidraw into your own app.
+- Editor — IN PROGRESS. Docs describing the internals of the Excalidraw editor to help in contributing to the codebase.
diff --git a/dev-docs/docs/package/overview.md b/dev-docs/docs/package/overview.md
deleted file mode 100644
index b411dcf29..000000000
--- a/dev-docs/docs/package/overview.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-sidebar_position: 1
-title: Overview
----
-
-In development. For now, refer to [excalidraw package readme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md).
diff --git a/dev-docs/docusaurus.config.js b/dev-docs/docusaurus.config.js
index c1f9952df..390c619af 100644
--- a/dev-docs/docusaurus.config.js
+++ b/dev-docs/docusaurus.config.js
@@ -1,15 +1,12 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
-const lightCodeTheme = require("prism-react-renderer/themes/github");
-const darkCodeTheme = require("prism-react-renderer/themes/dracula");
-
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Excalidraw developer docs",
tagline:
"For Excalidraw contributors or those integrating the Excalidraw editor",
- url: "https://docs.excalidraw.com.com",
+ url: "https://docs.excalidraw.com",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
@@ -33,10 +30,16 @@ const config = {
docs: {
sidebarPath: require.resolve("./sidebars.js"),
// Please change this to your repo.
- editUrl: "https://github.com/excalidraw/docs/tree/master/",
+ editUrl:
+ "https://github.com/excalidraw/excalidraw/tree/master/dev-docs/",
+ showLastUpdateAuthor: true,
+ showLastUpdateTime: true,
},
theme: {
- customCss: require.resolve("./src/css/custom.css"),
+ customCss: [
+ require.resolve("./src/css/custom.scss"),
+ require.resolve("../src/packages/excalidraw/example/App.scss"),
+ ],
},
}),
],
@@ -45,18 +48,20 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
+ colorMode: {
+ respectPrefersColorScheme: true,
+ },
navbar: {
- title: "Excalidraw Docs",
+ title: "Excalidraw",
logo: {
alt: "Excalidraw Logo",
src: "img/logo.svg",
},
items: [
{
- type: "doc",
- docId: "get-started",
+ to: "/docs",
position: "left",
- label: "Get started",
+ label: "Docs",
},
{
to: "https://blog.excalidraw.com",
@@ -78,7 +83,7 @@ const config = {
items: [
{
label: "Get Started",
- to: "/docs/get-started",
+ to: "/docs",
},
],
},
@@ -93,6 +98,10 @@ const config = {
label: "Twitter",
href: "https://twitter.com/excalidraw",
},
+ {
+ label: "Linkedin",
+ href: "https://www.linkedin.com/company/excalidraw",
+ },
],
},
{
@@ -109,13 +118,28 @@ const config = {
],
},
],
- copyright: `Made with ❤️ Built with Docusaurus`,
+ copyright: `Copyright © 2023 Excalidraw community. Built with Docusaurus ❤️`,
},
prism: {
- theme: lightCodeTheme,
- darkTheme: darkCodeTheme,
+ theme: require("prism-react-renderer/themes/dracula"),
+ },
+ image: "img/og-image.png",
+ docs: {
+ sidebar: {
+ hideable: true,
+ },
+ },
+ tableOfContents: {
+ maxHeadingLevel: 4,
+ },
+ algolia: {
+ appId: "8FEAOD28DI",
+ apiKey: "4b07cca33ff2d2919bc95ff98f148e9e",
+ indexName: "excalidraw",
},
}),
+ themes: ["@docusaurus/theme-live-codeblock"],
+ plugins: ["docusaurus-plugin-sass"],
};
module.exports = config;
diff --git a/dev-docs/package.json b/dev-docs/package.json
index 9f9d4a760..dd3c45872 100644
--- a/dev-docs/package.json
+++ b/dev-docs/package.json
@@ -15,13 +15,17 @@
"typecheck": "tsc"
},
"dependencies": {
- "@docusaurus/core": "2.0.0-rc.1",
- "@docusaurus/preset-classic": "2.0.0-rc.1",
+ "@docusaurus/core": "2.2.0",
+ "@docusaurus/preset-classic": "2.2.0",
+ "@docusaurus/theme-live-codeblock": "2.2.0",
+ "@excalidraw/excalidraw": "0.14.2",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
+ "docusaurus-plugin-sass": "0.2.3",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react-dom": "^17.0.2",
+ "sass": "1.57.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
diff --git a/dev-docs/sidebars.js b/dev-docs/sidebars.js
index 966215066..00879c12f 100644
--- a/dev-docs/sidebars.js
+++ b/dev-docs/sidebars.js
@@ -13,19 +13,86 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
- // By default, Docusaurus generates a sidebar from the docs folder structure
- tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
-
- // But you can create a sidebar manually
- /*
- tutorialSidebar: [
+ docs: [
{
- type: 'category',
- label: 'Tutorial',
- items: ['hello'],
+ type: "category",
+ label: "Introduction",
+ link: {
+ type: "doc",
+ id: "introduction/get-started",
+ },
+ items: ["introduction/development", "introduction/contributing"],
+ },
+
+ {
+ type: "category",
+ label: "@excalidraw/excalidraw",
+ collapsed: false,
+ items: [
+ "@excalidraw/excalidraw/installation",
+ "@excalidraw/excalidraw/integration",
+ "@excalidraw/excalidraw/customizing-styles",
+ {
+ type: "category",
+ label: "API",
+ link: {
+ type: "doc",
+ id: "@excalidraw/excalidraw/api/api-intro",
+ },
+ items: [
+ {
+ type: "category",
+ label: "Props",
+ link: {
+ type: "doc",
+ id: "@excalidraw/excalidraw/api/props/props",
+ },
+ items: [
+ "@excalidraw/excalidraw/api/props/initialdata",
+ "@excalidraw/excalidraw/api/props/ref",
+ "@excalidraw/excalidraw/api/props/render-props",
+ "@excalidraw/excalidraw/api/props/ui-options",
+ ],
+ },
+ {
+ type: "category",
+ label: "Children Components",
+ link: {
+ type: "doc",
+ id: "@excalidraw/excalidraw/api/children-components/children-components-intro",
+ },
+ items: [
+ "@excalidraw/excalidraw/api/children-components/main-menu",
+ "@excalidraw/excalidraw/api/children-components/welcome-screen",
+ "@excalidraw/excalidraw/api/children-components/footer",
+ "@excalidraw/excalidraw/api/children-components/live-collaboration-trigger",
+ ],
+ },
+ {
+ type: "category",
+ label: "Utils",
+ link: {
+ type: "doc",
+ id: "@excalidraw/excalidraw/api/utils/utils-intro",
+ },
+ items: [
+ "@excalidraw/excalidraw/api/utils/export",
+ "@excalidraw/excalidraw/api/utils/restore",
+ ],
+ },
+ {
+ type: "category",
+ label: "Constants",
+ link: { type: "doc", id: "@excalidraw/excalidraw/api/constants" },
+ items: [],
+ },
+ ],
+ },
+ "@excalidraw/excalidraw/faq",
+ "@excalidraw/excalidraw/development",
+ ],
},
],
- */
};
module.exports = sidebars;
diff --git a/dev-docs/src/components/Highlight.js b/dev-docs/src/components/Highlight.js
new file mode 100644
index 000000000..6ef30412b
--- /dev/null
+++ b/dev-docs/src/components/Highlight.js
@@ -0,0 +1,15 @@
+import React from "react";
+export default function Highlight({ children }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/dev-docs/src/css/custom.css b/dev-docs/src/css/custom.scss
similarity index 54%
rename from dev-docs/src/css/custom.css
rename to dev-docs/src/css/custom.scss
index bbdc13c56..93c7f90ab 100644
--- a/dev-docs/src/css/custom.css
+++ b/dev-docs/src/css/custom.scss
@@ -14,11 +14,13 @@
--ifm-color-primary-lighter: #5b57d1;
--ifm-color-primary-lightest: #5b57d1;
--ifm-code-font-size: 95%;
+
+ scrollbar-gutter: stable;
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme="dark"] {
- --ifm-color-primary: #5650f0;
+ --ifm-color-primary: #8784e3;
--ifm-color-primary-dark: #4b46d8;
--ifm-color-primary-darker: #4b46d8;
--ifm-color-primary-darkest: #3e39be;
@@ -41,3 +43,59 @@
[data-theme="dark"] .navbar__logo {
filter: invert(93%) hue-rotate(180deg);
}
+
+pre a {
+ color: #5dccff;
+
+ &:hover {
+ color: #8fd3f3;
+ }
+}
+
+.custom-button {
+ height: 40px;
+ max-width: 200px;
+ margin: 10px 0;
+ padding: 5px;
+ background: #70b1ec;
+ color: white;
+ font-weight: bold;
+ border: none;
+}
+
+.custom-styles .excalidraw {
+ --color-primary: #fcc6d9;
+ --color-primary-darker: #f783ac;
+ --color-primary-darkest: #e64980;
+ --color-primary-light: #f2a9c4;
+}
+
+.custom-styles .excalidraw.theme--dark {
+ --color-primary: #d494aa;
+ --color-primary-darker: #d64c7e;
+ --color-primary-darkest: #e86e99;
+ --color-primary-light: #dcbec9;
+}
+
+/* The global css conflicts with Excal css hence overriding */
+
+.excalidraw .context-menu-item__shortcut {
+ background-color: transparent;
+ border: none;
+ box-shadow: none;
+ padding: 0;
+}
+.excalidraw .Stats table td,
+.excalidraw .Stats table th,
+.excalidraw .Stats table tr {
+ border: none;
+ background: none;
+ padding: 0;
+}
+.excalidraw .Stats .close {
+ padding: 0;
+}
+
+.excalidraw .Stats table {
+ display: table;
+}
diff --git a/dev-docs/src/initialData.js b/dev-docs/src/initialData.js
new file mode 100644
index 000000000..d0a9b2127
--- /dev/null
+++ b/dev-docs/src/initialData.js
@@ -0,0 +1,1230 @@
+export const libraryItems = [
+ [
+ {
+ type: "line",
+ version: 1699,
+ versionNonce: 1813275999,
+ isDeleted: false,
+ id: "1OMHrnYMU3LJ3w3IaXU_R",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: 209.72304760646858,
+ y: 338.83587294718825,
+ strokeColor: "#881fa3",
+ backgroundColor: "#be4bdb",
+ width: 116.42036295658873,
+ height: 103.65107323746608,
+ seed: 1445523839,
+ groupIds: [],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [-92.28090097254909, 7.105427357601002e-15],
+ [-154.72281841151394, 19.199290805487394],
+ [-155.45758928571422, 79.43840749607878],
+ [-99.89923520113778, 103.6510732374661],
+ [-40.317783799181804, 79.1587107641305],
+ [-39.037226329125524, 21.285677238400705],
+ [-92.28090097254909, 7.105427357601002e-15],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ ],
+ [
+ {
+ type: "line",
+ version: 3901,
+ versionNonce: 540959103,
+ isDeleted: false,
+ id: "b-rwW8s76ztV_uTu1SHq1",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -249.48446738689245,
+ y: 374.851387389359,
+ strokeColor: "#0a11d3",
+ backgroundColor: "#228be6",
+ width: 88.21658171083376,
+ height: 113.8575037534261,
+ seed: 1513238033,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [-0.22814350714115691, -43.414939319563715],
+ [0.06274947619197979, 42.63794490105306],
+ [-0.21453039840335475, 52.43469208825097],
+ [4.315205554872581, 56.66774540453215],
+ [20.089784992984285, 60.25027917349701],
+ [46.7532926683984, 61.365826671969444],
+ [72.22851104292477, 59.584691681394986],
+ [85.76368213524371, 55.325139565662596],
+ [87.67263486434864, 51.7342924478499],
+ [87.94074036468018, 43.84700272879395],
+ [87.73030872197806, -36.195582644606276],
+ [87.2559282533682, -43.758132174307036],
+ [81.5915337527493, -47.984890854524416],
+ [69.66352776578219, -50.4328058257654],
+ [42.481213744224995, -52.49167708145666],
+ [20.68789182864576, -51.26396751574663],
+ [3.5475921483286084, -47.099726468136254],
+ [-0.2758413461535838, -43.46664538034193],
+ [-0.22814350714115691, -43.414939319563715],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 1635,
+ versionNonce: 1383184881,
+ isDeleted: false,
+ id: "3CMZYj34FwjhgPB7jUC3f",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -249.02524930453623,
+ y: 398.8804363713438,
+ strokeColor: "#0a11d3",
+ backgroundColor: "transparent",
+ width: 88.30808627974527,
+ height: 9.797916664247975,
+ seed: 683951089,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, -2.1538602707609424],
+ [2.326538897826852, 1.751753055375216],
+ [12.359939318521995, 5.028526743934819],
+ [25.710950037209347, 7.012921076245119],
+ [46.6269757640547, 7.193749997581346],
+ [71.03526003420632, 5.930375670950649],
+ [85.2899738827162, 1.3342483900732343],
+ [88.30808627974527, -2.6041666666666288],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 1722,
+ versionNonce: 303290783,
+ isDeleted: false,
+ id: "DX3fUhBWtlJwYyrBDhebG",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -250.11899081659772,
+ y: 365.80628180927204,
+ strokeColor: "#0a11d3",
+ backgroundColor: "transparent",
+ width: 88.30808627974527,
+ height: 9.797916664247975,
+ seed: 1817746897,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, -2.1538602707609424],
+ [2.326538897826852, 1.751753055375216],
+ [12.359939318521995, 5.028526743934819],
+ [25.710950037209347, 7.012921076245119],
+ [46.6269757640547, 7.193749997581346],
+ [71.03526003420632, 5.930375670950649],
+ [85.2899738827162, 1.3342483900732343],
+ [88.30808627974527, -2.6041666666666288],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "ellipse",
+ version: 4738,
+ versionNonce: 753357777,
+ isDeleted: false,
+ id: "a-Snvp2FgqDYqSLylF44S",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -251.23981350275943,
+ y: 323.4117518426986,
+ strokeColor: "#0a11d3",
+ backgroundColor: "#fff",
+ width: 87.65074610854188,
+ height: 17.72670397681366,
+ seed: 1409727409,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "sharp",
+ boundElementIds: ["bxuMGTzXLn7H-uBCptINx"],
+ },
+ {
+ type: "ellipse",
+ version: 109,
+ versionNonce: 1992641983,
+ isDeleted: false,
+ id: "7-6c-JFuB2yGoNQRgb2WM",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -179.73008120217884,
+ y: 347.98755471983213,
+ strokeColor: "#0a11d3",
+ backgroundColor: "#fff",
+ width: 12.846057046979809,
+ height: 13.941904362416096,
+ seed: 1073094033,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 158,
+ versionNonce: 1028567473,
+ isDeleted: false,
+ id: "150XitJtlKDhTPRCyzv56",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -179.73008120217884,
+ y: 378.5900085788926,
+ strokeColor: "#0a11d3",
+ backgroundColor: "#fff",
+ width: 12.846057046979809,
+ height: 13.941904362416096,
+ seed: 526271345,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 212,
+ versionNonce: 158547423,
+ isDeleted: false,
+ id: "cmwAR3NBl1VqvSorrQN2W",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -179.73008120217884,
+ y: 411.8508097533892,
+ strokeColor: "#0a11d3",
+ backgroundColor: "#fff",
+ width: 12.846057046979809,
+ height: 13.941904362416096,
+ seed: 243707217,
+ groupIds: ["N2YAi9nU-wlRb0rDaDZoe"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ ],
+ [
+ {
+ type: "diamond",
+ version: 659,
+ versionNonce: 1294871039,
+ isDeleted: false,
+ id: "aDDArXRjZugwyEawdhCeZ",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -109.55894395256101,
+ y: 381.22641397493356,
+ strokeColor: "#c92a2a",
+ backgroundColor: "#fd8888",
+ width: 112.64736525303451,
+ height: 36.77344700318558,
+ seed: 511870335,
+ groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "diamond",
+ version: 700,
+ versionNonce: 60864881,
+ isDeleted: false,
+ id: "Hzx8zkeyDs3YicO2Tdv6G",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -109.55894395256101,
+ y: 372.354634046675,
+ strokeColor: "#c92a2a",
+ backgroundColor: "#fd8888",
+ width: 112.64736525303451,
+ height: 36.77344700318558,
+ seed: 1283079231,
+ groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "diamond",
+ version: 780,
+ versionNonce: 251040287,
+ isDeleted: false,
+ id: "PNzYhT295VNCT5EXmqvmw",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -109.55894395256101,
+ y: 359.72407445196296,
+ strokeColor: "#c92a2a",
+ backgroundColor: "#fd8888",
+ width: 112.64736525303451,
+ height: 36.77344700318558,
+ seed: 996251633,
+ groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "diamond",
+ version: 822,
+ versionNonce: 1862951761,
+ isDeleted: false,
+ id: "jiMMAhQF3__7bF-obgXc0",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -109.55894395256101,
+ y: 347.1924021546656,
+ strokeColor: "#c92a2a",
+ backgroundColor: "#fd8888",
+ width: 112.64736525303451,
+ height: 36.77344700318558,
+ seed: 1764842481,
+ groupIds: ["M6ByXuSmtHCr3RtPPKJQh"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ ],
+ [
+ {
+ type: "line",
+ version: 4766,
+ versionNonce: 2003030321,
+ isDeleted: false,
+ id: "BXfdLRoPYZ9MIumzzoA9-",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 1.5707963267948957,
+ x: -471.6208001976387,
+ y: 520.7681448415112,
+ strokeColor: "#087f5b",
+ backgroundColor: "#40c057",
+ width: 52.317507746132115,
+ height: 154.56722543646003,
+ seed: 1424381745,
+ groupIds: ["HSrtfEf-CssQTf160Fb6R"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [-0.24755378372925183, -40.169554027464216],
+ [-0.07503751055611152, 76.6515171914404],
+ [-0.23948042713317108, 89.95108885873196],
+ [2.446913573036335, 95.69766931810295],
+ [11.802146636255692, 100.56113713047068],
+ [27.615140546177496, 102.07554835500338],
+ [42.72341054254274, 99.65756899883291],
+ [50.75054563137204, 93.87501510096598],
+ [51.88266441510958, 89.00026150397161],
+ [52.04166639997853, 78.29287333983132],
+ [51.916868330459295, -30.36891819848148],
+ [51.635533423123285, -40.63545540065934],
+ [48.27622163143906, -46.37349057843314],
+ [41.202227904674494, -49.69665692879073],
+ [25.081551986374073, -52.49167708145666],
+ [12.15685839679867, -50.825000270901],
+ [1.9916746648394732, -45.171835889467935],
+ [-0.2758413461535838, -40.23974757720194],
+ [-0.24755378372925183, -40.169554027464216],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 2405,
+ versionNonce: 2120341087,
+ isDeleted: false,
+ id: "TYsYe2VvJ60T_yKa3kyOw",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 1.5707963267948957,
+ x: -496.3957643857249,
+ y: 541.7241190920508,
+ strokeColor: "#087f5b",
+ backgroundColor: "transparent",
+ width: 50.7174766392476,
+ height: 12.698053371678215,
+ seed: 726657713,
+ groupIds: ["HSrtfEf-CssQTf160Fb6R"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, -2.0205717204386002],
+ [1.3361877396713384, 3.0410845646550486],
+ [7.098613049589299, 7.287767671898479],
+ [14.766422451441104, 9.859533283467512],
+ [26.779003528407447, 10.093886705011586],
+ [40.79727342221974, 8.456559589697127],
+ [48.98410145879092, 2.500000505196364],
+ [50.7174766392476, -2.6041666666666288],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 2538,
+ versionNonce: 1913946897,
+ isDeleted: false,
+ id: "VIuxhGjvYUBniitomEkKm",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 1.5707963267948957,
+ x: -450.969983237283,
+ y: 542.1789894334747,
+ strokeColor: "#087f5b",
+ backgroundColor: "transparent",
+ width: 50.57247907260371,
+ height: 10.178760037658167,
+ seed: 1977326481,
+ groupIds: ["HSrtfEf-CssQTf160Fb6R"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, -2.136356936862347],
+ [1.332367676378171, 1.9210669226078037],
+ [7.078318632616268, 5.325208253515953],
+ [14.724206326638113, 7.386735659885842],
+ [26.70244431044034, 7.574593370991538],
+ [40.68063699304561, 6.262111896696538],
+ [48.84405948536458, 1.4873339211608216],
+ [50.57247907260371, -2.6041666666666288],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "ellipse",
+ version: 5503,
+ versionNonce: 1236644479,
+ isDeleted: false,
+ id: "1acGiqpJjntE3sr1JVnBP",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 1.5707963267948957,
+ x: -404.36521010516793,
+ y: 534.1894365757241,
+ strokeColor: "#087f5b",
+ backgroundColor: "#fff",
+ width: 51.27812853552538,
+ height: 22.797152568995934,
+ seed: 1774660383,
+ groupIds: ["HSrtfEf-CssQTf160Fb6R"],
+ strokeSharpness: "sharp",
+ boundElementIds: ["bxuMGTzXLn7H-uBCptINx"],
+ },
+ ],
+ [
+ {
+ type: "rectangle",
+ version: 4270,
+ versionNonce: 309922463,
+ isDeleted: false,
+ id: "SqGRpNqls7OV1QB2Eq-0m",
+ fillStyle: "solid",
+ strokeWidth: 2,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -393.3000561423187,
+ y: 338.9742643666818,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 70.67858069123133,
+ height: 107.25081879410921,
+ seed: 371096063,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "sharp",
+ boundElementIds: [
+ "CFu0B4Mw_1wC1Hbgx8Fs0",
+ "XIl_NhaFtRO00pX5Pq6VU",
+ "EndiSTFlx1AT7vcBVjgve",
+ ],
+ },
+ {
+ type: "rectangle",
+ version: 4319,
+ versionNonce: 896119505,
+ isDeleted: false,
+ id: "fayss6b_GPh6LK1x4iX-q",
+ fillStyle: "solid",
+ strokeWidth: 2,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -400.8474891780329,
+ y: 331.95417508096745,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 70.67858069123133,
+ height: 107.25081879410921,
+ seed: 685932433,
+ groupIds: ["0RJwA-yKP5dqk5oMiSeot", "9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "sharp",
+ boundElementIds: [
+ "CFu0B4Mw_1wC1Hbgx8Fs0",
+ "XIl_NhaFtRO00pX5Pq6VU",
+ "EndiSTFlx1AT7vcBVjgve",
+ ],
+ },
+ {
+ type: "rectangle",
+ version: 4417,
+ versionNonce: 1968987839,
+ isDeleted: false,
+ id: "HgAnv2rwYhUpLiJiZAXv-",
+ fillStyle: "solid",
+ strokeWidth: 2,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -410.24257846374826,
+ y: 323.7002688309677,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 70.67858069123133,
+ height: 107.25081879410921,
+ seed: 58634943,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "sharp",
+ boundElementIds: [
+ "CFu0B4Mw_1wC1Hbgx8Fs0",
+ "XIl_NhaFtRO00pX5Pq6VU",
+ "EndiSTFlx1AT7vcBVjgve",
+ ],
+ },
+ {
+ type: "draw",
+ version: 3541,
+ versionNonce: 1680683185,
+ isDeleted: false,
+ id: "12aO-Bs9HdALZN_-tuQTr",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -398.2561518768373,
+ y: 371.84603609547054,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 46.57983585730082,
+ height: 3.249953844290203,
+ seed: 1673003743,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0.6014697828497827],
+ [40.42449133807562, 0.7588628355182573],
+ [46.57983585730082, -2.491091008771946],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "draw",
+ version: 3567,
+ versionNonce: 620768991,
+ isDeleted: false,
+ id: "Ck_Y0EVPh_fsY0qoRnGiD",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -396.400899638823,
+ y: 340.9822185794818,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 45.567415680676426,
+ height: 2.8032978840147194,
+ seed: 1821527807,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0],
+ [16.832548902953302, -2.8032978840147194],
+ [45.567415680676426, -0.3275477042019195],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "draw",
+ version: 3592,
+ versionNonce: 1300624017,
+ isDeleted: false,
+ id: "a_7IZapEuD918VW1P8Ss_",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -396.4774991551924,
+ y: 408.37659284983897,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 48.33668263438425,
+ height: 4.280657518731036,
+ seed: 1485707039,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0],
+ [26.41225578429045, -0.2552319773002338],
+ [37.62000339651456, 2.3153712935189787],
+ [48.33668263438425, -1.9652862252120569],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "draw",
+ version: 3629,
+ versionNonce: 737475327,
+ isDeleted: false,
+ id: "8io6FVNdFOLsQ266W8Lni",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -399.6615463367227,
+ y: 419.61974125811776,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 54.40694982784246,
+ height: 2.9096445412231735,
+ seed: 1042012991,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0],
+ [10.166093050596771, -1.166642430373031],
+ [16.130660965377448, -0.8422655250909383],
+ [46.26079588567538, 0.6125567455206506],
+ [54.40694982784246, -2.297087795702523],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "draw",
+ version: 3594,
+ versionNonce: 1982560369,
+ isDeleted: false,
+ id: "LJI5kY6tg7UFAjPV3fKL-",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -399.3767034411569,
+ y: 356.042820132743,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 46.92865289294453,
+ height: 2.4757501798128,
+ seed: 295443295,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0],
+ [18.193786115221407, -0.5912874140789839],
+ [46.92865289294453, 1.884462765733816],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "draw",
+ version: 3609,
+ versionNonce: 1857766175,
+ isDeleted: false,
+ id: "zCrZOHW-q8YWKLw6ltKxX",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -399.26921524500654,
+ y: 390.5261491685826,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 46.92865289294453,
+ height: 2.4757501798128,
+ seed: 1734301567,
+ groupIds: ["9ppmKFUbA4iKjt8FaDFox"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ points: [
+ [0, 0],
+ [8.093938105125233, 1.4279702913643746],
+ [18.193786115221407, -0.5912874140789839],
+ [46.92865289294453, 1.884462765733816],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ ],
+ [
+ {
+ type: "rectangle",
+ version: 676,
+ versionNonce: 1841530687,
+ isDeleted: false,
+ id: "XOD3vRhtoLWoxC9wF9Sk8",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -593.9896997899341,
+ y: 343.9798351106279,
+ strokeColor: "#000000",
+ backgroundColor: "transparent",
+ width: 127.88383573213892,
+ height: 76.53703389977764,
+ seed: 106569279,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "line",
+ version: 462,
+ versionNonce: 1737150513,
+ isDeleted: false,
+ id: "WBkTga1PjKzYK-tcGjnjZ",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -595.0652975408293,
+ y: 354.6963695028721,
+ strokeColor: "#000000",
+ backgroundColor: "transparent",
+ width: 128.84193229844433,
+ height: 0,
+ seed: 73916127,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, 0],
+ [128.84193229844433, 0],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "ellipse",
+ version: 282,
+ versionNonce: 1198409567,
+ isDeleted: false,
+ id: "FHX0ZsIzUUfYPJqrZ8Lso",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 100,
+ angle: 0,
+ x: -589.5016643209792,
+ y: 348.2514049106367,
+ strokeColor: "#000000",
+ backgroundColor: "#fa5252",
+ width: 5.001953125,
+ height: 5.001953125,
+ seed: 387857791,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 327,
+ versionNonce: 1661182481,
+ isDeleted: false,
+ id: "ugVRR0f_uDOjrllO10yAs",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 100,
+ angle: 0,
+ x: -579.2389690084792,
+ y: 348.2514049106367,
+ strokeColor: "#000000",
+ backgroundColor: "#fab005",
+ width: 5.001953125,
+ height: 5.001953125,
+ seed: 1486370207,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 385,
+ versionNonce: 2047607679,
+ isDeleted: false,
+ id: "SBzNA0Sn-ou4QGxotj0SB",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 100,
+ angle: 0,
+ x: -568.525552542133,
+ y: 348.7021260644829,
+ strokeColor: "#000000",
+ backgroundColor: "#40c057",
+ width: 5.001953125,
+ height: 5.001953125,
+ seed: 610150847,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 664,
+ versionNonce: 2135373809,
+ isDeleted: false,
+ id: "VKcfbELTVlyJ90m0bGsj0",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 90,
+ angle: 0,
+ x: -552.4984915525058,
+ y: 364.75449494249875,
+ strokeColor: "#000000",
+ backgroundColor: "#04aaf7",
+ width: 42.72020253937572,
+ height: 42.72020253937572,
+ seed: 144280593,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "draw",
+ version: 1281,
+ versionNonce: 1708997535,
+ isDeleted: false,
+ id: "zWrJVrKnkF5K8iXNxi9Aa",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 100,
+ angle: 0,
+ x: -530.327851842306,
+ y: 378.9357912947449,
+ strokeColor: "#087f5b",
+ backgroundColor: "#40c057",
+ width: 28.226201983883442,
+ height: 24.44112284281997,
+ seed: 29167967,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [4.907524351775825, 2.043055712211473],
+ [3.0769604829149455, 1.6284171290602836],
+ [-2.66472604008681, -4.228569719133945],
+ [-6.450168189798415, -2.304577297733668],
+ [-7.704241049212052, 4.416384506147983],
+ [-6.361372181234263, 8.783101300254884],
+ [-12.516984713388897, 10.9291595737194],
+ [-12.295677738198286, 15.686226498407976],
+ [-7.473371426945252, 15.393030178104425],
+ [-3.787654025313423, 11.5207568827343],
+ [1.2873793872375165, 19.910682356036197],
+ [4.492232250183542, 20.212553123686025],
+ [1.1302787567009416, 6.843494873631317],
+ [6.294108177816019, 6.390688722156585],
+ [8.070028349098962, 7.910451897221202],
+ [14.143675334886687, 7.910451897221202],
+ [15.709217270494545, 2.6780252579576427],
+ [9.128749989671498, 3.1533849725326517],
+ [10.393751588600717, -3.7167773257046695],
+ [7.380151667177483, -3.30213874255348],
+ [4.669824267311791, 1.1200945145694894],
+ [4.907524351775825, 2.043055712211473],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 701,
+ versionNonce: 1583157713,
+ isDeleted: false,
+ id: "LX6kTl9A8K36ld2MEV4tI",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 90,
+ angle: 0,
+ x: -551.4394290784783,
+ y: 385.71736850567976,
+ strokeColor: "#000000",
+ backgroundColor: "#99bcff",
+ width: 42.095115772272244,
+ height: 0,
+ seed: 1443027377,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, 0],
+ [42.095115772272244, 0],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "line",
+ version: 2908,
+ versionNonce: 252866495,
+ isDeleted: false,
+ id: "SHmV_QtcwxIE-peI_QOX1",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 90,
+ angle: 0,
+ x: -546.3441000487039,
+ y: 372.6245229061568,
+ strokeColor: "#000000",
+ backgroundColor: "#99bcff",
+ width: 29.31860660384862,
+ height: 5.711199931375845,
+ seed: 244310513,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, -2.341683327443203],
+ [0.7724193963150375, -0.06510358900749044],
+ [4.103544916365185, 1.84492589414448],
+ [8.536129150893453, 3.0016281808630056],
+ [15.480325949120388, 3.1070332647092163],
+ [23.583965316012858, 2.3706131055211244],
+ [28.316582284417855, -0.3084668090492442],
+ [29.31860660384862, -2.6041666666666288],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ {
+ type: "ellipse",
+ version: 725,
+ versionNonce: 1969008561,
+ isDeleted: false,
+ id: "PKRg6SqIetkWIgRqBAnDY",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 90,
+ angle: 0,
+ x: -538.2701841247845,
+ y: 363.37196531290607,
+ strokeColor: "#000000",
+ backgroundColor: "transparent",
+ width: 15.528434353116108,
+ height: 44.82230388130942,
+ seed: 683572113,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "line",
+ version: 3113,
+ versionNonce: 533471199,
+ isDeleted: false,
+ id: "HrelUAgvfxi_4v8MyL_iT",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 0,
+ opacity: 90,
+ angle: 0,
+ x: -544.828148539078,
+ y: 402.0199316371545,
+ strokeColor: "#000000",
+ backgroundColor: "#99bcff",
+ width: 29.31860660384862,
+ height: 5.896061363392446,
+ seed: 318798801,
+ groupIds: ["TC0RSM64Cxmu17MlE12-o"],
+ strokeSharpness: "round",
+ boundElementIds: [],
+ startBinding: null,
+ endBinding: null,
+ points: [
+ [0, 0],
+ [4.103544916365185, -4.322122351104391],
+ [8.536129150893453, -5.516265043290966],
+ [15.480325949120388, -5.625081903117008],
+ [23.583965316012858, -4.8648251269605955],
+ [28.316582284417855, -2.0990281379671547],
+ [29.31860660384862, 0.2709794602754383],
+ ],
+ lastCommittedPoint: null,
+ startArrowhead: null,
+ endArrowhead: null,
+ },
+ ],
+ [
+ {
+ type: "rectangle",
+ version: 685,
+ versionNonce: 706399231,
+ isDeleted: false,
+ id: "dba8s5bDYEnF20oGn2a8b",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -715.1043446306466,
+ y: 330.4231266309418,
+ strokeColor: "#000000",
+ backgroundColor: "#ced4da",
+ width: 70.81644178885557,
+ height: 108.30428902193904,
+ seed: 1914896753,
+ groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "rectangle",
+ version: 835,
+ versionNonce: 851916657,
+ isDeleted: false,
+ id: "3HxCT4mFZF-jJ6m9pyOCt",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -706.996640540555,
+ y: 338.68030798133873,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 55.801163535143246,
+ height: 82.83278895375764,
+ seed: 1306468145,
+ groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "ellipse",
+ version: 881,
+ versionNonce: 704574495,
+ isDeleted: false,
+ id: "xX9mcMHy_0Bn-D0UAMyCc",
+ fillStyle: "solid",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -684.8099707762028,
+ y: 425.0579911039235,
+ strokeColor: "#000000",
+ backgroundColor: "#fff",
+ width: 11.427824006438863,
+ height: 11.427824006438863,
+ seed: 93422161,
+ groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "rectangle",
+ version: 528,
+ versionNonce: 816914769,
+ isDeleted: false,
+ id: "h60d2h6UPYkopTlW_XEs4",
+ fillStyle: "cross-hatch",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -698.7169501405845,
+ y: 349.2244646574789,
+ strokeColor: "#000000",
+ backgroundColor: "#fab005",
+ width: 39.2417827352022,
+ height: 19.889460471185775,
+ seed: 11646495,
+ groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ {
+ type: "rectangle",
+ version: 570,
+ versionNonce: 1198069823,
+ isDeleted: false,
+ id: "bZbx28BjXM33JV1UezMcH",
+ fillStyle: "cross-hatch",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ angle: 0,
+ x: -698.7169501405845,
+ y: 384.7822247024333,
+ strokeColor: "#000000",
+ backgroundColor: "#fab005",
+ width: 39.2417827352022,
+ height: 19.889460471185775,
+ seed: 291717649,
+ groupIds: ["GMZ-NW9lG7c1AtfBInZ0n"],
+ strokeSharpness: "sharp",
+ boundElementIds: [],
+ },
+ ],
+];
+
+const initialData = {
+ libraryItems,
+};
+export default initialData;
diff --git a/dev-docs/src/pages/index.js b/dev-docs/src/pages/index.js
deleted file mode 100644
index 9437c6c71..000000000
--- a/dev-docs/src/pages/index.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from "react";
-import clsx from "clsx";
-import Layout from "@theme/Layout";
-import Link from "@docusaurus/Link";
-import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
-import styles from "./index.module.css";
-import HomepageFeatures from "@site/src/components/Homepage";
-
-function HomepageHeader() {
- const { siteConfig } = useDocusaurusContext();
- return (
-
-
-
{siteConfig.title}
-
{siteConfig.tagline}
-
-
- Get started
-
-
-
-
- );
-}
-
-export default function Home() {
- const { siteConfig } = useDocusaurusContext();
- return (
-
-
-
-
-
-
- );
-}
diff --git a/dev-docs/src/pages/index.tsx b/dev-docs/src/pages/index.tsx
index 9437c6c71..f35c2e4a7 100644
--- a/dev-docs/src/pages/index.tsx
+++ b/dev-docs/src/pages/index.tsx
@@ -14,10 +14,7 @@ function HomepageHeader() {
{siteConfig.title}
{siteConfig.tagline}
-
+
Get started
@@ -27,12 +24,8 @@ function HomepageHeader() {
}
export default function Home() {
- const { siteConfig } = useDocusaurusContext();
return (
-
+
diff --git a/dev-docs/src/theme/MDXComponents.js b/dev-docs/src/theme/MDXComponents.js
new file mode 100644
index 000000000..35234301f
--- /dev/null
+++ b/dev-docs/src/theme/MDXComponents.js
@@ -0,0 +1,11 @@
+// Import the original mapper
+import MDXComponents from "@theme-original/MDXComponents";
+import Highlight from "@site/src/components/Highlight";
+
+export default {
+ // Re-use the default mapping
+ ...MDXComponents,
+ // Map the "highlight" tag to our component!
+ // `Highlight` will receive all props that were passed to `highlight` in MDX
+ highlight: Highlight,
+};
diff --git a/dev-docs/src/theme/ReactLiveScope/index.js b/dev-docs/src/theme/ReactLiveScope/index.js
new file mode 100644
index 000000000..a282ad6f0
--- /dev/null
+++ b/dev-docs/src/theme/ReactLiveScope/index.js
@@ -0,0 +1,29 @@
+import React from "react";
+import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
+import initialData from "@site/src/initialData";
+import { useColorMode } from "@docusaurus/theme-common";
+
+let ExcalidrawComp = {};
+if (ExecutionEnvironment.canUseDOM) {
+ ExcalidrawComp = require("@excalidraw/excalidraw");
+}
+const Excalidraw = React.forwardRef((props, ref) => {
+ const { colorMode } = useColorMode();
+ return ;
+});
+// Add react-live imports you need here
+const ExcalidrawScope = {
+ React,
+ ...React,
+ Excalidraw,
+ Footer: ExcalidrawComp.Footer,
+ useDevice: ExcalidrawComp.useDevice,
+ MainMenu: ExcalidrawComp.MainMenu,
+ WelcomeScreen: ExcalidrawComp.WelcomeScreen,
+ LiveCollaborationTrigger: ExcalidrawComp.LiveCollaborationTrigger,
+ Sidebar: ExcalidrawComp.Sidebar,
+ exportToCanvas: ExcalidrawComp.exportToCanvas,
+ initialData,
+};
+
+export default ExcalidrawScope;
diff --git a/src/packages/excalidraw/example/public/doremon.png b/dev-docs/static/img/doremon.png
similarity index 100%
rename from src/packages/excalidraw/example/public/doremon.png
rename to dev-docs/static/img/doremon.png
diff --git a/dev-docs/static/img/og-image-sm.png b/dev-docs/static/img/og-image-sm.png
new file mode 100644
index 000000000..5e88dba05
Binary files /dev/null and b/dev-docs/static/img/og-image-sm.png differ
diff --git a/dev-docs/static/img/og-image.png b/dev-docs/static/img/og-image.png
new file mode 100644
index 000000000..bf8b449a6
Binary files /dev/null and b/dev-docs/static/img/og-image.png differ
diff --git a/src/packages/excalidraw/example/public/pika.jpeg b/dev-docs/static/img/pika.jpeg
similarity index 100%
rename from src/packages/excalidraw/example/public/pika.jpeg
rename to dev-docs/static/img/pika.jpeg
diff --git a/dev-docs/static/img/welcome-screen-overview.png b/dev-docs/static/img/welcome-screen-overview.png
new file mode 100644
index 000000000..106776162
Binary files /dev/null and b/dev-docs/static/img/welcome-screen-overview.png differ
diff --git a/dev-docs/yarn.lock b/dev-docs/yarn.lock
index 1cf4b7358..5aaa9689f 100644
--- a/dev-docs/yarn.lock
+++ b/dev-docs/yarn.lock
@@ -1191,10 +1191,10 @@
"@docsearch/css" "3.1.1"
algoliasearch "^4.0.0"
-"@docusaurus/core@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-rc.1.tgz#828d93d241171565d8947a9ab404091e04759141"
- integrity sha512-b9FX0Z+EddfQ6wAiNh+Wx4fysKfcvEcWJrZ5USROn3C+EVU5P4luaa8mwWK//O+hTwD9ur7/A44IZ/tWCTAoLQ==
+"@docusaurus/core@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.2.0.tgz#64c9ee31502c23b93c869f8188f73afaf5fd4867"
+ integrity sha512-Vd6XOluKQqzG12fEs9prJgDtyn6DPok9vmUWDR2E6/nV5Fl9SVkhEQOBxwObjk3kQh7OY7vguFaLh0jqdApWsA==
dependencies:
"@babel/core" "^7.18.6"
"@babel/generator" "^7.18.7"
@@ -1206,13 +1206,13 @@
"@babel/runtime" "^7.18.6"
"@babel/runtime-corejs3" "^7.18.6"
"@babel/traverse" "^7.18.8"
- "@docusaurus/cssnano-preset" "2.0.0-rc.1"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
+ "@docusaurus/cssnano-preset" "2.2.0"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/mdx-loader" "2.2.0"
"@docusaurus/react-loadable" "5.5.2"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-common" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-common" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
"@slorber/static-site-generator-webpack-plugin" "^4.0.7"
"@svgr/webpack" "^6.2.1"
autoprefixer "^10.4.7"
@@ -1268,33 +1268,33 @@
webpack-merge "^5.8.0"
webpackbar "^5.0.2"
-"@docusaurus/cssnano-preset@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-rc.1.tgz#76bbd7f6912779a0667f8f2fd8fc1a05618a6148"
- integrity sha512-9/KmQvF+eTlMqUqG6UcXbRgxbGed/8bQInXuKEs+95/jI6jO/3xSzuRwuHHHP0naUvSVWjnNI9jngPrQerXE5w==
+"@docusaurus/cssnano-preset@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.2.0.tgz#fc05044659051ae74ab4482afcf4a9936e81d523"
+ integrity sha512-mAAwCo4n66TMWBH1kXnHVZsakW9VAXJzTO4yZukuL3ro4F+JtkMwKfh42EG75K/J/YIFQG5I/Bzy0UH/hFxaTg==
dependencies:
cssnano-preset-advanced "^5.3.8"
postcss "^8.4.14"
postcss-sort-media-queries "^4.2.1"
tslib "^2.4.0"
-"@docusaurus/logger@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-rc.1.tgz#db95e9b15bc243695830a5b791c0eff705ef1b54"
- integrity sha512-daa3g+SXuO9K60PVMiSUmDEK9Vro+Ed7i7uF8CH6QQJLcNZy/zJc0Xz62eH7ip1x77fmeb6Rg4Us1TqTFc9AbQ==
+"@docusaurus/logger@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.2.0.tgz#ea2f7feda7b8675485933b87f06d9c976d17423f"
+ integrity sha512-DF3j1cA5y2nNsu/vk8AG7xwpZu6f5MKkPPMaaIbgXLnWGfm6+wkOeW7kNrxnM95YOhKUkJUophX69nGUnLsm0A==
dependencies:
chalk "^4.1.2"
tslib "^2.4.0"
-"@docusaurus/mdx-loader@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-rc.1.tgz#e78d7d416aacc289f2427c5ccdb9145820acb0cb"
- integrity sha512-8Fg0c/ceu39knmr7w0dutm7gq3YxKYCqWVS2cB/cPATzChCCNH/AGLfBT6sz/Z4tjVXE+NyREq2pfOFvkhjVXg==
+"@docusaurus/mdx-loader@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.2.0.tgz#fd558f429e5d9403d284bd4214e54d9768b041a0"
+ integrity sha512-X2bzo3T0jW0VhUU+XdQofcEeozXOTmKQMvc8tUnWRdTnCvj4XEcBVdC3g+/jftceluiwSTNRAX4VBOJdNt18jA==
dependencies:
"@babel/parser" "^7.18.8"
"@babel/traverse" "^7.18.8"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
"@mdx-js/mdx" "^1.6.22"
escape-html "^1.0.3"
file-loader "^6.2.0"
@@ -1323,18 +1323,32 @@
react-helmet-async "*"
react-loadable "npm:@docusaurus/react-loadable@5.5.2"
-"@docusaurus/plugin-content-blog@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-rc.1.tgz#8ae5d5ec2da08c583a057bf2754a5b9278b3eb08"
- integrity sha512-BVVrAGZujpjS/0rarY2o24rlylRRh2NZuM65kg0JNkkViF79SeEHsepog7IuHyoqGWPm1N/I7LpEp7k+gowZzQ==
+"@docusaurus/module-type-aliases@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.2.0.tgz#1e23e54a1bbb6fde1961e4fa395b1b69f4803ba5"
+ integrity sha512-wDGW4IHKoOr9YuJgy7uYuKWrDrSpsUSDHLZnWQYM9fN7D5EpSmYHjFruUpKWVyxLpD/Wh0rW8hYZwdjJIQUQCQ==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-common" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/react-loadable" "5.5.2"
+ "@docusaurus/types" "2.2.0"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router-config" "*"
+ "@types/react-router-dom" "*"
+ react-helmet-async "*"
+ react-loadable "npm:@docusaurus/react-loadable@5.5.2"
+
+"@docusaurus/plugin-content-blog@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.2.0.tgz#dc55982e76771f4e678ac10e26d10e1da2011dc1"
+ integrity sha512-0mWBinEh0a5J2+8ZJXJXbrCk1tSTNf7Nm4tYAl5h2/xx+PvH/Bnu0V+7mMljYm/1QlDYALNIIaT/JcoZQFUN3w==
+ dependencies:
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/mdx-loader" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-common" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
cheerio "^1.0.0-rc.12"
feed "^4.2.2"
fs-extra "^10.1.0"
@@ -1345,18 +1359,18 @@
utility-types "^3.10.0"
webpack "^5.73.0"
-"@docusaurus/plugin-content-docs@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-rc.1.tgz#2dda88166bf21b0eeb3821ef748059b20c8c49f7"
- integrity sha512-Yk5Hu6uaw3tRplzJnbDygwRhmZ3PCzEXD4SJpBA6cPC73ylfqOEh6qhiU+BWhMTtDXNhY+athk5Kycfk3DW1aQ==
+"@docusaurus/plugin-content-docs@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.2.0.tgz#0fcb85226fcdb80dc1e2d4a36ef442a650dcc84d"
+ integrity sha512-BOazBR0XjzsHE+2K1wpNxz5QZmrJgmm3+0Re0EVPYFGW8qndCWGNtXW/0lGKhecVPML8yyFeAmnUCIs7xM2wPw==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
- "@docusaurus/module-type-aliases" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/mdx-loader" "2.2.0"
+ "@docusaurus/module-type-aliases" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
"@types/react-router-config" "^5.0.6"
combine-promises "^1.1.0"
fs-extra "^10.1.0"
@@ -1367,84 +1381,84 @@
utility-types "^3.10.0"
webpack "^5.73.0"
-"@docusaurus/plugin-content-pages@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-rc.1.tgz#2be82f53d6c77e6aa66787726c30dc60b210e6f8"
- integrity sha512-FdO79WC5hfWDQu3/CTFLRQzTNc0e5n+HNzavm2MNkSzGV08BFJ6RAkbPbtra5CWef+6iXZav6D/tzv2jDPvLzA==
+"@docusaurus/plugin-content-pages@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.2.0.tgz#e3f40408787bbe229545dd50595f87e1393bc3ae"
+ integrity sha512-+OTK3FQHk5WMvdelz8v19PbEbx+CNT6VSpx7nVOvMNs5yJCKvmqBJBQ2ZSxROxhVDYn+CZOlmyrC56NSXzHf6g==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/mdx-loader" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
fs-extra "^10.1.0"
tslib "^2.4.0"
webpack "^5.73.0"
-"@docusaurus/plugin-debug@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-rc.1.tgz#73c06ad08d66810941e456d50b07be008f5235cb"
- integrity sha512-aOsyYrPMbnsyqHwsVZ+0frrMRtnYqm4eaJpG4sC/6LYAJ07IDRQ9j3GOku2dKr5GsFK1Vx7VlE6ZLwe0MaGstg==
+"@docusaurus/plugin-debug@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.2.0.tgz#b38741d2c492f405fee01ee0ef2e0029cedb689a"
+ integrity sha512-p9vOep8+7OVl6r/NREEYxf4HMAjV8JMYJ7Bos5fCFO0Wyi9AZEo0sCTliRd7R8+dlJXZEgcngSdxAUo/Q+CJow==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
fs-extra "^10.1.0"
react-json-view "^1.21.3"
tslib "^2.4.0"
-"@docusaurus/plugin-google-analytics@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-rc.1.tgz#0136cc7534573ca56e023178ec2bda5c1e89ce71"
- integrity sha512-f+G8z5OJWfg5QqWDLIdcN2SDoK5J5Gg8HMrqCI6Pfl+rxPb5I1niA+/UkAM+kMCpnekvhSt5AWz2fgkRenkPLA==
+"@docusaurus/plugin-google-analytics@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.2.0.tgz#63c7137eff5a1208d2059fea04b5207c037d7954"
+ integrity sha512-+eZVVxVeEnV5nVQJdey9ZsfyEVMls6VyWTIj8SmX0k5EbqGvnIfET+J2pYEuKQnDIHxy+syRMoRM6AHXdHYGIg==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
tslib "^2.4.0"
-"@docusaurus/plugin-google-gtag@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-rc.1.tgz#61698fdc41a4ace912fb8f6c834efd288edad3c0"
- integrity sha512-yE1Et9hhhX9qMRnMJzpNq0854qIYiSEc2dZaXNk537HN7Q0rKkr/YONUHz2iqNYwPX2hGOY4LdpTxlMP88uVhA==
+"@docusaurus/plugin-google-gtag@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.2.0.tgz#7b086d169ac5fe9a88aca10ab0fd2bf00c6c6b12"
+ integrity sha512-6SOgczP/dYdkqUMGTRqgxAS1eTp6MnJDAQMy8VCF1QKbWZmlkx4agHDexihqmYyCujTYHqDAhm1hV26EET54NQ==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
tslib "^2.4.0"
-"@docusaurus/plugin-sitemap@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-rc.1.tgz#0b638e774b253d90e9f2d11663e961250f557bc4"
- integrity sha512-5JmbNpssUF03odFM4ArvIsrO9bv7HnAJ0VtefXhh0WBpaFs8NgI3rTkCTFimvtRQjDR9U2bh23fXz2vjQQz6oA==
+"@docusaurus/plugin-sitemap@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.2.0.tgz#876da60937886032d63143253d420db6a4b34773"
+ integrity sha512-0jAmyRDN/aI265CbWZNZuQpFqiZuo+5otk2MylU9iVrz/4J7gSc+ZJ9cy4EHrEsW7PV8s1w18hIEsmcA1YgkKg==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-common" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-common" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
fs-extra "^10.1.0"
sitemap "^7.1.1"
tslib "^2.4.0"
-"@docusaurus/preset-classic@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-rc.1.tgz#5e5b1cf80b3dd4e2c3f824c78a111f105858d853"
- integrity sha512-5jjTVZkhArjyoNHwCI9x4PSG0zPmBJILjZLVrxPcHpm/K0ltkYcp6J3GxYpf5EbMuOh5+yCWM63cSshGcNOo3Q==
+"@docusaurus/preset-classic@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.2.0.tgz#bece5a043eeb74430f7c6c7510000b9c43669eb7"
+ integrity sha512-yKIWPGNx7BT8v2wjFIWvYrS+nvN04W+UameSFf8lEiJk6pss0kL6SG2MRvyULiI3BDxH+tj6qe02ncpSPGwumg==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/plugin-content-blog" "2.0.0-rc.1"
- "@docusaurus/plugin-content-docs" "2.0.0-rc.1"
- "@docusaurus/plugin-content-pages" "2.0.0-rc.1"
- "@docusaurus/plugin-debug" "2.0.0-rc.1"
- "@docusaurus/plugin-google-analytics" "2.0.0-rc.1"
- "@docusaurus/plugin-google-gtag" "2.0.0-rc.1"
- "@docusaurus/plugin-sitemap" "2.0.0-rc.1"
- "@docusaurus/theme-classic" "2.0.0-rc.1"
- "@docusaurus/theme-common" "2.0.0-rc.1"
- "@docusaurus/theme-search-algolia" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/plugin-content-blog" "2.2.0"
+ "@docusaurus/plugin-content-docs" "2.2.0"
+ "@docusaurus/plugin-content-pages" "2.2.0"
+ "@docusaurus/plugin-debug" "2.2.0"
+ "@docusaurus/plugin-google-analytics" "2.2.0"
+ "@docusaurus/plugin-google-gtag" "2.2.0"
+ "@docusaurus/plugin-sitemap" "2.2.0"
+ "@docusaurus/theme-classic" "2.2.0"
+ "@docusaurus/theme-common" "2.2.0"
+ "@docusaurus/theme-search-algolia" "2.2.0"
+ "@docusaurus/types" "2.2.0"
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
version "5.5.2"
@@ -1454,23 +1468,23 @@
"@types/react" "*"
prop-types "^15.6.2"
-"@docusaurus/theme-classic@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-rc.1.tgz#4ab30745e6b03b0f277837debae786a0a83aee6a"
- integrity sha512-qNiz7ieeq3AC+V8TbW6S63pWLJph1CbzWDDPTqxDLHgA8VQaNaSmJM8S92pH+yKALRb9u14ogjjYYc75Nj2JmQ==
+"@docusaurus/theme-classic@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.2.0.tgz#a048bb1bc077dee74b28bec25f4b84b481863742"
+ integrity sha512-kjbg/qJPwZ6H1CU/i9d4l/LcFgnuzeiGgMQlt6yPqKo0SOJIBMPuz7Rnu3r/WWbZFPi//o8acclacOzmXdUUEg==
dependencies:
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
- "@docusaurus/module-type-aliases" "2.0.0-rc.1"
- "@docusaurus/plugin-content-blog" "2.0.0-rc.1"
- "@docusaurus/plugin-content-docs" "2.0.0-rc.1"
- "@docusaurus/plugin-content-pages" "2.0.0-rc.1"
- "@docusaurus/theme-common" "2.0.0-rc.1"
- "@docusaurus/theme-translations" "2.0.0-rc.1"
- "@docusaurus/types" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-common" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/mdx-loader" "2.2.0"
+ "@docusaurus/module-type-aliases" "2.2.0"
+ "@docusaurus/plugin-content-blog" "2.2.0"
+ "@docusaurus/plugin-content-docs" "2.2.0"
+ "@docusaurus/plugin-content-pages" "2.2.0"
+ "@docusaurus/theme-common" "2.2.0"
+ "@docusaurus/theme-translations" "2.2.0"
+ "@docusaurus/types" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-common" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
"@mdx-js/react" "^1.6.22"
clsx "^1.2.1"
copy-text-to-clipboard "^3.0.1"
@@ -1485,17 +1499,17 @@
tslib "^2.4.0"
utility-types "^3.10.0"
-"@docusaurus/theme-common@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-rc.1.tgz#ea5d9732a16b03b488555e50107161bfa2abad98"
- integrity sha512-1r9ZLKD9SeoCYVzWzcdR79Dia4ANlrlRjNl6uzETOEybjK6FF7yEa9Yra8EJcOCbi3coyYz5xFh/r1YHFTFHug==
+"@docusaurus/theme-common@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.2.0.tgz#2303498d80448aafdd588b597ce9d6f4cfa930e4"
+ integrity sha512-R8BnDjYoN90DCL75gP7qYQfSjyitXuP9TdzgsKDmSFPNyrdE3twtPNa2dIN+h+p/pr+PagfxwWbd6dn722A1Dw==
dependencies:
- "@docusaurus/mdx-loader" "2.0.0-rc.1"
- "@docusaurus/module-type-aliases" "2.0.0-rc.1"
- "@docusaurus/plugin-content-blog" "2.0.0-rc.1"
- "@docusaurus/plugin-content-docs" "2.0.0-rc.1"
- "@docusaurus/plugin-content-pages" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
+ "@docusaurus/mdx-loader" "2.2.0"
+ "@docusaurus/module-type-aliases" "2.2.0"
+ "@docusaurus/plugin-content-blog" "2.2.0"
+ "@docusaurus/plugin-content-docs" "2.2.0"
+ "@docusaurus/plugin-content-pages" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router-config" "*"
@@ -1505,19 +1519,34 @@
tslib "^2.4.0"
utility-types "^3.10.0"
-"@docusaurus/theme-search-algolia@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-rc.1.tgz#e78c0aeaea6a3717ae3a6ecd75a8652bd7c8e974"
- integrity sha512-H5yq6V/B4qo6GZrDKMbeSpk3T9e9K2MliDzLonRu0w3QHW9orVGe0c/lZvRbGlDZjnsOo7XGddhXXIDWGwnpaA==
+"@docusaurus/theme-live-codeblock@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-2.2.0.tgz#a507e496a1a74d261beee30ad072e4341310809a"
+ integrity sha512-4XRFxfZGcyqmbLmNbnbZ2ZOsoY7FYCJUZKsYW5yzhZYjmjGg7lkdJH5trt9otUoKBsZopBpPWvcDZwCu1SENYg==
+ dependencies:
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/theme-common" "2.2.0"
+ "@docusaurus/theme-translations" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
+ "@philpl/buble" "^0.19.7"
+ clsx "^1.2.1"
+ fs-extra "^10.1.0"
+ react-live "2.2.3"
+ tslib "^2.4.0"
+
+"@docusaurus/theme-search-algolia@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.2.0.tgz#77fd9f7a600917e6024fe3ac7fb6cfdf2ce84737"
+ integrity sha512-2h38B0tqlxgR2FZ9LpAkGrpDWVdXZ7vltfmTdX+4RsDs3A7khiNsmZB+x/x6sA4+G2V2CvrsPMlsYBy5X+cY1w==
dependencies:
"@docsearch/react" "^3.1.1"
- "@docusaurus/core" "2.0.0-rc.1"
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/plugin-content-docs" "2.0.0-rc.1"
- "@docusaurus/theme-common" "2.0.0-rc.1"
- "@docusaurus/theme-translations" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
- "@docusaurus/utils-validation" "2.0.0-rc.1"
+ "@docusaurus/core" "2.2.0"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/plugin-content-docs" "2.2.0"
+ "@docusaurus/theme-common" "2.2.0"
+ "@docusaurus/theme-translations" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
+ "@docusaurus/utils-validation" "2.2.0"
algoliasearch "^4.13.1"
algoliasearch-helper "^3.10.0"
clsx "^1.2.1"
@@ -1527,10 +1556,10 @@
tslib "^2.4.0"
utility-types "^3.10.0"
-"@docusaurus/theme-translations@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-rc.1.tgz#bd647f78c741ee7f6c6d2cbbd3e3f282ef2f89ad"
- integrity sha512-JLhNdlnbQhxVQzOnLyiCaTzKFa1lpVrM3nCrkGQKscoG2rY6ARGYMgMN2DkoH6hm7TflQ8+PE1S5MzzASeLs4Q==
+"@docusaurus/theme-translations@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.2.0.tgz#5fbd4693679806f80c26eeae1381e1f2c23d83e7"
+ integrity sha512-3T140AG11OjJrtKlY4pMZ5BzbGRDjNs2co5hJ6uYJG1bVWlhcaFGqkaZ5lCgKflaNHD7UHBHU9Ec5f69jTdd6w==
dependencies:
fs-extra "^10.1.0"
tslib "^2.4.0"
@@ -1549,30 +1578,44 @@
webpack "^5.73.0"
webpack-merge "^5.8.0"
-"@docusaurus/utils-common@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-rc.1.tgz#3e233a28794325d5d9d3af3f7b1c22b59aa8b847"
- integrity sha512-+iZICpeFPZJ9oGJXuG92WTWee6WRnVx5BdzlcfuKf/f5KQX8PvwXR2tDME78FGGhShB8zr+vjuNEXuLvXT7j2A==
+"@docusaurus/types@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.2.0.tgz#02c577a4041ab7d058a3c214ccb13647e21a9857"
+ integrity sha512-b6xxyoexfbRNRI8gjblzVOnLr4peCJhGbYGPpJ3LFqpi5nsFfoK4mmDLvWdeah0B7gmJeXabN7nQkFoqeSdmOw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ commander "^5.1.0"
+ joi "^17.6.0"
+ react-helmet-async "^1.3.0"
+ utility-types "^3.10.0"
+ webpack "^5.73.0"
+ webpack-merge "^5.8.0"
+
+"@docusaurus/utils-common@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.2.0.tgz#a401c1b93a8697dd566baf6ac64f0fdff1641a78"
+ integrity sha512-qebnerHp+cyovdUseDQyYFvMW1n1nv61zGe5JJfoNQUnjKuApch3IVsz+/lZ9a38pId8kqehC1Ao2bW/s0ntDA==
dependencies:
tslib "^2.4.0"
-"@docusaurus/utils-validation@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-rc.1.tgz#dded12f036cda8a54a19e01694b35859fe0cf1d5"
- integrity sha512-lj36gm9Ksu4tt/EUeLDWoMbXe3sfBxeIPIUUdqYcBYkF/rpQkh+uL/dncjNGiw6uvBOqXhOfsFVP045HtgShVw==
+"@docusaurus/utils-validation@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.2.0.tgz#04d4d103137ad0145883971d3aa497f4a1315f25"
+ integrity sha512-I1hcsG3yoCkasOL5qQAYAfnmVoLei7apugT6m4crQjmDGxq+UkiRrq55UqmDDyZlac/6ax/JC0p+usZ6W4nVyg==
dependencies:
- "@docusaurus/logger" "2.0.0-rc.1"
- "@docusaurus/utils" "2.0.0-rc.1"
+ "@docusaurus/logger" "2.2.0"
+ "@docusaurus/utils" "2.2.0"
joi "^17.6.0"
js-yaml "^4.1.0"
tslib "^2.4.0"
-"@docusaurus/utils@2.0.0-rc.1":
- version "2.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-rc.1.tgz#53584b800df9e13864d5ef1a76aa7655a90ec86e"
- integrity sha512-ym9I1OwIYbKs1LGaUajaA/vDG8VweJj/6YoZjHp+eDQHhTRIrHXiYoGDqorafRhftKwnA1EnyomuXpNd9bq8Gg==
+"@docusaurus/utils@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.2.0.tgz#3d6f9b7a69168d5c92d371bf21c556a4f50d1da6"
+ integrity sha512-oNk3cjvx7Tt1Lgh/aeZAmFpGV2pDr5nHKrBVx6hTkzGhrnMuQqLt6UPlQjdYQ3QHXwyF/ZtZMO1D5Pfi0lu7SA==
dependencies:
- "@docusaurus/logger" "2.0.0-rc.1"
+ "@docusaurus/logger" "2.2.0"
"@svgr/webpack" "^6.2.1"
file-loader "^6.2.0"
fs-extra "^10.1.0"
@@ -1588,6 +1631,11 @@
url-loader "^4.1.1"
webpack "^5.73.0"
+"@excalidraw/excalidraw@0.14.2":
+ version "0.14.2"
+ resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz#150cb4b7a1bf0d11cd64295936c930e7e0db8375"
+ integrity sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==
+
"@hapi/hoek@^9.0.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
@@ -1709,6 +1757,21 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@philpl/buble@^0.19.7":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@philpl/buble/-/buble-0.19.7.tgz#27231e6391393793b64bc1c982fc7b593198b893"
+ integrity sha512-wKTA2DxAGEW+QffRQvOhRQ0VBiYU2h2p8Yc1oBNlqSKws48/8faxqKNIuub0q4iuyTuLwtB8EkwiKwhlfV1PBA==
+ dependencies:
+ acorn "^6.1.1"
+ acorn-class-fields "^0.2.1"
+ acorn-dynamic-import "^4.0.0"
+ acorn-jsx "^5.0.1"
+ chalk "^2.4.2"
+ magic-string "^0.25.2"
+ minimist "^1.2.0"
+ os-homedir "^1.0.1"
+ regexpu-core "^4.5.4"
+
"@polka/url@^1.0.0-next.20":
version "1.0.0-next.21"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
@@ -2242,16 +2305,36 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
+acorn-class-fields@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-0.2.1.tgz#748058bceeb0ef25164bbc671993984083f5a085"
+ integrity sha512-US/kqTe0H8M4LN9izoL+eykVAitE68YMuYZ3sHn3i1fjniqR7oQ3SPvuMK/VT1kjOQHrx5Q88b90TtOKgAv2hQ==
+
+acorn-dynamic-import@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948"
+ integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==
+
acorn-import-assertions@^1.7.6:
version "1.8.0"
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
+acorn-jsx@^5.0.1:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
acorn-walk@^8.0.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+acorn@^6.1.1:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
+ integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
+
acorn@^8.0.4, acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
@@ -2618,6 +2701,18 @@ browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4
node-releases "^2.0.6"
update-browserslist-db "^1.0.4"
+buble@0.19.6:
+ version "0.19.6"
+ resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.6.tgz#915909b6bd5b11ee03b1c885ec914a8b974d34d3"
+ integrity sha512-9kViM6nJA1Q548Jrd06x0geh+BG2ru2+RMDkIHHgJY/8AcyCs34lTHwra9BX7YdPrZXd5aarkpr/SY8bmPgPdg==
+ dependencies:
+ chalk "^2.4.1"
+ magic-string "^0.25.1"
+ minimist "^1.2.0"
+ os-homedir "^1.0.1"
+ regexpu-core "^4.2.0"
+ vlq "^1.0.0"
+
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -2697,7 +2792,7 @@ ccount@^1.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==
-chalk@^2.0.0:
+chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -2754,7 +2849,7 @@ cheerio@^1.0.0-rc.12:
parse5 "^7.0.0"
parse5-htmlparser2-tree-adapter "^7.0.0"
-chokidar@^3.4.2, chokidar@^3.5.3:
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2, chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@@ -2905,6 +3000,16 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
+component-props@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/component-props/-/component-props-1.1.1.tgz#f9b7df9b9927b6e6d97c9bd272aa867670f34944"
+ integrity sha512-69pIRJs9fCCHRqCz3390YF2LV1Lu6iEMZ5zuVqqUn+G20V9BNXlMs0cWawWeW9g4Ynmg29JmkG6R7/lUJoGd1Q==
+
+component-xor@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/component-xor/-/component-xor-0.0.4.tgz#c55d83ccc1b94cd5089a4e93fa7891c7263e59aa"
+ integrity sha512-ZIt6sla8gfo+AFVRZoZOertcnD5LJaY2T9CKE2j13NJxQt/mUafD69Bl7/Y4AnpI2LGjiXH7cOfJDx/n2G9edA==
+
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@@ -3016,6 +3121,11 @@ core-js-pure@^3.20.2:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.0.tgz#10eeb90dbf0d670a6b22b081aecc7deb2faec7e1"
integrity sha512-uzMmW8cRh7uYw4JQtzqvGWRyC2T5+4zipQLQdi2FmiRqP83k3d6F3stv2iAlNhOs6cXN401FCD5TL0vvleuHgA==
+core-js@^2.4.1:
+ version "2.6.12"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
+ integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
+
core-js@^3.23.3:
version "3.24.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.24.0.tgz#4928d4e99c593a234eb1a1f9abd3122b04d3ac57"
@@ -3345,6 +3455,13 @@ dns-packet@^5.2.2:
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
+docusaurus-plugin-sass@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/docusaurus-plugin-sass/-/docusaurus-plugin-sass-0.2.3.tgz#5b61f7e560d236cfc1531ed497ac32fc166fc5e2"
+ integrity sha512-FbaE06K8NF8SPUYTwiG+83/jkXrwHJ/Afjqz3SUIGon6QvFwSSoKOcoxGQmUBnjTOk+deUONDx8jNWsegFJcBQ==
+ dependencies:
+ sass-loader "^10.1.1"
+
dom-converter@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@@ -3352,6 +3469,14 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
+dom-iterator@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-1.0.0.tgz#9c09899846ec41c2d257adc4d6015e4759ef05ad"
+ integrity sha512-7dsMOQI07EMU98gQM8NSB3GsAiIeBYIPKpnxR3c9xOvdvBjChAcOM0iJ222I3p5xyiZO9e5oggkNaCusuTdYig==
+ dependencies:
+ component-props "1.1.1"
+ component-xor "0.0.4"
+
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
@@ -4340,6 +4465,11 @@ immer@^9.0.7:
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.15.tgz#0b9169e5b1d22137aba7d43f8a81a495dd1b62dc"
integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==
+immutable@^4.0.0:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.2.tgz#2da9ff4384a4330c36d4d1bc88e90f9e0b0ccd16"
+ integrity sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==
+
import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -4692,9 +4822,9 @@ json-schema-traverse@^1.0.0:
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.1.0"
@@ -4722,7 +4852,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-klona@^2.0.5:
+klona@^2.0.4, klona@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
@@ -4851,6 +4981,13 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+magic-string@^0.25.1, magic-string@^0.25.2:
+ version "0.25.9"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
+ integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
+ dependencies:
+ sourcemap-codec "^1.4.8"
+
make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -5208,6 +5345,11 @@ opener@^1.5.2:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+os-homedir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+ integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
+
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
@@ -5737,7 +5879,7 @@ pretty-time@^1.1.0:
resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e"
integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==
-prism-react-renderer@^1.3.5:
+prism-react-renderer@^1.0.1, prism-react-renderer@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085"
integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==
@@ -5767,7 +5909,7 @@ prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -5967,6 +6109,19 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+react-live@2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/react-live/-/react-live-2.2.3.tgz#260f99194213799f0005e473e7a4154c699d6a7c"
+ integrity sha512-tpKruvfytNETuzO3o1mrQUj180GVrq35IE8F5gH1NJVPt4szYCx83/dOSCOyjgRhhc3gQvl0pQ3k/CjOjwJkKQ==
+ dependencies:
+ buble "0.19.6"
+ core-js "^2.4.1"
+ dom-iterator "^1.0.0"
+ prism-react-renderer "^1.0.1"
+ prop-types "^15.5.8"
+ react-simple-code-editor "^0.10.0"
+ unescape "^1.0.1"
+
react-loadable-ssr-addon-v5-slorber@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883"
@@ -6010,6 +6165,11 @@ react-router@5.3.3, react-router@^5.3.3:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
+react-simple-code-editor@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz#73e7ac550a928069715482aeb33ccba36efe2373"
+ integrity sha512-bL5W5mAxSW6+cLwqqVWY47Silqgy2DKDTR4hDBrLrUqC5BXc29YVx17l2IZk5v36VcDEq1Bszu2oHm1qBwKqBA==
+
react-textarea-autosize@^8.3.2:
version "8.3.4"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524"
@@ -6082,6 +6242,13 @@ regenerate-unicode-properties@^10.0.1:
dependencies:
regenerate "^1.4.2"
+regenerate-unicode-properties@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
+ integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==
+ dependencies:
+ regenerate "^1.4.2"
+
regenerate@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
@@ -6099,6 +6266,18 @@ regenerator-transform@^0.15.0:
dependencies:
"@babel/runtime" "^7.8.4"
+regexpu-core@^4.2.0, regexpu-core@^4.5.4:
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0"
+ integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==
+ dependencies:
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^9.0.0"
+ regjsgen "^0.5.2"
+ regjsparser "^0.7.0"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.0.0"
+
regexpu-core@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d"
@@ -6125,11 +6304,23 @@ registry-url@^5.0.0:
dependencies:
rc "^1.2.8"
+regjsgen@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+ integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
+
regjsgen@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==
+regjsparser@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968"
+ integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==
+ dependencies:
+ jsesc "~0.5.0"
+
regjsparser@^0.8.2:
version "0.8.4"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f"
@@ -6317,6 +6508,26 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+sass-loader@^10.1.1:
+ version "10.4.1"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.4.1.tgz#bea4e173ddf512c9d7f53e9ec686186146807cbf"
+ integrity sha512-aX/iJZTTpNUNx/OSYzo2KsjIUQHqvWsAhhUijFjAPdZTEhstjZI9zTNvkTTwsx+uNUJqUwOw5gacxQMx4hJxGQ==
+ dependencies:
+ klona "^2.0.4"
+ loader-utils "^2.0.0"
+ neo-async "^2.6.2"
+ schema-utils "^3.0.0"
+ semver "^7.3.2"
+
+sass@1.57.1:
+ version "1.57.1"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5"
+ integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -6594,7 +6805,7 @@ sort-css-media-queries@2.0.4:
resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.0.4.tgz#b2badfa519cb4a938acbc6d3aaa913d4949dc908"
integrity sha512-PAIsEK/XupCQwitjv7XxoMvYhT7EAfyzI3hsy/MyDgTvc+Ft55ctdkctJLOy6cQejaIC+zjpUL4djFVm2ivOOw==
-source-map-js@^1.0.2:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -6617,6 +6828,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+sourcemap-codec@^1.4.8:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
space-separated-tokens@^1.0.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
@@ -6947,6 +7163,13 @@ ua-parser-js@^0.7.30:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
+unescape@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/unescape/-/unescape-1.0.1.tgz#956e430f61cad8a4d57d82c518f5e6cc5d0dda96"
+ integrity sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==
+ dependencies:
+ extend-shallow "^2.0.1"
+
unherit@^1.0.4:
version "1.1.3"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"
@@ -7203,6 +7426,11 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
+vlq@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468"
+ integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==
+
wait-on@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e"
diff --git a/package.json b/package.json
index 60a831e14..a19b2fb89 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
]
},
"dependencies": {
+ "@dwelle/tunnel-rat": "0.1.1",
"@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5",
"@testing-library/jest-dom": "5.16.2",
@@ -31,6 +32,7 @@
"@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.29.1",
"clsx": "1.1.1",
+ "cross-env": "7.0.3",
"fake-indexeddb": "3.1.7",
"firebase": "8.3.3",
"i18next-browser-languagedetector": "6.1.4",
@@ -54,7 +56,20 @@
"roughjs": "4.5.2",
"sass": "1.51.0",
"socket.io-client": "2.3.1",
- "typescript": "4.5.5"
+ "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",
+ "workbox-core": "^6.5.4",
+ "workbox-expiration": "^6.5.4",
+ "workbox-google-analytics": "^6.5.4",
+ "workbox-navigation-preload": "^6.5.4",
+ "workbox-precaching": "^6.5.4",
+ "workbox-range-requests": "^6.5.4",
+ "workbox-routing": "^6.5.4",
+ "workbox-strategies": "^6.5.4",
+ "workbox-streams": "^6.5.4"
},
"devDependencies": {
"@excalidraw/eslint-config": "1.0.0",
@@ -67,6 +82,7 @@
"dotenv": "16.0.1",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "3.3.1",
+ "http-server": "14.1.1",
"husky": "7.0.4",
"jest-canvas-mock": "2.4.0",
"lint-staged": "12.3.7",
@@ -89,11 +105,10 @@
"private": true,
"scripts": {
"build-node": "node ./scripts/build-node.js",
- "build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
- "build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
+ "build:app:docker": "cross-env REACT_APP_DISABLE_SENTRY=true REACT_APP_DISABLE_TRACKING=true react-scripts build",
+ "build:app": "cross-env REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
"build:version": "node ./scripts/build-version.js",
- "build:prebuild": "node ./scripts/prebuild.js",
- "build": "yarn build:prebuild && yarn build:app && yarn build:version",
+ "build": "yarn build:app && yarn build:version",
"eject": "react-scripts eject",
"fix:code": "yarn test:code --fix",
"fix:other": "yarn prettier --write",
@@ -103,6 +118,7 @@
"prepare": "husky install",
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"start": "react-scripts start",
+ "start:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watchAll=false",
"test:app": "react-scripts test --passWithNoTests",
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
diff --git a/public/index.html b/public/index.html
index e065e6700..35640c0dc 100644
--- a/public/index.html
+++ b/public/index.html
@@ -146,7 +146,8 @@
// setting this so that libraries installation reuses this window tab.
window.name = "_excalidraw";
- <% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
+ <% if (process.env.REACT_APP_DISABLE_TRACKING !== 'true' &&
+ process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
-```
-
-For production use :point_down:
-
-```js
-
-```
-
-You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
-
-View Example
-
-```html
-
-
-
- Excalidraw in browser
-
-
-
-
-
-
-
-
-
-
Excalidraw Embed Example
-
-
-
-
-
-```
-
-```js
-/*eslint-disable */
-import "./styles.css";
-import InitialData from "./initialData";
-
-const App = () => {
- const excalidrawRef = React.useRef(null);
-
- const [viewModeEnabled, setViewModeEnabled] = React.useState(false);
- const [zenModeEnabled, setZenModeEnabled] = React.useState(false);
- const [gridModeEnabled, setGridModeEnabled] = React.useState(false);
-
- const updateScene = () => {
- const sceneData = {
- elements: [
- {
- type: "rectangle",
- version: 141,
- versionNonce: 361174001,
- isDeleted: false,
- id: "oDVXy8D6rom3H1-LLH2-f",
- fillStyle: "hachure",
- strokeWidth: 1,
- strokeStyle: "solid",
- roughness: 1,
- opacity: 100,
- angle: 0,
- x: 100.50390625,
- y: 93.67578125,
- strokeColor: "#c92a2a",
- backgroundColor: "transparent",
- width: 186.47265625,
- height: 141.9765625,
- seed: 1968410350,
- groupIds: [],
- },
- ],
- appState: {
- viewBackgroundColor: "#edf2ff",
- },
- };
- excalidrawRef.current.updateScene(sceneData);
- };
-
- return React.createElement(
- React.Fragment,
- null,
- React.createElement(
- "div",
- { className: "button-wrapper" },
- React.createElement(
- "button",
- {
- className: "update-scene",
- onClick: updateScene,
- },
- "Update Scene",
- ),
- React.createElement(
- "button",
- {
- className: "reset-scene",
- onClick: () => excalidrawRef.current.resetScene(),
- },
- "Reset Scene",
- ),
- React.createElement(
- "label",
- null,
- React.createElement("input", {
- type: "checkbox",
- checked: viewModeEnabled,
- onChange: () => setViewModeEnabled(!viewModeEnabled),
- }),
- "View mode",
- ),
- React.createElement(
- "label",
- null,
- React.createElement("input", {
- type: "checkbox",
- checked: zenModeEnabled,
- onChange: () => setZenModeEnabled(!zenModeEnabled),
- }),
- "Zen mode",
- ),
- React.createElement(
- "label",
- null,
- React.createElement("input", {
- type: "checkbox",
- checked: gridModeEnabled,
- onChange: () => setGridModeEnabled(!gridModeEnabled),
- }),
- "Grid mode",
- ),
- ),
- React.createElement(
- "div",
- {
- className: "excalidraw-wrapper",
- ref: excalidrawWrapperRef,
- },
- React.createElement(ExcalidrawLib.Excalidraw, {
- initialData: InitialData,
- onChange: (elements, state) =>
- console.log("Elements :", elements, "State : ", state),
- onPointerUpdate: (payload) => console.log(payload),
- onCollabButtonClick: () => window.alert("You clicked on collab button"),
- viewModeEnabled: viewModeEnabled,
- zenModeEnabled: zenModeEnabled,
- gridModeEnabled: gridModeEnabled,
- }),
- ),
- );
-};
-
-const excalidrawWrapper = document.getElementById("app");
-
-ReactDOM.render(React.createElement(App), excalidrawWrapper);
-```
-
-To view the full example visit :point_down:
-
-[](https://codesandbox.io/s/excalidraw-in-browser-tlqom?fontsize=14&hidenavigation=1&theme=dark)
-
-
-
-### Customizing styles
-
-Excalidraw is using CSS variables to style certain components. To override them, you should set your own on the `.excalidraw` and `.excalidraw.theme--dark` (for dark mode variables) selectors.
-
-Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:
-
-```css
-.your-app .excalidraw {
- --color-primary: red;
-}
-.your-app .excalidraw.theme--dark {
- --color-primary: pink;
-}
-```
-
-Most notably, you can customize the primary colors, by overriding these variables:
-
-- `--color-primary`
-- `--color-primary-darker`
-- `--color-primary-darkest`
-- `--color-primary-light`
-- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
-
-For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
-
-### Does this package support collaboration ?
-
-No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx).
-
-### Props
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| [`onChange`](#onChange) | Function | | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw elements and the current app state. |
-| [`initialData`](#initialData) | {elements?: ExcalidrawElement[] , appState?: AppState }
| null | The initial data with which app loads. |
-| [`ref`](#ref) | [`createRef`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs) | [`useRef`](https://reactjs.org/docs/hooks-reference.html#useref) | [`callbackRef`](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs) | { current: { readyPromise: resolvablePromise } }
| | Ref to be passed to Excalidraw |
-| [`onCollabButtonClick`](#onCollabButtonClick) | Function | | Callback to be triggered when the collab button is clicked |
-| [`isCollaborating`](#isCollaborating) | `boolean` | | This implies if the app is in collaboration mode |
-| [`onPointerUpdate`](#onPointerUpdate) | Function | | Callback triggered when mouse pointer is updated. |
-| [`langCode`](#langCode) | string | `en` | Language code string |
-| [`renderTopRightUI`](#renderTopRightUI) | Function | | Function that renders custom UI in top right corner |
-| [`renderFooter `](#renderFooter) | Function | | Function that renders custom UI footer |
-| [`renderCustomStats`](#renderCustomStats) | Function | | Function that can be used to render custom stats on the stats dialog. |
-| [`renderSIdebar`](#renderSIdebar) | Function | | Render function that renders custom sidebar. |
-| [`viewModeEnabled`](#viewModeEnabled) | boolean | | This implies if the app is in view mode. |
-| [`zenModeEnabled`](#zenModeEnabled) | boolean | | This implies if the zen mode is enabled |
-| [`gridModeEnabled`](#gridModeEnabled) | boolean | | This implies if the grid mode is enabled |
-| [`libraryReturnUrl`](#libraryReturnUrl) | string | | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
-| [`theme`](#theme) | [THEME.LIGHT](#THEME-1) | [THEME.DARK](#THEME-1) | [THEME.LIGHT](#THEME-1) | The theme of the Excalidraw component |
-| [`name`](#name) | string | | Name of the drawing |
-| [`UIOptions`](#UIOptions) | { canvasActions: CanvasActions }
| [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L129) | To customise UI options. Currently we support customising [`canvas actions`](#canvasActions) |
-| [`onPaste`](#onPaste) | (data: ClipboardData , event: ClipboardEvent | null) => boolean
| | Callback to be triggered if passed when the something is pasted in to the scene |
-| [`detectScroll`](#detectScroll) | boolean | true | Indicates whether to update the offsets when nearest ancestor is scrolled. |
-| [`handleKeyboardGlobally`](#handleKeyboardGlobally) | boolean | false | Indicates whether to bind the keyboard events to document. |
-| [`onLibraryChange`](#onLibraryChange) | (items: LibraryItems ) => void | Promise<any>
| | The callback if supplied is triggered when the library is updated and receives the library items. |
-| [`autoFocus`](#autoFocus) | boolean | false | Implies whether to focus the Excalidraw component on page load |
-| [`generateIdForFile`](#generateIdForFile) | `(file: File) => string | Promise` | Allows you to override `id` generation for files added on canvas |
-| [`onLinkOpen`](#onLinkOpen) | (element: NonDeletedExcalidrawElement , event: CustomEvent)
| | This prop if passed will be triggered when link of an element is clicked. |
-| [`onPointerDown`](#onPointerDown) | (activeTool: AppState["activeTool"] , pointerDownState: PointerDownState ) => void
| | This prop if passed gets triggered on pointer down evenets |
-| [`onScrollChange`](#onScrollChange) | (scrollX: number, scrollY: number)
| | This prop if passed gets triggered when scrolling the canvas. |
-
-### Dimensions of Excalidraw
-
-Excalidraw takes `100%` of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
-
-#### `onChange`
-
-Every time component updates, this callback if passed will get triggered and has the below signature.
-
-```js
-(excalidrawElements, appState, files) => void;
-```
-
-1.`excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106) in the scene.
-
-2.`appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79) of the scene.
-
-3. `files`: The [`BinaryFiles`]([BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
-
-Here you can try saving the data to your backend or local storage for example.
-
-#### `initialData`
-
-This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
-
-| Name | Type | Description |
-| --- | --- | --- |
-| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106) | The elements with which Excalidraw should be mounted. |
-| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79) | The App state with which Excalidraw should be mounted. |
-| `scrollToContent` | boolean | This attribute implies whether to scroll to the nearest element to center once Excalidraw is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
-| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> | This library items with which Excalidraw should be mounted. |
-| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | The files added to the scene. |
-
-```json
-{
- "elements": [
- {
- "type": "rectangle",
- "version": 141,
- "versionNonce": 361174001,
- "isDeleted": false,
- "id": "oDVXy8D6rom3H1-LLH2-f",
- "fillStyle": "hachure",
- "strokeWidth": 1,
- "strokeStyle": "solid",
- "roughness": 1,
- "opacity": 100,
- "angle": 0,
- "x": 100.50390625,
- "y": 93.67578125,
- "strokeColor": "#000000",
- "backgroundColor": "transparent",
- "width": 186.47265625,
- "height": 141.9765625,
- "seed": 1968410350,
- "groupIds": []
- }
- ],
- "appState": { "zenModeEnabled": true, "viewBackgroundColor": "#AFEEEE" }
-}
-```
-
-You might want to use this when you want to load excalidraw with some initial elements and app state.
-
-#### Storing custom data on Excalidraw elements
-
-Beyond attributes that Excalidraw elements already support, you can store custom data on each element in a `customData` object. The type of the attribute is [`Record`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L59) and is optional.
-
-You can use this to add any extra information you need to keep track of.
-
-You can add `customData` to elements when passing them as `initialData`, or using [`updateScene`](#updateScene)/[`updateLibrary`](#updateLibrary) afterwards.
-
-#### `ref`
-
-You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
-
-| API | Signature | Usage |
-| --- | --- | --- |
-| ready | `boolean` | This is set to true once Excalidraw is rendered |
-| readyPromise | [resolvablePromise](https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L317) | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readyPromise) |
-| [updateScene](#updateScene) | (scene: sceneData ) => void
| updates the scene with the sceneData |
-| [updateLibrary](#updateLibrary) | (opts ) => Promise<LibraryItems >
| updates the scene with the sceneData |
-| [addFiles](#addFiles) | (files: BinaryFileData ) => void
| add files data to the appState |
-| resetScene | `({ resetLoadingState: boolean }) => void` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
-| getSceneElementsIncludingDeleted | () => ExcalidrawElement[]
| Returns all the elements including the deleted in the scene |
-| getSceneElements | () => ExcalidrawElement[]
| Returns all the elements excluding the deleted in the scene |
-| getAppState | () => AppState
| Returns current appState |
-| history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
-| scrollToContent | (target?: ExcalidrawElement | ExcalidrawElement []) => void
| Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene. |
-| refresh | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You don't have to call this when the position is changed on page scroll or when the excalidraw container resizes (we handle that ourselves). For any other cases if the position of excalidraw is updated (example due to scroll on parent container and not page scroll) you should call this API. |
-| [importLibrary](#importlibrary) | (url: string, token?: string) => void
| Imports library from given URL |
-| [setToast](#setToast) | ({ message: string, closable?:boolean, duration?:number } | null) => void
| This API can be used to show the toast with custom message. |
-| [id](#id) | string | Unique ID for the excalidraw component. |
-| [getFiles](#getFiles) | () => files
| This API can be used to get the files present in the scene. It may contain files that aren't referenced by any element, so if you're persisting the files to a storage, you should compare them against stored elements. |
-| [setActiveTool](#setActiveTool) | (tool: { type: typeof SHAPES [number]["value"]| "eraser" } | { type: "custom"; customType: string }) => void
| This API can be used to set the active tool |
-| [setCursor](#setCursor) | (cursor: string) => void
| This API can be used to set customise the mouse cursor on the canvas |
-| [resetCursor](#resetCursor) | () => void
| This API can be used to reset to default mouse cursor on the canvas |
-| [toggleMenu](#toggleMenu) | (type: string, force?: boolean) => boolean
| Toggles specific menus on/off |
-
-#### `readyPromise`
-
-
-const excalidrawRef = { current: { readyPromise: resolvablePromise }}
-
-
-Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves. You can check the [example](https://codesandbox.io/s/eexcalidraw-resolvable-promise-d0qg3?file=/src/App.js) for the usage.
-
-### `updateScene`
-
-
-(scene: sceneData ) => void
-
-
-You can use this function to update the scene with the sceneData. It accepts the below attributes.
-
-| Name | Type | Description |
-| --- | --- | --- |
-| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L17) | The `elements` to be updated in the scene |
-| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L18) | The `appState` to be updated in the scene. |
-| `collaborators` | MapCollaborator> | The list of collaborators to be updated in the scene. |
-| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
-| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> | ((currentItems: [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) => [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)>) | The `libraryItems` to be update in the scene. |
-
-### `updateLibrary`
-
-
-(opts: {
- libraryItems: LibraryItemsSource ;
- merge?: boolean;
- prompt?: boolean;
- openLibraryMenu?: boolean;
- defaultStatus?: "unpublished" | "published";
-}) => Promise<LibraryItems >
-
-
-You can use this function to update the library. It accepts the below attributes.
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| `libraryItems` | | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L224) | The `libraryItems` to be replaced/merged with current library |
-| `merge` | boolean | `false` | Whether to merge with existing library items. |
-| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
-| `openLibraryMenu` | boolean | `false` | Whether to open the library menu before importing. |
-| `defaultStatus` | "unpublished" | "published"
| `"unpublished"` | Default library item's `status` if not present. |
-
-### `addFiles`
-
-(files: BinaryFileData ) => void
-
-Adds supplied files data to the `appState.files` cache on top of existing files present in the cache.
-
-#### `onCollabButtonClick`
-
-This callback is triggered when clicked on the collab button in excalidraw. If not supplied, the collab dialog button is not rendered.
-
-#### `isCollaborating`
-
-This prop indicates if the app is in collaboration mode.
-
-#### `onPointerUpdate`
-
-This callback is triggered when mouse pointer is updated.
-
-```js
-({ x, y }, button, pointersMap}) => void;
-```
-
-1.`{x, y}`: Pointer coordinates
-
-2.`button`: The position of the button. This will be one of `["down", "up"]`
-
-3.`pointersMap`: [`pointers map`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) of the scene
-
-```js
-(exportedElements, appState, canvas) => void
-```
-
-1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L87) which needs to be exported.
-2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79) of the scene.
-3. `canvas`: The `HTMLCanvasElement` of the scene.
-
-#### `langCode`
-
-Determines the language of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
-
-```js
-import { defaultLang, languages } from "@excalidraw/excalidraw";
-```
-
-| name | type |
-| --- | --- |
-| defaultLang | string |
-| languages | [Language[]](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L8) |
-
-#### `renderTopRightUI`
-
-
-(isMobile: boolean, appState: AppState ) => JSX | null
-
-
-A function returning JSX to render custom UI in the top right corner of the app.
-
-#### `renderFooter`
-
-
-(isMobile: boolean, appState: AppState ) => JSX | null
-
-
-A function returning JSX to render custom UI footer. For example, you can use this to render a language picker that was previously being rendered by Excalidraw itself (for now, you'll need to implement your own language picker).
-
-#### `renderCustomStats`
-
-A function that can be used to render custom stats (returns JSX) in the nerd stats dialog. For example you can use this prop to render the size of the elements in the storage.
-
-#### `renderSidebar`
-
-
-() => JSX | null
-
-
-Optional function that can render custom sidebar. This sidebar is the same that the library menu sidebar is using, and can be used for any purposes your app needs. The render function should return a `` instance — a component that is exported from the Excalidraw package. It accepts `children` which can be any content you like to render inside.
-
-The `` component takes these props (all are optional except `children`):
-
-| name | type | description |
-| --- | --- | --- |
-| className | string |
-| children | React.ReactNode | Content you want to render inside the sidebar. |
-| onClose | () => void | Invoked when the component is closed (by user, or the editor). No need to act on this event, as the editor manages the sidebar open state on its own. |
-| onDock | (isDocked: boolean) => void | Invoked when the user toggles the dock button. |
-| docked | boolean | Indicates whether the sidebar is docked. By default, the sidebar is undocked. If passed, the docking becomes controlled, and you are responsible for updating the `docked` state by listening on `onDock` callback. See [here](#dockedSidebarBreakpoint) for more info docking. |
-| dockable | boolean | Indicates whether the sidebar can be docked by user (=the dock button is shown). If `false`, you can still dock programmatically by passing `docked=true` |
-
-The sidebar will always include a header with close/dock buttons (when applicable).
-
-You can also add custom content to the header, by rendering `` as a child of the `` component. Note that the custom header will still include the default buttons.
-
-The `` component takes these props children (all are optional):
-
-| name | type | description |
-| --- | --- | --- |
-| className | string |
-| children | React.ReactNode | Content you want to render inside the sidebar header, sibling of the header buttons. |
-
-For example code, see the example [`App.tsx`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/example/App.tsx#L524) file.
-
-#### `viewModeEnabled`
-
-This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over `intialData.appState.viewModeEnabled`, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
-
-#### `zenModeEnabled`
-
-This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over `intialData.appState.zenModeEnabled`, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
-
-#### `gridModeEnabled`
-
-This prop indicates whether the shows the grid. When supplied, the value takes precedence over `intialData.appState.gridModeEnabled`, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
-
-#### `libraryReturnUrl`
-
-If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Defaults to `window.location.origin + window.location.pathname`. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
-
-#### `theme`
-
-This prop controls Excalidraw's theme. When supplied, the value takes precedence over `intialData.appState.theme`, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless `UIOptions.canvasActions.toggleTheme` is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onChange) callback).
-
-You can use [`THEME`](#THEME-1) to specify the theme.
-
-#### `name`
-
-This prop sets the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
-
-#### `UIOptions`
-
-This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasActions) and [`dockedSidebarBreakpoint`](dockedSidebarBreakpoint). It accepts the below parameters
-
-
-{ canvasActions: CanvasActions }
-
-
-##### canvasActions
-
-| Attribute | Type | Default | Description |
-| --- | --- | --- | --- |
-| `changeViewBackgroundColor` | boolean | true | Implies whether to show `Background color picker` |
-| `clearCanvas` | boolean | true | Implies whether to show `Clear canvas button` |
-| `export` | false | [exportOpts](#exportOpts) | { saveFileToDisk: true } | This prop allows to customize the UI inside the export dialog. By default it shows the "saveFileToDisk". If this prop is `false` the export button will not be rendered. For more details visit [`exportOpts`](#exportOpts). |
-| `loadScene` | boolean | true | Implies whether to show `Load button` |
-| `saveToActiveFile` | boolean | true | Implies whether to show `Save button` to save to current file |
-| `toggleTheme` | boolean | null | null | Implies whether to show `Theme toggle`. When defined as `boolean`, takes precedence over [`props.theme`](#theme) to show `Theme toggle` |
-| `saveAsImage` | boolean | true | Implies whether to show `Save as image button` |
-
-##### `dockedSidebarBreakpoint`
-
-This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L167). If the `width` of the `excalidraw` container exceeds `dockedSidebarBreakpoint`, the sidebar will be dockable. If user choses to dock the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
-
-
-
-#### `exportOpts`
-
-The below attributes can be set in `UIOptions.canvasActions.export` to customize the export dialog. If `UIOptions.canvasActions.export` is `false` the export button will not be rendered.
-
-| Attribute | Type | Default | Description |
-| --- | --- | --- | --- |
-| `saveFileToDisk` | boolean | true | Implies if save file to disk button should be shown |
-| `onExportToBackend` | (exportedElements: readonly NonDeletedExcalidrawElement[],appState: AppState,canvas: HTMLCanvasElement | null) => void | | This callback is triggered when the shareable-link button is clicked in the export dialog. The link button will only be shown if this callback is passed. |
-| `renderCustomUI` | (exportedElements: readonly NonDeletedExcalidrawElement[],appState: AppState,canvas: HTMLCanvasElement | null) => void | | This callback should be supplied if you want to render custom UI in the export dialog. |
-
-#### `onPaste`
-
-This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
-
-
-(data: ClipboardData , event: ClipboardEvent | null) => boolean
-
-
-This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
-
-In case you want to prevent the excalidraw paste action you must return `false`, it will stop the native excalidraw clipboard management flow (nothing will be pasted into the scene).
-
-#### `importLibrary`
-
-Imports library from given URL. You should call this on `hashchange`, passing the `addLibrary` value if you detect it as shown below. Optionally pass a CSRF `token` to skip prompting during installation (retrievable via `token` key from the url coming from [https://libraries.excalidraw.com](https://libraries.excalidraw.com/)).
-
-```js
-useEffect(() => {
- const onHashChange = () => {
- const hash = new URLSearchParams(window.location.hash.slice(1));
- const libraryUrl = hash.get("addLibrary");
- if (libraryUrl) {
- excalidrawRef.current.importLibrary(libraryUrl, hash.get("token"));
- }
- };
- window.addEventListener("hashchange", onHashChange, false);
- return () => {
- window.removeEventListener("hashchange", onHashChange);
- };
-}, []);
-```
-
-Try out the [Demo](#Demo) to see it in action.
-
-#### `setToast`
-
-This API can be used to show the toast with custom message.
-
-
-({ message: string,
- closable?:boolean,
- duration?:number } | null) => void
-
-
-| Attribute | type | Description |
-| --- | --- | --- |
-| message | string | The message to be shown on the toast. |
-| closable | boolean | Indicates whether to show the closable button on toast to dismiss the toast. |
-| duration | number | Determines the duration after which the toast should auto dismiss. To prevent autodimiss you can pass `Infinity`. |
-
-To dismiss an existing toast you can simple pass `null`
-
-```js
-setToast(null);
-```
-
-#### `setActiveTool`
-
-This API has the below signature. It sets the `tool` passed in param as the active tool.
-
-
-(tool: { type: typeof SHAPES [number]["value"] | "eraser" } | { type: "custom"; customType: string }) => void
-
-
-#### `setCursor`
-
-This API can be used to customise the mouse cursor on the canvas and has the below signature. It sets the mouse cursor to the cursor passed in param.
-
-
-(cursor: string) => void
-
-
-#### `toggleMenu`
-
-
-(type: "library" | "customSidebar", force?: boolean) => boolean
-
-
-This API can be used to toggle a specific menu (currently only the sidebars), and returns whether the menu was toggled on or off. If the `force` flag passed, it will force the menu to be toggled either on/off based on the boolean passed.
-
-This API is especially useful when you render a custom sidebar using [`renderSidebar`](#renderSidebar) prop, and you want to toggle it from your app based on a user action.
-
-#### `resetCursor`
-
-This API can be used to reset to default mouse cursor.
-
-#### `detectScroll`
-
-Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method).
-
-#### `handleKeyboardGlobally`
-
-Indicates whether to bind keyboard events to `document`. Disabled by default, meaning the keyboard events are bound to the Excalidraw component. This allows for multiple Excalidraw components to live on the same page, and ensures that Excalidraw keyboard handling doesn't collide with your app's (or the browser) when the component isn't focused.
-
-Enable this if you want Excalidraw to handle keyboard even if the component isn't focused (e.g. a user is interacting with the navbar, sidebar, or similar).
-
-#### `onLibraryChange`
-
-This callback if supplied will get triggered when the library is updated and has the below signature.
-
-
-(items: LibraryItems ) => void | Promise
-
-
-It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
-
-#### `id`
-
-The unique id of the excalidraw component. This can be used to identify the excalidraw component, for example importing the library items to the excalidraw component from where it was initiated when you have multiple excalidraw components rendered on the same page as shown in [multiple excalidraw demo](https://codesandbox.io/s/multiple-excalidraw-k1xx5).
-
-#### `autoFocus`
-
-This prop implies whether to focus the Excalidraw component on page load. Defaults to false.
-
-#### `generateIdForFile`
-
-Allows you to override `id` generation for files added on canvas (images). By default, an SHA-1 digest of the file is used.
-
-```
-(file: File) => string | Promise
-```
-
-#### `onLinkOpen`
-
-This prop if passed will be triggered when clicked on link. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
-
-```
-(element: ExcalidrawElement, event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
-```
-
-Example:
-
-```ts
-const history = useHistory();
-
-// open internal links using the app's router, but opens external links in
-// a new tab/window
-const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
- (element, event) => {
- const link = element.link;
- const { nativeEvent } = event.detail;
- const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
- const isNewWindow = nativeEvent.shiftKey;
- const isInternalLink =
- link.startsWith("/") || link.includes(window.location.origin);
- if (isInternalLink && !isNewTab && !isNewWindow) {
- history.push(link.replace(window.location.origin, ""));
- // signal that we're handling the redirect ourselves
- event.preventDefault();
- }
- },
- [history],
-);
-```
-
-#### `onPointerDown`
-
-This prop if passed will be triggered on pointer down events and has the below signature.
-
-
-(activeTool: AppState["activeTool"] , pointerDownState: PointerDownState ) => void
-
-
-#### `onScrollChange`
-
-This prop if passed will be triggered when canvas is scrolled and has the below signature.
-
-```ts
-(scrollX: number, scrollY: number) => void
-```
-
-### Restore utilities
-
-#### `restoreAppState`
-
-**_Signature_**
-
-
-restoreAppState(appState: ImportedDataState["appState"] , localAppState: Partial<AppState > | null): AppState
-
-
-**_How to use_**
-
-```js
-import { restoreAppState } from "@excalidraw/excalidraw";
-```
-
-This function will make sure all the keys have appropriate values in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79) and if any key is missing, it will be set to default value.
-
-When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of defaults. Use this as a way to not override user's defaults if you persist them. Required: supply `null`/`undefined` if not applicable.
-
-#### `restoreElements`
-
-**_Signature_**
-
-
-restoreElements(
- elements: ImportedDataState["elements"] ,
- localElements: ExcalidrawElement[] | null | undefined): ExcalidrawElement[] ,
- refreshDimensions: boolean
-)
-
-
-**_How to use_**
-
-```js
-import { restoreElements } from "@excalidraw/excalidraw";
-```
-
-This function will make sure all properties of element is correctly set and if any attribute is missing, it will be set to default value.
-
-When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. Use this when you import elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the updates.
-
-Parameter `refreshDimensions` indicates whether we should also recalculate text element dimensions. Defaults to `false`. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration.
-
-#### `restore`
-
-**_Signature_**
-
-
-restoreElements(
- data: ImportedDataState ,
- localAppState: Partial<AppState > | null | undefined,
- localElements: ExcalidrawElement[] | null | undefined): DataState
-)
-
-
-See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
-
-**_How to use_**
-
-```js
-import { restore } from "@excalidraw/excalidraw";
-```
-
-This function makes sure elements and state is set to appropriate values and set to default value if not present. It is a combination of [restoreElements](#restoreElements) and [restoreAppState](#restoreAppState).
-
-#### `restoreLibraryItems`
-
-**_Signature_**
-
-
-restoreLibraryItems(libraryItems: ImportedDataState["libraryItems"] , defaultStatus: "published" | "unpublished")
-
-
-**_How to use_**
-
-```js
-import { restoreLibraryItems } from "@excalidraw/excalidraw";
-
-restoreLibraryItems(libraryItems, "unpublished");
-```
-
-This function normalizes library items elements, adding missing values when needed.
-
-### Export utilities
-
-#### `exportToCanvas`
-
-**_Signature_**
-
-exportToCanvas({
- elements,
- appState
- getDimensions,
- files,
- exportPadding?: number;
-}: ExportOpts
-
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types) | | The elements to be exported to canvas |
-| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L12) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The app state of the scene |
-| getDimensions | `(width: number, height: number) => { width: number, height: number, scale?: number }` | undefined | A function which returns the `width`, `height`, and optionally `scale` (defaults `1`), with which canvas is to be exported. |
-| maxWidthOrHeight | `number` | undefined | The maximum width or height of the exported image. If provided, `getDimensions` is ignored. |
-| files | [BinaryFiles](The [`BinaryFiles`](<[BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64)>) | undefined | The files added to the scene. |
-| exportPadding | number | 10 | The padding to be added on canvas |
-
-**How to use**
-
-```js
-import { exportToCanvas } from "@excalidraw/excalidraw";
-```
-
-This function returns the canvas with the exported elements, appState and dimensions.
-
-#### `exportToBlob`
-
-**_Signature_**
-
-
-exportToBlob(
- opts: ExportOpts & {
- mimeType?: string,
- quality?: number,
- exportPadding?: number;
-})
-
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| opts | | | This param is passed to `exportToCanvas`. You can refer to [`exportToCanvas`](#exportToCanvas) |
-| mimeType | string | "image/png" | Indicates the image format |
-| quality | number | 0.92 | A value between 0 and 1 indicating the [image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#parameters). Applies only to `image/jpeg`/`image/webp` MIME types. |
-| exportPadding | number | 10 | The padding to be added on canvas |
-
-**How to use**
-
-```js
-import { exportToBlob } from "@excalidraw/excalidraw";
-```
-
-Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob). It internally uses [canvas.ToBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob).
-
-#### `exportToSvg`
-
-**_Signature_**
-
-
-exportToSvg({
- elements: ExcalidrawElement[] ,
- appState: AppState ,
- exportPadding?: number,
- metadata?: string,
- files?: BinaryFiles
-})
-
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L106) | | The elements to exported as svg |
-| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L79) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The app state of the scene |
-| exportPadding | number | 10 | The padding to be added on canvas |
-| files | [BinaryFiles](The [`BinaryFiles`](<[BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64)>) | undefined | The files added to the scene. |
-
-This function returns a promise which resolves to svg of the exported drawing.
-
-#### `exportToClipboard`
-
-**_Signature_**
-
-
-exportToClipboard(
- opts: ExportOpts & {
- mimeType?: string,
- quality?: number;
- type: 'png' | 'svg' |'json'
-})
-
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- | --- | --- |
-| opts | | | This param is same as the params passed to `exportToCanvas`. You can refer to [`exportToCanvas`](#exportToCanvas). |
-| mimeType | string | "image/png" | Indicates the image format, this will be used when exporting as `png`. |
-| quality | number | 0.92 | A value between 0 and 1 indicating the [image quality](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#parameters). Applies only to `image/jpeg`/`image/webp` MIME types. This will be used when exporting as `png`. |
-| type | 'png' | 'svg' | 'json' | | This determines the format to which the scene data should be exported. |
-
-**How to use**
-
-```js
-import { exportToClipboard } from "@excalidraw/excalidraw";
-```
-
-Copies the scene data in the specified format (determined by `type`) to clipboard.
-
-##### Additional attributes of appState for `export\*` APIs
-
-| Name | Type | Default | Description |
-| --- | --- | --- | --- |
-| exportBackground | boolean | true | Indicates whether background should be exported |
-| viewBackgroundColor | string | #fff | The default background color |
-| exportWithDarkMode | boolean | false | Indicates whether to export with dark mode |
-| exportEmbedScene | boolean | false | Indicates whether scene data should be embedded in svg/png. This will increase the image size. |
-
-### Extra API's
-
-#### `serializeAsJSON`
-
-**_Signature_**
-
-
-serializeAsJSON({
- elements: ExcalidrawElement[] ,
- appState: AppState ,
-}): string
-
-
-Takes the scene elements and state and returns a JSON string. Deleted `elements`as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L16) source for details).
-
-If you want to overwrite the source field in the JSON string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
-
-#### `serializeLibraryAsJSON`
-
-**_Signature_**
-
-
-serializeLibraryAsJSON({
- libraryItems: LibraryItems[] ,
-
-
-Takes the library items and returns a JSON string.
-
-If you want to overwrite the source field in the JSON string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
-
-#### `getSceneVersion`
-
-**How to use**
-
-
-import { getSceneVersion } from "@excalidraw/excalidraw";
-getSceneVersion(elements: ExcalidrawElement[] )
-
-
-This function returns the current scene version.
-
-#### `isInvisiblySmallElement`
-
-**_Signature_**
-
-
-isInvisiblySmallElement(element: ExcalidrawElement ): boolean
-
-
-**How to use**
-
-```js
-import { isInvisiblySmallElement } from "@excalidraw/excalidraw";
-```
-
-Returns `true` if element is invisibly small (e.g. width & height are zero).
-
-#### `loadLibraryFromBlob`
-
-```js
-import { loadLibraryFromBlob } from "@excalidraw/excalidraw";
-```
-
-**_Signature_**
-
-
-loadLibraryFromBlob(blob: Blob , defaultStatus: "published" | "unpublished")
-
-
-This function loads the library from the blob. Additonally takes `defaultStatus` param which sets the default status for library item if not present, defaults to `unpublished`.
-
-#### `loadFromBlob`
-
-**How to use**
-
-```js
-import { loadFromBlob } from "@excalidraw/excalidraw";
-
-const scene = await loadFromBlob(file, null, null);
-excalidrawAPI.updateScene(scene);
-```
-
-**Signature**
-
-
-loadFromBlob(
- blob: Blob ,
- localAppState: AppState | null,
- localElements: ExcalidrawElement[] | null,
- fileHandle?: FileSystemHandle | null
-) => Promise<RestoredDataState >
-
-
-This function loads the scene data from the blob (or file). If you pass `localAppState`, `localAppState` value will be preferred over the `appState` derived from `blob`. Throws if blob doesn't contain valid scene data.
-
-#### `loadSceneOrLibraryFromBlob`
-
-**How to use**
-
-```js
-import { loadSceneOrLibraryFromBlob, MIME_TYPES } from "@excalidraw/excalidraw";
-
-const contents = await loadSceneOrLibraryFromBlob(file, null, null);
-if (contents.type === MIME_TYPES.excalidraw) {
- excalidrawAPI.updateScene(contents.data);
-} else if (contents.type === MIME_TYPES.excalidrawlib) {
- excalidrawAPI.updateLibrary(contents.data);
-}
-```
-
-**Signature**
-
-
-loadSceneOrLibraryFromBlob(
- blob: Blob ,
- localAppState: AppState | null,
- localElements: ExcalidrawElement[] | null,
- fileHandle?: FileSystemHandle | null
-) => Promise<{ type: string, data: RestoredDataState | ImportedLibraryState }>
-
-
-This function loads either scene or library data from the supplied blob. If the blob contains scene data, and you pass `localAppState`, `localAppState` value will be preferred over the `appState` derived from `blob`. Throws if blob doesn't contain neither valid scene data or library data.
-
-#### `getFreeDrawSvgPath`
-
-**How to use**
-
-```js
-import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
-```
-
-**Signature**
-
-
-getFreeDrawSvgPath(element: ExcalidrawFreeDrawElement
-
-
-This function returns the free draw svg path for the element.
-
-#### `isLinearElement`
-
-**How to use**
-
-```js
-import { isLinearElement } from "@excalidraw/excalidraw";
-```
-
-**Signature**
-
-
-isLinearElement(elementType?: ExcalidrawElement ): boolean
-
-
-This function returns true if the element is linear type (`arrow` |`line`) else returns false.
-
-#### `getNonDeletedElements`
-
-**How to use**
-
-```js
-import { getNonDeletedElements } from "@excalidraw/excalidraw";
-```
-
-**Signature**
-
-
-getNonDeletedElements(elements: readonly ExcalidrawElement[] ): as readonly NonDeletedExcalidrawElement[]
-
-
-This function returns an array of deleted elements.
-
-#### `mergeLibraryItems`
-
-```js
-import { mergeLibraryItems } from "@excalidraw/excalidraw";
-```
-
-**_Signature_**
-
-
-mergeLibraryItems(localItems: LibraryItems , otherItems: LibraryItems ) => LibraryItems
-
-
-This function merges two `LibraryItems` arrays, where unique items from `otherItems` are sorted first in the returned array.
-
-#### `parseLibraryTokensFromUrl`
-
-**How to use**
-
-```js
-import { parseLibraryTokensFromUrl } from "@excalidraw/excalidraw";
-```
-
-**Signature**
-
-
-parseLibraryTokensFromUrl(): {
- libraryUrl: string;
- idToken: string | null;
-} | null
-
-
-Parses library parameters from URL if present (expects the `#addLibrary` hash key), and returns an object with the `libraryUrl` and `idToken`. Returns `null` if `#addLibrary` hash key not found.
-
-#### `useHandleLibrary`
-
-**How to use**
-
-```js
-import { useHandleLibrary } from "@excalidraw/excalidraw";
-
-export const App = () => {
- // ...
- useHandleLibrary({ excalidrawAPI });
-};
-```
-
-**Signature**
-
-
-useHandleLibrary(opts: {
- excalidrawAPI: ExcalidrawAPI ,
- getInitialLibraryItems?: () => LibraryItemsSource
-});
-
-
-A hook that automatically imports library from url if `#addLibrary` hash key exists on initial load, or when it changes during the editing session (e.g. when a user installs a new library), and handles initial library load if `getInitialLibraryItems` getter is supplied.
-
-In the future, we will be adding support for handling library persistence to browser storage (or elsewhere).
-
-#### `sceneCoordsToViewportCoords`
-
-```js
-import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
-```
-
-**_Signature_**
-
-
-sceneCoordsToViewportCoords({sceneX: number, sceneY: number}, appState: AppState ): {x: number, y: number}
-
-
-This function returns equivalent viewport coords for the provided scene coords in params.
-
-#### `viewportCoordsToSceneCoords`
-
-```js
-import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
-```
-
-**_Signature_**
-
-
-viewportCoordsToSceneCoords({clientX: number, clientY: number}, appState: AppState ): {x: number, y: number}
-
-
-This function returns equivalent scene coords for the provided viewport coords in params.
-
-### Exported constants
-
-#### `FONT_FAMILY`
-
-**How to use**
-
-```js
-import { FONT_FAMILY } from "@excalidraw/excalidraw";
-```
-
-`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
-
-| Font Family | Description |
-| ----------- | -------------------- |
-| Virgil | The handwritten font |
-| Helvetica | The Normal Font |
-| Cascadia | The Code Font |
-
-Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
-
-#### `THEME`
-
-**How to use**
-
-```js
-import { THEME } from "@excalidraw/excalidraw";
-```
-
-`THEME` contains all the themes supported by `Excalidraw` as explained below
-
-| Theme | Description |
-| ----- | --------------- |
-| LIGHT | The light theme |
-| DARK | The Dark theme |
-
-Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
-
-### `MIME_TYPES`
-
-**How to use **
-
-```js
-import { MIME_TYPES } from "@excalidraw/excalidraw";
-```
-
-[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L92) contains all the mime types supported by `Excalidraw`.
-
-## 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).
-
-### Development
-
-#### Install the dependencies
-
-```bash
-yarn
-```
-
-#### Start the server
-
-```bash
-yarn start
-```
-
-[http://localhost:3001](http://localhost:3001) will open in your default browser.
-
-The example is same as the [codesandbox example](https://ehlz3.csb.app/)
-
-#### Create a test release
-
-You can create a test release by posting the below comment in your pull request
-
-```
-@excalibot trigger release
-```
-
-Once the version is released `@excalibot` will post a comment with the release version.
-
-#### Creating a production release
-
-To release the next stable version follow the below steps
-
-```
-yarn prerelease version
-```
-
-You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
-
-The next step is to run the `release` script
-
-```
-yarn release
-```
-
-This will publish the package.
-
-Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
+Head over to the [docs](https://docs.excalidraw.com/docs/package/api)
diff --git a/src/packages/excalidraw/example/App.scss b/src/packages/excalidraw/example/App.scss
index 6115eef86..7f37540d8 100644
--- a/src/packages/excalidraw/example/App.scss
+++ b/src/packages/excalidraw/example/App.scss
@@ -66,10 +66,18 @@
button.custom-element {
width: 2rem;
height: 2rem;
- margin: 0.4rem;
- margin-left: -10px;
}
- .layer-ui__wrapper__footer-center {
- display: flex;
+
+ .custom-footer,
+ .custom-element {
+ padding: 0.1rem;
+ margin: 0 8px;
+ }
+ .layer-ui__wrapper__footer.App-menu_bottom {
+ align-items: stretch;
+ }
+ // till its merged in OSS
+ .App-toolbar-container .mobile-misc-tools-container {
+ position: absolute;
}
}
diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx
index ee555f37d..1f4a6c7fd 100644
--- a/src/packages/excalidraw/example/App.tsx
+++ b/src/packages/excalidraw/example/App.tsx
@@ -13,7 +13,7 @@ import {
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../../../utils";
-import { EVENT } from "../../../constants";
+import { EVENT, ROUNDNESS } from "../../../constants";
import { distance2d } from "../../../math";
import { fileOpen } from "../../../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils";
@@ -28,6 +28,8 @@ import {
} from "../../../types";
import { NonDeletedExcalidrawElement } from "../../../element/types";
import { ImportedLibraryData } from "../../../data/types";
+import CustomFooter from "./CustomFooter";
+import MobileFooter from "./MobileFooter";
declare global {
interface Window {
@@ -68,41 +70,23 @@ const {
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
+ Footer,
+ WelcomeScreen,
+ MainMenu,
+ LiveCollaborationTrigger,
} = window.ExcalidrawLib;
-const COMMENT_SVG = (
-
-
-
-);
const COMMENT_ICON_DIMENSION = 32;
const COMMENT_INPUT_HEIGHT = 50;
const COMMENT_INPUT_WIDTH = 150;
-const renderTopRightUI = () => {
- return (
- alert("This is dummy top right UI")}
- style={{ height: "2.5rem" }}
- >
- {" "}
- Click me{" "}
-
- );
-};
+export interface AppProps {
+ appTitle: string;
+ useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
+ customArgs?: any[];
+}
-export default function App() {
+export default function App({ appTitle, useCustom, customArgs }: AppProps) {
const appRef = useRef(null);
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
@@ -129,6 +113,8 @@ export default function App() {
const [excalidrawAPI, setExcalidrawAPI] =
useState(null);
+ useCustom(excalidrawAPI, customArgs);
+
useHandleLibrary({ excalidrawAPI });
useEffect(() => {
@@ -136,7 +122,7 @@ export default function App() {
return;
}
const fetchData = async () => {
- const res = await fetch("/rocket.jpeg");
+ const res = await fetch("/images/rocket.jpeg");
const imageData = await res.blob();
const reader = new FileReader();
reader.readAsDataURL(imageData);
@@ -160,41 +146,23 @@ export default function App() {
fetchData();
}, [excalidrawAPI]);
- const renderFooter = () => {
+ const renderTopRightUI = (isMobile: boolean) => {
return (
<>
- {" "}
+ {!isMobile && (
+ {
+ window.alert("Collab dialog clicked");
+ }}
+ />
+ )}
{
- excalidrawAPI?.setActiveTool({
- type: "custom",
- customType: "comment",
- });
- const url = `data:${MIME_TYPES.svg},${encodeURIComponent(
- `
-
- `,
- )}`;
- excalidrawAPI?.setCursor(`url(${url}), auto`);
- }}
+ onClick={() => alert("This is an empty top right UI")}
+ style={{ height: "2.5rem" }}
>
- {COMMENT_SVG}
-
- alert("This is dummy footer")}>
{" "}
- custom footer{" "}
+ Click me{" "}
>
);
@@ -241,7 +209,10 @@ export default function App() {
locked: false,
link: null,
updated: 1,
- strokeSharpness: "round",
+ roundness: {
+ type: ROUNDNESS.ADAPTIVE_RADIUS,
+ value: 32,
+ },
},
],
null,
@@ -379,6 +350,7 @@ export default function App() {
}
});
};
+
const renderCommentIcons = () => {
return Object.values(commentIcons).map((commentIcon) => {
if (!excalidrawAPI) {
@@ -433,7 +405,7 @@ export default function App() {
}}
>
-
+
);
@@ -531,9 +503,37 @@ export default function App() {
);
};
+ const renderMenu = () => {
+ return (
+
diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx
index 4ac071592..4f768c6d5 100644
--- a/src/packages/excalidraw/index.tsx
+++ b/src/packages/excalidraw/index.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, forwardRef } from "react";
import { InitializeApp } from "../../components/InitializeApp";
import App from "../../components/App";
+import { isShallowEqual } from "../../utils";
import "../../css/app.scss";
import "../../css/styles.scss";
@@ -10,17 +11,19 @@ import { defaultLang } from "../../i18n";
import { DEFAULT_UI_OPTIONS } from "../../constants";
import { Provider } from "jotai";
import { jotaiScope, jotaiStore } from "../../jotai";
+import Footer from "../../components/footer/FooterCenter";
+import MainMenu from "../../components/main-menu/MainMenu";
+import WelcomeScreen from "../../components/welcome-screen/WelcomeScreen";
+import LiveCollaborationTrigger from "../../components/live-collaboration/LiveCollaborationTrigger";
const ExcalidrawBase = (props: ExcalidrawProps) => {
const {
onChange,
initialData,
excalidrawRef,
- onCollabButtonClick,
isCollaborating = false,
onPointerUpdate,
renderTopRightUI,
- renderFooter,
renderSidebar,
langCode = defaultLang.code,
viewModeEnabled,
@@ -39,6 +42,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
onLinkOpen,
onPointerDown,
onScrollChange,
+ children,
} = props;
const canvasActions = props.UIOptions?.canvasActions;
@@ -83,17 +87,15 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
}, []);
return (
-
- jotaiStore} scope={jotaiScope}>
+ jotaiStore} scope={jotaiScope}>
+
{
onPointerDown={onPointerDown}
onScrollChange={onScrollChange}
renderSidebar={renderSidebar}
- />
-
-
+ >
+ {children}
+
+
+
);
};
@@ -125,6 +129,11 @@ const areEqual = (
prevProps: PublicExcalidrawProps,
nextProps: PublicExcalidrawProps,
) => {
+ // short-circuit early
+ if (prevProps.children !== nextProps.children) {
+ return false;
+ }
+
const {
initialData: prevInitialData,
UIOptions: prevUIOptions = {},
@@ -153,7 +162,7 @@ const areEqual = (
const canvasOptionKeys = Object.keys(
prevUIOptions.canvasActions!,
) as (keyof Partial
)[];
- canvasOptionKeys.every((key) => {
+ return canvasOptionKeys.every((key) => {
if (
key === "export" &&
prevUIOptions?.canvasActions?.export &&
@@ -170,16 +179,10 @@ const areEqual = (
);
});
}
- return true;
+ return prevUIOptions[key] === nextUIOptions[key];
});
- const prevKeys = Object.keys(prevProps) as (keyof typeof prev)[];
- const nextKeys = Object.keys(nextProps) as (keyof typeof next)[];
- return (
- isUIOptionsSame &&
- prevKeys.length === nextKeys.length &&
- prevKeys.every((key) => prev[key] === next[key])
- );
+ return isUIOptionsSame && isShallowEqual(prev, next);
};
const forwardedRefComp = forwardRef<
@@ -195,7 +198,7 @@ export {
isInvisiblySmallElement,
getNonDeletedElements,
} from "../../element";
-export { defaultLang, languages } from "../../i18n";
+export { defaultLang, useI18n, languages } from "../../i18n";
export {
restore,
restoreAppState,
@@ -236,3 +239,9 @@ export {
} from "../../utils";
export { Sidebar } from "../../components/Sidebar/Sidebar";
+export { Button } from "../../components/Button";
+export { Footer };
+export { MainMenu };
+export { useDevice } from "../../components/App";
+export { WelcomeScreen };
+export { LiveCollaborationTrigger };
diff --git a/src/packages/excalidraw/package.json b/src/packages/excalidraw/package.json
index b830dc619..a23a3b1e8 100644
--- a/src/packages/excalidraw/package.json
+++ b/src/packages/excalidraw/package.json
@@ -1,6 +1,6 @@
{
"name": "@excalidraw/excalidraw",
- "version": "0.13.0",
+ "version": "0.14.2",
"main": "main.js",
"types": "types/packages/excalidraw/index.d.ts",
"files": [
diff --git a/src/packages/excalidraw/yarn.lock b/src/packages/excalidraw/yarn.lock
index ecd10278f..09838174b 100644
--- a/src/packages/excalidraw/yarn.lock
+++ b/src/packages/excalidraw/yarn.lock
@@ -2672,9 +2672,9 @@ json-schema-traverse@^1.0.0:
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@@ -2697,9 +2697,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1"
- integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
+ integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
diff --git a/src/packages/utils/README.md b/src/packages/utils/README.md
index e9d53a52f..a6e4eabc2 100644
--- a/src/packages/utils/README.md
+++ b/src/packages/utils/README.md
@@ -28,7 +28,7 @@ Export an Excalidraw diagram to a [SVGElement](https://developer.mozilla.org/en-
## Usage
-Excalidraw utils is published as a UMD (Universal Module Definition). If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module:
+Excalidraw utils is published as a UMD (Universal Module Definition). If you are using a module bundler (for instance, Webpack), you can import it as an ES6 module:
```js
import { exportToSvg, exportToBlob } from "@excalidraw/utils";
@@ -68,7 +68,7 @@ const excalidrawDiagram = {
roughness: 1,
opacity: 100,
groupIds: [],
- strokeSharpness: "sharp",
+ roundness: null,
seed: 1041657908,
version: 120,
versionNonce: 1188004276,
diff --git a/src/packages/utils/yarn.lock b/src/packages/utils/yarn.lock
index fd2043a0f..483399b2f 100644
--- a/src/packages/utils/yarn.lock
+++ b/src/packages/utils/yarn.lock
@@ -1895,9 +1895,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
diff --git a/src/points.ts b/src/points.ts
index 641a332de..84aea9277 100644
--- a/src/points.ts
+++ b/src/points.ts
@@ -51,6 +51,5 @@ export const rescalePoints = (
return currentDimension === dimension ? value + translation : value;
}) as [number, number],
);
-
return nextPoints;
};
diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts
index df88cdb70..755a45327 100644
--- a/src/renderer/renderElement.ts
+++ b/src/renderer/renderElement.ts
@@ -6,12 +6,14 @@ import {
NonDeletedExcalidrawElement,
ExcalidrawFreeDrawElement,
ExcalidrawImageElement,
+ ExcalidrawTextElementWithContainer,
} from "../element/types";
import {
isTextElement,
isLinearElement,
isFreeDrawElement,
isInitializedImageElement,
+ isArrowElement,
} from "../element/typeChecks";
import {
getDiamondPoints,
@@ -25,7 +27,7 @@ import { RoughGenerator } from "roughjs/bin/generator";
import { RenderConfig } from "../scene/types";
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
-import { isPathALoop } from "../math";
+import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
import rough from "roughjs/bin/rough";
import { AppState, BinaryFiles, Zoom } from "../types";
import { getDefaultAppState } from "../appState";
@@ -34,10 +36,14 @@ import {
MAX_DECIMALS_FOR_SVG_EXPORT,
MIME_TYPES,
SVG_NS,
- VERTICAL_ALIGN,
} from "../constants";
import { getStroke, StrokeOptions } from "perfect-freehand";
-import { getApproxLineHeight } from "../element/textElement";
+import {
+ getApproxLineHeight,
+ getBoundTextElement,
+ getContainerElement,
+} from "../element/textElement";
+import { LinearElementEditor } from "../element/linearElementEditor";
// using a stronger invert (100% vs our regular 93%) and saturate
// as a temp hack to make images in dark theme look closer to original
@@ -80,6 +86,7 @@ export interface ExcalidrawElementWithCanvas {
canvasZoom: Zoom["value"];
canvasOffsetX: number;
canvasOffsetY: number;
+ boundTextElementVersion: number | null;
}
const generateElementCanvas = (
@@ -148,6 +155,7 @@ const generateElementCanvas = (
canvasZoom: zoom.value,
canvasOffsetX,
canvasOffsetY,
+ boundTextElementVersion: getBoundTextElement(element)?.version || null,
};
};
@@ -270,22 +278,19 @@ const drawElementOnCanvas = (
const lineHeight = element.containerId
? getApproxLineHeight(getFontString(element))
: element.height / lines.length;
- let verticalOffset = element.height - element.baseline;
- if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
- verticalOffset = BOUND_TEXT_PADDING;
- }
-
const horizontalOffset =
element.textAlign === "center"
? element.width / 2
: element.textAlign === "right"
? element.width
: 0;
+ context.textBaseline = "bottom";
+
for (let index = 0; index < lines.length; index++) {
context.fillText(
lines[index],
horizontalOffset,
- (index + 1) * lineHeight - verticalOffset,
+ (index + 1) * lineHeight,
);
}
context.restore();
@@ -414,10 +419,10 @@ const generateElementShape = (
switch (element.type) {
case "rectangle":
- if (element.strokeSharpness === "round") {
+ if (element.roundness) {
const w = element.width;
const h = element.height;
- const r = Math.min(w, h) * 0.25;
+ const r = getCornerRadius(Math.min(w, h), element);
shape = generator.path(
`M ${r} 0 L ${w - r} 0 Q ${w} 0, ${w} ${r} L ${w} ${
h - r
@@ -441,32 +446,36 @@ const generateElementShape = (
case "diamond": {
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
getDiamondPoints(element);
- if (element.strokeSharpness === "round") {
+ if (element.roundness) {
+ const verticalRadius = getCornerRadius(
+ Math.abs(topX - leftX),
+ element,
+ );
+
+ const horizontalRadius = getCornerRadius(
+ Math.abs(rightY - topY),
+ element,
+ );
+
shape = generator.path(
- `M ${topX + (rightX - topX) * 0.25} ${
- topY + (rightY - topY) * 0.25
- } L ${rightX - (rightX - topX) * 0.25} ${
- rightY - (rightY - topY) * 0.25
- }
+ `M ${topX + verticalRadius} ${topY + horizontalRadius} L ${
+ rightX - verticalRadius
+ } ${rightY - horizontalRadius}
C ${rightX} ${rightY}, ${rightX} ${rightY}, ${
- rightX - (rightX - bottomX) * 0.25
- } ${rightY + (bottomY - rightY) * 0.25}
- L ${bottomX + (rightX - bottomX) * 0.25} ${
- bottomY - (bottomY - rightY) * 0.25
- }
+ rightX - verticalRadius
+ } ${rightY + horizontalRadius}
+ L ${bottomX + verticalRadius} ${bottomY - horizontalRadius}
C ${bottomX} ${bottomY}, ${bottomX} ${bottomY}, ${
- bottomX - (bottomX - leftX) * 0.25
- } ${bottomY - (bottomY - leftY) * 0.25}
- L ${leftX + (bottomX - leftX) * 0.25} ${
- leftY + (bottomY - leftY) * 0.25
+ bottomX - verticalRadius
+ } ${bottomY - horizontalRadius}
+ L ${leftX + verticalRadius} ${leftY + horizontalRadius}
+ C ${leftX} ${leftY}, ${leftX} ${leftY}, ${leftX + verticalRadius} ${
+ leftY - horizontalRadius
}
- C ${leftX} ${leftY}, ${leftX} ${leftY}, ${
- leftX + (topX - leftX) * 0.25
- } ${leftY - (leftY - topY) * 0.25}
- L ${topX - (topX - leftX) * 0.25} ${topY + (leftY - topY) * 0.25}
- C ${topX} ${topY}, ${topX} ${topY}, ${
- topX + (rightX - topX) * 0.25
- } ${topY + (rightY - topY) * 0.25}`,
+ L ${topX - verticalRadius} ${topY + horizontalRadius}
+ C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
+ topY + horizontalRadius
+ }`,
generateRoughOptions(element, true),
);
} else {
@@ -505,7 +514,7 @@ const generateElementShape = (
// curve is always the first element
// this simplifies finding the curve for an element
- if (element.strokeSharpness === "sharp") {
+ if (!element.roundness) {
if (options.fill) {
shape = [generator.polygon(points as [number, number][], options)];
} else {
@@ -656,11 +665,13 @@ const generateElementWithCanvas = (
prevElementWithCanvas &&
prevElementWithCanvas.canvasZoom !== zoom.value &&
!renderConfig?.shouldCacheIgnoreZoom;
+ const boundTextElementVersion = getBoundTextElement(element)?.version || null;
if (
!prevElementWithCanvas ||
shouldRegenerateBecauseZoom ||
- prevElementWithCanvas.theme !== renderConfig.theme
+ prevElementWithCanvas.theme !== renderConfig.theme ||
+ prevElementWithCanvas.boundTextElementVersion !== boundTextElementVersion
) {
const elementWithCanvas = generateElementCanvas(
element,
@@ -683,6 +694,7 @@ const drawElementFromCanvas = (
) => {
const element = elementWithCanvas.element;
const padding = getCanvasPadding(element);
+ const zoom = elementWithCanvas.canvasZoom;
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
// Free draw elements will otherwise "shuffle" as the min x and y change
@@ -696,34 +708,110 @@ const drawElementFromCanvas = (
const cx = ((x1 + x2) / 2 + renderConfig.scrollX) * window.devicePixelRatio;
const cy = ((y1 + y2) / 2 + renderConfig.scrollY) * window.devicePixelRatio;
- const _isPendingImageElement = isPendingImageElement(element, renderConfig);
-
- const scaleXFactor =
- "scale" in elementWithCanvas.element && !_isPendingImageElement
- ? elementWithCanvas.element.scale[0]
- : 1;
- const scaleYFactor =
- "scale" in elementWithCanvas.element && !_isPendingImageElement
- ? elementWithCanvas.element.scale[1]
- : 1;
-
context.save();
- context.scale(
- (1 / window.devicePixelRatio) * scaleXFactor,
- (1 / window.devicePixelRatio) * scaleYFactor,
- );
- context.translate(cx * scaleXFactor, cy * scaleYFactor);
- context.rotate(element.angle * scaleXFactor * scaleYFactor);
+ context.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio);
+ const boundTextElement = getBoundTextElement(element);
- context.drawImage(
- elementWithCanvas.canvas!,
- (-(x2 - x1) / 2) * window.devicePixelRatio -
- (padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
- (-(y2 - y1) / 2) * window.devicePixelRatio -
- (padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
- elementWithCanvas.canvas!.width / elementWithCanvas.canvasZoom,
- elementWithCanvas.canvas!.height / elementWithCanvas.canvasZoom,
- );
+ if (isArrowElement(element) && boundTextElement) {
+ const tempCanvas = document.createElement("canvas");
+ const tempCanvasContext = tempCanvas.getContext("2d")!;
+
+ // Take max dimensions of arrow canvas so that when canvas is rotated
+ // the arrow doesn't get clipped
+ const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
+ tempCanvas.width =
+ maxDim * window.devicePixelRatio * zoom +
+ padding * elementWithCanvas.canvasZoom * 10;
+ tempCanvas.height =
+ maxDim * window.devicePixelRatio * zoom +
+ padding * elementWithCanvas.canvasZoom * 10;
+ const offsetX = (tempCanvas.width - elementWithCanvas.canvas!.width) / 2;
+ const offsetY = (tempCanvas.height - elementWithCanvas.canvas!.height) / 2;
+
+ tempCanvasContext.translate(tempCanvas.width / 2, tempCanvas.height / 2);
+ tempCanvasContext.rotate(element.angle);
+
+ tempCanvasContext.drawImage(
+ elementWithCanvas.canvas!,
+ -elementWithCanvas.canvas.width / 2,
+ -elementWithCanvas.canvas.height / 2,
+ elementWithCanvas.canvas.width,
+ elementWithCanvas.canvas.height,
+ );
+
+ const [, , , , boundTextCx, boundTextCy] =
+ getElementAbsoluteCoords(boundTextElement);
+
+ tempCanvasContext.rotate(-element.angle);
+
+ // Shift the canvas to the center of the bound text element
+ const shiftX =
+ tempCanvas.width / 2 -
+ (boundTextCx - x1) * window.devicePixelRatio * zoom -
+ offsetX -
+ padding * zoom;
+
+ const shiftY =
+ tempCanvas.height / 2 -
+ (boundTextCy - y1) * window.devicePixelRatio * zoom -
+ offsetY -
+ padding * zoom;
+ tempCanvasContext.translate(-shiftX, -shiftY);
+
+ // Clear the bound text area
+ tempCanvasContext.clearRect(
+ -(boundTextElement.width / 2 + BOUND_TEXT_PADDING) *
+ window.devicePixelRatio *
+ zoom,
+ -(boundTextElement.height / 2 + BOUND_TEXT_PADDING) *
+ window.devicePixelRatio *
+ zoom,
+ (boundTextElement.width + BOUND_TEXT_PADDING * 2) *
+ window.devicePixelRatio *
+ zoom,
+ (boundTextElement.height + BOUND_TEXT_PADDING * 2) *
+ window.devicePixelRatio *
+ zoom,
+ );
+
+ context.translate(cx, cy);
+ context.drawImage(
+ tempCanvas,
+ (-(x2 - x1) / 2) * window.devicePixelRatio - offsetX / zoom - padding,
+ (-(y2 - y1) / 2) * window.devicePixelRatio - offsetY / zoom - padding,
+ tempCanvas.width / zoom,
+ tempCanvas.height / zoom,
+ );
+ } else {
+ // we translate context to element center so that rotation and scale
+ // originates from the element center
+ context.translate(cx, cy);
+
+ context.rotate(element.angle);
+
+ if (
+ "scale" in elementWithCanvas.element &&
+ !isPendingImageElement(element, renderConfig)
+ ) {
+ context.scale(
+ elementWithCanvas.element.scale[0],
+ elementWithCanvas.element.scale[1],
+ );
+ }
+
+ // revert afterwards we don't have account for it during drawing
+ context.translate(-cx, -cy);
+
+ context.drawImage(
+ elementWithCanvas.canvas!,
+ (x1 + renderConfig.scrollX) * window.devicePixelRatio -
+ (padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
+ (y1 + renderConfig.scrollY) * window.devicePixelRatio -
+ (padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
+ elementWithCanvas.canvas!.width / elementWithCanvas.canvasZoom,
+ elementWithCanvas.canvas!.height / elementWithCanvas.canvasZoom,
+ );
+ }
context.restore();
// Clear the nested element we appended to the DOM
@@ -734,6 +822,7 @@ export const renderElement = (
rc: RoughCanvas,
context: CanvasRenderingContext2D,
renderConfig: RenderConfig,
+ appState: AppState,
) => {
const generator = rc.generator;
switch (element.type) {
@@ -796,21 +885,97 @@ export const renderElement = (
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2 + renderConfig.scrollX;
const cy = (y1 + y2) / 2 + renderConfig.scrollY;
- const shiftX = (x2 - x1) / 2 - (element.x - x1);
- const shiftY = (y2 - y1) / 2 - (element.y - y1);
+ let shiftX = (x2 - x1) / 2 - (element.x - x1);
+ let shiftY = (y2 - y1) / 2 - (element.y - y1);
+ if (isTextElement(element)) {
+ const container = getContainerElement(element);
+ if (isArrowElement(container)) {
+ const boundTextCoords =
+ LinearElementEditor.getBoundTextElementPosition(
+ container,
+ element as ExcalidrawTextElementWithContainer,
+ );
+ shiftX = (x2 - x1) / 2 - (boundTextCoords.x - x1);
+ shiftY = (y2 - y1) / 2 - (boundTextCoords.y - y1);
+ }
+ }
context.save();
context.translate(cx, cy);
- context.rotate(element.angle);
- if (element.type === "image") {
- context.scale(element.scale[0], element.scale[1]);
- }
- context.translate(-shiftX, -shiftY);
if (shouldResetImageFilter(element, renderConfig)) {
context.filter = "none";
}
+ const boundTextElement = getBoundTextElement(element);
+
+ if (isArrowElement(element) && boundTextElement) {
+ const tempCanvas = document.createElement("canvas");
+
+ const tempCanvasContext = tempCanvas.getContext("2d")!;
+
+ // Take max dimensions of arrow canvas so that when canvas is rotated
+ // the arrow doesn't get clipped
+ const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
+ const padding = getCanvasPadding(element);
+ tempCanvas.width =
+ maxDim * appState.exportScale + padding * 10 * appState.exportScale;
+ tempCanvas.height =
+ maxDim * appState.exportScale + padding * 10 * appState.exportScale;
+
+ tempCanvasContext.translate(
+ tempCanvas.width / 2,
+ tempCanvas.height / 2,
+ );
+ tempCanvasContext.scale(appState.exportScale, appState.exportScale);
+
+ // Shift the canvas to left most point of the arrow
+ shiftX = element.width / 2 - (element.x - x1);
+ shiftY = element.height / 2 - (element.y - y1);
+
+ tempCanvasContext.rotate(element.angle);
+ const tempRc = rough.canvas(tempCanvas);
+
+ tempCanvasContext.translate(-shiftX, -shiftY);
+
+ drawElementOnCanvas(element, tempRc, tempCanvasContext, renderConfig);
+
+ tempCanvasContext.translate(shiftX, shiftY);
+
+ tempCanvasContext.rotate(-element.angle);
+
+ // Shift the canvas to center of bound text
+ const [, , , , boundTextCx, boundTextCy] =
+ getElementAbsoluteCoords(boundTextElement);
+ const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
+ const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
+ tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
+
+ // Clear the bound text area
+ tempCanvasContext.clearRect(
+ -boundTextElement.width / 2,
+ -boundTextElement.height / 2,
+ boundTextElement.width,
+ boundTextElement.height,
+ );
+ context.scale(1 / appState.exportScale, 1 / appState.exportScale);
+ context.drawImage(
+ tempCanvas,
+ -tempCanvas.width / 2,
+ -tempCanvas.height / 2,
+ tempCanvas.width,
+ tempCanvas.height,
+ );
+ } else {
+ context.rotate(element.angle);
+
+ if (element.type === "image") {
+ // note: scale must be applied *after* rotating
+ context.scale(element.scale[0], element.scale[1]);
+ }
+
+ context.translate(-shiftX, -shiftY);
+ drawElementOnCanvas(element, rc, context, renderConfig);
+ }
- drawElementOnCanvas(element, rc, context, renderConfig);
context.restore();
// not exporting → optimized rendering (cache & render from element
// canvases)
@@ -819,7 +984,33 @@ export const renderElement = (
element,
renderConfig,
);
+
+ const currentImageSmoothingStatus = context.imageSmoothingEnabled;
+
+ if (
+ // do not disable smoothing during zoom as blurry shapes look better
+ // on low resolution (while still zooming in) than sharp ones
+ !renderConfig?.shouldCacheIgnoreZoom &&
+ // angle is 0 -> always disable smoothing
+ (!element.angle ||
+ // or check if angle is a right angle in which case we can still
+ // disable smoothing without adversely affecting the result
+ isRightAngle(element.angle))
+ ) {
+ // Disabling smoothing makes output much sharper, especially for
+ // text. Unless for non-right angles, where the aliasing is really
+ // terrible on Chromium.
+ //
+ // Note that `context.imageSmoothingQuality="high"` has almost
+ // zero effect.
+ //
+ context.imageSmoothingEnabled = false;
+ }
+
drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig);
+
+ // reset
+ context.imageSmoothingEnabled = currentImageSmoothingStatus;
}
break;
}
@@ -851,13 +1042,28 @@ export const renderElementToSvg = (
rsvg: RoughSVG,
svgRoot: SVGElement,
files: BinaryFiles,
- offsetX?: number,
- offsetY?: number,
+ offsetX: number,
+ offsetY: number,
exportWithDarkMode?: boolean,
) => {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
- const cx = (x2 - x1) / 2 - (element.x - x1);
- const cy = (y2 - y1) / 2 - (element.y - y1);
+ let cx = (x2 - x1) / 2 - (element.x - x1);
+ let cy = (y2 - y1) / 2 - (element.y - y1);
+ if (isTextElement(element)) {
+ const container = getContainerElement(element);
+ if (isArrowElement(container)) {
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(container);
+
+ const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
+ container,
+ element as ExcalidrawTextElementWithContainer,
+ );
+ cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
+ cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
+ offsetX = offsetX + boundTextCoords.x - element.x;
+ offsetY = offsetY + boundTextCoords.y - element.y;
+ }
+ }
const degree = (180 * element.angle) / Math.PI;
const generator = rsvg.generator;
@@ -904,8 +1110,54 @@ export const renderElementToSvg = (
}
case "line":
case "arrow": {
+ const boundText = getBoundTextElement(element);
+ const maskPath = svgRoot.ownerDocument!.createElementNS(SVG_NS, "mask");
+ if (boundText) {
+ maskPath.setAttribute("id", `mask-${element.id}`);
+ const maskRectVisible = svgRoot.ownerDocument!.createElementNS(
+ SVG_NS,
+ "rect",
+ );
+ offsetX = offsetX || 0;
+ offsetY = offsetY || 0;
+ maskRectVisible.setAttribute("x", "0");
+ maskRectVisible.setAttribute("y", "0");
+ maskRectVisible.setAttribute("fill", "#fff");
+ maskRectVisible.setAttribute(
+ "width",
+ `${element.width + 100 + offsetX}`,
+ );
+ maskRectVisible.setAttribute(
+ "height",
+ `${element.height + 100 + offsetY}`,
+ );
+
+ maskPath.appendChild(maskRectVisible);
+ const maskRectInvisible = svgRoot.ownerDocument!.createElementNS(
+ SVG_NS,
+ "rect",
+ );
+ const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
+ element,
+ boundText,
+ );
+
+ const maskX = offsetX + boundTextCoords.x - element.x;
+ const maskY = offsetY + boundTextCoords.y - element.y;
+
+ maskRectInvisible.setAttribute("x", maskX.toString());
+ maskRectInvisible.setAttribute("y", maskY.toString());
+ maskRectInvisible.setAttribute("fill", "#000");
+ maskRectInvisible.setAttribute("width", `${boundText.width}`);
+ maskRectInvisible.setAttribute("height", `${boundText.height}`);
+ maskRectInvisible.setAttribute("opacity", "1");
+ maskPath.appendChild(maskRectInvisible);
+ }
generateElementShape(element, generator);
const group = svgRoot.ownerDocument!.createElementNS(SVG_NS, "g");
+ if (boundText) {
+ group.setAttribute("mask", `url(#mask-${element.id})`);
+ }
const opacity = element.opacity / 100;
group.setAttribute("stroke-linecap", "round");
@@ -935,6 +1187,7 @@ export const renderElementToSvg = (
group.appendChild(node);
});
root.appendChild(group);
+ root.append(maskPath);
break;
}
case "freedraw": {
@@ -1033,6 +1286,7 @@ export const renderElementToSvg = (
node.setAttribute("stroke-opacity", `${opacity}`);
node.setAttribute("fill-opacity", `${opacity}`);
}
+
node.setAttribute(
"transform",
`translate(${offsetX || 0} ${
@@ -1041,7 +1295,6 @@ export const renderElementToSvg = (
);
const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
const lineHeight = element.height / lines.length;
- const verticalOffset = element.height - element.baseline;
const horizontalOffset =
element.textAlign === "center"
? element.width / 2
@@ -1059,13 +1312,14 @@ export const renderElementToSvg = (
const text = svgRoot.ownerDocument!.createElementNS(SVG_NS, "text");
text.textContent = lines[i];
text.setAttribute("x", `${horizontalOffset}`);
- text.setAttribute("y", `${(i + 1) * lineHeight - verticalOffset}`);
+ text.setAttribute("y", `${i * lineHeight}`);
text.setAttribute("font-family", getFontFamilyString(element));
text.setAttribute("font-size", `${element.fontSize}px`);
text.setAttribute("fill", element.strokeColor);
text.setAttribute("text-anchor", textAnchor);
text.setAttribute("style", "white-space: pre;");
text.setAttribute("direction", direction);
+ text.setAttribute("dominant-baseline", "text-before-edge");
node.appendChild(text);
}
root.appendChild(node);
diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts
index 7c089ed53..c8b64b47b 100644
--- a/src/renderer/renderScene.ts
+++ b/src/renderer/renderScene.ts
@@ -348,7 +348,6 @@ export const _renderScene = ({
context.setTransform(1, 0, 0, 1, 0, 0);
context.save();
context.scale(scale, scale);
-
// When doing calculations based on canvas width we should used normalized one
const normalizedCanvasWidth = canvas.width / scale;
const normalizedCanvasHeight = canvas.height / scale;
@@ -410,7 +409,7 @@ export const _renderScene = ({
undefined;
visibleElements.forEach((element) => {
try {
- renderElement(element, rc, context, renderConfig);
+ renderElement(element, rc, context, renderConfig, appState);
// Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to
// ShapeCache returns empty hence making sure that we get the
// correct element from visible elements
@@ -440,7 +439,13 @@ export const _renderScene = ({
// Paint selection element
if (appState.selectionElement) {
try {
- renderElement(appState.selectionElement, rc, context, renderConfig);
+ renderElement(
+ appState.selectionElement,
+ rc,
+ context,
+ renderConfig,
+ appState,
+ );
} catch (error: any) {
console.error(error);
}
@@ -453,6 +458,22 @@ export const _renderScene = ({
renderBindingHighlight(context, renderConfig, suggestedBinding!);
});
}
+ const locallySelectedElements = getSelectedElements(elements, appState);
+
+ // Getting the element using LinearElementEditor during collab mismatches version - being one head of visible elements due to
+ // ShapeCache returns empty hence making sure that we get the
+ // correct element from visible elements
+ if (
+ locallySelectedElements.length === 1 &&
+ appState.editingLinearElement?.elementId === locallySelectedElements[0].id
+ ) {
+ renderLinearPointHandles(
+ context,
+ appState,
+ renderConfig,
+ locallySelectedElements[0] as NonDeleted,
+ );
+ }
if (
appState.selectedLinearElement &&
@@ -466,7 +487,6 @@ export const _renderScene = ({
!appState.multiElement &&
!appState.editingLinearElement
) {
- const locallySelectedElements = getSelectedElements(elements, appState);
const showBoundingBox = shouldShowBoundingBox(
locallySelectedElements,
appState,
@@ -515,8 +535,8 @@ export const _renderScene = ({
}
if (selectionColors.length) {
- const [elementX1, elementY1, elementX2, elementY2] =
- getElementAbsoluteCoords(element);
+ const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
+ getElementAbsoluteCoords(element, true);
acc.push({
angle: element.angle,
elementX1,
@@ -525,10 +545,12 @@ export const _renderScene = ({
elementY2,
selectionColors,
dashed: !!renderConfig.remoteSelectedElementIds[element.id],
+ cx,
+ cy,
});
}
return acc;
- }, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean }[]);
+ }, [] as { angle: number; elementX1: number; elementY1: number; elementX2: number; elementY2: number; selectionColors: string[]; dashed?: boolean; cx: number; cy: number }[]);
const addSelectionForGroupId = (groupId: GroupId) => {
const groupElements = getElementsInGroup(elements, groupId);
@@ -540,8 +562,10 @@ export const _renderScene = ({
elementX2,
elementY1,
elementY2,
- selectionColors: [selectionColor],
+ selectionColors: [oc.black],
dashed: true,
+ cx: elementX1 + (elementX2 - elementX1) / 2,
+ cy: elementY1 + (elementY2 - elementY1) / 2,
});
};
@@ -600,7 +624,7 @@ export const _renderScene = ({
context.lineWidth = lineWidth;
context.setLineDash(initialLineDash);
const transformHandles = getTransformHandlesFromCoords(
- [x1, y1, x2, y2],
+ [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
0,
renderConfig.zoom,
"mouse",
@@ -861,6 +885,8 @@ const renderSelectionBorder = (
elementY2: number;
selectionColors: string[];
dashed?: boolean;
+ cx: number;
+ cy: number;
},
padding = DEFAULT_SPACING * 2,
) => {
@@ -871,6 +897,8 @@ const renderSelectionBorder = (
elementX2,
elementY2,
selectionColors,
+ cx,
+ cy,
dashed,
} = elementProperties;
const elementWidth = elementX2 - elementX1;
@@ -900,8 +928,8 @@ const renderSelectionBorder = (
elementY1 - linePadding,
elementWidth + linePadding * 2,
elementHeight + linePadding * 2,
- elementX1 + elementWidth / 2,
- elementY1 + elementHeight / 2,
+ cx,
+ cy,
angle,
);
}
@@ -1117,7 +1145,7 @@ export const renderSceneToSvg = (
return;
}
// render elements
- elements.forEach((element) => {
+ elements.forEach((element, index) => {
if (!element.isDeleted) {
try {
renderElementToSvg(
diff --git a/src/scene/Fonts.ts b/src/scene/Fonts.ts
new file mode 100644
index 000000000..cc206c776
--- /dev/null
+++ b/src/scene/Fonts.ts
@@ -0,0 +1,93 @@
+import { isTextElement, refreshTextDimensions } from "../element";
+import { newElementWith } from "../element/mutateElement";
+import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
+import { invalidateShapeForElement } from "../renderer/renderElement";
+import { getFontString } from "../utils";
+import type Scene from "./Scene";
+
+export class Fonts {
+ private scene: Scene;
+ private onSceneUpdated: () => void;
+
+ constructor({
+ scene,
+ onSceneUpdated,
+ }: {
+ scene: Scene;
+ onSceneUpdated: () => void;
+ }) {
+ this.scene = scene;
+ this.onSceneUpdated = onSceneUpdated;
+ }
+
+ // it's ok to track fonts across multiple instances only once, so let's use
+ // a static member to reduce memory footprint
+ private static loadedFontFaces = new Set();
+
+ /**
+ * if we load a (new) font, it's likely that text elements using it have
+ * already been rendered using a fallback font. Thus, we want invalidate
+ * their shapes and rerender. See #637.
+ *
+ * Invalidates text elements and rerenders scene, provided that at least one
+ * of the supplied fontFaces has not already been processed.
+ */
+ public onFontsLoaded = (fontFaces: readonly FontFace[]) => {
+ if (
+ // bail if all fonts with have been processed. We're checking just a
+ // subset of the font properties (though it should be enough), so it
+ // can technically bail on a false positive.
+ fontFaces.every((fontFace) => {
+ const sig = `${fontFace.family}-${fontFace.style}-${fontFace.weight}`;
+ if (Fonts.loadedFontFaces.has(sig)) {
+ return true;
+ }
+ Fonts.loadedFontFaces.add(sig);
+ return false;
+ })
+ ) {
+ return false;
+ }
+
+ let didUpdate = false;
+
+ this.scene.mapElements((element) => {
+ if (isTextElement(element)) {
+ invalidateShapeForElement(element);
+ didUpdate = true;
+ return newElementWith(element, {
+ ...refreshTextDimensions(element),
+ });
+ }
+ return element;
+ });
+
+ if (didUpdate) {
+ this.onSceneUpdated();
+ }
+ };
+
+ public loadFontsForElements = async (
+ elements: readonly ExcalidrawElement[],
+ ) => {
+ const fontFaces = await Promise.all(
+ [
+ ...new Set(
+ elements
+ .filter((element) => isTextElement(element))
+ .map((element) => (element as ExcalidrawTextElement).fontFamily),
+ ),
+ ].map((fontFamily) => {
+ const fontString = getFontString({
+ fontFamily,
+ fontSize: 16,
+ });
+ if (!document.fonts?.check?.(fontString)) {
+ return document.fonts?.load?.(fontString);
+ }
+ return undefined;
+ }),
+ );
+ this.onFontsLoaded(fontFaces.flat().filter(Boolean) as FontFace[]);
+ };
+}
diff --git a/src/scene/Scene.ts b/src/scene/Scene.ts
index 8b5b84eb4..e9fc98f89 100644
--- a/src/scene/Scene.ts
+++ b/src/scene/Scene.ts
@@ -150,6 +150,24 @@ class Scene {
// (I guess?)
this.callbacks.clear();
}
+
+ insertElementAtIndex(element: ExcalidrawElement, index: number) {
+ if (!Number.isFinite(index) || index < 0) {
+ throw new Error(
+ "insertElementAtIndex can only be called with index >= 0",
+ );
+ }
+ const nextElements = [
+ ...this.elements.slice(0, index),
+ element,
+ ...this.elements.slice(index),
+ ];
+ this.replaceAllElements(nextElements);
+ }
+
+ getElementIndex(elementId: string) {
+ return this.elements.findIndex((element) => element.id === elementId);
+ }
}
export default Scene;
diff --git a/src/scene/comparisons.ts b/src/scene/comparisons.ts
index d94be0a69..b88c8b1ea 100644
--- a/src/scene/comparisons.ts
+++ b/src/scene/comparisons.ts
@@ -1,11 +1,4 @@
-import {
- ExcalidrawElement,
- ExcalidrawTextContainer,
- NonDeletedExcalidrawElement,
-} from "../element/types";
-
-import { getElementAbsoluteCoords } from "../element";
-import { isTextBindableContainer } from "../element/typeChecks";
+import { NonDeletedExcalidrawElement } from "../element/types";
export const hasBackground = (type: string) =>
type === "rectangle" ||
@@ -31,7 +24,7 @@ export const hasStrokeStyle = (type: string) =>
type === "arrow" ||
type === "line";
-export const canChangeSharpness = (type: string) =>
+export const canChangeRoundness = (type: string) =>
type === "rectangle" ||
type === "arrow" ||
type === "line" ||
@@ -73,23 +66,3 @@ export const getElementsAtPosition = (
(element) => !element.isDeleted && isAtPositionFn(element),
);
};
-
-export const getTextBindableContainerAtPosition = (
- elements: readonly ExcalidrawElement[],
- x: number,
- y: number,
-): ExcalidrawTextContainer | null => {
- let hitElement = null;
- // We need to to hit testing from front (end of the array) to back (beginning of the array)
- for (let index = elements.length - 1; index >= 0; --index) {
- if (elements[index].isDeleted) {
- continue;
- }
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index]);
- if (x1 < x && x < x2 && y1 < y && y < y2) {
- hitElement = elements[index];
- break;
- }
- }
- return isTextBindableContainer(hitElement, false) ? hitElement : null;
-};
diff --git a/src/scene/index.ts b/src/scene/index.ts
index 3e1d50b24..08568aa41 100644
--- a/src/scene/index.ts
+++ b/src/scene/index.ts
@@ -12,9 +12,8 @@ export {
hasStrokeWidth,
hasStrokeStyle,
canHaveArrowheads,
- canChangeSharpness,
+ canChangeRoundness,
getElementAtPosition,
- getTextBindableContainerAtPosition,
hasText,
getElementsAtPosition,
} from "./comparisons";
diff --git a/src/scene/scroll.ts b/src/scene/scroll.ts
index 4fce5e641..114d6db05 100644
--- a/src/scene/scroll.ts
+++ b/src/scene/scroll.ts
@@ -41,8 +41,8 @@ export const centerScrollOn = ({
zoom: Zoom;
}) => {
return {
- scrollX: (viewportDimensions.width / 2) * (1 / zoom.value) - scenePoint.x,
- scrollY: (viewportDimensions.height / 2) * (1 / zoom.value) - scenePoint.y,
+ scrollX: viewportDimensions.width / 2 / zoom.value - scenePoint.x,
+ scrollY: viewportDimensions.height / 2 / zoom.value - scenePoint.y,
};
};
diff --git a/src/service-worker.ts b/src/service-worker.ts
new file mode 100644
index 000000000..3e3a4355f
--- /dev/null
+++ b/src/service-worker.ts
@@ -0,0 +1,147 @@
+///
+/* eslint-disable no-restricted-globals */
+
+// This service worker can be customized!
+// See https://developers.google.com/web/tools/workbox/modules
+// for the list of available Workbox modules, or add any other
+// code you'd like.
+// You can also remove this file if you'd prefer not to use a
+// service worker, and the Workbox build step will be skipped.
+
+import { clientsClaim } from "workbox-core";
+import { ExpirationPlugin } from "workbox-expiration";
+import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
+import { registerRoute } from "workbox-routing";
+import { CacheFirst, StaleWhileRevalidate } from "workbox-strategies";
+
+declare const self: ServiceWorkerGlobalScope;
+
+clientsClaim();
+
+// Precache assets generated by your build process.
+//
+// Their URLs are injected into the __WB_MANIFEST during build (by workbox).
+//
+// This variable must be present somewhere in your service worker file,
+// even if you decide not to use precaching. See https://cra.link/PWA.
+//
+// We don't want to precache i18n files so we filter them out
+// (normally this should be configured in a webpack workbox plugin, but we don't
+// have access to it in CRA) — this is because all users will use at most
+// one or two languages, so there's no point fetching all of them. (They'll
+// be cached as you load them.)
+const manifest = self.__WB_MANIFEST.filter((entry) => {
+ return !/locales\/[\w-]+json/.test(
+ typeof entry === "string" ? entry : entry.url,
+ );
+});
+
+precacheAndRoute(manifest);
+
+// Set up App Shell-style routing, so that all navigation requests
+// are fulfilled with your index.html shell. Learn more at
+// https://developer.chrome.com/docs/workbox/app-shell-model/
+//
+// below is copied verbatim from CRA@5
+const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$");
+registerRoute(
+ // Return false to exempt requests from being fulfilled by index.html.
+ ({ request, url }: { request: Request; url: URL }) => {
+ // If this isn't a navigation, skip.
+ if (request.mode !== "navigate") {
+ return false;
+ }
+
+ // If this is a URL that starts with /_, skip.
+ if (url.pathname.startsWith("/_")) {
+ return false;
+ }
+
+ // If this looks like a URL for a resource, because it contains
+ // a file extension, skip.
+ if (url.pathname.match(fileExtensionRegexp)) {
+ return false;
+ }
+
+ // Return true to signal that we want to use the handler.
+ return true;
+ },
+ createHandlerBoundToURL(`${process.env.PUBLIC_URL}/index.html`),
+);
+
+// Cache resources that aren't being precached
+// -----------------------------------------------------------------------------
+
+registerRoute(
+ new RegExp("/fonts.css"),
+ new StaleWhileRevalidate({
+ cacheName: "fonts",
+ plugins: [
+ // Ensure that once this runtime cache reaches a maximum size the
+ // least-recently used images are removed.
+ new ExpirationPlugin({ maxEntries: 50 }),
+ ],
+ }),
+);
+
+// since we serve fonts from, don't forget to append new ?v= param when
+// updating fonts (glyphs) without changing the filename
+registerRoute(
+ new RegExp("/.+.(ttf|woff2|otf)"),
+ new CacheFirst({
+ cacheName: "fonts",
+ plugins: [
+ // Ensure that once this runtime cache reaches a maximum size the
+ // least-recently used images are removed.
+ new ExpirationPlugin({
+ maxEntries: 50,
+ // 90 days
+ maxAgeSeconds: 7776000000,
+ }),
+ ],
+ }),
+);
+
+registerRoute(
+ new RegExp("/locales\\/[\\w-]+json"),
+ // Customize this strategy as needed, e.g., by changing to CacheFirst.
+ new CacheFirst({
+ cacheName: "locales",
+ plugins: [
+ // Ensure that once this runtime cache reaches a maximum size the
+ // least-recently used images are removed.
+ new ExpirationPlugin({
+ maxEntries: 50,
+ // 30 days
+ maxAgeSeconds: 2592000000,
+ }),
+ ],
+ }),
+);
+
+// -----------------------------------------------------------------------------
+
+self.addEventListener("fetch", (event) => {
+ if (
+ event.request.method === "POST" &&
+ event.request.url.endsWith("/web-share-target")
+ ) {
+ return event.respondWith(
+ (async () => {
+ const formData = await event.request.formData();
+ const file = formData.get("file");
+ const webShareTargetCache = await caches.open("web-share-target");
+ await webShareTargetCache.put("shared-file", new Response(file));
+ return Response.redirect("/?web-share-target", 303);
+ })(),
+ );
+ }
+});
+
+// This allows the web app to trigger skipWaiting via
+// registration.waiting.postMessage({type: 'SKIP_WAITING'})
+self.addEventListener("message", (event) => {
+ if (event.data && event.data.type === "SKIP_WAITING") {
+ self.skipWaiting();
+ }
+});
diff --git a/src/serviceWorker.tsx b/src/serviceWorkerRegistration.ts
similarity index 100%
rename from src/serviceWorker.tsx
rename to src/serviceWorkerRegistration.ts
diff --git a/src/tests/MobileMenu.test.tsx b/src/tests/MobileMenu.test.tsx
new file mode 100644
index 000000000..41d5d0169
--- /dev/null
+++ b/src/tests/MobileMenu.test.tsx
@@ -0,0 +1,45 @@
+import ExcalidrawApp from "../excalidraw-app";
+import {
+ mockBoundingClientRect,
+ render,
+ restoreOriginalGetBoundingClientRect,
+} from "./test-utils";
+
+import { UI } from "./helpers/ui";
+
+describe("Test MobileMenu", () => {
+ const { h } = window;
+ const dimensions = { height: 400, width: 800 };
+
+ beforeEach(async () => {
+ await render( );
+ //@ts-ignore
+ h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
+ });
+
+ beforeAll(() => {
+ mockBoundingClientRect(dimensions);
+ });
+
+ afterAll(() => {
+ restoreOriginalGetBoundingClientRect();
+ });
+
+ it("should set device correctly", () => {
+ expect(h.app.device).toMatchInlineSnapshot(`
+ Object {
+ "canDeviceFitSidebar": false,
+ "isMobile": true,
+ "isSmScreen": false,
+ "isTouchScreen": false,
+ }
+ `);
+ });
+
+ it("should initialize with welcome screen and hide once user interacts", async () => {
+ expect(document.querySelector(".welcome-screen-center")).toMatchSnapshot();
+
+ UI.clickTool("rectangle");
+ expect(document.querySelector(".welcome-screen-center")).toBeNull();
+ });
+});
diff --git a/src/tests/__snapshots__/MobileMenu.test.tsx.snap b/src/tests/__snapshots__/MobileMenu.test.tsx.snap
new file mode 100644
index 000000000..f3e55fd99
--- /dev/null
+++ b/src/tests/__snapshots__/MobileMenu.test.tsx.snap
@@ -0,0 +1,240 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test MobileMenu should initialize with welcome screen and hide once user interacts 1`] = `
+
+
+
+
+
+
+
+
+ Excalidraw
+
+
+ All your data is saved locally in your browser.
+
+
+
+`;
diff --git a/src/tests/__snapshots__/contextmenu.test.tsx.snap b/src/tests/__snapshots__/contextmenu.test.tsx.snap
index c16a47c30..326cde0ed 100644
--- a/src/tests/__snapshots__/contextmenu.test.tsx.snap
+++ b/src/tests/__snapshots__/contextmenu.test.tsx.snap
@@ -4,23 +4,273 @@ exports[`contextMenu element right-clicking on a group should select whole group
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.cut",
+ "keyTest": [Function],
+ "name": "cut",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copy",
+ "keyTest": undefined,
+ "name": "copy",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyStyles",
+ "keyTest": [Function],
+ "name": "copyStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.pasteStyles",
+ "keyTest": [Function],
+ "name": "pasteStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.group",
+ "keyTest": [Function],
+ "name": "group",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.unbindText",
+ "name": "unbindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.bindText",
+ "name": "bindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.ungroup",
+ "keyTest": [Function],
+ "name": "ungroup",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.addToLibrary",
+ "name": "addToLibrary",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendBackward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "sendBackward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringForward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "bringForward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendToBack",
+ "keyTest": [Function],
+ "name": "sendToBack",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringToFront",
+ "keyTest": [Function],
+ "name": "bringToFront",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.flipHorizontal",
+ "keyTest": [Function],
+ "name": "flipHorizontal",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.flipVertical",
+ "keyTest": [Function],
+ "name": "flipVertical",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": [Function],
+ "name": "toggleLinearEditor",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "hyperlink",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "action": "click",
+ "category": "hyperlink",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.duplicateSelection",
+ "keyTest": [Function],
+ "name": "duplicateSelection",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "toggleLock",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.delete",
+ "keyTest": [Function],
+ "name": "deleteSelectedElements",
+ "perform": [Function],
+ "trackEvent": Object {
+ "action": "delete",
+ "category": "element",
+ },
+ },
+ ],
+ "left": 30,
+ "top": 40,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -106,9 +356,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -137,9 +389,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -174,29 +428,29 @@ Object {
exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of renders 1`] = `6`;
+exports[`contextMenu element right-clicking on a group should select whole group: [end of test] number of renders 1`] = `7`;
exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -279,9 +533,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -335,9 +591,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -356,29 +614,29 @@ Object {
exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of elements 1`] = `1`;
-exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `11`;
+exports[`contextMenu element selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `14`;
exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -459,9 +717,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -488,9 +748,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -544,9 +806,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -584,9 +848,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -610,9 +876,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -650,9 +918,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -676,9 +946,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -697,29 +969,29 @@ Object {
exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `16`;
+exports[`contextMenu element selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `18`;
exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -800,9 +1072,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -829,9 +1103,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -885,9 +1161,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -925,9 +1203,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -951,9 +1231,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -991,9 +1273,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1017,9 +1301,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1038,29 +1324,29 @@ Object {
exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `16`;
+exports[`contextMenu element selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `18`;
exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1143,9 +1429,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1199,9 +1487,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1220,29 +1510,29 @@ Object {
exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`;
-exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `11`;
+exports[`contextMenu element selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `14`;
exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1321,9 +1611,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1377,9 +1669,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1415,9 +1709,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1436,29 +1732,29 @@ Object {
exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of elements 1`] = `1`;
-exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `11`;
+exports[`contextMenu element selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `14`;
exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1539,9 +1835,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1568,9 +1866,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1624,9 +1924,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1664,9 +1966,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1690,9 +1994,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1711,29 +2017,29 @@ Object {
exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `11`;
+exports[`contextMenu element selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `14`;
exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1822,9 +2128,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1853,9 +2161,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1909,9 +2219,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1949,9 +2261,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1975,9 +2289,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2021,9 +2337,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2049,9 +2367,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2070,29 +2390,29 @@ Object {
exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `17`;
+exports[`contextMenu element selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `20`;
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "#e64980",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "cross-hatch",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 60,
"currentItemRoughness": 2,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#c92a2a",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "dotted",
"currentItemStrokeWidth": 2,
"currentItemTextAlign": "left",
@@ -2175,9 +2495,11 @@ Object {
"locked": false,
"opacity": 60,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2204,9 +2526,11 @@ Object {
"locked": false,
"opacity": 60,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2260,9 +2584,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2300,9 +2626,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2326,9 +2654,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2366,9 +2696,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2392,9 +2724,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2432,9 +2766,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2458,9 +2794,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2498,9 +2836,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2524,9 +2864,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2564,9 +2906,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2590,9 +2934,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
@@ -2630,9 +2976,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2656,9 +3004,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2696,9 +3046,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2722,9 +3074,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2762,9 +3116,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2788,9 +3144,11 @@ Object {
"locked": false,
"opacity": 60,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2828,9 +3186,11 @@ Object {
"locked": false,
"opacity": 60,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2854,9 +3214,11 @@ Object {
"locked": false,
"opacity": 60,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#c92a2a",
- "strokeSharpness": "sharp",
"strokeStyle": "dotted",
"strokeWidth": 2,
"type": "rectangle",
@@ -2875,29 +3237,29 @@ Object {
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `28`;
+exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `33`;
exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -2978,9 +3340,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3007,9 +3371,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3063,9 +3429,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3103,9 +3471,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3129,9 +3499,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3169,9 +3541,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3195,9 +3569,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3216,29 +3592,29 @@ Object {
exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `15`;
+exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `18`;
exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3319,9 +3695,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3348,9 +3726,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3404,9 +3784,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3444,9 +3826,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3470,9 +3854,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3510,9 +3896,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3536,9 +3924,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3557,29 +3947,29 @@ Object {
exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `15`;
+exports[`contextMenu element selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `18`;
exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3664,9 +4054,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3693,9 +4085,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3749,9 +4143,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3789,9 +4185,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3815,9 +4213,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3861,9 +4261,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3889,9 +4291,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3931,9 +4335,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3957,9 +4363,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3978,29 +4386,279 @@ Object {
exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `18`;
+exports[`contextMenu element selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `21`;
exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.cut",
+ "keyTest": [Function],
+ "name": "cut",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copy",
+ "keyTest": undefined,
+ "name": "copy",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyStyles",
+ "keyTest": [Function],
+ "name": "copyStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.pasteStyles",
+ "keyTest": [Function],
+ "name": "pasteStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.group",
+ "keyTest": [Function],
+ "name": "group",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.unbindText",
+ "name": "unbindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.bindText",
+ "name": "bindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.ungroup",
+ "keyTest": [Function],
+ "name": "ungroup",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.addToLibrary",
+ "name": "addToLibrary",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendBackward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "sendBackward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringForward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "bringForward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendToBack",
+ "keyTest": [Function],
+ "name": "sendToBack",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringToFront",
+ "keyTest": [Function],
+ "name": "bringToFront",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.flipHorizontal",
+ "keyTest": [Function],
+ "name": "flipHorizontal",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.flipVertical",
+ "keyTest": [Function],
+ "name": "flipVertical",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": [Function],
+ "name": "toggleLinearEditor",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "hyperlink",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "action": "click",
+ "category": "hyperlink",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.duplicateSelection",
+ "keyTest": [Function],
+ "name": "duplicateSelection",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "toggleLock",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.delete",
+ "keyTest": [Function],
+ "name": "deleteSelectedElements",
+ "perform": [Function],
+ "trackEvent": Object {
+ "action": "delete",
+ "category": "element",
+ },
+ },
+ ],
+ "left": -19,
+ "top": -9,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4087,9 +4745,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4116,9 +4776,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4172,9 +4834,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4212,9 +4876,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4238,9 +4904,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4259,29 +4927,279 @@ Object {
exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `18`;
+exports[`contextMenu element shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `20`;
exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.cut",
+ "keyTest": [Function],
+ "name": "cut",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copy",
+ "keyTest": undefined,
+ "name": "copy",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyStyles",
+ "keyTest": [Function],
+ "name": "copyStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.pasteStyles",
+ "keyTest": [Function],
+ "name": "pasteStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.group",
+ "keyTest": [Function],
+ "name": "group",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.unbindText",
+ "name": "unbindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.bindText",
+ "name": "bindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.ungroup",
+ "keyTest": [Function],
+ "name": "ungroup",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.addToLibrary",
+ "name": "addToLibrary",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendBackward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "sendBackward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringForward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "bringForward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendToBack",
+ "keyTest": [Function],
+ "name": "sendToBack",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringToFront",
+ "keyTest": [Function],
+ "name": "bringToFront",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.flipHorizontal",
+ "keyTest": [Function],
+ "name": "flipHorizontal",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.flipVertical",
+ "keyTest": [Function],
+ "name": "flipVertical",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": [Function],
+ "name": "toggleLinearEditor",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "hyperlink",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "action": "click",
+ "category": "hyperlink",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.duplicateSelection",
+ "keyTest": [Function],
+ "name": "duplicateSelection",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "toggleLock",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.delete",
+ "keyTest": [Function],
+ "name": "deleteSelectedElements",
+ "perform": [Function],
+ "trackEvent": Object {
+ "action": "delete",
+ "category": "element",
+ },
+ },
+ ],
+ "left": -19,
+ "top": -9,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4372,9 +5290,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4403,9 +5323,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4459,9 +5381,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4499,9 +5423,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4525,9 +5451,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4572,9 +5500,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4600,9 +5530,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4621,29 +5553,134 @@ Object {
exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of elements 1`] = `2`;
-exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `19`;
+exports[`contextMenu element shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `21`;
exports[`contextMenu element shows context menu for canvas: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.selectAll",
+ "keyTest": [Function],
+ "name": "selectAll",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "canvas",
+ },
+ },
+ "separator",
+ Object {
+ "checked": [Function],
+ "contextItemLabel": "labels.showGrid",
+ "keyTest": [Function],
+ "name": "gridMode",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "canvas",
+ "predicate": [Function],
+ },
+ "viewMode": true,
+ },
+ Object {
+ "checked": [Function],
+ "contextItemLabel": "buttons.zenMode",
+ "keyTest": [Function],
+ "name": "zenMode",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "canvas",
+ "predicate": [Function],
+ },
+ "viewMode": true,
+ },
+ Object {
+ "checked": [Function],
+ "contextItemLabel": "labels.viewMode",
+ "keyTest": [Function],
+ "name": "viewMode",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "canvas",
+ "predicate": [Function],
+ },
+ "viewMode": true,
+ },
+ Object {
+ "checked": [Function],
+ "contextItemLabel": "stats.title",
+ "keyTest": [Function],
+ "name": "stats",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "menu",
+ },
+ "viewMode": true,
+ },
+ ],
+ "left": -19,
+ "top": -9,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4730,29 +5767,279 @@ Object {
exports[`contextMenu element shows context menu for canvas: [end of test] number of elements 1`] = `0`;
-exports[`contextMenu element shows context menu for canvas: [end of test] number of renders 1`] = `4`;
+exports[`contextMenu element shows context menu for canvas: [end of test] number of renders 1`] = `6`;
exports[`contextMenu element shows context menu for element: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.cut",
+ "keyTest": [Function],
+ "name": "cut",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copy",
+ "keyTest": undefined,
+ "name": "copy",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyStyles",
+ "keyTest": [Function],
+ "name": "copyStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.pasteStyles",
+ "keyTest": [Function],
+ "name": "pasteStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.group",
+ "keyTest": [Function],
+ "name": "group",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.unbindText",
+ "name": "unbindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.bindText",
+ "name": "bindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.ungroup",
+ "keyTest": [Function],
+ "name": "ungroup",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.addToLibrary",
+ "name": "addToLibrary",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendBackward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "sendBackward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringForward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "bringForward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendToBack",
+ "keyTest": [Function],
+ "name": "sendToBack",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringToFront",
+ "keyTest": [Function],
+ "name": "bringToFront",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.flipHorizontal",
+ "keyTest": [Function],
+ "name": "flipHorizontal",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.flipVertical",
+ "keyTest": [Function],
+ "name": "flipVertical",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": [Function],
+ "name": "toggleLinearEditor",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "hyperlink",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "action": "click",
+ "category": "hyperlink",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.duplicateSelection",
+ "keyTest": [Function],
+ "name": "duplicateSelection",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "toggleLock",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.delete",
+ "keyTest": [Function],
+ "name": "deleteSelectedElements",
+ "perform": [Function],
+ "trackEvent": Object {
+ "action": "delete",
+ "category": "element",
+ },
+ },
+ ],
+ "left": -19,
+ "top": -9,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4823,23 +6110,273 @@ exports[`contextMenu element shows context menu for element: [end of test] appSt
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": Object {
+ "items": Array [
+ Object {
+ "contextItemLabel": "labels.cut",
+ "keyTest": [Function],
+ "name": "cut",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copy",
+ "keyTest": undefined,
+ "name": "copy",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.paste",
+ "keyTest": undefined,
+ "name": "paste",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyAsPng",
+ "keyTest": [Function],
+ "name": "copyAsPng",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyAsSvg",
+ "name": "copyAsSvg",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.copyText",
+ "name": "copyText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.copyStyles",
+ "keyTest": [Function],
+ "name": "copyStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.pasteStyles",
+ "keyTest": [Function],
+ "name": "pasteStyles",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.group",
+ "keyTest": [Function],
+ "name": "group",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.unbindText",
+ "name": "unbindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.bindText",
+ "name": "bindText",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.ungroup",
+ "keyTest": [Function],
+ "name": "ungroup",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.addToLibrary",
+ "name": "addToLibrary",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendBackward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "sendBackward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringForward",
+ "keyPriority": 40,
+ "keyTest": [Function],
+ "name": "bringForward",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.sendToBack",
+ "keyTest": [Function],
+ "name": "sendToBack",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.bringToFront",
+ "keyTest": [Function],
+ "name": "bringToFront",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": "labels.flipHorizontal",
+ "keyTest": [Function],
+ "name": "flipHorizontal",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": "labels.flipVertical",
+ "keyTest": [Function],
+ "name": "flipVertical",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "contextItemLabel": [Function],
+ "name": "toggleLinearEditor",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "hyperlink",
+ "perform": [Function],
+ "predicate": [Function],
+ "trackEvent": Object {
+ "action": "click",
+ "category": "hyperlink",
+ },
+ },
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.duplicateSelection",
+ "keyTest": [Function],
+ "name": "duplicateSelection",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ Object {
+ "contextItemLabel": [Function],
+ "keyTest": [Function],
+ "name": "toggleLock",
+ "perform": [Function],
+ "trackEvent": Object {
+ "category": "element",
+ },
+ },
+ "separator",
+ Object {
+ "PanelComponent": [Function],
+ "contextItemLabel": "labels.delete",
+ "keyTest": [Function],
+ "name": "deleteSelectedElements",
+ "perform": [Function],
+ "trackEvent": Object {
+ "action": "delete",
+ "category": "element",
+ },
+ },
+ ],
+ "left": 80,
+ "top": 90,
+ },
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4920,9 +6457,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4949,9 +6488,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4978,9 +6519,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5034,9 +6577,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5077,6 +6622,6 @@ exports[`contextMenu element shows context menu for element: [end of test] numbe
exports[`contextMenu element shows context menu for element: [end of test] number of elements 2`] = `2`;
-exports[`contextMenu element shows context menu for element: [end of test] number of renders 1`] = `10`;
+exports[`contextMenu element shows context menu for element: [end of test] number of renders 1`] = `12`;
-exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `7`;
+exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `11`;
diff --git a/src/tests/__snapshots__/dragCreate.test.tsx.snap b/src/tests/__snapshots__/dragCreate.test.tsx.snap
index ef7b8a685..ffcf57b0d 100644
--- a/src/tests/__snapshots__/dragCreate.test.tsx.snap
+++ b/src/tests/__snapshots__/dragCreate.test.tsx.snap
@@ -29,11 +29,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -62,9 +64,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -93,9 +97,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -135,11 +141,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -168,9 +176,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
diff --git a/src/tests/__snapshots__/linearElementEditor.test.tsx.snap b/src/tests/__snapshots__/linearElementEditor.test.tsx.snap
new file mode 100644
index 000000000..5ea0ab4b1
--- /dev/null
+++ b/src/tests/__snapshots__/linearElementEditor.test.tsx.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test Linear Elements Test bound text element should match styles for text editor 1`] = `
+
+`;
diff --git a/src/tests/__snapshots__/move.test.tsx.snap b/src/tests/__snapshots__/move.test.tsx.snap
index 68a733837..7c0eed29b 100644
--- a/src/tests/__snapshots__/move.test.tsx.snap
+++ b/src/tests/__snapshots__/move.test.tsx.snap
@@ -14,9 +14,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -43,9 +45,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -72,9 +76,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -106,9 +112,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -140,9 +148,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -186,6 +196,9 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"startArrowhead": null,
"startBinding": Object {
@@ -194,7 +207,6 @@ Object {
"gap": 10,
},
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
diff --git a/src/tests/__snapshots__/multiPointCreate.test.tsx.snap b/src/tests/__snapshots__/multiPointCreate.test.tsx.snap
index 2289ec869..03ef0d26b 100644
--- a/src/tests/__snapshots__/multiPointCreate.test.tsx.snap
+++ b/src/tests/__snapshots__/multiPointCreate.test.tsx.snap
@@ -34,11 +34,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -85,11 +87,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap
index 53e721001..35f9eb7c8 100644
--- a/src/tests/__snapshots__/regressionTests.test.tsx.snap
+++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap
@@ -4,23 +4,23 @@ exports[`given element A and group of elements B and given both are selected whe
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -112,9 +112,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -143,9 +145,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -174,9 +178,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -230,9 +236,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -270,9 +278,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -296,9 +306,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -336,9 +348,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -362,9 +376,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -388,9 +404,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -433,9 +451,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -461,9 +481,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -489,9 +511,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -516,23 +540,23 @@ exports[`given element A and group of elements B and given both are selected whe
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -631,9 +655,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -662,9 +688,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -693,9 +721,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -749,9 +779,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -789,9 +821,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -815,9 +849,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -855,9 +891,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -881,9 +919,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -907,9 +947,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -951,9 +993,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -979,9 +1023,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1007,9 +1053,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1034,23 +1082,23 @@ exports[`regression tests Cmd/Ctrl-click exclusively select element under pointe
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1135,9 +1183,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1167,9 +1217,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1198,9 +1250,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1254,9 +1308,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1294,9 +1350,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1320,9 +1378,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1367,9 +1427,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1395,9 +1457,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1438,9 +1502,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1466,9 +1532,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1508,9 +1576,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1536,9 +1606,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1562,9 +1634,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1611,9 +1685,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1640,9 +1716,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1668,9 +1746,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1712,9 +1792,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1741,9 +1823,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1769,9 +1853,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1813,9 +1899,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1842,9 +1930,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1870,9 +1960,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -1897,23 +1989,23 @@ exports[`regression tests Drags selected element when hitting only bounding box
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -1997,9 +2089,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -2053,9 +2147,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -2094,9 +2190,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -2121,23 +2219,23 @@ exports[`regression tests adjusts z order when grouping: [end of test] appState
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -2226,9 +2324,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2257,9 +2357,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2288,9 +2390,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2344,9 +2448,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2384,9 +2490,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2410,9 +2518,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2450,9 +2560,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2476,9 +2588,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2502,9 +2616,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2547,9 +2663,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2575,9 +2693,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2603,9 +2723,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2630,23 +2752,23 @@ exports[`regression tests alt-drag duplicates an element: [end of test] appState
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -2730,9 +2852,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2759,9 +2883,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2815,9 +2941,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2856,9 +2984,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2882,9 +3012,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -2909,23 +3041,23 @@ exports[`regression tests arrow keys: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3006,9 +3138,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3062,9 +3196,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3089,23 +3225,23 @@ exports[`regression tests can drag element that covers another element, while an
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3189,9 +3325,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3218,9 +3356,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3247,9 +3387,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -3303,9 +3445,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3343,9 +3487,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3369,9 +3515,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3409,9 +3557,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3435,9 +3585,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3461,9 +3613,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -3502,9 +3656,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3528,9 +3684,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 453191,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3554,9 +3712,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -3581,23 +3741,23 @@ exports[`regression tests change the properties of a shape: [end of test] appSta
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#5f3dc4",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3678,9 +3838,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#5f3dc4",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3734,9 +3896,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3774,9 +3938,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3814,9 +3980,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#5f3dc4",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3841,23 +4009,23 @@ exports[`regression tests click on an element and drag it: [dragged] appState 1`
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -3941,9 +4109,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -3997,9 +4167,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4038,9 +4210,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4065,23 +4239,23 @@ exports[`regression tests click on an element and drag it: [end of test] appStat
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4167,9 +4341,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4223,9 +4399,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4264,9 +4442,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4306,9 +4486,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4333,23 +4515,23 @@ exports[`regression tests click to select a shape: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4433,9 +4615,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4462,9 +4646,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4518,9 +4704,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4558,9 +4746,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4584,9 +4774,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4611,23 +4803,23 @@ exports[`regression tests click-drag to select a group: [end of test] appState 1
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -4712,9 +4904,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4741,9 +4935,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4770,9 +4966,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4826,9 +5024,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4866,9 +5066,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4892,9 +5094,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4932,9 +5136,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4958,9 +5164,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -4984,9 +5192,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5011,23 +5221,23 @@ exports[`regression tests deselects group of selected elements on pointer down w
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -5045,9 +5255,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -5115,9 +5327,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -5160,9 +5374,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5189,9 +5405,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -5245,9 +5463,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5285,9 +5505,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5311,9 +5533,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -5338,23 +5562,23 @@ exports[`regression tests deselects group of selected elements on pointer up whe
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -5372,9 +5596,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 2019559783,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -5462,9 +5688,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5491,9 +5719,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -5547,9 +5777,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5587,9 +5819,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5613,9 +5847,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -5640,23 +5876,23 @@ exports[`regression tests deselects selected element on pointer down when pointe
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -5674,9 +5910,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -5742,9 +5980,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -5787,9 +6027,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5843,9 +6085,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -5870,23 +6114,23 @@ exports[`regression tests deselects selected element, on pointer up, when click
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -5969,9 +6213,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -6025,9 +6271,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -6052,23 +6300,23 @@ exports[`regression tests double click to edit a group: [end of test] appState 1
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -6151,9 +6399,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6182,9 +6432,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6213,9 +6465,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6269,9 +6523,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6309,9 +6565,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6335,9 +6593,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6375,9 +6635,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6401,9 +6663,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6427,9 +6691,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6473,9 +6739,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6501,9 +6769,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6529,9 +6799,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6556,23 +6828,23 @@ exports[`regression tests drags selected elements from point inside common bound
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -6660,9 +6932,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6689,9 +6963,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -6745,9 +7021,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6785,9 +7063,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6811,9 +7091,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -6854,9 +7136,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -6880,9 +7164,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -6907,23 +7193,23 @@ exports[`regression tests draw every type of shape: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "freedraw",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -7004,9 +7290,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7033,9 +7321,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7062,9 +7352,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -7104,11 +7396,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -7148,11 +7442,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -7199,11 +7495,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -7250,11 +7548,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 760410951,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -7304,10 +7604,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 941653321,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -7361,9 +7661,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7401,9 +7703,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7427,9 +7731,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7467,9 +7773,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7493,9 +7801,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7519,9 +7829,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -7559,9 +7871,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7585,9 +7899,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7611,9 +7927,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -7650,11 +7968,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -7692,9 +8012,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7718,9 +8040,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7744,9 +8068,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -7783,11 +8109,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -7824,11 +8152,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -7866,9 +8196,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -7892,9 +8224,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -7918,9 +8252,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -7957,11 +8293,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -7998,11 +8336,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8042,11 +8382,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8084,9 +8426,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -8110,9 +8454,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -8136,9 +8482,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -8175,11 +8523,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8216,11 +8566,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8264,11 +8616,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8306,9 +8660,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -8332,9 +8688,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -8358,9 +8716,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -8397,11 +8757,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8438,11 +8800,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8486,11 +8850,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8530,11 +8896,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 760410951,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8572,9 +8940,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -8598,9 +8968,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -8624,9 +8996,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -8663,11 +9037,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8704,11 +9080,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8752,11 +9130,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8800,11 +9180,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 760410951,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -8842,9 +9224,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -8868,9 +9252,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -8894,9 +9280,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -8933,11 +9321,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1150084233,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -8974,11 +9364,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 238820263,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -9022,11 +9414,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1505387817,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -9070,11 +9464,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 760410951,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -9121,10 +9517,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 941653321,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -9149,23 +9545,23 @@ exports[`regression tests given a group of selected elements with an element tha
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -9251,9 +9647,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9280,9 +9678,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -9309,9 +9709,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -9365,9 +9767,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9405,9 +9809,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9431,9 +9837,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -9471,9 +9879,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9497,9 +9907,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -9523,9 +9935,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -9550,23 +9964,23 @@ exports[`regression tests given a selected element A and a not selected element
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -9653,9 +10067,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9682,9 +10098,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -9736,9 +10154,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9776,9 +10196,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9802,9 +10224,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -9829,23 +10253,23 @@ exports[`regression tests given selected element A with lower z-index than unsel
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -9930,9 +10354,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -9959,9 +10385,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10016,9 +10444,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10042,9 +10472,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10069,23 +10501,23 @@ exports[`regression tests given selected element A with lower z-index than unsel
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -10171,9 +10603,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10200,9 +10634,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10257,9 +10693,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10283,9 +10721,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10325,9 +10765,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10351,9 +10793,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1278240551,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10378,23 +10822,23 @@ exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -10475,9 +10919,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10531,9 +10977,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -10558,23 +11006,23 @@ exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -10655,9 +11103,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -10711,9 +11161,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -10738,23 +11190,23 @@ exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -10835,9 +11287,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -10891,9 +11345,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -10918,23 +11374,23 @@ exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -10989,7 +11445,13 @@ Object {
"lastUncommittedPoint": null,
"pointerDownState": Object {
"lastClickedPoint": -1,
+ "origin": null,
"prevSelectedPointsIndices": null,
+ "segmentMidpoint": Object {
+ "added": false,
+ "index": null,
+ "value": null,
+ },
},
"pointerOffset": Object {
"x": 0,
@@ -11045,11 +11507,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -11116,11 +11580,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -11145,23 +11611,23 @@ exports[`regression tests key 6 selects line tool: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -11216,7 +11682,13 @@ Object {
"lastUncommittedPoint": null,
"pointerDownState": Object {
"lastClickedPoint": -1,
+ "origin": null,
"prevSelectedPointsIndices": null,
+ "segmentMidpoint": Object {
+ "added": false,
+ "index": null,
+ "value": null,
+ },
},
"pointerOffset": Object {
"x": 0,
@@ -11272,11 +11744,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -11343,11 +11817,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -11372,23 +11848,23 @@ exports[`regression tests key 7 selects freedraw tool: [end of test] appState 1`
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "freedraw",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -11492,10 +11968,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -11572,10 +12048,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -11600,23 +12076,23 @@ exports[`regression tests key a selects arrow tool: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -11671,7 +12147,13 @@ Object {
"lastUncommittedPoint": null,
"pointerDownState": Object {
"lastClickedPoint": -1,
+ "origin": null,
"prevSelectedPointsIndices": null,
+ "segmentMidpoint": Object {
+ "added": false,
+ "index": null,
+ "value": null,
+ },
},
"pointerOffset": Object {
"x": 0,
@@ -11727,11 +12209,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -11798,11 +12282,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -11827,23 +12313,23 @@ exports[`regression tests key d selects diamond tool: [end of test] appState 1`]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -11924,9 +12410,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -11980,9 +12468,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -12007,23 +12497,23 @@ exports[`regression tests key l selects line tool: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -12078,7 +12568,13 @@ Object {
"lastUncommittedPoint": null,
"pointerDownState": Object {
"lastClickedPoint": -1,
+ "origin": null,
"prevSelectedPointsIndices": null,
+ "segmentMidpoint": Object {
+ "added": false,
+ "index": null,
+ "value": null,
+ },
},
"pointerOffset": Object {
"x": 0,
@@ -12134,11 +12630,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -12205,11 +12703,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -12234,23 +12734,23 @@ exports[`regression tests key o selects ellipse tool: [end of test] appState 1`]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -12331,9 +12831,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -12387,9 +12889,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -12414,23 +12918,23 @@ exports[`regression tests key p selects freedraw tool: [end of test] appState 1`
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "freedraw",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -12534,10 +13038,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -12614,10 +13118,10 @@ Object {
0,
],
"roughness": 1,
+ "roundness": null,
"seed": 337897,
"simulatePressure": false,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -12642,23 +13146,23 @@ exports[`regression tests key r selects rectangle tool: [end of test] appState 1
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -12739,9 +13243,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -12795,9 +13301,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -12822,23 +13330,23 @@ exports[`regression tests make a group and duplicate it: [end of test] appState
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -12932,9 +13440,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 915032327,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -12963,9 +13473,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 747212839,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -12994,9 +13506,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 760410951,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13025,9 +13539,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13056,9 +13572,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13087,9 +13605,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13143,9 +13663,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13183,9 +13705,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13209,9 +13733,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13249,9 +13775,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13275,9 +13803,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13301,9 +13831,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13348,9 +13880,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13376,9 +13910,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13404,9 +13940,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13452,9 +13990,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 915032327,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13480,9 +14020,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 747212839,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13508,9 +14050,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 760410951,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13536,9 +14080,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13564,9 +14110,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13592,9 +14140,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13619,23 +14169,23 @@ exports[`regression tests noop interaction after undo shouldn't create history e
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -13720,9 +14270,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13749,9 +14301,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13805,9 +14359,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13845,9 +14401,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13871,9 +14429,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -13898,23 +14458,23 @@ exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -14009,23 +14569,23 @@ exports[`regression tests rerenders UI on language change: [end of test] appStat
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "rectangle",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -14053,7 +14613,7 @@ Object {
"offsetLeft": 0,
"offsetTop": 0,
"openDialog": null,
- "openMenu": null,
+ "openMenu": "canvas",
"openPopup": null,
"openSidebar": null,
"pasteDialog": Object {
@@ -14112,29 +14672,29 @@ Object {
exports[`regression tests rerenders UI on language change: [end of test] number of elements 1`] = `0`;
-exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `10`;
+exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `11`;
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -14218,9 +14778,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14274,9 +14836,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14301,23 +14865,23 @@ exports[`regression tests shift-click to multiselect, then drag: [end of test] a
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -14407,9 +14971,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14436,9 +15002,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14492,9 +15060,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14532,9 +15102,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14558,9 +15130,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14602,9 +15176,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14628,9 +15204,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14655,23 +15233,23 @@ exports[`regression tests should group elements and ungroup them: [end of test]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -14758,9 +15336,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14787,9 +15367,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14816,9 +15398,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14872,9 +15456,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14912,9 +15498,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14938,9 +15526,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -14978,9 +15568,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15004,9 +15596,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15030,9 +15624,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15077,9 +15673,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15105,9 +15703,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15133,9 +15733,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15177,9 +15779,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15203,9 +15807,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15229,9 +15835,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15256,23 +15864,23 @@ exports[`regression tests should show fill icons when element has non transparen
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -15353,9 +15961,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15409,9 +16019,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15449,9 +16061,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15476,23 +16090,23 @@ exports[`regression tests single-clicking on a subgroup of a selected group shou
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -15587,9 +16201,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15619,9 +16235,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15651,9 +16269,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1014066025,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15683,9 +16303,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15739,9 +16361,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15779,9 +16403,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15805,9 +16431,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15852,9 +16480,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15880,9 +16510,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15922,9 +16554,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15950,9 +16584,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -15976,9 +16612,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1014066025,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16018,9 +16656,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16046,9 +16686,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16072,9 +16714,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1014066025,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16098,9 +16742,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16145,9 +16791,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16173,9 +16821,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16201,9 +16851,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1014066025,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16229,9 +16881,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16277,9 +16931,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16306,9 +16962,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16335,9 +16993,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 1014066025,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16364,9 +17024,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 400692809,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16391,23 +17053,23 @@ exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appS
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -16500,23 +17162,23 @@ exports[`regression tests supports nested groups: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -16601,9 +17263,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16633,9 +17297,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16665,9 +17331,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16721,9 +17389,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16761,9 +17431,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16787,9 +17459,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16827,9 +17501,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16853,9 +17529,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16879,9 +17557,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16925,9 +17605,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16953,9 +17635,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -16981,9 +17665,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17025,9 +17711,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17053,9 +17741,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17081,9 +17771,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17127,9 +17819,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17156,9 +17850,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17185,9 +17881,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17232,9 +17930,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17261,9 +17961,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17290,9 +17992,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17317,23 +18021,23 @@ exports[`regression tests switches from group of selected elements to another el
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -17351,9 +18055,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1116226695,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -17423,9 +18129,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 1116226695,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -17468,9 +18176,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17497,9 +18207,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -17526,9 +18238,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -17582,9 +18296,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17622,9 +18338,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17648,9 +18366,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -17688,9 +18408,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17714,9 +18436,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -17740,9 +18464,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -17767,23 +18493,23 @@ exports[`regression tests switches selected element on pointer down: [end of tes
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -17801,9 +18527,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -17871,9 +18599,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
@@ -17916,9 +18646,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -17945,9 +18677,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -18001,9 +18735,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18041,9 +18777,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18067,9 +18805,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -18094,23 +18834,23 @@ exports[`regression tests two-finger scroll works: [end of test] appState 1`] =
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -18205,23 +18945,23 @@ exports[`regression tests undo/redo drawing an element: [end of test] appState 1
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -18302,9 +19042,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18331,9 +19073,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18376,11 +19120,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -18422,9 +19168,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18448,9 +19196,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18494,11 +19244,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -18536,9 +19288,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18562,9 +19316,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18604,11 +19360,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 401146281,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -18659,9 +19417,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18699,9 +19459,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18725,9 +19487,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 449462985,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
@@ -18752,23 +19516,23 @@ exports[`regression tests updates fontSize & fontFamily appState: [end of test]
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "text",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 3,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
@@ -18861,23 +19625,23 @@ exports[`regression tests zoom hotkeys: [end of test] appState 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
diff --git a/src/tests/__snapshots__/selection.test.tsx.snap b/src/tests/__snapshots__/selection.test.tsx.snap
index 1bf265d1b..6379fa0aa 100644
--- a/src/tests/__snapshots__/selection.test.tsx.snap
+++ b/src/tests/__snapshots__/selection.test.tsx.snap
@@ -21,27 +21,25 @@ Object {
0,
0,
],
- Array [
- 15,
- 25,
- ],
Array [
30,
50,
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
"updated": 1,
- "version": 4,
- "versionNonce": 453191,
+ "version": 3,
+ "versionNonce": 449462985,
"width": 30,
"x": 10,
"y": 10,
@@ -69,27 +67,25 @@ Object {
0,
0,
],
- Array [
- 15,
- 25,
- ],
Array [
30,
50,
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "round",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
"updated": 1,
- "version": 4,
- "versionNonce": 453191,
+ "version": 3,
+ "versionNonce": 449462985,
"width": 30,
"x": 10,
"y": 10,
@@ -110,9 +106,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "diamond",
@@ -139,9 +137,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "ellipse",
@@ -168,9 +168,11 @@ Object {
"locked": false,
"opacity": 100,
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": 337897,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "rectangle",
diff --git a/src/tests/clients.test.ts b/src/tests/clients.test.ts
index f3fd174b4..a6a6901b1 100644
--- a/src/tests/clients.test.ts
+++ b/src/tests/clients.test.ts
@@ -36,4 +36,9 @@ describe("getClientInitials", () => {
result = getClientInitials(null);
expect(result).toBe("?");
});
+
+ it('returns "?" when value is blank', () => {
+ const result = getClientInitials(" ");
+ expect(result).toBe("?");
+ });
});
diff --git a/src/tests/clipboard.test.tsx b/src/tests/clipboard.test.tsx
new file mode 100644
index 000000000..26fcf4f0b
--- /dev/null
+++ b/src/tests/clipboard.test.tsx
@@ -0,0 +1,254 @@
+import ReactDOM from "react-dom";
+import { render, waitFor, GlobalTestState } from "./test-utils";
+import { Pointer, Keyboard } from "./helpers/ui";
+import ExcalidrawApp from "../excalidraw-app";
+import { KEYS } from "../keys";
+import { getApproxLineHeight } from "../element/textElement";
+import { getFontString } from "../utils";
+import { getElementBounds } from "../element";
+import { NormalizedZoomValue } from "../types";
+
+const { h } = window;
+
+const mouse = new Pointer("mouse");
+
+jest.mock("../keys.ts", () => {
+ const actual = jest.requireActual("../keys.ts");
+ return {
+ __esmodule: true,
+ ...actual,
+ isDarwin: false,
+ KEYS: {
+ ...actual.KEYS,
+ CTRL_OR_CMD: "ctrlKey",
+ },
+ };
+});
+
+const setClipboardText = (text: string) => {
+ Object.assign(navigator, {
+ clipboard: {
+ readText: () => text,
+ },
+ });
+};
+
+const sendPasteEvent = () => {
+ const clipboardEvent = new Event("paste", {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ });
+
+ // set `clipboardData` properties.
+ // @ts-ignore
+ clipboardEvent.clipboardData = {
+ getData: () => window.navigator.clipboard.readText(),
+ files: [],
+ };
+
+ document.dispatchEvent(clipboardEvent);
+};
+
+const pasteWithCtrlCmdShiftV = () => {
+ Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
+ //triggering keydown with an empty clipboard
+ Keyboard.keyPress(KEYS.V);
+ //triggering paste event with faked clipboard
+ sendPasteEvent();
+ });
+};
+
+const pasteWithCtrlCmdV = () => {
+ Keyboard.withModifierKeys({ ctrl: true }, () => {
+ //triggering keydown with an empty clipboard
+ Keyboard.keyPress(KEYS.V);
+ //triggering paste event with faked clipboard
+ sendPasteEvent();
+ });
+};
+
+const sleep = (ms: number) => {
+ return new Promise((resolve) => setTimeout(() => resolve(null), ms));
+};
+
+beforeEach(async () => {
+ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+
+ localStorage.clear();
+
+ mouse.reset();
+
+ await render( );
+ h.app.setAppState({ zoom: { value: 1 as NormalizedZoomValue } });
+ setClipboardText("");
+ Object.assign(document, {
+ elementFromPoint: () => GlobalTestState.canvas,
+ });
+});
+
+describe("paste text as single lines", () => {
+ it("should create an element for each line when copying with Ctrl/Cmd+V", async () => {
+ const text = "sajgfakfn\naaksfnknas\nakefnkasf";
+ setClipboardText(text);
+ pasteWithCtrlCmdV();
+ await waitFor(() => {
+ expect(h.elements.length).toEqual(text.split("\n").length);
+ });
+ });
+
+ it("should ignore empty lines when creating an element for each line", async () => {
+ const text = "\n\nsajgfakfn\n\n\naaksfnknas\n\nakefnkasf\n\n\n";
+ setClipboardText(text);
+ pasteWithCtrlCmdV();
+ await waitFor(() => {
+ expect(h.elements.length).toEqual(3);
+ });
+ });
+
+ it("should not create any element if clipboard has only new lines", async () => {
+ const text = "\n\n\n\n\n";
+ setClipboardText(text);
+ pasteWithCtrlCmdV();
+ await waitFor(async () => {
+ await sleep(50); // elements lenght will always be zero if we don't wait, since paste is async
+ expect(h.elements.length).toEqual(0);
+ });
+ });
+
+ it("should space items correctly", async () => {
+ const text = "hkhkjhki\njgkjhffjh\njgkjhffjh";
+ const lineHeight =
+ getApproxLineHeight(
+ getFontString({
+ fontSize: h.app.state.currentItemFontSize,
+ fontFamily: h.app.state.currentItemFontFamily,
+ }),
+ ) +
+ 10 / h.app.state.zoom.value;
+ mouse.moveTo(100, 100);
+ setClipboardText(text);
+ pasteWithCtrlCmdV();
+ await waitFor(async () => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [fx, firstElY] = getElementBounds(h.elements[0]);
+ for (let i = 1; i < h.elements.length; i++) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [fx, elY] = getElementBounds(h.elements[i]);
+ expect(elY).toEqual(firstElY + lineHeight * i);
+ }
+ });
+ });
+
+ it("should leave a space for blank new lines", async () => {
+ const text = "hkhkjhki\n\njgkjhffjh";
+ const lineHeight =
+ getApproxLineHeight(
+ getFontString({
+ fontSize: h.app.state.currentItemFontSize,
+ fontFamily: h.app.state.currentItemFontFamily,
+ }),
+ ) +
+ 10 / h.app.state.zoom.value;
+ mouse.moveTo(100, 100);
+ setClipboardText(text);
+ pasteWithCtrlCmdV();
+ await waitFor(async () => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [fx, firstElY] = getElementBounds(h.elements[0]);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [lx, lastElY] = getElementBounds(h.elements[1]);
+ expect(lastElY).toEqual(firstElY + lineHeight * 2);
+ });
+ });
+});
+
+describe("paste text as a single element", () => {
+ it("should create single text element when copying text with Ctrl/Cmd+Shift+V", async () => {
+ const text = "sajgfakfn\naaksfnknas\nakefnkasf";
+ setClipboardText(text);
+ pasteWithCtrlCmdShiftV();
+ await waitFor(() => {
+ expect(h.elements.length).toEqual(1);
+ });
+ });
+ it("should not create any element when only new lines in clipboard", async () => {
+ const text = "\n\n\n\n";
+ setClipboardText(text);
+ pasteWithCtrlCmdShiftV();
+ await waitFor(async () => {
+ await sleep(50);
+ expect(h.elements.length).toEqual(0);
+ });
+ });
+});
+
+describe("Paste bound text container", () => {
+ const container = {
+ type: "ellipse",
+ id: "container-id",
+ x: 554.984375,
+ y: 196.0234375,
+ width: 166,
+ height: 187.01953125,
+ roundness: { type: 2 },
+ boundElements: [{ type: "text", id: "text-id" }],
+ };
+ const textElement = {
+ type: "text",
+ id: "text-id",
+ x: 560.51171875,
+ y: 202.033203125,
+ width: 154,
+ height: 175,
+ fontSize: 20,
+ fontFamily: 1,
+ text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
+ baseline: 168,
+ textAlign: "center",
+ verticalAlign: "middle",
+ containerId: container.id,
+ originalText:
+ "Excalidraw is a virtual opensource whiteboard for sketching hand-drawn like diagrams",
+ };
+
+ it("should fix ellipse bounding box", async () => {
+ const data = JSON.stringify({
+ type: "excalidraw/clipboard",
+ elements: [container, textElement],
+ });
+ setClipboardText(data);
+ pasteWithCtrlCmdShiftV();
+
+ await waitFor(async () => {
+ await sleep(1);
+ expect(h.elements.length).toEqual(2);
+ const container = h.elements[0];
+ expect(container.height).toBe(354);
+ expect(container.width).toBe(166);
+ });
+ });
+
+ it("should fix diamond bounding box", async () => {
+ const data = JSON.stringify({
+ type: "excalidraw/clipboard",
+ elements: [
+ {
+ ...container,
+ type: "diamond",
+ },
+ textElement,
+ ],
+ });
+ setClipboardText(data);
+ pasteWithCtrlCmdShiftV();
+
+ await waitFor(async () => {
+ await sleep(1);
+ expect(h.elements.length).toEqual(2);
+ const container = h.elements[0];
+ expect(container.height).toBe(740);
+ expect(container.width).toBe(166);
+ });
+ });
+});
diff --git a/src/tests/data/__snapshots__/restore.test.ts.snap b/src/tests/data/__snapshots__/restore.test.ts.snap
index 34da06732..b88803cd4 100644
--- a/src/tests/data/__snapshots__/restore.test.ts.snap
+++ b/src/tests/data/__snapshots__/restore.test.ts.snap
@@ -27,11 +27,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": Any,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "arrow",
@@ -62,9 +64,11 @@ Object {
"locked": false,
"opacity": 10,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"strokeColor": "red",
- "strokeSharpness": "round",
"strokeStyle": "dashed",
"strokeWidth": 2,
"type": "rectangle",
@@ -95,9 +99,11 @@ Object {
"locked": false,
"opacity": 10,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"strokeColor": "red",
- "strokeSharpness": "round",
"strokeStyle": "dashed",
"strokeWidth": 2,
"type": "ellipse",
@@ -128,9 +134,11 @@ Object {
"locked": false,
"opacity": 10,
"roughness": 2,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"strokeColor": "red",
- "strokeSharpness": "round",
"strokeStyle": "dashed",
"strokeWidth": 2,
"type": "diamond",
@@ -160,10 +168,12 @@ Object {
"points": Array [],
"pressures": Array [],
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"simulatePressure": true,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "freedraw",
@@ -203,11 +213,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": Any,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -247,11 +259,13 @@ Object {
],
],
"roughness": 1,
+ "roundness": Object {
+ "type": 2,
+ },
"seed": Any,
"startArrowhead": null,
"startBinding": null,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "line",
@@ -268,7 +282,6 @@ exports[`restoreElements should restore text element correctly passing value for
Object {
"angle": 0,
"backgroundColor": "transparent",
- "baseline": 0,
"boundElements": Array [],
"containerId": null,
"fillStyle": "hachure",
@@ -283,9 +296,11 @@ Object {
"opacity": 100,
"originalText": "text",
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"text": "text",
@@ -296,8 +311,8 @@ Object {
"versionNonce": 0,
"verticalAlign": "middle",
"width": 100,
- "x": -0.5,
- "y": 0,
+ "x": -20,
+ "y": -8.4,
}
`;
@@ -305,7 +320,6 @@ exports[`restoreElements should restore text element correctly with unknown font
Object {
"angle": 0,
"backgroundColor": "transparent",
- "baseline": 0,
"boundElements": Array [],
"containerId": null,
"fillStyle": "hachure",
@@ -320,9 +334,11 @@ Object {
"opacity": 100,
"originalText": "test",
"roughness": 1,
+ "roundness": Object {
+ "type": 3,
+ },
"seed": Any,
"strokeColor": "#000000",
- "strokeSharpness": "sharp",
"strokeStyle": "solid",
"strokeWidth": 1,
"text": "",
diff --git a/src/tests/data/restore.test.ts b/src/tests/data/restore.test.ts
index 3ce03e58d..afd5ef918 100644
--- a/src/tests/data/restore.test.ts
+++ b/src/tests/data/restore.test.ts
@@ -10,16 +10,20 @@ import { API } from "../helpers/api";
import { getDefaultAppState } from "../../appState";
import { ImportedDataState } from "../../data/types";
import { NormalizedZoomValue } from "../../types";
-import { FONT_FAMILY } from "../../constants";
+import { FONT_FAMILY, ROUNDNESS } from "../../constants";
import { newElementWith } from "../../element/mutateElement";
-const mockSizeHelper = jest.spyOn(sizeHelpers, "isInvisiblySmallElement");
-
-beforeEach(() => {
- mockSizeHelper.mockReset();
-});
-
describe("restoreElements", () => {
+ const mockSizeHelper = jest.spyOn(sizeHelpers, "isInvisiblySmallElement");
+
+ beforeEach(() => {
+ mockSizeHelper.mockReset();
+ });
+
+ afterAll(() => {
+ mockSizeHelper.mockRestore();
+ });
+
it("should return empty array when element is null", () => {
expect(restore.restoreElements(null, null)).toStrictEqual([]);
});
@@ -255,7 +259,7 @@ describe("restoreElements", () => {
width: 100,
height: 200,
groupIds: ["1", "2", "3"],
- strokeSharpness: "round",
+ roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
});
elements.push(element);
@@ -528,3 +532,236 @@ describe("restore", () => {
]);
});
});
+
+describe("repairing bindings", () => {
+ it("should repair container boundElements when repair is true", () => {
+ const container = API.createElement({
+ type: "rectangle",
+ boundElements: [],
+ });
+ const boundElement = API.createElement({
+ type: "text",
+ containerId: container.id,
+ });
+
+ expect(container.boundElements).toEqual([]);
+
+ let restoredElements = restore.restoreElements(
+ [container, boundElement],
+ null,
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+
+ restoredElements = restore.restoreElements(
+ [container, boundElement],
+ null,
+ { repairBindings: true },
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [{ type: boundElement.type, id: boundElement.id }],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+ });
+
+ it("should repair containerId of boundElements when repair is true", () => {
+ const boundElement = API.createElement({
+ type: "text",
+ containerId: null,
+ });
+ const container = API.createElement({
+ type: "rectangle",
+ boundElements: [{ type: boundElement.type, id: boundElement.id }],
+ });
+
+ let restoredElements = restore.restoreElements(
+ [container, boundElement],
+ null,
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [{ type: boundElement.type, id: boundElement.id }],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: null,
+ }),
+ ]);
+
+ restoredElements = restore.restoreElements(
+ [container, boundElement],
+ null,
+ { repairBindings: true },
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [{ type: boundElement.type, id: boundElement.id }],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+ });
+
+ it("should ignore bound element if deleted", () => {
+ const container = API.createElement({
+ type: "rectangle",
+ boundElements: [],
+ });
+ const boundElement = API.createElement({
+ type: "text",
+ containerId: container.id,
+ isDeleted: true,
+ });
+
+ expect(container.boundElements).toEqual([]);
+
+ const restoredElements = restore.restoreElements(
+ [container, boundElement],
+ null,
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+ });
+
+ it("should remove bindings of deleted elements from boundElements when repair is true", () => {
+ const container = API.createElement({
+ type: "rectangle",
+ boundElements: [],
+ });
+ const boundElement = API.createElement({
+ type: "text",
+ containerId: container.id,
+ isDeleted: true,
+ });
+ const invisibleBoundElement = API.createElement({
+ type: "text",
+ containerId: container.id,
+ width: 0,
+ height: 0,
+ });
+
+ const obsoleteBinding = { type: boundElement.type, id: boundElement.id };
+ const invisibleBinding = {
+ type: invisibleBoundElement.type,
+ id: invisibleBoundElement.id,
+ };
+ expect(container.boundElements).toEqual([]);
+
+ const nonExistentBinding = { type: "text", id: "non-existent" };
+ // @ts-ignore
+ container.boundElements = [
+ obsoleteBinding,
+ invisibleBinding,
+ nonExistentBinding,
+ ];
+
+ let restoredElements = restore.restoreElements(
+ [container, invisibleBoundElement, boundElement],
+ null,
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [obsoleteBinding, invisibleBinding, nonExistentBinding],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+
+ restoredElements = restore.restoreElements(
+ [container, invisibleBoundElement, boundElement],
+ null,
+ { repairBindings: true },
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: container.id,
+ boundElements: [],
+ }),
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: container.id,
+ }),
+ ]);
+ });
+
+ it("should remove containerId if container not exists when repair is true", () => {
+ const boundElement = API.createElement({
+ type: "text",
+ containerId: "non-existent",
+ });
+ const boundElementDeleted = API.createElement({
+ type: "text",
+ containerId: "non-existent",
+ isDeleted: true,
+ });
+
+ let restoredElements = restore.restoreElements(
+ [boundElement, boundElementDeleted],
+ null,
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: "non-existent",
+ }),
+ expect.objectContaining({
+ id: boundElementDeleted.id,
+ containerId: "non-existent",
+ }),
+ ]);
+
+ restoredElements = restore.restoreElements(
+ [boundElement, boundElementDeleted],
+ null,
+ { repairBindings: true },
+ );
+
+ expect(restoredElements).toEqual([
+ expect.objectContaining({
+ id: boundElement.id,
+ containerId: null,
+ }),
+ expect.objectContaining({
+ id: boundElementDeleted.id,
+ containerId: null,
+ }),
+ ]);
+ });
+});
diff --git a/src/tests/elementLocking.test.tsx b/src/tests/elementLocking.test.tsx
index f23e2d214..9cf0968d4 100644
--- a/src/tests/elementLocking.test.tsx
+++ b/src/tests/elementLocking.test.tsx
@@ -152,7 +152,7 @@ describe("element locking", () => {
expect(contextMenu).not.toBeNull();
expect(
contextMenu?.querySelector(
- `li[data-testid="toggleLock"] .context-menu-option__label`,
+ `li[data-testid="toggleLock"] .context-menu-item__label`,
),
).toHaveTextContent(t("labels.elementLock.unlock"));
});
@@ -232,7 +232,7 @@ describe("element locking", () => {
API.setSelectedElements([container]);
Keyboard.keyPress(KEYS.ENTER);
expect(h.state.editingElement?.id).not.toBe(text.id);
- expect(h.state.editingElement?.id).toBe(h.elements[2].id);
+ expect(h.state.editingElement?.id).toBe(h.elements[1].id);
});
it("should ignore locked text under cursor when clicked with text tool", () => {
diff --git a/src/tests/fixtures/elementFixture.ts b/src/tests/fixtures/elementFixture.ts
index ea5980ca1..e9db64891 100644
--- a/src/tests/fixtures/elementFixture.ts
+++ b/src/tests/fixtures/elementFixture.ts
@@ -15,7 +15,7 @@ const elementBase: Omit = {
roughness: 1,
opacity: 100,
groupIds: [],
- strokeSharpness: "sharp",
+ roundness: null,
seed: 1041657908,
version: 120,
versionNonce: 1188004276,
diff --git a/src/tests/flip.test.tsx b/src/tests/flip.test.tsx
index a388ad4eb..45a5e1477 100644
--- a/src/tests/flip.test.tsx
+++ b/src/tests/flip.test.tsx
@@ -1,22 +1,53 @@
import ReactDOM from "react-dom";
-import { render } from "./test-utils";
-import App from "../components/App";
-import { defaultLang, setLanguage } from "../i18n";
+import { GlobalTestState, render, waitFor } from "./test-utils";
import { UI, Pointer } from "./helpers/ui";
import { API } from "./helpers/api";
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
+import { getElementAbsoluteCoords } from "../element";
+import {
+ ExcalidrawElement,
+ ExcalidrawImageElement,
+ ExcalidrawLinearElement,
+ FileId,
+} from "../element/types";
+import { newLinearElement } from "../element";
+import ExcalidrawApp from "../excalidraw-app";
+import { mutateElement } from "../element/mutateElement";
+import { NormalizedZoomValue } from "../types";
+import { ROUNDNESS } from "../constants";
const { h } = window;
const mouse = new Pointer("mouse");
+jest.mock("../data/blob", () => {
+ const originalModule = jest.requireActual("../data/blob");
+ //Prevent Node.js modules errors (document is not defined etc...)
+ return {
+ __esModule: true,
+ ...originalModule,
+ resizeImageFile: (imageFile: File) => imageFile,
+ generateIdFromFile: () => "fileId" as FileId,
+ };
+});
beforeEach(async () => {
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
- mouse.reset();
- await setLanguage(defaultLang);
- await render( );
+ mouse.reset();
+ localStorage.clear();
+ sessionStorage.clear();
+ jest.clearAllMocks();
+
+ Object.assign(document, {
+ elementFromPoint: () => GlobalTestState.canvas,
+ });
+ await render( );
+ h.setState({
+ zoom: {
+ value: 1 as NormalizedZoomValue,
+ },
+ });
});
const createAndSelectOneRectangle = (angle: number = 0) => {
@@ -79,593 +110,676 @@ const createAndReturnOneDraw = (angle: number = 0) => {
});
};
-const FLIP_PRECISION_DECIMALS = 7;
+const createLinearElementWithCurveInsideMinMaxPoints = (
+ type: "line" | "arrow",
+ extraProps: any = {},
+) => {
+ return newLinearElement({
+ type,
+ x: 2256.910668124894,
+ y: -2412.5069664197654,
+ width: 1750.4888916015625,
+ height: 410.51605224609375,
+ angle: 0,
+ strokeColor: "#000000",
+ backgroundColor: "#fa5252",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ groupIds: [],
+ roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
+ boundElements: null,
+ link: null,
+ locked: false,
+ points: [
+ [0, 0],
+ [-922.4761962890625, 300.3277587890625],
+ [828.0126953125, 410.51605224609375],
+ ],
+ startArrowhead: null,
+ endArrowhead: null,
+ });
+};
+
+const createLinearElementsWithCurveOutsideMinMaxPoints = (
+ type: "line" | "arrow",
+ extraProps: any = {},
+) => {
+ return newLinearElement({
+ type,
+ x: -1388.6555370382996,
+ y: 1037.698247710191,
+ width: 591.2804897585779,
+ height: 69.32871961377737,
+ angle: 0,
+ strokeColor: "#000000",
+ backgroundColor: "transparent",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ opacity: 100,
+ groupIds: [],
+ roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
+ boundElements: null,
+ link: null,
+ locked: false,
+ points: [
+ [0, 0],
+ [-584.1485186423079, -15.365636022723947],
+ [-591.2804897585779, 36.09360810181511],
+ [-148.56510566829502, 53.96308359105342],
+ ],
+ startArrowhead: null,
+ endArrowhead: null,
+ ...extraProps,
+ });
+};
+
+const checkElementsBoundingBox = async (
+ element1: ExcalidrawElement,
+ element2: ExcalidrawElement,
+ toleranceInPx: number = 0,
+) => {
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element1);
+
+ const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2);
+
+ debugger;
+ await waitFor(() => {
+ // Check if width and height did not change
+ expect(x1 - toleranceInPx <= x12 && x12 <= x1 + toleranceInPx).toBeTruthy();
+ expect(y1 - toleranceInPx <= y12 && y12 <= y1 + toleranceInPx).toBeTruthy();
+ expect(x2 - toleranceInPx <= x22 && x22 <= x2 + toleranceInPx).toBeTruthy();
+ expect(y2 - toleranceInPx <= y22 && y22 <= y2 + toleranceInPx).toBeTruthy();
+ });
+};
+
+const checkHorizontalFlip = async (toleranceInPx: number = 0.00001) => {
+ const originalElement = JSON.parse(JSON.stringify(h.elements[0]));
+ h.app.actionManager.executeAction(actionFlipHorizontal);
+ const newElement = h.elements[0];
+ await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
+};
+
+const checkTwoPointsLineHorizontalFlip = async () => {
+ const originalElement = JSON.parse(
+ JSON.stringify(h.elements[0]),
+ ) as ExcalidrawLinearElement;
+ h.app.actionManager.executeAction(actionFlipHorizontal);
+ const newElement = h.elements[0] as ExcalidrawLinearElement;
+ await waitFor(() => {
+ expect(originalElement.points[0][0]).toEqual(
+ newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
+ );
+ expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
+ expect(originalElement.points[1][0]).toEqual(
+ newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
+ );
+ expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]);
+ });
+};
+
+const checkTwoPointsLineVerticalFlip = async () => {
+ const originalElement = JSON.parse(
+ JSON.stringify(h.elements[0]),
+ ) as ExcalidrawLinearElement;
+ h.app.actionManager.executeAction(actionFlipVertical);
+ const newElement = h.elements[0] as ExcalidrawLinearElement;
+ await waitFor(() => {
+ expect(originalElement.points[0][0]).toEqual(
+ newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0,
+ );
+ expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]);
+ expect(originalElement.points[1][0]).toEqual(
+ newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0,
+ );
+ expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]);
+ });
+};
+
+const checkRotatedHorizontalFlip = async (
+ expectedAngle: number,
+ toleranceInPx: number = 0.00001,
+) => {
+ const originalElement = JSON.parse(JSON.stringify(h.elements[0]));
+ h.app.actionManager.executeAction(actionFlipHorizontal);
+ const newElement = h.elements[0];
+ await waitFor(() => {
+ expect(newElement.angle).toBeCloseTo(expectedAngle);
+ });
+ await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
+};
+
+const checkRotatedVerticalFlip = async (
+ expectedAngle: number,
+ toleranceInPx: number = 0.00001,
+) => {
+ const originalElement = JSON.parse(JSON.stringify(h.elements[0]));
+ h.app.actionManager.executeAction(actionFlipVertical);
+ const newElement = h.elements[0];
+ await waitFor(() => {
+ expect(newElement.angle).toBeCloseTo(expectedAngle);
+ });
+ await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
+};
+
+const checkVerticalFlip = async (toleranceInPx: number = 0.00001) => {
+ const originalElement = JSON.parse(JSON.stringify(h.elements[0]));
+
+ h.app.actionManager.executeAction(actionFlipVertical);
+
+ const newElement = h.elements[0];
+ await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
+};
+
+const checkVerticalHorizontalFlip = async (toleranceInPx: number = 0.00001) => {
+ const originalElement = JSON.parse(JSON.stringify(h.elements[0]));
+
+ h.app.actionManager.executeAction(actionFlipHorizontal);
+ h.app.actionManager.executeAction(actionFlipVertical);
+
+ const newElement = h.elements[0];
+ await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
+};
+
+const TWO_POINTS_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS = 5;
+const MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS = 20;
// Rectangle element
+describe("rectangle", () => {
+ it("flips an unrotated rectangle horizontally correctly", async () => {
+ createAndSelectOneRectangle();
-it("flips an unrotated rectangle horizontally correctly", () => {
- createAndSelectOneRectangle();
+ await checkHorizontalFlip();
+ });
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ it("flips an unrotated rectangle vertically correctly", async () => {
+ createAndSelectOneRectangle();
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ await checkVerticalFlip();
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips a rotated rectangle horizontally correctly", async () => {
+ const originalAngle = (3 * Math.PI) / 4;
+ const expectedAngle = (5 * Math.PI) / 4;
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ createAndSelectOneRectangle(originalAngle);
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ await checkRotatedHorizontalFlip(expectedAngle);
+ });
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ it("flips a rotated rectangle vertically correctly", async () => {
+ const originalAngle = (3 * Math.PI) / 4;
+ const expectedAgnle = Math.PI / 4;
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
+ createAndSelectOneRectangle(originalAngle);
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips an unrotated rectangle vertically correctly", () => {
- createAndSelectOneRectangle();
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips a rotated rectangle horizontally correctly", () => {
- const originalAngle = (3 * Math.PI) / 4;
- const expectedAngle = (5 * Math.PI) / 4;
-
- createAndSelectOneRectangle(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipHorizontal);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
-});
-
-it("flips a rotated rectangle vertically correctly", () => {
- const originalAngle = (3 * Math.PI) / 4;
- const expectedAgnle = Math.PI / 4;
-
- createAndSelectOneRectangle(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAgnle);
+ await checkRotatedVerticalFlip(expectedAgnle);
+ });
});
// Diamond element
+describe("diamond", () => {
+ it("flips an unrotated diamond horizontally correctly", async () => {
+ createAndSelectOneDiamond();
-it("flips an unrotated diamond horizontally correctly", () => {
- createAndSelectOneDiamond();
+ await checkHorizontalFlip();
+ });
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ it("flips an unrotated diamond vertically correctly", async () => {
+ createAndSelectOneDiamond();
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ await checkVerticalFlip();
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips a rotated diamond horizontally correctly", async () => {
+ const originalAngle = (5 * Math.PI) / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ createAndSelectOneDiamond(originalAngle);
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ await checkRotatedHorizontalFlip(expectedAngle);
+ });
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ it("flips a rotated diamond vertically correctly", async () => {
+ const originalAngle = (5 * Math.PI) / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
+ createAndSelectOneDiamond(originalAngle);
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips an unrotated diamond vertically correctly", () => {
- createAndSelectOneDiamond();
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips a rotated diamond horizontally correctly", () => {
- const originalAngle = (5 * Math.PI) / 4;
- const expectedAngle = (3 * Math.PI) / 4;
-
- createAndSelectOneDiamond(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipHorizontal);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
-});
-
-it("flips a rotated diamond vertically correctly", () => {
- const originalAngle = (5 * Math.PI) / 4;
- const expectedAngle = (7 * Math.PI) / 4;
-
- createAndSelectOneDiamond(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
+ await checkRotatedVerticalFlip(expectedAngle);
+ });
});
// Ellipse element
+describe("ellipse", () => {
+ it("flips an unrotated ellipse horizontally correctly", async () => {
+ createAndSelectOneEllipse();
-it("flips an unrotated ellipse horizontally correctly", () => {
- createAndSelectOneEllipse();
+ await checkHorizontalFlip();
+ });
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ it("flips an unrotated ellipse vertically correctly", async () => {
+ createAndSelectOneEllipse();
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ await checkVerticalFlip();
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips a rotated ellipse horizontally correctly", async () => {
+ const originalAngle = (7 * Math.PI) / 4;
+ const expectedAngle = Math.PI / 4;
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ createAndSelectOneEllipse(originalAngle);
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
+ await checkRotatedHorizontalFlip(expectedAngle);
+ });
- expect(API.getSelectedElements()[0].y).toEqual(0);
+ it("flips a rotated ellipse vertically correctly", async () => {
+ const originalAngle = (7 * Math.PI) / 4;
+ const expectedAngle = (5 * Math.PI) / 4;
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
+ createAndSelectOneEllipse(originalAngle);
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips an unrotated ellipse vertically correctly", () => {
- createAndSelectOneEllipse();
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-});
-
-it("flips a rotated ellipse horizontally correctly", () => {
- const originalAngle = (7 * Math.PI) / 4;
- const expectedAngle = Math.PI / 4;
-
- createAndSelectOneEllipse(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipHorizontal);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
-});
-
-it("flips a rotated ellipse vertically correctly", () => {
- const originalAngle = (7 * Math.PI) / 4;
- const expectedAngle = (5 * Math.PI) / 4;
-
- createAndSelectOneEllipse(originalAngle);
-
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if x position did not change
- expect(API.getSelectedElements()[0].x).toEqual(0);
-
- expect(API.getSelectedElements()[0].y).toEqual(0);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
-
- expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
+ await checkRotatedVerticalFlip(expectedAngle);
+ });
});
// Arrow element
+describe("arrow", () => {
+ it("flips an unrotated arrow horizontally with line inside min/max points bounds", async () => {
+ const arrow = createLinearElementWithCurveInsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([arrow]);
+ h.app.setState({ selectedElementIds: { [arrow.id]: true } });
+ await checkHorizontalFlip(
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
-it("flips an unrotated arrow horizontally correctly", () => {
- createAndSelectOneArrow();
+ it("flips an unrotated arrow vertically with line inside min/max points bounds", async () => {
+ const arrow = createLinearElementWithCurveInsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([arrow]);
+ h.app.setState({ selectedElementIds: { [arrow.id]: true } });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ await checkVerticalFlip(50);
+ });
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ it("flips a rotated arrow horizontally with line inside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
+ const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([line]);
+ h.app.state.selectedElementIds[line.id] = true;
+ mutateElement(line, {
+ angle: originalAngle,
+ });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ await checkRotatedHorizontalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-});
+ it("flips a rotated arrow vertically with line inside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+ const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([line]);
+ h.app.state.selectedElementIds[line.id] = true;
+ mutateElement(line, {
+ angle: originalAngle,
+ });
-it("flips an unrotated arrow vertically correctly", () => {
- createAndSelectOneArrow();
+ await checkRotatedVerticalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ //TODO: elements with curve outside minMax points have a wrong bounding box!!!
+ it.skip("flips an unrotated arrow horizontally with line outside min/max points bounds", async () => {
+ const arrow = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([arrow]);
+ h.app.setState({ selectedElementIds: { [arrow.id]: true } });
- h.app.actionManager.executeAction(actionFlipVertical);
+ await checkHorizontalFlip(
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ //TODO: elements with curve outside minMax points have a wrong bounding box!!!
+ it.skip("flips a rotated arrow horizontally with line outside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
+ mutateElement(line, { angle: originalAngle });
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-});
+ await checkRotatedVerticalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
-//@TODO fix the tests with rotation
-it.skip("flips a rotated arrow horizontally correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (7 * Math.PI) / 4;
- createAndSelectOneArrow(originalAngle);
+ //TODO: elements with curve outside minMax points have a wrong bounding box!!!
+ it.skip("flips an unrotated arrow vertically with line outside min/max points bounds", async () => {
+ const arrow = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
+ h.app.scene.replaceAllElements([arrow]);
+ h.app.setState({ selectedElementIds: { [arrow.id]: true } });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
+ });
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ //TODO: elements with curve outside minMax points have a wrong bounding box!!!
+ it.skip("flips a rotated arrow vertically with line outside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
+ mutateElement(line, { angle: originalAngle });
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ await checkRotatedVerticalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
-});
+ it("flips an unrotated arrow horizontally correctly", async () => {
+ createAndSelectOneArrow();
+ await checkHorizontalFlip(
+ TWO_POINTS_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
-it.skip("flips a rotated arrow vertically correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (3 * Math.PI) / 4;
- createAndSelectOneArrow(originalAngle);
+ it("flips an unrotated arrow vertically correctly", async () => {
+ createAndSelectOneArrow();
+ await checkVerticalFlip(TWO_POINTS_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips a two points arrow horizontally correctly", async () => {
+ createAndSelectOneArrow();
+ await checkTwoPointsLineHorizontalFlip();
+ });
- h.app.actionManager.executeAction(actionFlipVertical);
+ it("flips a two points arrow vertically correctly", async () => {
+ createAndSelectOneArrow();
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
-
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
+ await checkTwoPointsLineVerticalFlip();
+ });
});
// Line element
+describe("line", () => {
+ it("flips an unrotated line horizontally with line inside min/max points bounds", async () => {
+ const line = createLinearElementWithCurveInsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
-it("flips an unrotated line horizontally correctly", () => {
- createAndSelectOneLine();
+ await checkHorizontalFlip(
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips an unrotated line vertically with line inside min/max points bounds", async () => {
+ const line = createLinearElementWithCurveInsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
+ });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ it("flips an unrotated line horizontally correctly", async () => {
+ createAndSelectOneLine();
+ await checkHorizontalFlip(
+ TWO_POINTS_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
+ //TODO: elements with curve outside minMax points have a wrong bounding box
+ it.skip("flips an unrotated line horizontally with line outside min/max points bounds", async () => {
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-});
+ await checkHorizontalFlip(
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
-it("flips an unrotated line vertically correctly", () => {
- createAndSelectOneLine();
+ //TODO: elements with curve outside minMax points have a wrong bounding box
+ it.skip("flips an unrotated line vertically with line outside min/max points bounds", async () => {
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
+ });
- h.app.actionManager.executeAction(actionFlipVertical);
+ //TODO: elements with curve outside minMax points have a wrong bounding box
+ it.skip("flips a rotated line horizontally with line outside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
+ mutateElement(line, { angle: originalAngle });
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ await checkRotatedHorizontalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-});
+ //TODO: elements with curve outside minMax points have a wrong bounding box
+ it.skip("flips a rotated line vertically with line outside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+ const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
+ mutateElement(line, { angle: originalAngle });
+ h.app.scene.replaceAllElements([line]);
+ h.app.setState({ selectedElementIds: { [line.id]: true } });
-it.skip("flips a rotated line horizontally correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (7 * Math.PI) / 4;
+ await checkRotatedVerticalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- createAndSelectOneLine(originalAngle);
+ it("flips an unrotated line vertically correctly", async () => {
+ createAndSelectOneLine();
+ await checkVerticalFlip(TWO_POINTS_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
+ });
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
+ it("flips a rotated line horizontally with line inside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
+ const line = createLinearElementWithCurveInsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.state.selectedElementIds[line.id] = true;
+ mutateElement(line, {
+ angle: originalAngle,
+ });
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ await checkRotatedHorizontalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
+ it("flips a rotated line vertically with line inside min/max points bounds", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+ const line = createLinearElementWithCurveInsideMinMaxPoints("line");
+ h.app.scene.replaceAllElements([line]);
+ h.app.state.selectedElementIds[line.id] = true;
+ mutateElement(line, {
+ angle: originalAngle,
+ });
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
+ await checkRotatedVerticalFlip(
+ expectedAngle,
+ MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
+ );
+ });
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
-});
+ it("flips a two points line horizontally correctly", async () => {
+ createAndSelectOneLine();
+ await checkTwoPointsLineHorizontalFlip();
+ });
-it.skip("flips a rotated line vertically correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (3 * Math.PI) / 4;
-
- createAndSelectOneLine(originalAngle);
-
- const originalWidth = API.getSelectedElements()[0].width;
- const originalHeight = API.getSelectedElements()[0].height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if width and height did not change
- expect(API.getSelectedElements()[0].width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
-
- expect(API.getSelectedElements()[0].height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-
- // Check angle
- expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
+ it("flips a two points line vertically correctly", async () => {
+ createAndSelectOneLine();
+ await checkTwoPointsLineVerticalFlip();
+ });
});
// Draw element
+describe("freedraw", () => {
+ it("flips an unrotated drawing horizontally correctly", async () => {
+ const draw = createAndReturnOneDraw();
+ // select draw, since not done automatically
+ h.state.selectedElementIds[draw.id] = true;
+ await checkHorizontalFlip();
+ });
-it("flips an unrotated drawing horizontally correctly", () => {
- const draw = createAndReturnOneDraw();
- // select draw, since not done automatically
- h.state.selectedElementIds[draw.id] = true;
+ it("flips an unrotated drawing vertically correctly", async () => {
+ const draw = createAndReturnOneDraw();
+ // select draw, since not done automatically
+ h.state.selectedElementIds[draw.id] = true;
+ await checkVerticalFlip();
+ });
- const originalWidth = draw.width;
- const originalHeight = draw.height;
+ it("flips a rotated drawing horizontally correctly", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
- h.app.actionManager.executeAction(actionFlipHorizontal);
+ const draw = createAndReturnOneDraw(originalAngle);
+ // select draw, since not done automatically
+ h.state.selectedElementIds[draw.id] = true;
- // Check if width and height did not change
- expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
+ await checkRotatedHorizontalFlip(expectedAngle);
+ });
- expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
+ it("flips a rotated drawing vertically correctly", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+
+ const draw = createAndReturnOneDraw(originalAngle);
+ // select draw, since not done automatically
+ h.state.selectedElementIds[draw.id] = true;
+
+ await checkRotatedVerticalFlip(expectedAngle);
+ });
});
-it("flips an unrotated drawing vertically correctly", () => {
- const draw = createAndReturnOneDraw();
- // select draw, since not done automatically
- h.state.selectedElementIds[draw.id] = true;
+//image
+//TODO: currently there is no test for pixel colors at flipped positions.
+describe("image", () => {
+ const createImage = async () => {
+ const sendPasteEvent = (file?: File) => {
+ const clipboardEvent = new Event("paste", {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ });
- const originalWidth = draw.width;
- const originalHeight = draw.height;
+ // set `clipboardData` properties.
+ // @ts-ignore
+ clipboardEvent.clipboardData = {
+ getData: () => window.navigator.clipboard.readText(),
+ files: [file],
+ };
- h.app.actionManager.executeAction(actionFlipVertical);
+ document.dispatchEvent(clipboardEvent);
+ };
- // Check if width and height did not change
- expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
+ sendPasteEvent(await API.loadFile("./fixtures/smiley_embedded_v2.png"));
+ };
- expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
-});
-
-it("flips a rotated drawing horizontally correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (7 * Math.PI) / 4;
-
- const draw = createAndReturnOneDraw(originalAngle);
- // select draw, since not done automatically
- h.state.selectedElementIds[draw.id] = true;
-
- const originalWidth = draw.width;
- const originalHeight = draw.height;
-
- h.app.actionManager.executeAction(actionFlipHorizontal);
-
- // Check if width and height did not change
- expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
-
- expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
-
- // Check angle
- expect(draw.angle).toBeCloseTo(expectedAngle);
-});
-
-it("flips a rotated drawing vertically correctly", () => {
- const originalAngle = Math.PI / 4;
- const expectedAngle = (3 * Math.PI) / 4;
-
- const draw = createAndReturnOneDraw(originalAngle);
- // select draw, since not done automatically
- h.state.selectedElementIds[draw.id] = true;
-
- const originalWidth = draw.width;
- const originalHeight = draw.height;
-
- h.app.actionManager.executeAction(actionFlipVertical);
-
- // Check if width and height did not change
-
- expect(API.getSelectedElement().width).toBeCloseTo(
- originalWidth,
- FLIP_PRECISION_DECIMALS,
- );
-
- expect(API.getSelectedElement().height).toBeCloseTo(
- originalHeight,
- FLIP_PRECISION_DECIMALS,
- );
-
- // Check angle
- expect(API.getSelectedElement().angle).toBeCloseTo(expectedAngle);
+ it("flips an unrotated image horizontally correctly", async () => {
+ //paste image
+ await createImage();
+
+ await waitFor(() => {
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(API.getSelectedElements().length).toBeGreaterThan(0);
+ expect(API.getSelectedElements()[0].type).toEqual("image");
+ expect(h.app.files.fileId).toBeDefined();
+ });
+ await checkHorizontalFlip();
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
+ expect(h.elements[0].angle).toBeCloseTo(0);
+ });
+
+ it("flips an unrotated image vertically correctly", async () => {
+ //paste image
+ await createImage();
+ await waitFor(() => {
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(API.getSelectedElements().length).toBeGreaterThan(0);
+ expect(API.getSelectedElements()[0].type).toEqual("image");
+ expect(h.app.files.fileId).toBeDefined();
+ });
+
+ await checkVerticalFlip();
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
+ expect(h.elements[0].angle).toBeCloseTo(Math.PI);
+ });
+
+ it("flips an rotated image horizontally correctly", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (7 * Math.PI) / 4;
+ //paste image
+ await createImage();
+ await waitFor(() => {
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(API.getSelectedElements().length).toBeGreaterThan(0);
+ expect(API.getSelectedElements()[0].type).toEqual("image");
+ expect(h.app.files.fileId).toBeDefined();
+ });
+ mutateElement(h.elements[0], {
+ angle: originalAngle,
+ });
+ await checkRotatedHorizontalFlip(expectedAngle);
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
+ });
+
+ it("flips an rotated image vertically correctly", async () => {
+ const originalAngle = Math.PI / 4;
+ const expectedAngle = (3 * Math.PI) / 4;
+ //paste image
+ await createImage();
+ await waitFor(() => {
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(h.elements[0].angle).toEqual(0);
+ expect(API.getSelectedElements().length).toBeGreaterThan(0);
+ expect(API.getSelectedElements()[0].type).toEqual("image");
+ expect(h.app.files.fileId).toBeDefined();
+ });
+ mutateElement(h.elements[0], {
+ angle: originalAngle,
+ });
+
+ await checkRotatedVerticalFlip(expectedAngle);
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]);
+ expect(h.elements[0].angle).toBeCloseTo(expectedAngle);
+ });
+
+ it("flips an image both vertically & horizontally", async () => {
+ //paste image
+ await createImage();
+ await waitFor(() => {
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(API.getSelectedElements().length).toBeGreaterThan(0);
+ expect(API.getSelectedElements()[0].type).toEqual("image");
+ expect(h.app.files.fileId).toBeDefined();
+ });
+
+ await checkVerticalHorizontalFlip();
+ expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]);
+ expect(h.elements[0].angle).toBeCloseTo(Math.PI);
+ });
});
diff --git a/src/tests/helpers/api.ts b/src/tests/helpers/api.ts
index 14d9a8e2c..7f3e958ca 100644
--- a/src/tests/helpers/api.ts
+++ b/src/tests/helpers/api.ts
@@ -8,7 +8,7 @@ import {
FileId,
} from "../../element/types";
import { newElement, newTextElement, newLinearElement } from "../../element";
-import { DEFAULT_VERTICAL_ALIGN } from "../../constants";
+import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
import { getDefaultAppState } from "../../appState";
import { GlobalTestState, createEvent, fireEvent } from "../test-utils";
import fs from "fs";
@@ -18,6 +18,7 @@ import { getMimeType } from "../../data/blob";
import { newFreeDrawElement, newImageElement } from "../../element/newElement";
import { Point } from "../../types";
import { getSelectedElements } from "../../scene/selection";
+import { isLinearElementType } from "../../element/typeChecks";
const readFile = util.promisify(fs.readFile);
@@ -89,7 +90,7 @@ export class API {
fillStyle?: ExcalidrawGenericElement["fillStyle"];
strokeWidth?: ExcalidrawGenericElement["strokeWidth"];
strokeStyle?: ExcalidrawGenericElement["strokeStyle"];
- strokeSharpness?: ExcalidrawGenericElement["strokeSharpness"];
+ roundness?: ExcalidrawGenericElement["roundness"];
roughness?: ExcalidrawGenericElement["roughness"];
opacity?: ExcalidrawGenericElement["opacity"];
// text props
@@ -109,6 +110,9 @@ export class API {
fileId?: T extends "image" ? string : never;
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
+ endBinding?: T extends "arrow"
+ ? ExcalidrawLinearElement["endBinding"]
+ : never;
}): T extends "arrow" | "line"
? ExcalidrawLinearElement
: T extends "freedraw"
@@ -122,7 +126,20 @@ export class API {
const appState = h?.state || getDefaultAppState();
- const base = {
+ const base: Omit<
+ ExcalidrawGenericElement,
+ | "id"
+ | "width"
+ | "height"
+ | "type"
+ | "seed"
+ | "version"
+ | "versionNonce"
+ | "isDeleted"
+ | "groupIds"
+ | "link"
+ | "updated"
+ > = {
x,
y,
angle: rest.angle ?? 0,
@@ -132,8 +149,17 @@ export class API {
fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth,
strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
- strokeSharpness:
- rest.strokeSharpness ?? appState.currentItemStrokeSharpness,
+ roundness: (
+ rest.roundness === undefined
+ ? appState.currentItemRoundness === "round"
+ : rest.roundness
+ )
+ ? {
+ type: isLinearElementType(type)
+ ? ROUNDNESS.PROPORTIONAL_RADIUS
+ : ROUNDNESS.ADAPTIVE_RADIUS,
+ }
+ : null,
roughness: rest.roughness ?? appState.currentItemRoughness,
opacity: rest.opacity ?? appState.currentItemOpacity,
boundElements: rest.boundElements ?? null,
diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx
index 56993b72c..a606fb384 100644
--- a/src/tests/linearElementEditor.test.tsx
+++ b/src/tests/linearElementEditor.test.tsx
@@ -1,20 +1,34 @@
import ReactDOM from "react-dom";
-import { ExcalidrawLinearElement } from "../element/types";
+import {
+ ExcalidrawElement,
+ ExcalidrawLinearElement,
+ ExcalidrawTextElementWithContainer,
+ FontString,
+} from "../element/types";
import ExcalidrawApp from "../excalidraw-app";
import { centerPoint } from "../math";
import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene";
-import { Keyboard, Pointer } from "./helpers/ui";
+import { Keyboard, Pointer, UI } from "./helpers/ui";
import { screen, render, fireEvent, GlobalTestState } from "./test-utils";
import { API } from "../tests/helpers/api";
import { Point } from "../types";
import { KEYS } from "../keys";
import { LinearElementEditor } from "../element/linearElementEditor";
-import { queryByText } from "@testing-library/react";
+import { queryByTestId, queryByText } from "@testing-library/react";
+import { resize, rotate } from "./utils";
+import {
+ getBoundTextElementPosition,
+ wrapText,
+ getMaxContainerWidth,
+} from "../element/textElement";
+import * as textElementUtils from "../element/textElement";
+import { ROUNDNESS } from "../constants";
const renderScene = jest.spyOn(Renderer, "renderScene");
const { h } = window;
+const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
describe("Test Linear Elements", () => {
let container: HTMLElement;
@@ -41,52 +55,52 @@ describe("Test Linear Elements", () => {
const createTwoPointerLinearElement = (
type: ExcalidrawLinearElement["type"],
- strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp",
+ roundness: ExcalidrawElement["roundness"] = null,
roughness: ExcalidrawLinearElement["roughness"] = 0,
) => {
- h.elements = [
- API.createElement({
- x: p1[0],
- y: p1[1],
- width: p2[0] - p1[0],
- height: 0,
- type,
- roughness,
- points: [
- [0, 0],
- [p2[0] - p1[0], p2[1] - p1[1]],
- ],
- strokeSharpness,
- }),
- ];
+ const line = API.createElement({
+ x: p1[0],
+ y: p1[1],
+ width: p2[0] - p1[0],
+ height: 0,
+ type,
+ roughness,
+ points: [
+ [0, 0],
+ [p2[0] - p1[0], p2[1] - p1[1]],
+ ],
+ roundness,
+ });
+ h.elements = [line];
mouse.clickAt(p1[0], p1[1]);
+ return line;
};
const createThreePointerLinearElement = (
type: ExcalidrawLinearElement["type"],
- strokeSharpness: ExcalidrawLinearElement["strokeSharpness"] = "sharp",
+ roundness: ExcalidrawElement["roundness"] = null,
roughness: ExcalidrawLinearElement["roughness"] = 0,
) => {
//dragging line from midpoint
const p3 = [midpoint[0] + delta - p1[0], midpoint[1] + delta - p1[1]];
- h.elements = [
- API.createElement({
- x: p1[0],
- y: p1[1],
- width: p3[0] - p1[0],
- height: 0,
- type,
- roughness,
- points: [
- [0, 0],
- [p3[0], p3[1]],
- [p2[0] - p1[0], p2[1] - p1[1]],
- ],
- strokeSharpness,
- }),
- ];
+ const line = API.createElement({
+ x: p1[0],
+ y: p1[1],
+ width: p3[0] - p1[0],
+ height: 0,
+ type,
+ roughness,
+ points: [
+ [0, 0],
+ [p3[0], p3[1]],
+ [p2[0] - p1[0], p2[1] - p1[1]],
+ ],
+ roundness,
+ });
+ h.elements = [line];
mouse.clickAt(p1[0], p1[1]);
+ return line;
};
const enterLineEditingMode = (
@@ -98,7 +112,9 @@ describe("Test Linear Elements", () => {
} else {
mouse.clickAt(p1[0], p1[1]);
}
- Keyboard.keyPress(KEYS.ENTER);
+ Keyboard.withModifierKeys({ ctrl: true }, () => {
+ Keyboard.keyPress(KEYS.ENTER);
+ });
expect(h.state.editingLinearElement?.elementId).toEqual(line.id);
};
@@ -129,6 +145,28 @@ describe("Test Linear Elements", () => {
Keyboard.keyPress(KEYS.DELETE);
};
+ it("should not drag line and add midpoint until dragged beyond a threshold", () => {
+ createTwoPointerLinearElement("line");
+ const line = h.elements[0] as ExcalidrawLinearElement;
+ const originalX = line.x;
+ const originalY = line.y;
+ expect(line.points.length).toEqual(2);
+
+ mouse.clickAt(midpoint[0], midpoint[1]);
+ drag(midpoint, [midpoint[0] + 1, midpoint[1] + 1]);
+
+ expect(line.points.length).toEqual(2);
+
+ expect(line.x).toBe(originalX);
+ expect(line.y).toBe(originalY);
+ expect(line.points.length).toEqual(2);
+
+ drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
+ expect(line.x).toBe(originalX);
+ expect(line.y).toBe(originalY);
+ expect(line.points.length).toEqual(3);
+ });
+
it("should allow dragging line from midpoint in 2 pointer lines outside editor", async () => {
createTwoPointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
@@ -138,7 +176,7 @@ describe("Test Linear Elements", () => {
// drag line from midpoint
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
- expect(renderScene).toHaveBeenCalledTimes(10);
+ expect(renderScene).toHaveBeenCalledTimes(11);
expect(line.points.length).toEqual(3);
expect(line.points).toMatchInlineSnapshot(`
Array [
@@ -194,7 +232,35 @@ describe("Test Linear Elements", () => {
expect(h.state.editingLinearElement?.elementId).toBeUndefined();
});
+ it("should enter line editor when using double clicked with ctrl key", () => {
+ createTwoPointerLinearElement("line");
+ expect(h.state.editingLinearElement?.elementId).toBeUndefined();
+
+ Keyboard.withModifierKeys({ ctrl: true }, () => {
+ mouse.doubleClick();
+ });
+ expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id);
+ });
+
describe("Inside editor", () => {
+ it("should not drag line and add midpoint when dragged irrespective of threshold", () => {
+ createTwoPointerLinearElement("line");
+ const line = h.elements[0] as ExcalidrawLinearElement;
+ const originalX = line.x;
+ const originalY = line.y;
+ enterLineEditingMode(line);
+
+ expect(line.points.length).toEqual(2);
+
+ mouse.clickAt(midpoint[0], midpoint[1]);
+ expect(line.points.length).toEqual(2);
+
+ drag(midpoint, [midpoint[0] + 1, midpoint[1] + 1]);
+ expect(line.x).toBe(originalX);
+ expect(line.y).toBe(originalY);
+ expect(line.points.length).toEqual(3);
+ });
+
it("should allow dragging line from midpoint in 2 pointer lines", async () => {
createTwoPointerLinearElement("line");
@@ -203,7 +269,7 @@ describe("Test Linear Elements", () => {
// drag line from midpoint
drag(midpoint, [midpoint[0] + delta, midpoint[1] + delta]);
- expect(renderScene).toHaveBeenCalledTimes(14);
+ expect(renderScene).toHaveBeenCalledTimes(15);
expect(line.points.length).toEqual(3);
expect(line.points).toMatchInlineSnapshot(`
@@ -224,7 +290,7 @@ describe("Test Linear Elements", () => {
`);
});
- it("should update the midpoints when element sharpness changed", async () => {
+ it("should update the midpoints when element roundness changed", async () => {
createThreePointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
@@ -237,7 +303,7 @@ describe("Test Linear Elements", () => {
h.state,
);
- // update sharpness
+ // update roundness
fireEvent.click(screen.getByTitle("Round"));
expect(renderScene).toHaveBeenCalledTimes(12);
@@ -263,7 +329,9 @@ describe("Test Linear Elements", () => {
});
it("should update all the midpoints when element position changed", async () => {
- createThreePointerLinearElement("line", "round");
+ createThreePointerLinearElement("line", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
const line = h.elements[0] as ExcalidrawLinearElement;
expect(line.points.length).toEqual(3);
@@ -282,7 +350,7 @@ describe("Test Linear Elements", () => {
// Move the element
drag(startPoint, endPoint);
- expect(renderScene).toHaveBeenCalledTimes(15);
+ expect(renderScene).toHaveBeenCalledTimes(16);
expect([line.x, line.y]).toEqual([
points[0][0] + deltaX,
points[0][1] + deltaY,
@@ -308,8 +376,8 @@ describe("Test Linear Elements", () => {
`);
});
- describe("When edges are sharp", () => {
- // This is the expected midpoint for line with sharp edge
+ describe("When edges are round", () => {
+ // This is the expected midpoint for line with round edge
// hence hardcoding it so if later some bug is introduced
// this will fail and we can fix it
const firstSegmentMidpoint: Point = [55, 45];
@@ -318,8 +386,8 @@ describe("Test Linear Elements", () => {
let line: ExcalidrawLinearElement;
beforeEach(() => {
- createThreePointerLinearElement("line");
- line = h.elements[0] as ExcalidrawLinearElement;
+ line = createThreePointerLinearElement("line");
+
expect(line.points.length).toEqual(3);
enterLineEditingMode(line);
@@ -339,7 +407,7 @@ describe("Test Linear Elements", () => {
lastSegmentMidpoint[1] + delta,
]);
- expect(renderScene).toHaveBeenCalledTimes(19);
+ expect(renderScene).toHaveBeenCalledTimes(21);
expect(line.points.length).toEqual(5);
expect((h.elements[0] as ExcalidrawLinearElement).points)
@@ -359,7 +427,7 @@ describe("Test Linear Elements", () => {
],
Array [
105,
- 75,
+ 70,
],
Array [
40,
@@ -378,7 +446,7 @@ describe("Test Linear Elements", () => {
// Drag from first point
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
- expect(renderScene).toHaveBeenCalledTimes(15);
+ expect(renderScene).toHaveBeenCalledTimes(16);
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
@@ -404,7 +472,7 @@ describe("Test Linear Elements", () => {
// Drag from first point
drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]);
- expect(renderScene).toHaveBeenCalledTimes(15);
+ expect(renderScene).toHaveBeenCalledTimes(16);
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
@@ -438,7 +506,7 @@ describe("Test Linear Elements", () => {
// delete 3rd point
deletePoint(points[2]);
expect(line.points.length).toEqual(3);
- expect(renderScene).toHaveBeenCalledTimes(20);
+ expect(renderScene).toHaveBeenCalledTimes(22);
const newMidPoints = LinearElementEditor.getEditorMidPoints(
line,
@@ -463,8 +531,9 @@ describe("Test Linear Elements", () => {
let line: ExcalidrawLinearElement;
beforeEach(() => {
- createThreePointerLinearElement("line", "round");
- line = h.elements[0] as ExcalidrawLinearElement;
+ line = createThreePointerLinearElement("line", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
expect(line.points.length).toEqual(3);
enterLineEditingMode(line);
@@ -483,7 +552,7 @@ describe("Test Linear Elements", () => {
lastSegmentMidpoint[0] + delta,
lastSegmentMidpoint[1] + delta,
]);
- expect(renderScene).toHaveBeenCalledTimes(19);
+ expect(renderScene).toHaveBeenCalledTimes(21);
expect(line.points.length).toEqual(5);
@@ -503,8 +572,8 @@ describe("Test Linear Elements", () => {
50,
],
Array [
- 104.58050066266131,
- 74.24758482724201,
+ 106.08587175006699,
+ 73.29416593965323,
],
Array [
40,
@@ -559,7 +628,7 @@ describe("Test Linear Elements", () => {
// Drag from first point
drag(hitCoords, [hitCoords[0] + delta, hitCoords[1] + delta]);
- expect(renderScene).toHaveBeenCalledTimes(15);
+ expect(renderScene).toHaveBeenCalledTimes(16);
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
@@ -627,8 +696,6 @@ describe("Test Linear Elements", () => {
fillStyle: "solid",
}),
];
-
- const origPoints = line.points.map((point) => [...point]);
const dragEndPositionOffset = [100, 100] as const;
API.setSelectedElements([line]);
enterLineEditingMode(line, true);
@@ -643,11 +710,474 @@ describe("Test Linear Elements", () => {
0,
],
Array [
- ${origPoints[1][0] - dragEndPositionOffset[0]},
- ${origPoints[1][1] - dragEndPositionOffset[1]},
+ -60,
+ -100,
],
]
`);
});
});
+
+ describe("Test bound text element", () => {
+ const DEFAULT_TEXT = "Online whiteboard collaboration made easy";
+
+ const createBoundTextElement = (
+ text: string,
+ container: ExcalidrawLinearElement,
+ ) => {
+ const textElement = API.createElement({
+ type: "text",
+ x: 0,
+ y: 0,
+ text: wrapText(text, font, getMaxContainerWidth(container)),
+ containerId: container.id,
+ width: 30,
+ height: 20,
+ }) as ExcalidrawTextElementWithContainer;
+
+ container = {
+ ...container,
+ boundElements: (container.boundElements || []).concat({
+ type: "text",
+ id: textElement.id,
+ }),
+ };
+ const elements: ExcalidrawElement[] = [];
+ h.elements.forEach((element) => {
+ if (element.id === container.id) {
+ elements.push(container);
+ } else {
+ elements.push(element);
+ }
+ });
+ const updatedTextElement = { ...textElement, originalText: text };
+ h.elements = [...elements, updatedTextElement];
+ return { textElement: updatedTextElement, container };
+ };
+
+ describe("Test getBoundTextElementPosition", () => {
+ it("should return correct position for 2 pointer arrow", () => {
+ createTwoPointerLinearElement("arrow");
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+ const position = LinearElementEditor.getBoundTextElementPosition(
+ container,
+ textElement,
+ );
+ expect(position).toMatchInlineSnapshot(`
+ Object {
+ "x": 25,
+ "y": 10,
+ }
+ `);
+ });
+
+ it("should return correct position for arrow with odd points", () => {
+ createThreePointerLinearElement("arrow", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+
+ const position = LinearElementEditor.getBoundTextElementPosition(
+ container,
+ textElement,
+ );
+ expect(position).toMatchInlineSnapshot(`
+ Object {
+ "x": 75,
+ "y": 60,
+ }
+ `);
+ });
+
+ it("should return correct position for arrow with even points", () => {
+ createThreePointerLinearElement("arrow", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+ enterLineEditingMode(container);
+ // This is the expected midpoint for line with round edge
+ // hence hardcoding it so if later some bug is introduced
+ // this will fail and we can fix it
+ const firstSegmentMidpoint: Point = [
+ 55.9697848965255, 47.442326230998205,
+ ];
+ // drag line from first segment midpoint
+ drag(firstSegmentMidpoint, [
+ firstSegmentMidpoint[0] + delta,
+ firstSegmentMidpoint[1] + delta,
+ ]);
+
+ const position = LinearElementEditor.getBoundTextElementPosition(
+ container,
+ textElement,
+ );
+ expect(position).toMatchInlineSnapshot(`
+ Object {
+ "x": 85.82201843191861,
+ "y": 75.63461309860818,
+ }
+ `);
+ });
+ });
+
+ it("should match styles for text editor", () => {
+ createTwoPointerLinearElement("arrow");
+ Keyboard.keyPress(KEYS.ENTER);
+ const editor = document.querySelector(
+ ".excalidraw-textEditorContainer > textarea",
+ ) as HTMLTextAreaElement;
+ expect(editor).toMatchSnapshot();
+ });
+
+ it("should bind text to arrow when double clicked", async () => {
+ createTwoPointerLinearElement("arrow");
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+
+ expect(h.elements.length).toBe(1);
+ expect(h.elements[0].id).toBe(arrow.id);
+ mouse.doubleClickAt(arrow.x, arrow.y);
+ expect(h.elements.length).toBe(2);
+
+ const text = h.elements[1] as ExcalidrawTextElementWithContainer;
+ expect(text.type).toBe("text");
+ expect(text.containerId).toBe(arrow.id);
+ mouse.down();
+ const editor = document.querySelector(
+ ".excalidraw-textEditorContainer > textarea",
+ ) as HTMLTextAreaElement;
+
+ fireEvent.change(editor, {
+ target: { value: DEFAULT_TEXT },
+ });
+
+ await new Promise((r) => setTimeout(r, 0));
+ editor.blur();
+ expect(arrow.boundElements).toStrictEqual([
+ { id: text.id, type: "text" },
+ ]);
+ expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
+ .toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ });
+
+ it("should bind text to arrow when clicked on arrow and enter pressed", async () => {
+ const arrow = createTwoPointerLinearElement("arrow");
+
+ expect(h.elements.length).toBe(1);
+ expect(h.elements[0].id).toBe(arrow.id);
+
+ Keyboard.keyPress(KEYS.ENTER);
+
+ expect(h.elements.length).toBe(2);
+
+ const textElement = h.elements[1] as ExcalidrawTextElementWithContainer;
+ expect(textElement.type).toBe("text");
+ expect(textElement.containerId).toBe(arrow.id);
+ const editor = document.querySelector(
+ ".excalidraw-textEditorContainer > textarea",
+ ) as HTMLTextAreaElement;
+
+ await new Promise((r) => setTimeout(r, 0));
+
+ fireEvent.change(editor, {
+ target: { value: DEFAULT_TEXT },
+ });
+ editor.blur();
+ expect(arrow.boundElements).toStrictEqual([
+ { id: textElement.id, type: "text" },
+ ]);
+ expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
+ .toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ });
+
+ it("should not bind text to line when double clicked", async () => {
+ const line = createTwoPointerLinearElement("line");
+
+ expect(h.elements.length).toBe(1);
+ mouse.doubleClickAt(line.x, line.y);
+
+ expect(h.elements.length).toBe(2);
+
+ const text = h.elements[1] as ExcalidrawTextElementWithContainer;
+ expect(text.type).toBe("text");
+ expect(text.containerId).toBeNull();
+ expect(line.boundElements).toBeNull();
+ });
+
+ it("should not rotate the bound text and update position of bound text and bounding box correctly when arrow rotated", () => {
+ createThreePointerLinearElement("arrow", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
+
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+
+ expect(container.angle).toBe(0);
+ expect(textElement.angle).toBe(0);
+ expect(getBoundTextElementPosition(arrow, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 75,
+ "y": 60,
+ }
+ `);
+ expect(textElement.text).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
+ .toMatchInlineSnapshot(`
+ Array [
+ 20,
+ 20,
+ 105,
+ 80,
+ 55.45893770831013,
+ 45,
+ ]
+ `);
+
+ rotate(container, -35, 55);
+ expect(container.angle).toMatchInlineSnapshot(`1.3988061968364685`);
+ expect(textElement.angle).toBe(0);
+ expect(getBoundTextElementPosition(container, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 21.73926141863671,
+ "y": 73.31003398390868,
+ }
+ `);
+ expect(textElement.text).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
+ .toMatchInlineSnapshot(`
+ Array [
+ 20,
+ 20,
+ 102.41961302274555,
+ 86.49012635273976,
+ 55.45893770831013,
+ 45,
+ ]
+ `);
+ });
+
+ it("should resize and position the bound text and bounding box correctly when 3 pointer arrow element resized", () => {
+ createThreePointerLinearElement("arrow", {
+ type: ROUNDNESS.PROPORTIONAL_RADIUS,
+ });
+
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+ expect(container.width).toBe(70);
+ expect(container.height).toBe(50);
+ expect(getBoundTextElementPosition(container, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 75,
+ "y": 60,
+ }
+ `);
+ expect(textElement.text).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
+ .toMatchInlineSnapshot(`
+ Array [
+ 20,
+ 20,
+ 105,
+ 80,
+ 55.45893770831013,
+ 45,
+ ]
+ `);
+
+ resize(container, "ne", [300, 200]);
+
+ expect({ width: container.width, height: container.height })
+ .toMatchInlineSnapshot(`
+ Object {
+ "height": 128,
+ "width": 367,
+ }
+ `);
+
+ expect(getBoundTextElementPosition(container, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 272,
+ "y": 46,
+ }
+ `);
+ expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
+ .toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made easy"
+ `);
+ expect(LinearElementEditor.getElementAbsoluteCoords(container, true))
+ .toMatchInlineSnapshot(`
+ Array [
+ 20,
+ 36,
+ 502,
+ 94,
+ 205.9061448421403,
+ 53,
+ ]
+ `);
+ });
+
+ it("should resize and position the bound text correctly when 2 pointer linear element resized", () => {
+ createTwoPointerLinearElement("arrow");
+
+ const arrow = h.elements[0] as ExcalidrawLinearElement;
+ const { textElement, container } = createBoundTextElement(
+ DEFAULT_TEXT,
+ arrow,
+ );
+ expect(container.width).toBe(40);
+ expect(getBoundTextElementPosition(container, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 25,
+ "y": 10,
+ }
+ `);
+ expect(textElement.text).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ const points = LinearElementEditor.getPointsGlobalCoordinates(container);
+
+ // Drag from last point
+ drag(points[1], [points[1][0] + 300, points[1][1]]);
+
+ expect({ width: container.width, height: container.height })
+ .toMatchInlineSnapshot(`
+ Object {
+ "height": 128,
+ "width": 340,
+ }
+ `);
+
+ expect(getBoundTextElementPosition(container, textElement))
+ .toMatchInlineSnapshot(`
+ Object {
+ "x": 75,
+ "y": -4,
+ }
+ `);
+ expect(textElement.text).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made easy"
+ `);
+ });
+
+ it("should not render vertical 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-top")).toBeNull();
+ expect(queryByTestId(container, "align-middle")).toBeNull();
+ expect(queryByTestId(container, "align-bottom")).toBeNull();
+ });
+
+ it("should wrap the bound text when arrow bound container moves", async () => {
+ const rect = UI.createElement("rectangle", {
+ x: 400,
+ width: 200,
+ height: 500,
+ });
+ const arrow = UI.createElement("arrow", {
+ x: 210,
+ y: 250,
+ width: 400,
+ height: 1,
+ });
+
+ mouse.select(arrow);
+ Keyboard.keyPress(KEYS.ENTER);
+ const editor = document.querySelector(
+ ".excalidraw-textEditorContainer > textarea",
+ ) as HTMLTextAreaElement;
+ await new Promise((r) => setTimeout(r, 0));
+ fireEvent.change(editor, { target: { value: DEFAULT_TEXT } });
+ editor.blur();
+
+ const textElement = h.elements[2] as ExcalidrawTextElementWithContainer;
+
+ expect(arrow.endBinding?.elementId).toBe(rect.id);
+ expect(arrow.width).toBe(400);
+ expect(rect.x).toBe(400);
+ expect(rect.y).toBe(0);
+ expect(
+ wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
+ ).toMatchInlineSnapshot(`
+ "Online whiteboard collaboration
+ made easy"
+ `);
+ const handleBindTextResizeSpy = jest.spyOn(
+ textElementUtils,
+ "handleBindTextResize",
+ );
+
+ mouse.select(rect);
+ mouse.downAt(rect.x, rect.y);
+ mouse.moveTo(200, 0);
+ mouse.upAt(200, 0);
+
+ expect(arrow.width).toBe(170);
+ expect(rect.x).toBe(200);
+ expect(rect.y).toBe(0);
+ expect(handleBindTextResizeSpy).toHaveBeenCalledWith(
+ h.elements[1],
+ false,
+ );
+ expect(
+ wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
+ ).toMatchInlineSnapshot(`
+ "Online whiteboard
+ collaboration made
+ easy"
+ `);
+ });
+ });
});
diff --git a/src/tests/packages/__snapshots__/excalidraw.test.tsx.snap b/src/tests/packages/__snapshots__/excalidraw.test.tsx.snap
index 2fbca5345..09402981e 100644
--- a/src/tests/packages/__snapshots__/excalidraw.test.tsx.snap
+++ b/src/tests/packages/__snapshots__/excalidraw.test.tsx.snap
@@ -1,161 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` Test UIOptions prop Test canvasActions should not hide any UI element when canvasActions is "undefined" 1`] = `
- should render main menu with host menu items if passed from host 1`] = `
+
`;
-exports[` Test UIOptions prop should not hide any UI element when the UIOptions prop is "undefined" 1`] = `
- Test UIOptions prop Test canvasActions should render menu with default items when "UIOPtions" is "undefined" 1`] = `
+
+
+
-
-
-
+
+ Canvas background
-
-
- English
-
-
- Bahasa Indonesia
-
-
- Català
-
-
- Deutsch
-
-
- English
-
-
- Español
-
-
- Euskara
-
-
- Français
-
-
- Italiano
-
-
- Kurdî
-
-
- Latviešu
-
-
- Magyar
-
-
- Nederlands
-
-
- Norsk bokmål
-
-
- Norsk nynorsk
-
-
- Occitan
-
-
- Polski
-
-
- Português
-
-
- Português Brasileiro
-
-
- Română
-
-
- Slovenčina
-
-
- Slovenščina
-
-
- Suomi
-
-
- Svenska
-
-
- Taqbaylit
-
-
- Türkçe
-
-
- Ελληνικά
-
-
- Русский
-
-
- Українська
-
-
- עברית
-
-
- العربية
-
-
- فارسی
-
-
- मराठी
-
-
- ਪੰਜਾਬੀ
-
-
- 日本語
-
-
- 简体中文
-
-
- 繁體中文
-
-
- 한국어
-
-
-
-
- Canvas background
-
-
-
-
+
+
+
+
+
-
+ #
-
-
- #
-
-
-
-
+
+
-
+
`;
diff --git a/src/tests/packages/__snapshots__/utils.test.ts.snap b/src/tests/packages/__snapshots__/utils.test.ts.snap
index 75397290c..b1002a406 100644
--- a/src/tests/packages/__snapshots__/utils.test.ts.snap
+++ b/src/tests/packages/__snapshots__/utils.test.ts.snap
@@ -4,23 +4,23 @@ exports[`exportToSvg with default arguments 1`] = `
Object {
"activeTool": Object {
"customType": null,
- "lastActiveToolBeforeEraser": null,
+ "lastActiveTool": null,
"locked": false,
"type": "selection",
},
"collaborators": Map {},
+ "contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
- "currentItemLinearStrokeSharpness": "round",
"currentItemOpacity": 100,
"currentItemRoughness": 1,
+ "currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#000000",
- "currentItemStrokeSharpness": "sharp",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
diff --git a/src/tests/packages/excalidraw.test.tsx b/src/tests/packages/excalidraw.test.tsx
index 2957fd476..a0d495936 100644
--- a/src/tests/packages/excalidraw.test.tsx
+++ b/src/tests/packages/excalidraw.test.tsx
@@ -1,12 +1,20 @@
-import { fireEvent, GlobalTestState, render } from "../test-utils";
-import { Excalidraw } from "../../packages/excalidraw/index";
+import { fireEvent, GlobalTestState, toggleMenu, render } from "../test-utils";
+import { Excalidraw, Footer, MainMenu } from "../../packages/excalidraw/index";
import { queryByText, queryByTestId } from "@testing-library/react";
import { GRID_SIZE, THEME } from "../../constants";
import { t } from "../../i18n";
+import { useMemo } from "react";
const { h } = window;
describe(" ", () => {
+ afterEach(() => {
+ const menu = document.querySelector(".dropdown-menu");
+ if (menu) {
+ toggleMenu(document.querySelector(".excalidraw")!);
+ }
+ });
+
describe("Test zenModeEnabled prop", () => {
it('should show exit zen mode button when zen mode is set and zen mode option in context menu when zenModeEnabled is "undefined"', async () => {
const { container } = await render( );
@@ -49,6 +57,34 @@ describe(" ", () => {
});
});
+ it("should render the footer only when Footer is passed as children", async () => {
+ //Footer not passed hence it will not render the footer
+ let { container } = await render(
+
+ This is a custom footer
+ ,
+ );
+ expect(container.querySelector(".footer-center")).toBe(null);
+
+ // Footer passed hence it will render the footer
+ ({ container } = await render(
+
+
+ This is a custom footer
+
+ ,
+ ));
+ expect(container.querySelector(".footer-center")).toMatchInlineSnapshot(`
+
+ `);
+ });
+
describe("Test gridModeEnabled prop", () => {
it('should show grid mode in context menu when gridModeEnabled is "undefined"', async () => {
const { container } = await render( );
@@ -87,98 +123,23 @@ describe(" ", () => {
});
});
- describe("Test theme prop", () => {
- it("should show the theme toggle by default", async () => {
- const { container } = await render( );
-
- expect(h.state.theme).toBe(THEME.LIGHT);
-
- queryByTestId(container, "menu-button")!.click();
- const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
- expect(darkModeToggle).toBeTruthy();
- });
-
- it("should not show theme toggle when the theme prop is defined", async () => {
- const { container } = await render( );
- expect(h.state.theme).toBe(THEME.DARK);
- expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
- });
-
- it("should show theme mode toggle when `UIOptions.canvasActions.toggleTheme` is true", async () => {
- const { container } = await render(
- ,
- );
- expect(h.state.theme).toBe(THEME.DARK);
- const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
- expect(darkModeToggle).toBeTruthy();
- });
-
- it("should not show theme toggle when `UIOptions.canvasActions.toggleTheme` is false", async () => {
- const { container } = await render(
- ,
- );
- expect(h.state.theme).toBe(THEME.DARK);
- const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
- expect(darkModeToggle).toBeFalsy();
- });
- });
-
- describe("Test name prop", () => {
- it('should allow editing name when the name prop is "undefined"', async () => {
- const { container } = await render( );
-
- fireEvent.click(queryByTestId(container, "image-export-button")!);
- const textInput: HTMLInputElement | null = document.querySelector(
- ".ExportDialog .ProjectName .TextInput",
- );
- expect(textInput?.value).toContain(`${t("labels.untitled")}`);
- expect(textInput?.nodeName).toBe("INPUT");
- });
-
- it('should set the name and not allow editing when the name prop is present"', async () => {
- const name = "test";
- const { container } = await render( );
-
- await fireEvent.click(queryByTestId(container, "image-export-button")!);
- const textInput = document.querySelector(
- ".ExportDialog .ProjectName .TextInput--readonly",
- );
- expect(textInput?.textContent).toEqual(name);
- expect(textInput?.nodeName).toBe("SPAN");
- });
- });
-
describe("Test UIOptions prop", () => {
- it('should not hide any UI element when the UIOptions prop is "undefined"', async () => {
- await render( );
-
- const canvasActions = document.querySelector(
- 'section[aria-labelledby="test-id-canvasActions-title"]',
- );
-
- expect(canvasActions).toMatchSnapshot();
- });
-
describe("Test canvasActions", () => {
- it('should not hide any UI element when canvasActions is "undefined"', async () => {
- await render( );
- const canvasActions = document.querySelector(
- 'section[aria-labelledby="test-id-canvasActions-title"]',
+ it('should render menu with default items when "UIOPtions" is "undefined"', async () => {
+ const { container } = await render(
+ ,
);
- expect(canvasActions).toMatchSnapshot();
+ //open menu
+ toggleMenu(container);
+ expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
});
it("should hide clear canvas button when clearCanvas is false", async () => {
const { container } = await render(
,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "clear-canvas-button")).toBeNull();
});
@@ -186,7 +147,8 @@ describe(" ", () => {
const { container } = await render(
,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "json-export-button")).toBeNull();
});
@@ -194,7 +156,8 @@ describe(" ", () => {
const { container } = await render(
,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "image-export-button")).toBeNull();
});
@@ -212,7 +175,8 @@ describe(" ", () => {
UIOptions={{ canvasActions: { export: { saveFileToDisk: false } } }}
/>,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "save-as-button")).toBeNull();
});
@@ -222,7 +186,8 @@ describe(" ", () => {
UIOptions={{ canvasActions: { saveToActiveFile: false } }}
/>,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "save-button")).toBeNull();
});
@@ -232,7 +197,8 @@ describe(" ", () => {
UIOptions={{ canvasActions: { changeViewBackgroundColor: false } }}
/>,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "canvas-background-picker")).toBeNull();
});
@@ -240,9 +206,108 @@ describe(" ", () => {
const { container } = await render(
,
);
-
+ //open menu
+ toggleMenu(container);
expect(queryByTestId(container, "toggle-dark-mode")).toBeNull();
});
+
+ it("should not render default items in custom menu even if passed if the prop in `canvasActions` is set to false", async () => {
+ const { container } = await render(
+
+
+
+ window.alert("custom menu item")}
+ >
+ {" "}
+ custom item
+
+
+
+
+ ,
+ );
+ //open menu
+ toggleMenu(container);
+ // load button shouldn't be rendered since `UIActions.canvasActions.loadScene` is `false`
+ expect(queryByTestId(container, "load-button")).toBeNull();
+ });
+ });
+ });
+
+ describe("Test theme prop", () => {
+ it("should show the theme toggle by default", async () => {
+ const { container } = await render( );
+ expect(h.state.theme).toBe(THEME.LIGHT);
+ //open menu
+ toggleMenu(container);
+ const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
+ expect(darkModeToggle).toBeTruthy();
+ });
+
+ it("should not show theme toggle when the theme prop is defined", async () => {
+ const { container } = await render( );
+
+ expect(h.state.theme).toBe(THEME.DARK);
+ //open menu
+ toggleMenu(container);
+ expect(queryByTestId(container, "toggle-dark-mode")).toBe(null);
+ });
+
+ it("should show theme mode toggle when `UIOptions.canvasActions.toggleTheme` is true", async () => {
+ const { container } = await render(
+ ,
+ );
+ expect(h.state.theme).toBe(THEME.DARK);
+ //open menu
+ toggleMenu(container);
+ const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
+ expect(darkModeToggle).toBeTruthy();
+ });
+
+ it("should not show theme toggle when `UIOptions.canvasActions.toggleTheme` is false", async () => {
+ const { container } = await render(
+ ,
+ );
+ expect(h.state.theme).toBe(THEME.DARK);
+ //open menu
+ toggleMenu(container);
+ const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
+ expect(darkModeToggle).toBe(null);
+ });
+ });
+
+ describe("Test name prop", () => {
+ it('should allow editing name when the name prop is "undefined"', async () => {
+ const { container } = await render( );
+ //open menu
+ toggleMenu(container);
+ fireEvent.click(queryByTestId(container, "image-export-button")!);
+ const textInput: HTMLInputElement | null = document.querySelector(
+ ".ExportDialog .ProjectName .TextInput",
+ );
+ expect(textInput?.value).toContain(`${t("labels.untitled")}`);
+ expect(textInput?.nodeName).toBe("INPUT");
+ });
+
+ it('should set the name and not allow editing when the name prop is present"', async () => {
+ const name = "test";
+ const { container } = await render( );
+ //open menu
+ toggleMenu(container);
+ await fireEvent.click(queryByTestId(container, "image-export-button")!);
+ const textInput = document.querySelector(
+ ".ExportDialog .ProjectName .TextInput--readonly",
+ );
+ expect(textInput?.textContent).toEqual(name);
+ expect(textInput?.nodeName).toBe("SPAN");
});
});
@@ -263,4 +328,64 @@ describe(" ", () => {
).toBe(true);
});
});
+
+ describe(" ", () => {
+ it("should render main menu with host menu items if passed from host", async () => {
+ const { container } = await render(
+
+
+ window.alert("Clicked")}>
+ Click me
+
+
+ Excalidraw blog
+
+
+ window.alert("custom menu item")}
+ >
+ {" "}
+ custom menu item
+
+
+
+
+ ,
+ );
+ //open menu
+ toggleMenu(container);
+ expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
+ });
+
+ it("should update themeToggle text even if MainMenu memoized", async () => {
+ const CustomExcalidraw = () => {
+ const customMenu = useMemo(() => {
+ return (
+
+
+
+ );
+ }, []);
+
+ return {customMenu} ;
+ };
+
+ const { container } = await render( );
+ //open menu
+ toggleMenu(container);
+
+ expect(h.state.theme).toBe(THEME.LIGHT);
+
+ expect(
+ queryByTestId(container, "toggle-dark-mode")?.textContent,
+ ).toContain(t("buttons.darkMode"));
+
+ fireEvent.click(queryByTestId(container, "toggle-dark-mode")!);
+
+ expect(
+ queryByTestId(container, "toggle-dark-mode")?.textContent,
+ ).toContain(t("buttons.lightMode"));
+ });
+ });
});
diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx
index d8e149d9a..07a6a449f 100644
--- a/src/tests/regressionTests.test.tsx
+++ b/src/tests/regressionTests.test.tsx
@@ -446,7 +446,7 @@ describe("regression tests", () => {
UI.clickTool("rectangle");
// english lang should display `thin` label
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
- fireEvent.click(document.querySelector(".menu-button")!);
+ fireEvent.click(document.querySelector(".dropdown-menu-button")!);
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: "de-DE" },
diff --git a/src/tests/scene/__snapshots__/export.test.ts.snap b/src/tests/scene/__snapshots__/export.test.ts.snap
index 9ad1ce8cd..8015ebc4c 100644
--- a/src/tests/scene/__snapshots__/export.test.ts.snap
+++ b/src/tests/scene/__snapshots__/export.test.ts.snap
@@ -95,7 +95,7 @@ exports[`exportToSvg with elements that have a link 1`] = `
exports[`exportToSvg with exportEmbedScene 1`] = `
"
- eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SPW/CMFx1MDAxMN35XHUwMDE1kbtcIpGk4aNstFRVpapcdTAwMWRcdTAwMTiQWnUw8YVYMbaxXHUwMDFkPoT477VccsRtxNpcclx1MDAwZpbu+b278907dKJcYpm9XHUwMDA0NI5cdTAwMTDscswoUXiLulx1MDAwZd+A0lRw+5T6WIta5Z5ZXHUwMDFhI8e9XHUwMDFlXHUwMDEzVlBcbm1OfGCwXHUwMDAybrRlfNk4ilx1MDAwZf62L5Q41Wau1lx1MDAxZpOiopyk63w1fJtOXj691JN2lpMlWVx1MDAxM+9d4fthXHUwMDEzbykxpcWSOG6wXHUwMDEy6LI0LVx1MDAxMPMlc21cdTAwMDZEXHUwMDFiJSp4XHUwMDEyTCjXyF3sTyi9wHm1VKLmJHCSPsaLXCJwXG7K2Mzs2WlcdTAwMDA4L2tcdTAwMDWoVWF+abGFNzot7ICDypZcXJZcdTAwMWO0/qNcdTAwMTFcdTAwMTLn1Oxbv3L9yVfip/vdzl9iJc95kHbBr85cdTAwMDCIT5Ulg/7wIVx1MDAxZTUvYb9JXHUwMDFht9F3wf2uk2Q0iuMsXHUwMDFkXHUwMDBlXHUwMDFhXHUwMDA21VO7auPTXHUwMDE2mGlcYnN0I3xcdTAwMGU24DVjzWMtXHQ+icJXXHUwMDE55VWbZ11VXcl9cSmheCU4QVx1MDAxZT92b0a7XHUwMDE57X+MXHUwMDA2jFGp4Ww0e/thICzlzNj8lnKyXHUwMDFk2lDYPl5ZbOGP03ubusWCa/Zw7Fx1MDAxY39cdTAwMDCLqmbvIn0=
+ eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SsW7CMFx1MDAxMN35iihdkUjSQChcdTAwMWItVVWpalx1MDAwN1x1MDAwNqRWXHUwMDFkTHxJrFx1MDAxONvEXHUwMDBlXHUwMDEwIf69tlx1MDAwM3GJXHUwMDE4O9aDpXt+7+58945cdTAwMDPP81UjwJ95Plx1MDAxY1JEXHSu0N5cdTAwMWZcdTAwMWF8XHUwMDA3lSSc6afIxpLXVWqZhVJiNlx1MDAxYVGuXHUwMDA1XHUwMDA1l6rlXHUwMDAzhVxyMCU140vHnne0t34h2Kh2q2r7Mc9KwnC0TTfJ22L+8mmllnTQnDiMu7gxhe+TLt5cdTAwMTOsXG6NhUHQYVx1MDAwNZC8UD1cdTAwMTCxnJo2XHUwMDFkXCJVxUt44pRXppG7wFx1MDAxZVd6jdIyr3jNsOOEY4TWmeNkhNKlamg7XHUwMDAwlFx1MDAxNnVcdTAwMDV+r8Lq0mJcdTAwMGbvdJLrXHUwMDAxO5UumVx1MDAxN1xmpLzScIFSoprer0x/4lx1MDAxNdvpfv/OwPA5XHUwMDAzqyl1hVx1MDAwMbDNXHUwMDEwh5Nx8lx1MDAxMEy7XHUwMDE3t9YwXG766DtndsVhOJ1cdTAwMDZBXHUwMDFjJZOOQeRCb1jZtFx1MDAxOaJcdTAwMTLc+ExcdTAwMTPPbvtXjdRcdTAwMDKjVuR+SFx0K/s8babyRu6LOTFBXHUwMDFizrBv8dPw31///vpTf1x1MDAwMaVESDj7S992XHUwMDA2Plx1MDAxMmKpdH5Nad3m71xi7Fx1MDAxZm/sM7PH6K07zT7BNHs8XHJOP7VXYMUifQ==