mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge branch 'master' into arnost/scroll-in-read-only-links
# Conflicts: # packages/excalidraw/appState.ts # packages/excalidraw/components/App.tsx # packages/excalidraw/element/textWysiwyg.test.tsx # packages/excalidraw/scene/scrollConstraints.ts # packages/excalidraw/scene/types.ts # packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap # packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap # packages/excalidraw/tests/__snapshots__/move.test.tsx.snap # packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap # packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap # packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap # packages/excalidraw/tests/linearElementEditor.test.tsx # packages/excalidraw/types.ts # packages/utils/export.ts
This commit is contained in:
commit
0f99e823f4
652 changed files with 35097 additions and 19495 deletions
|
@ -6,6 +6,6 @@
|
||||||
!.prettierrc
|
!.prettierrc
|
||||||
!package.json
|
!package.json
|
||||||
!public/
|
!public/
|
||||||
!src/
|
!packages/
|
||||||
!tsconfig.json
|
!tsconfig.json
|
||||||
!yarn.lock
|
!yarn.lock
|
||||||
|
|
|
@ -13,6 +13,8 @@ VITE_APP_PORTAL_URL=
|
||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
||||||
|
|
||||||
|
VITE_APP_AI_BACKEND=http://localhost:3015
|
||||||
|
|
||||||
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
|
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
|
||||||
|
|
||||||
# put these in your .env.local, or make sure you don't commit!
|
# put these in your .env.local, or make sure you don't commit!
|
||||||
|
|
|
@ -9,6 +9,8 @@ VITE_APP_PORTAL_URL=https://portal.excalidraw.com
|
||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
VITE_APP_PLUS_LP=https://plus.excalidraw.com
|
||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
VITE_APP_PLUS_APP=https://app.excalidraw.com
|
||||||
|
|
||||||
|
VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
|
||||||
|
|
||||||
# Fill to set socket server URL used for collaboration.
|
# Fill to set socket server URL used for collaboration.
|
||||||
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
|
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
|
||||||
VITE_APP_WS_SERVER_URL=
|
VITE_APP_WS_SERVER_URL=
|
||||||
|
|
|
@ -5,4 +5,4 @@ package-lock.json
|
||||||
firebase/
|
firebase/
|
||||||
dist/
|
dist/
|
||||||
public/workbox
|
public/workbox
|
||||||
src/packages/excalidraw/types
|
packages/excalidraw/types
|
||||||
|
|
2
.github/workflows/autorelease-preview.yml
vendored
2
.github/workflows/autorelease-preview.yml
vendored
|
@ -44,7 +44,7 @@ jobs:
|
||||||
- name: Auto release preview
|
- name: Auto release preview
|
||||||
id: "autorelease"
|
id: "autorelease"
|
||||||
run: |
|
run: |
|
||||||
yarn add @actions/core
|
yarn add @actions/core -W
|
||||||
yarn autorelease preview ${{ github.event.issue.number }}
|
yarn autorelease preview ${{ github.event.issue.number }}
|
||||||
- name: Post comment post release
|
- name: Post comment post release
|
||||||
if: always()
|
if: always()
|
||||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install and lint
|
- name: Install and lint
|
||||||
run: |
|
run: |
|
||||||
yarn --frozen-lockfile
|
yarn install
|
||||||
yarn test:other
|
yarn test:other
|
||||||
yarn test:code
|
yarn test:code
|
||||||
yarn test:typecheck
|
yarn test:typecheck
|
||||||
|
|
4
.github/workflows/locales-coverage.yml
vendored
4
.github/workflows/locales-coverage.yml
vendored
|
@ -22,11 +22,11 @@ jobs:
|
||||||
- name: Create report file
|
- name: Create report file
|
||||||
run: |
|
run: |
|
||||||
yarn locales-coverage
|
yarn locales-coverage
|
||||||
FILE_CHANGED=$(git diff src/locales/percentages.json)
|
FILE_CHANGED=$(git diff packages/excalidraw/locales/percentages.json)
|
||||||
if [ ! -z "${FILE_CHANGED}" ]; then
|
if [ ! -z "${FILE_CHANGED}" ]; then
|
||||||
git config --global user.name 'Excalidraw Bot'
|
git config --global user.name 'Excalidraw Bot'
|
||||||
git config --global user.email 'bot@excalidraw.com'
|
git config --global user.email 'bot@excalidraw.com'
|
||||||
git add src/locales/percentages.json
|
git add packages/excalidraw/locales/percentages.json
|
||||||
git commit -am "Auto commit: Calculate translation coverage"
|
git commit -am "Auto commit: Calculate translation coverage"
|
||||||
git push
|
git push
|
||||||
fi
|
fi
|
||||||
|
|
12
.github/workflows/size-limit.yml
vendored
12
.github/workflows/size-limit.yml
vendored
|
@ -15,16 +15,14 @@ jobs:
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- name: Install
|
- name: Install in packages/excalidraw
|
||||||
run: yarn --frozen-lockfile
|
run: yarn
|
||||||
- name: Install in src/packages/excalidraw
|
working-directory: packages/excalidraw
|
||||||
run: yarn --frozen-lockfile
|
|
||||||
working-directory: src/packages/excalidraw
|
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
- uses: andresz1/size-limit-action@v1
|
- uses: andresz1/size-limit-action@v1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build_script: build:umd
|
build_script: build:esm
|
||||||
skip_step: install
|
skip_step: install
|
||||||
directory: src/packages/excalidraw
|
directory: packages/excalidraw
|
||||||
|
|
2
.github/workflows/test-coverage-pr.yml
vendored
2
.github/workflows/test-coverage-pr.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: "18.x"
|
node-version: "18.x"
|
||||||
- name: "Install Deps"
|
- name: "Install Deps"
|
||||||
run: yarn --frozen-lockfile
|
run: yarn install
|
||||||
- name: "Test Coverage"
|
- name: "Test Coverage"
|
||||||
run: yarn test:coverage
|
run: yarn test:coverage
|
||||||
- name: "Report Coverage"
|
- name: "Report Coverage"
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -13,5 +13,5 @@ jobs:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- name: Install and test
|
- name: Install and test
|
||||||
run: |
|
run: |
|
||||||
yarn --frozen-lockfile
|
yarn install
|
||||||
yarn test:app
|
yarn test:app
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -21,10 +21,7 @@ npm-debug.log*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
src/packages/excalidraw/types
|
packages/excalidraw/types
|
||||||
src/packages/excalidraw/example/public/bundle.js
|
|
||||||
src/packages/excalidraw/example/public/excalidraw-assets-dev
|
|
||||||
src/packages/excalidraw/example/public/excalidraw.development.js
|
|
||||||
coverage
|
coverage
|
||||||
dev-dist
|
dev-dist
|
||||||
html
|
html
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
## 2020-10-13
|
|
||||||
|
|
||||||
- Added ability to embed scene source into exported PNG/SVG files so you can import the scene from them (open via `Load` button or drag & drop). #2219
|
|
|
@ -25,6 +25,9 @@
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
|
||||||
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
|
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://www.npmjs.com/package/@excalidraw/excalidraw">
|
||||||
|
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" />
|
||||||
|
</a>
|
||||||
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
||||||
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
|
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -70,7 +73,7 @@ The Excalidraw editor (npm package) supports:
|
||||||
|
|
||||||
## Excalidraw.com
|
## 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:
|
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/excalidraw-app) is part of this repository as well, and the app features:
|
||||||
|
|
||||||
- 📡 PWA support (works offline).
|
- 📡 PWA support (works offline).
|
||||||
- 🤼 Real-time collaboration.
|
- 🤼 Real-time collaboration.
|
||||||
|
@ -82,7 +85,7 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
|
**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install react react-dom @excalidraw/excalidraw
|
npm install react react-dom @excalidraw/excalidraw
|
||||||
|
@ -94,7 +97,7 @@ or via yarn
|
||||||
yarn add react react-dom @excalidraw/excalidraw
|
yarn add react react-dom @excalidraw/excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
|
Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details!
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
files:
|
files:
|
||||||
- source: /src/locales/en.json
|
- source: /packages/excalidraw/locales/en.json
|
||||||
translation: /src/locales/%locale%.json
|
translation: /packages/excalidraw/locales/%locale%.json
|
||||||
|
|
|
@ -34,7 +34,7 @@ Open the `Menu` in the below playground and you will see the `custom footer` ren
|
||||||
```jsx live noInline
|
```jsx live noInline
|
||||||
const MobileFooter = ({}) => {
|
const MobileFooter = ({}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
if (device.isMobile) {
|
if (device.editor.isMobile) {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -38,6 +38,7 @@ To render an item, its recommended to use `MainMenu.Item`.
|
||||||
| Prop | Type | Required | Default | Description |
|
| Prop | Type | Required | Default | Description |
|
||||||
| --- | --- | :-: | :-: | --- |
|
| --- | --- | :-: | :-: | --- |
|
||||||
| `onSelect` | `function` | Yes | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
|
| `onSelect` | `function` | Yes | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
|
||||||
|
| `selected` | `boolean` | No | `false` | Whether item is active |
|
||||||
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
|
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
|
||||||
| `icon` | `JSX.Element` | No | - | The icon used in 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 |
|
| `shortcut` | `string` | No | - | The shortcut to be shown for the menu item |
|
||||||
|
@ -70,6 +71,7 @@ function App() {
|
||||||
| Prop | Type | Required | Default | Description |
|
| Prop | Type | Required | Default | Description |
|
||||||
| --- | --- | :-: | :-: | --- |
|
| --- | --- | :-: | :-: | --- |
|
||||||
| `onSelect` | `function` | No | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
|
| `onSelect` | `function` | No | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
|
||||||
|
| `selected` | `boolean` | No | `false` | Whether item is active |
|
||||||
| `href` | `string` | Yes | - | The `href` attribute to be added to the `anchor` element. |
|
| `href` | `string` | Yes | - | The `href` attribute to be added to the `anchor` element. |
|
||||||
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
|
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
|
||||||
| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
|
| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
|
||||||
|
@ -131,7 +133,7 @@ function App() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/src/components/mainMenu/DefaultItems.tsx) of the default items.
|
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/components/mainMenu/DefaultItems.tsx) of the default items.
|
||||||
|
|
||||||
### MainMenu.Group
|
### MainMenu.Group
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ A given tab trigger button that switches to a given sidebar tab. It must be rend
|
||||||
| `className` | `string` | No | |
|
| `className` | `string` | No | |
|
||||||
| `style` | `React.CSSProperties` | No | |
|
| `style` | `React.CSSProperties` | No | |
|
||||||
|
|
||||||
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/ref#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
|
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
|
||||||
|
|
||||||
### MIME_TYPES
|
### MIME_TYPES
|
||||||
|
|
||||||
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
|
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
|
||||||
|
|
||||||
**How to use **
|
**How to use **
|
||||||
|
|
||||||
|
|
|
@ -2,21 +2,25 @@
|
||||||
|
|
||||||
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
|
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
|
||||||
|
|
||||||
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
|
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
|
||||||
|
|
||||||
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
|
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
|
||||||
|
|
||||||
## convertToExcalidrawElements
|
## convertToExcalidrawElements
|
||||||
|
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
```ts
|
||||||
convertToExcalidrawElements(elements:{" "}
|
convertToExcalidrawElements(
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133">
|
elements: ExcalidrawElementSkeleton,
|
||||||
ExcalidrawElementSkeleton
|
opts?: { regenerateIds: boolean }
|
||||||
</a>
|
): ExcalidrawElement[]
|
||||||
)
|
```
|
||||||
</pre>
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L137) | | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
|
||||||
|
| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
|
||||||
|
|
||||||
**_How to use_**
|
**_How to use_**
|
||||||
|
|
||||||
|
@ -24,13 +28,13 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/
|
||||||
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";
|
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";
|
||||||
```
|
```
|
||||||
|
|
||||||
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#updatescene) if you are using the Skeleton API
|
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) if you are using the Skeleton API
|
||||||
|
|
||||||
## Supported Features
|
## Supported Features
|
||||||
|
|
||||||
### Rectangle, Ellipse, and Diamond
|
### Rectangle, Ellipse, and Diamond
|
||||||
|
|
||||||
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional_.
|
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional\_.
|
||||||
|
|
||||||
For the Skeleton API to work, `convertToExcalidrawElements` needs to be called before passing it to Excalidraw Component via initialData, updateScene or any such API.
|
For the Skeleton API to work, `convertToExcalidrawElements` needs to be called before passing it to Excalidraw Component via initialData, updateScene or any such API.
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ function App() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L27) as well to decorate the shapes.
|
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L27) as well to decorate the shapes.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
|
@ -188,7 +192,7 @@ convertToExcalidrawElements([
|
||||||
|
|
||||||
### Text Containers
|
### Text Containers
|
||||||
|
|
||||||
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
|
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
|
||||||
|
|
||||||
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
|
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
|
||||||
|
|
||||||
|
@ -322,7 +326,7 @@ convertToExcalidrawElements([
|
||||||
|
|
||||||
### Arrow bindings
|
### Arrow bindings
|
||||||
|
|
||||||
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
|
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
|
||||||
|
|
||||||
```js
|
```js
|
||||||
convertToExcalidrawElements([
|
convertToExcalidrawElements([
|
||||||
|
@ -427,3 +431,45 @@ convertToExcalidrawElements([
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Frames
|
||||||
|
|
||||||
|
To create a frame, you need to pass `type`, `children` (list of Excalidraw element ids). The rest of the attributes are optional.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
type: "frame";
|
||||||
|
children: readonly ExcalidrawElement["id"][];
|
||||||
|
name?: string;
|
||||||
|
} & Partial<ExcalidrawFrameElement>);
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
convertToExcalidrawElements([
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"x": 10,
|
||||||
|
"y": 10,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"id": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "diamond",
|
||||||
|
"x": 120,
|
||||||
|
"y": 20,
|
||||||
|
"backgroundColor": "#fff3bf",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"label": {
|
||||||
|
"text": "HELLO EXCALIDRAW",
|
||||||
|
"strokeColor": "#099268",
|
||||||
|
"fontSize": 30
|
||||||
|
},
|
||||||
|
"id": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"children": ["1", "2"],
|
||||||
|
"name": "My frame"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
# ref
|
# excalidrawAPI
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">
|
(api:{" "}
|
||||||
createRef
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616">
|
||||||
</a>{" "}
|
ExcalidrawAPI
|
||||||
|{" "}
|
</a>
|
||||||
<a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>{" "}
|
) => void;
|
||||||
|{" "}
|
|
||||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">
|
|
||||||
callbackRef
|
|
||||||
</a>{" "}
|
|
||||||
| <br />
|
|
||||||
{ current: { readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
|
|
||||||
resolvablePromise
|
|
||||||
</a> } }
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
|
Once the callback is triggered, you will need to store the api in state to access it later.
|
||||||
|
|
||||||
|
```jsx showLineNumbers
|
||||||
|
export default function App() {
|
||||||
|
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
|
||||||
|
return <Excalidraw excalidrawAPI={{(api)=> setExcalidrawAPI(api)}} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616). We expose the below APIs :point_down:
|
||||||
|
|
||||||
| API | Signature | Usage |
|
| 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 |
|
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
|
||||||
| [updateLibrary](#updatelibrary) | `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 |
|
| [addFiles](#addfiles) | `function` | add files data to the appState |
|
||||||
|
@ -39,60 +38,21 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
|
||||||
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
|
| [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 |
|
| [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 |
|
| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
|
||||||
|
| [onChange](#onChange) | `function` | Subscribes to change events |
|
||||||
|
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
|
||||||
|
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
|
||||||
|
|
||||||
## readyPromise
|
:::info The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the `excalidrawAPI`.
|
||||||
|
|
||||||
<pre>
|
Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0.
|
||||||
const excalidrawRef = { current:{ readyPromise:
|
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
|
|
||||||
resolvablePromise
|
|
||||||
</a>
|
|
||||||
} }
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves.
|
:::
|
||||||
|
|
||||||
```jsx showLineNumbers
|
|
||||||
const resolvablePromise = () => {
|
|
||||||
let resolve;
|
|
||||||
let reject;
|
|
||||||
const promise = new Promise((_resolve, _reject) => {
|
|
||||||
resolve = _resolve;
|
|
||||||
reject = _reject;
|
|
||||||
});
|
|
||||||
promise.resolve = resolve;
|
|
||||||
promise.reject = reject;
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
const excalidrawRef = useMemo(
|
|
||||||
() => ({
|
|
||||||
current: {
|
|
||||||
readyPromise: resolvablePromise(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
excalidrawRef.current.readyPromise.then((api) => {
|
|
||||||
console.log("loaded", api);
|
|
||||||
});
|
|
||||||
}, [excalidrawRef]);
|
|
||||||
return (
|
|
||||||
<div style={{ height: "500px" }}>
|
|
||||||
<Excalidraw ref={excalidrawRef} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## updateScene
|
## updateScene
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(scene:{" "}
|
(scene:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L339">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L339">
|
||||||
sceneData
|
sceneData
|
||||||
</a>
|
</a>
|
||||||
) => void
|
) => void
|
||||||
|
@ -102,9 +62,9 @@ You can use this function to update the scene with the sceneData. It accepts the
|
||||||
|
|
||||||
| Name | Type | Description |
|
| 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 |
|
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L38) | The `elements` to be updated in the scene |
|
||||||
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L39) | The `appState` to be updated in the scene. |
|
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L39) | The `appState` to be updated in the scene. |
|
||||||
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
|
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
|
||||||
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
|
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
|
||||||
|
|
||||||
```jsx live
|
```jsx live
|
||||||
|
@ -165,13 +125,13 @@ function App() {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(opts: { <br /> libraryItems:{" "}
|
(opts: { <br /> libraryItems:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249">
|
||||||
LibraryItemsSource
|
LibraryItemsSource
|
||||||
</a>
|
</a>
|
||||||
;<br /> merge?: boolean; <br /> prompt?: boolean;
|
;<br /> merge?: boolean; <br /> prompt?: boolean;
|
||||||
<br /> openLibraryMenu?: boolean;
|
<br /> openLibraryMenu?: boolean;
|
||||||
<br /> defaultStatus?: "unpublished" | "published"; <br /> }) => Promise<
|
<br /> defaultStatus?: "unpublished" | "published"; <br /> }) => Promise<
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L246">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L246">
|
||||||
LibraryItems
|
LibraryItems
|
||||||
</a>
|
</a>
|
||||||
>
|
>
|
||||||
|
@ -181,7 +141,7 @@ You can use this function to update the library. It accepts the below attributes
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
|
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
|
||||||
| `merge` | boolean | `false` | Whether to merge with existing library items. |
|
| `merge` | boolean | `false` | Whether to merge with existing library items. |
|
||||||
| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
|
| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
|
||||||
| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
|
| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
|
||||||
|
@ -229,7 +189,7 @@ function App() {
|
||||||
</button>
|
</button>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
ref={(api) => setExcalidrawAPI(api)}
|
ref={(api) => setExcalidrawAPI(api)}
|
||||||
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/src/initialData.js
|
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/packages/excalidraw/initialData.js
|
||||||
initialData={{
|
initialData={{
|
||||||
libraryItems: initialData.libraryItems,
|
libraryItems: initialData.libraryItems,
|
||||||
appState: { openSidebar: "library" },
|
appState: { openSidebar: "library" },
|
||||||
|
@ -244,7 +204,7 @@ function App() {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(files:{" "}
|
(files:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
|
||||||
BinaryFileData
|
BinaryFileData
|
||||||
</a>
|
</a>
|
||||||
) => void
|
) => void
|
||||||
|
@ -264,7 +224,7 @@ Resets the scene. If `resetLoadingState` is passed as true then it will also for
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
() =>{" "}
|
() =>{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
|
||||||
ExcalidrawElement[]
|
ExcalidrawElement[]
|
||||||
</a>
|
</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -275,7 +235,7 @@ Returns all the elements including the deleted in the scene.
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
() => NonDeleted<
|
() => NonDeleted<
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
|
||||||
ExcalidrawElement
|
ExcalidrawElement
|
||||||
</a>
|
</a>
|
||||||
[]>
|
[]>
|
||||||
|
@ -287,7 +247,7 @@ Returns all the elements excluding the deleted in the scene
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
() =>{" "}
|
() =>{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
|
||||||
AppState
|
AppState
|
||||||
</a>
|
</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -328,7 +288,7 @@ Scroll the nearest element out of the elements supplied to the center of the vie
|
||||||
|
|
||||||
| Attribute | type | default | Description |
|
| Attribute | type | default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
|
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
|
||||||
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
|
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
|
||||||
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
|
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
|
||||||
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
|
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
|
||||||
|
@ -376,7 +336,7 @@ The unique id of the excalidraw component. This can be used to identify the exca
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
() =>{" "}
|
() =>{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82">
|
||||||
files
|
files
|
||||||
</a>
|
</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -387,14 +347,25 @@ This API can be used to get the files present in the scene. It may contain files
|
||||||
|
|
||||||
This API has the below signature. It sets the `tool` passed in param as the active tool.
|
This API has the below signature. It sets the `tool` passed in param as the active tool.
|
||||||
|
|
||||||
<pre>
|
```ts
|
||||||
(tool: <br /> { type:{" "}
|
(
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">
|
tool: (
|
||||||
SHAPES
|
| (
|
||||||
</a>
|
| { type: Exclude<ToolType, "image"> }
|
||||||
[number]["value"]| "eraser" } |
|
| {
|
||||||
<br /> { type: "custom"; customType: string }) => void
|
type: Extract<ToolType, "image">;
|
||||||
</pre>
|
insertOnCanvasDirectly?: boolean;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| { type: "custom"; customType: string }
|
||||||
|
) & { locked?: boolean },
|
||||||
|
) => {};
|
||||||
|
```
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
|
||||||
|
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
|
||||||
|
|
||||||
## setCursor
|
## setCursor
|
||||||
|
|
||||||
|
@ -421,3 +392,51 @@ This API is especially useful when you render a custom [`<Sidebar/>`](/docs/@exc
|
||||||
```
|
```
|
||||||
|
|
||||||
This API can be used to reset to default mouse cursor.
|
This API can be used to reset to default mouse cursor.
|
||||||
|
|
||||||
|
## onChange
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
(
|
||||||
|
callback: (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
appState: AppState,
|
||||||
|
files: BinaryFiles,
|
||||||
|
) => void
|
||||||
|
) => () => void
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscribes to change events, similar to [`props.onChange`](/docs/@excalidraw/excalidraw/api/props#onchange).
|
||||||
|
|
||||||
|
Returns an unsubscribe function.
|
||||||
|
|
||||||
|
## onPointerDown
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
(
|
||||||
|
callback: (
|
||||||
|
activeTool: AppState["activeTool"],
|
||||||
|
pointerDownState: PointerDownState,
|
||||||
|
event: React.PointerEvent<HTMLElement>,
|
||||||
|
) => void,
|
||||||
|
) => () => void
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscribes to canvas `pointerdown` events.
|
||||||
|
|
||||||
|
Returns an unsubscribe function.
|
||||||
|
|
||||||
|
## onPointerUp
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
(
|
||||||
|
callback: (
|
||||||
|
activeTool: AppState["activeTool"],
|
||||||
|
pointerDownState: PointerDownState,
|
||||||
|
event: PointerEvent,
|
||||||
|
) => void,
|
||||||
|
) => () => void
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscribes to canvas `pointerup` events.
|
||||||
|
|
||||||
|
Returns an unsubscribe function.
|
|
@ -1,18 +1,18 @@
|
||||||
# initialData
|
# initialData
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{ elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> }
|
{ elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> }
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
|
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 |
|
| 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. |
|
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
|
||||||
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
|
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
|
||||||
| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
|
| `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. |
|
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L247) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/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. |
|
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82) | The `files` added to the scene. |
|
||||||
|
|
||||||
You might want to use this when you want to load excalidraw with some initial elements and app state.
|
You might want to use this when you want to load excalidraw with some initial elements and app state.
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Props
|
# Props
|
||||||
|
|
||||||
All `props` are *optional*.
|
All `props` are _optional_.
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
||||||
| [`ref`](/docs/@excalidraw/excalidraw/api/props/ref) | `object` | _ | `Ref` to be passed to Excalidraw |
|
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | _ | Callback triggered with the excalidraw api once rendered |
|
||||||
| [`isCollaborating`](#iscollaborating) | `boolean` | _ | This indicates if the app is in `collaboration` mode |
|
| [`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`. |
|
| [`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. |
|
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
|
||||||
|
@ -23,7 +23,7 @@ All `props` are *optional*.
|
||||||
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
|
| [`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 |
|
| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
|
||||||
| [`name`](#name) | `string` | | Name of the drawing |
|
| [`name`](#name) | `string` | | Name of the drawing |
|
||||||
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
|
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
|
||||||
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
|
| [`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. |
|
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
||||||
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
|
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
|
||||||
|
@ -33,11 +33,11 @@ All `props` are *optional*.
|
||||||
|
|
||||||
### Storing custom data on Excalidraw elements
|
### Storing custom data on Excalidraw elements
|
||||||
|
|
||||||
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L66) and is optional.
|
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L66) and is optional.
|
||||||
|
|
||||||
You can use this to add any extra information you need to keep track of.
|
You can use this to add any extra information you need to keep track of.
|
||||||
|
|
||||||
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/ref#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/ref#updatelibrary) afterwards.
|
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatelibrary) afterwards.
|
||||||
|
|
||||||
```js showLineNumbers
|
```js showLineNumbers
|
||||||
{
|
{
|
||||||
|
@ -59,11 +59,11 @@ Every time component updates, this callback if passed will get triggered and has
|
||||||
(excalidrawElements, appState, files) => void;
|
(excalidrawElements, appState, files) => void;
|
||||||
```
|
```
|
||||||
|
|
||||||
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) in the scene.
|
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) in the scene.
|
||||||
|
|
||||||
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
|
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
|
||||||
|
|
||||||
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
|
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) which are added to the scene.
|
||||||
|
|
||||||
Here you can try saving the data to your backend or local storage for example.
|
Here you can try saving the data to your backend or local storage for example.
|
||||||
|
|
||||||
|
@ -79,22 +79,30 @@ This callback is triggered when mouse pointer is updated.
|
||||||
|
|
||||||
2.`button`: The position of the button. This will be one of `["down", "up"]`
|
2.`button`: The position of the button. This will be one of `["down", "up"]`
|
||||||
|
|
||||||
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) map of the scene
|
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L131) map of the scene
|
||||||
|
|
||||||
```js
|
```js
|
||||||
(exportedElements, appState, canvas) => void
|
(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.
|
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L87) which needs to be exported.
|
||||||
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
|
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
|
||||||
3. `canvas`: The `HTMLCanvasElement` of the scene.
|
3. `canvas`: The `HTMLCanvasElement` of the scene.
|
||||||
|
|
||||||
### onPointerDown
|
### onPointerDown
|
||||||
|
|
||||||
This prop if passed will be triggered on pointer down events and has the below signature.
|
This prop if passed will be triggered on pointer down events and has the below signature.
|
||||||
|
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(activeTool: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115"> AppState["activeTool"]</a>, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">PointerDownState</a>) => void
|
(activeTool:{" "}
|
||||||
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
|
||||||
|
{" "}
|
||||||
|
AppState["activeTool"]
|
||||||
|
</a>
|
||||||
|
, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L424">
|
||||||
|
PointerDownState
|
||||||
|
</a>) => void
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### onScrollChange
|
### onScrollChange
|
||||||
|
@ -110,7 +118,11 @@ This prop if passed will be triggered when canvas is scrolled and has the below
|
||||||
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
|
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">ClipboardData</a>, event: ClipboardEvent | null) => boolean
|
(data:{" "}
|
||||||
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/clipboard.ts#L18">
|
||||||
|
ClipboardData
|
||||||
|
</a>
|
||||||
|
, event: ClipboardEvent | null) => boolean
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
|
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
|
||||||
|
@ -123,7 +135,7 @@ This callback if supplied will get triggered when the library is updated and has
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(items:{" "}
|
(items:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">
|
||||||
LibraryItems
|
LibraryItems
|
||||||
</a>
|
</a>
|
||||||
) => void | Promise<any>
|
) => void | Promise<any>
|
||||||
|
@ -136,8 +148,11 @@ It is invoked with empty items when user clears the library. You can use this ca
|
||||||
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
|
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>,
|
(element:{" "}
|
||||||
event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
|
||||||
|
ExcalidrawElement
|
||||||
|
</a>
|
||||||
|
, event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
@ -167,7 +182,7 @@ const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
|
||||||
|
|
||||||
### langCode
|
### langCode
|
||||||
|
|
||||||
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
|
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { defaultLang, languages } from "@excalidraw/excalidraw";
|
import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||||
|
@ -176,34 +191,34 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||||
| name | type |
|
| name | type |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `defaultLang` | `string` |
|
| `defaultLang` | `string` |
|
||||||
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
|
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
|
||||||
|
|
||||||
### viewModeEnabled
|
### viewModeEnabled
|
||||||
|
|
||||||
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over *intialData.appState.viewModeEnabled*, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over _intialData.appState.viewModeEnabled_, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||||
|
|
||||||
### zenModeEnabled
|
### zenModeEnabled
|
||||||
|
|
||||||
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over *intialData.appState.zenModeEnabled*, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over _intialData.appState.zenModeEnabled_, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||||
|
|
||||||
### gridModeEnabled
|
### gridModeEnabled
|
||||||
|
|
||||||
This prop indicates whether the shows the grid. When supplied, the value takes precedence over *intialData.appState.gridModeEnabled*, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
This prop indicates whether the shows the grid. When supplied, the value takes precedence over _intialData.appState.gridModeEnabled_, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
|
||||||
|
|
||||||
### libraryReturnUrl
|
### libraryReturnUrl
|
||||||
|
|
||||||
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
||||||
Defaults to *window.location.origin + window.location.pathname*. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
|
||||||
|
|
||||||
### theme
|
### theme
|
||||||
|
|
||||||
This prop controls Excalidraw's theme. When supplied, the value takes precedence over *intialData.appState.theme*, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless *UIOptions.canvasActions.toggleTheme* is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
|
This prop controls Excalidraw's theme. When supplied, the value takes precedence over _intialData.appState.theme_, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless _UIOptions.canvasActions.toggleTheme_ is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
|
||||||
|
|
||||||
You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
|
You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
|
||||||
|
|
||||||
### name
|
### name
|
||||||
|
|
||||||
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over *intialData.appState.name*, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
|
||||||
|
|
||||||
|
|
||||||
### detectScroll
|
### detectScroll
|
||||||
|
@ -236,4 +251,4 @@ validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) =>
|
||||||
|
|
||||||
This is an optional property. By default we support a handful of well-known sites. You may allow additional sites or disallow the default ones by supplying a custom validator. If you pass `true`, all URLs will be allowed. You can also supply a list of hostnames, RegExp (or list of RegExp objects), or a function. If the function returns `undefined`, the built-in validator will be used.
|
This is an optional property. By default we support a handful of well-known sites. You may allow additional sites or disallow the default ones by supplying a custom validator. If you pass `true`, all URLs will be allowed. You can also supply a list of hostnames, RegExp (or list of RegExp objects), or a function. If the function returns `undefined`, the built-in validator will be used.
|
||||||
|
|
||||||
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
|
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(isMobile: boolean, appState:
|
(isMobile: boolean, appState:
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
|
||||||
AppState
|
AppState
|
||||||
</a>) => JSX | null
|
</a>) => JSX | null
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -66,7 +66,7 @@ function App() {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
(element: NonDeleted<ExcalidrawEmbeddableElement>, appState:{" "}
|
(element: NonDeleted<ExcalidrawEmbeddableElement>, appState:{" "}
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
|
||||||
AppState
|
AppState
|
||||||
</a>
|
</a>
|
||||||
) => JSX.Element | null
|
) => JSX.Element | null
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# UIOptions
|
# UIOptions
|
||||||
|
|
||||||
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) and [`welcomeScreen`](#welcmescreen).
|
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) [`welcomeScreen`](#welcmescreen) and [`tools`](#tools).
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{
|
{
|
||||||
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L372">
|
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L372">
|
||||||
CanvasActions
|
CanvasActions
|
||||||
</a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
|
</a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ If `UIOptions.canvasActions.export` is `false` the export button will not be ren
|
||||||
|
|
||||||
## dockedSidebarBreakpoint
|
## dockedSidebarBreakpoint
|
||||||
|
|
||||||
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L161).
|
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L161).
|
||||||
If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown
|
If 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.
|
If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
|
||||||
|
|
||||||
|
@ -70,3 +70,12 @@ function App() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## tools
|
||||||
|
|
||||||
|
This `prop` controls the visibility of the tools in the editor.
|
||||||
|
Currently you can control the visibility of `image` tool via this prop.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| image | boolean | true | Decides whether `image` tool should be visible.
|
||||||
|
|
|
@ -20,16 +20,16 @@ exportToCanvas({<br/>
|
||||||
getDimensions,<br/>
|
getDimensions,<br/>
|
||||||
files,<br/>
|
files,<br/>
|
||||||
exportPadding?: number;<br/>
|
exportPadding?: number;<br/>
|
||||||
}: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a>
|
}: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| 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. |
|
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to be exported to canvas. |
|
||||||
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/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. |
|
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L17) | The app state of the scene. |
|
||||||
| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to `1`), with which canvas is to be exported. |
|
| [`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. |
|
| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
|
||||||
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59) | _ | The files added to the scene. |
|
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59) | _ | The files added to the scene. |
|
||||||
| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
|
| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ function App() {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
exportToBlob(<br/>
|
exportToBlob(<br/>
|
||||||
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & {<br/>
|
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L14">ExportOpts</a> & {<br/>
|
||||||
mimeType?: string,<br/>
|
mimeType?: string,<br/>
|
||||||
quality?: number,<br/>
|
quality?: number,<br/>
|
||||||
exportPadding?: number;<br/>
|
exportPadding?: number;<br/>
|
||||||
|
@ -134,16 +134,16 @@ Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-
|
||||||
<pre>
|
<pre>
|
||||||
exportToSvg({<br/>
|
exportToSvg({<br/>
|
||||||
elements:
|
elements:
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
|
||||||
ExcalidrawElement[]
|
ExcalidrawElement[]
|
||||||
</a>,<br/>
|
</a>,<br/>
|
||||||
appState:
|
appState:
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95"> AppState
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95"> AppState
|
||||||
</a>,<br/>
|
</a>,<br/>
|
||||||
exportPadding: number,<br/>
|
exportPadding: number,<br/>
|
||||||
metadata: string,<br/>
|
metadata: string,<br/>
|
||||||
files:
|
files:
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
|
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
|
||||||
BinaryFiles
|
BinaryFiles
|
||||||
</a>,<br/>
|
</a>,<br/>
|
||||||
});
|
});
|
||||||
|
@ -151,10 +151,10 @@ exportToSvg({<br/>
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| 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 `|
|
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to exported as `svg `|
|
||||||
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The `appState` of the scene |
|
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L11) | The `appState` of the scene |
|
||||||
| exportPadding | number | 10 | The `padding` to be added on canvas |
|
| exportPadding | number | 10 | The `padding` to be added on canvas |
|
||||||
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | undefined | The `files` added to the scene. |
|
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) | undefined | The `files` added to the scene. |
|
||||||
|
|
||||||
This function returns a promise which resolves to `svg` of the exported drawing.
|
This function returns a promise which resolves to `svg` of the exported drawing.
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ This function returns a promise which resolves to `svg` of the exported drawing.
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
exportToClipboard(<br/>
|
exportToClipboard(<br/>
|
||||||
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a> & {<br/>
|
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a> & {<br/>
|
||||||
mimeType?: string,<br/>
|
mimeType?: string,<br/>
|
||||||
quality?: number;<br/>
|
quality?: number;<br/>
|
||||||
type: 'png' | 'svg' |'json'<br/>
|
type: 'png' | 'svg' |'json'<br/>
|
||||||
|
|
|
@ -8,7 +8,7 @@ id: "restore"
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/> localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>
|
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/> localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
**_How to use_**
|
**_How to use_**
|
||||||
|
@ -17,7 +17,7 @@ restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob
|
||||||
import { restoreAppState } from "@excalidraw/excalidraw";
|
import { restoreAppState } from "@excalidraw/excalidraw";
|
||||||
```
|
```
|
||||||
|
|
||||||
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) and if any key is missing, it will be set to its `default` value.
|
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) and if any key is missing, it will be set to its `default` value.
|
||||||
|
|
||||||
When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.
|
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.
|
Use this as a way to not override user's defaults if you persist them.
|
||||||
|
@ -29,16 +29,16 @@ You can pass `null` / `undefined` if not applicable.
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
restoreElements(
|
restoreElements(
|
||||||
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>
|
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
||||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
||||||
)
|
)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
| Prop | Type | Description |
|
| Prop | Type | Description |
|
||||||
| ---- | ---- | ---- |
|
| ---- | ---- | ---- |
|
||||||
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
|
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
|
||||||
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
|
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
|
||||||
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
|
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
|
||||||
|
|
||||||
#### localElements
|
#### localElements
|
||||||
|
@ -70,15 +70,15 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
restore(
|
restore(
|
||||||
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>
|
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/>
|
||||||
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>
|
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a><br/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L4">DataState</a><br/>
|
||||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
||||||
|
|
||||||
)
|
)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
|
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreElements) about `localElements`.
|
||||||
|
|
||||||
**_How to use_**
|
**_How to use_**
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ This function makes sure elements and state is set to appropriate values and set
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>
|
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>
|
||||||
defaultStatus: "published" | "unpublished")
|
defaultStatus: "published" | "unpublished")
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ These are pure Javascript functions exported from the @excalidraw/excalidraw [`@
|
||||||
|
|
||||||
### serializeAsJSON
|
### serializeAsJSON
|
||||||
|
|
||||||
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L42) source for details).
|
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/json.ts#L42) source for details).
|
||||||
|
|
||||||
If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
|
If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ If you want to overwrite the `source` field in the `JSON` string, you can set `w
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
serializeAsJSON({<br/>
|
serializeAsJSON({<br/>
|
||||||
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
||||||
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>,<br/>
|
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>,<br/>
|
||||||
}): string
|
}): string
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ If you want to overwrite the source field in the JSON string, you can set `windo
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
serializeLibraryAsJSON(
|
serializeLibraryAsJSON(
|
||||||
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems[]</a>)
|
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems[]</a>)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
**How to use**
|
**How to use**
|
||||||
|
@ -53,7 +53,7 @@ Returns `true` if element is invisibly small (e.g. width & height are zero).
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>): boolean
|
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement</a>): boolean
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
**How to use**
|
**How to use**
|
||||||
|
@ -80,10 +80,10 @@ excalidrawAPI.updateScene(scene);
|
||||||
<pre>
|
<pre>
|
||||||
loadFromBlob(<br/>
|
loadFromBlob(<br/>
|
||||||
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>
|
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>
|
||||||
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>
|
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>
|
||||||
fileHandle?: FileSystemHandle | null <br/>
|
fileHandle?: FileSystemHandle | null <br/>
|
||||||
) => Promise<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L61">RestoredDataState</a>>
|
) => Promise<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L61">RestoredDataState</a>>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### loadLibraryFromBlob
|
### loadLibraryFromBlob
|
||||||
|
@ -129,11 +129,11 @@ if (contents.type === MIME_TYPES.excalidraw) {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
loadSceneOrLibraryFromBlob(<br/>
|
loadSceneOrLibraryFromBlob(<br/>
|
||||||
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,
|
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>
|
||||||
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>
|
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>
|
||||||
fileHandle?: FileSystemHandle | null<br/>
|
fileHandle?: FileSystemHandle | null<br/>
|
||||||
) => Promise<{ type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L33">ImportedLibraryState</a>}>
|
) => Promise<{ type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L33">ImportedLibraryState</a>}>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### getFreeDrawSvgPath
|
### getFreeDrawSvgPath
|
||||||
|
@ -149,7 +149,7 @@ import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
|
||||||
**Signature**
|
**Signature**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
|
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### isLinearElement
|
### isLinearElement
|
||||||
|
@ -164,9 +164,9 @@ import { isLinearElement } from "@excalidraw/excalidraw";
|
||||||
|
|
||||||
**Signature**
|
**Signature**
|
||||||
|
|
||||||
```tsx
|
<pre>
|
||||||
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L80">ExcalidrawElement</a>): boolean
|
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L80">ExcalidrawElement</a>): boolean
|
||||||
```
|
</pre>
|
||||||
|
|
||||||
### getNonDeletedElements
|
### getNonDeletedElements
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ import { getNonDeletedElements } from "@excalidraw/excalidraw";
|
||||||
**Signature**
|
**Signature**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
|
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### mergeLibraryItems
|
### mergeLibraryItems
|
||||||
|
@ -195,8 +195,10 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw";
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
mergeLibraryItems(localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/>
|
mergeLibraryItems(<br/>
|
||||||
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a>) => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
|
localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>,<br/>
|
||||||
|
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems</a><br/>
|
||||||
|
): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### parseLibraryTokensFromUrl
|
### parseLibraryTokensFromUrl
|
||||||
|
@ -237,8 +239,8 @@ export const App = () => {
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
useHandleLibrary(opts: {<br/>
|
useHandleLibrary(opts: {<br/>
|
||||||
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L494">ExcalidrawAPI</a>,<br/>
|
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L494">ExcalidrawAPI</a>,<br/>
|
||||||
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L253">LibraryItemsSource</a><br/>
|
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L253">LibraryItemsSource</a><br/>
|
||||||
});
|
});
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
@ -251,7 +253,7 @@ This function returns the current `scene` version.
|
||||||
**_Signature_**
|
**_Signature_**
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>)
|
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
**How to use**
|
**How to use**
|
||||||
|
@ -272,7 +274,7 @@ import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
sceneCoordsToViewportCoords({ sceneX: number, sceneY: number },<br/>
|
sceneCoordsToViewportCoords({ sceneX: number, sceneY: number },<br/>
|
||||||
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): { x: number, y: number }
|
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): { x: number, y: number }
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### viewportCoordsToSceneCoords
|
### viewportCoordsToSceneCoords
|
||||||
|
@ -287,7 +289,7 @@ import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/>
|
viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/>
|
||||||
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): {x: number, y: number}
|
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): {x: number, y: number}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### useDevice
|
### useDevice
|
||||||
|
@ -299,7 +301,7 @@ Open the `main menu` in the below example to view the footer.
|
||||||
```jsx live noInline
|
```jsx live noInline
|
||||||
const MobileFooter = ({}) => {
|
const MobileFooter = ({}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
if (device.isMobile) {
|
if (device.editor.isMobile) {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
<button
|
<button
|
||||||
|
@ -331,14 +333,15 @@ const App = () => (
|
||||||
render(<App />);
|
render(<App />);
|
||||||
```
|
```
|
||||||
|
|
||||||
The `device` has the following `attributes`
|
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `isSmScreen` | `boolean` | Set to `true` when the device small screen is small (Width < `640px` ) |
|
| `viewport.isMobile` | `boolean` | Set to `true` when viewport is in `mobile` breakpoint |
|
||||||
| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
|
| `viewport.isLandscape` | `boolean` | Set to `true` when the viewport is in `landscape` mode |
|
||||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
|
| `editor.canFitSidebar` | `boolean` | Set to `true` if there's enough space to fit the `sidebar` |
|
||||||
| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
|
| `editor.isMobile` | `boolean` | Set to `true` when editor container is in `mobile` breakpoint |
|
||||||
|
| `isTouchScreen` | `boolean` | Set to `true` for `touch` when touch event detected |
|
||||||
|
|
||||||
### i18n
|
### i18n
|
||||||
|
|
||||||
|
@ -347,8 +350,8 @@ To help with localization, we export the following.
|
||||||
| name | type |
|
| name | type |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `defaultLang` | `string` |
|
| `defaultLang` | `string` |
|
||||||
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
|
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
|
||||||
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
|
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
|
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
|
||||||
|
@ -383,3 +386,94 @@ function App() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### getCommonBounds
|
||||||
|
|
||||||
|
This util can be used to get the common bounds of the passed elements.
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getCommonBounds(
|
||||||
|
elements: readonly ExcalidrawElement[]
|
||||||
|
): readonly [
|
||||||
|
minX: number,
|
||||||
|
minY: number,
|
||||||
|
maxX: number,
|
||||||
|
maxY: number,
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**_How to use_**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { getCommonBounds } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
||||||
|
### elementsOverlappingBBox
|
||||||
|
|
||||||
|
To filter `elements` that are inside, overlap, or contain the `bounds` rectangle.
|
||||||
|
|
||||||
|
The bounds check is approximate and does not precisely follow the element's shape. You can also supply `errorMargin` which effectively makes the `bounds` larger by that amount.
|
||||||
|
|
||||||
|
This API has 3 `type`s of operation: `overlap`, `contain`, and `inside`:
|
||||||
|
|
||||||
|
- `overlap` - filters elements that are overlapping or inside bounds.
|
||||||
|
- `contain` - filters elements that are inside bounds or bounds inside elements.
|
||||||
|
- `inside` - filters elements that are inside bounds.
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
elementsOverlappingBBox(<br/>
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];<br/>
|
||||||
|
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a> | ExcalidrawElement;<br/>
|
||||||
|
errorMargin?: number;<br/>
|
||||||
|
type: "overlap" | "contain" | "inside";<br/>
|
||||||
|
): NonDeletedExcalidrawElement[];
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**_How to use_**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { elementsOverlappingBBox } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
||||||
|
### isElementInsideBBox
|
||||||
|
|
||||||
|
Lower-level API than `elementsOverlappingBBox` to check if a single `element` is inside `bounds`. If `eitherDirection=true`, returns `true` if `element` is fully inside `bounds` rectangle, or vice versa. When `false`, it returns `true` only for the former case.
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
isElementInsideBBox(<br/>
|
||||||
|
element: NonDeletedExcalidrawElement,<br/>
|
||||||
|
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
|
||||||
|
eitherDirection = false,<br/>
|
||||||
|
): boolean
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**_How to use_**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { isElementInsideBBox } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
||||||
|
### elementPartiallyOverlapsWithOrContainsBBox
|
||||||
|
|
||||||
|
Checks if `element` is overlapping the `bounds` rectangle, or is fully inside.
|
||||||
|
|
||||||
|
**_Signature_**
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
elementPartiallyOverlapsWithOrContainsBBox(<br/>
|
||||||
|
element: NonDeletedExcalidrawElement,<br/>
|
||||||
|
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
|
||||||
|
): boolean
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**_How to use_**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { elementPartiallyOverlapsWithOrContainsBBox } from "@excalidraw/excalidraw";
|
||||||
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Customizing Styles
|
# 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.
|
Excalidraw uses 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:
|
Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ Most notably, you can customize the primary colors, by overriding these variable
|
||||||
- `--color-primary-light`
|
- `--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.
|
- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
|
||||||
|
|
||||||
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
|
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/css/theme.scss), though most of them will not make sense to override.
|
||||||
|
|
||||||
```css showLineNumbers
|
```css showLineNumbers
|
||||||
.custom-styles .excalidraw {
|
.custom-styles .excalidraw {
|
||||||
|
|
|
@ -13,7 +13,7 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
|
||||||
1. Install the dependencies
|
1. Install the dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/packages/excalidraw && yarn
|
cd packages/excalidraw && yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start the example app
|
2. Start the example app
|
||||||
|
@ -43,7 +43,7 @@ Once the version is released `@excalibot` will post a comment with the release v
|
||||||
To release the next stable version follow the below steps:
|
To release the next stable version follow the below steps:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn prerelease version
|
yarn prerelease:excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
|
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
|
||||||
|
@ -51,7 +51,7 @@ You need to pass the `version` for which you want to create the release. This wi
|
||||||
The next step is to run the `release` script:
|
The next step is to run the `release` script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn release
|
yarn release:excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
This will publish the package.
|
This will publish the package.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
### Does this package support collaboration ?
|
### Does this package support collaboration ?
|
||||||
|
|
||||||
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
|
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
|
||||||
|
|
||||||
### Turning off Aggressive Anti-Fingerprinting in Brave browser
|
### Turning off Aggressive Anti-Fingerprinting in Brave browser
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ We strongly recommend turning it off. You can follow the steps below on how to d
|
||||||
|
|
||||||
2. Once opened, look for **Aggressively Block Fingerprinting**
|
2. Once opened, look for **Aggressively Block Fingerprinting**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3. Switch to **Block Fingerprinting**
|
3. Switch to **Block Fingerprinting**
|
||||||
|
|
||||||
|
@ -31,6 +31,17 @@ We strongly recommend turning it off. You can follow the steps below on how to d
|
||||||
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
|
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
|
||||||
|
|
||||||
|
|
||||||
|
### ReferenceError: process is not defined
|
||||||
|
|
||||||
|
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
|
||||||
|
|
||||||
|
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
|
||||||
|
|
||||||
|
```
|
||||||
|
define: {
|
||||||
|
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
## Need help?
|
## Need help?
|
||||||
|
|
||||||
|
|
|
@ -30,25 +30,74 @@ function App() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rendering Excalidraw only on client
|
### Next.js
|
||||||
|
|
||||||
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
|
Since _Excalidraw_ doesn't support server side rendering, 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.
|
Here are two ways on how you can render **Excalidraw** on **Next.js**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. Using **Next.js Dynamic** import [Recommended].
|
||||||
|
|
||||||
|
Since Excalidraw doesn't support server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`.
|
||||||
|
|
||||||
|
```jsx showLineNumbers
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
const Excalidraw = dynamic(
|
||||||
|
async () => (await import("@excalidraw/excalidraw")).Excalidraw,
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
export default function App() {
|
||||||
|
return <Excalidraw />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-dynamic-k8yjq2).
|
||||||
|
|
||||||
|
|
||||||
|
2. Importing Excalidraw once **client** is rendered.
|
||||||
|
|
||||||
```jsx showLineNumbers
|
```jsx showLineNumbers
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [Excalidraw, setExcalidraw] = useState(null);
|
const [Excalidraw, setExcalidraw] = useState(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
import("@excalidraw/excalidraw").then((comp) => setExcalidraw(comp.Excalidraw));
|
import("@excalidraw/excalidraw").then((comp) =>
|
||||||
|
setExcalidraw(comp.Excalidraw),
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
return <>{Excalidraw && <Excalidraw />}</>;
|
return <>{Excalidraw && <Excalidraw />}</>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d)
|
||||||
|
|
||||||
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
|
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
|
||||||
|
|
||||||
|
### Preact
|
||||||
|
|
||||||
|
Since we support `umd` build ships with `react/jsx-runtime` and `react-dom/client` inlined with the package. This conflicts with `Preact` and hence the build doesn't work directly with `Preact`.
|
||||||
|
|
||||||
|
However we have shipped a separate build for `Preact` so if you are using `Preact` you need to set `process.env.IS_PREACT` to `true` to use the `Preact` build.
|
||||||
|
|
||||||
|
Once the above `env` variable is set, you will be able to use the package in `Preact` as well.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
|
||||||
|
|
||||||
|
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
|
||||||
|
|
||||||
|
```
|
||||||
|
define: {
|
||||||
|
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||||
|
},
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
## Browser
|
## Browser
|
||||||
|
|
||||||
To use it in a browser directly:
|
To use it in a browser directly:
|
||||||
|
@ -99,7 +148,7 @@ import TabItem from "@theme/TabItem";
|
||||||
<h1>Excalidraw Embed Example</h1>
|
<h1>Excalidraw Embed Example</h1>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="src/index.js"></script>
|
<script type="text/javascript" src="packages/excalidraw/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
|
@ -38,9 +38,9 @@ Add the diagram type in switch case in [`parseMermaid`](https://github.com/excal
|
||||||
|
|
||||||
## Writing the Excalidraw Skeleton Convertor
|
## Writing the Excalidraw Skeleton Convertor
|
||||||
|
|
||||||
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) format.
|
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) format.
|
||||||
|
|
||||||
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133).
|
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133).
|
||||||
|
|
||||||
Thats it, you have added the new diagram type 🥳, now lets test it out!
|
Thats it, you have added the new diagram type 🥳, now lets test it out!
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ In this section we will be diving into how the [flowchart parser](https://github
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
|
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
|
||||||
|
|
||||||
|
|
||||||
For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
|
For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
|
||||||
|
@ -42,7 +42,7 @@ Considering the same example this is the response from the API
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
|
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
|
||||||
|
|
||||||
The final output from `parseVertex` looks like :point_down:
|
The final output from `parseVertex` looks like :point_down:
|
||||||
|
|
||||||
|
|
|
@ -55,11 +55,11 @@ If you want to understand how flowchart parser works, you can navigate to [Flowc
|
||||||
|
|
||||||
## Converting to ExcalidrawElementSkeleton
|
## Converting to ExcalidrawElementSkeleton
|
||||||
|
|
||||||
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
|
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
|
||||||
|
|
||||||
For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
|
For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
|
||||||
For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
|
For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
|
||||||
|
|
||||||
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
|
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
|
||||||
|
|
||||||

|

|
22
dev-docs/docs/codebase/frames.mdx
Normal file
22
dev-docs/docs/codebase/frames.mdx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Frames
|
||||||
|
|
||||||
|
## Ordering
|
||||||
|
|
||||||
|
Frames should be ordered where frame children come first, followed by the frame element itself:
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
other_element,
|
||||||
|
frame1_child1,
|
||||||
|
frame1_child2,
|
||||||
|
frame1,
|
||||||
|
other_element,
|
||||||
|
frame2_child1,
|
||||||
|
frame2_child2,
|
||||||
|
frame2,
|
||||||
|
other_element,
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
If not ordered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.
|
|
@ -15,7 +15,7 @@ In case you want to pick up something from the roadmap, comment on that issue an
|
||||||
1. Run `yarn` to install dependencies
|
1. Run `yarn` to install dependencies
|
||||||
1. Create a branch for your PR with `git checkout -b your-branch-name`
|
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:
|
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork, run:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> git remote add upstream https://github.com/excalidraw/excalidraw.git
|
> git remote add upstream https://github.com/excalidraw/excalidraw.git
|
||||||
|
@ -52,15 +52,6 @@ Make sure the title starts with a semantic prefix:
|
||||||
- **chore**: Other changes that don't modify src or test files
|
- **chore**: Other changes that don't modify src or test files
|
||||||
- **revert**: Reverts a previous commit
|
- **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
|
### 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.
|
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.
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
// Note: type annotations allow type checking and IDEs autocompletion
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
|
||||||
|
// Set the env variable to false so the excalidraw npm package doesn't throw
|
||||||
|
// process undefined as docusaurus doesn't expose env variables by default
|
||||||
|
|
||||||
|
process.env.IS_PREACT = "false";
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
title: "Excalidraw developer docs",
|
title: "Excalidraw developer docs",
|
||||||
|
@ -36,10 +41,7 @@ const config = {
|
||||||
showLastUpdateTime: true,
|
showLastUpdateTime: true,
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
customCss: [
|
customCss: [require.resolve("./src/css/custom.scss")],
|
||||||
require.resolve("./src/css/custom.scss"),
|
|
||||||
require.resolve("../src/packages/excalidraw/example/App.scss"),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -139,7 +141,15 @@ const config = {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
themes: ["@docusaurus/theme-live-codeblock"],
|
themes: ["@docusaurus/theme-live-codeblock"],
|
||||||
plugins: ["docusaurus-plugin-sass"],
|
plugins: [
|
||||||
|
"docusaurus-plugin-sass",
|
||||||
|
[
|
||||||
|
"docusaurus2-dotenv",
|
||||||
|
{
|
||||||
|
systemvars: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"@docusaurus/core": "2.2.0",
|
"@docusaurus/core": "2.2.0",
|
||||||
"@docusaurus/preset-classic": "2.2.0",
|
"@docusaurus/preset-classic": "2.2.0",
|
||||||
"@docusaurus/theme-live-codeblock": "2.2.0",
|
"@docusaurus/theme-live-codeblock": "2.2.0",
|
||||||
"@excalidraw/excalidraw": "0.15.2-eb020d0",
|
"@excalidraw/excalidraw": "0.17.0",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"docusaurus-plugin-sass": "0.2.3",
|
"docusaurus-plugin-sass": "0.2.3",
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
|
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
|
||||||
"@tsconfig/docusaurus": "^1.0.5",
|
"@tsconfig/docusaurus": "^1.0.5",
|
||||||
|
"docusaurus2-dotenv": "1.4.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|
|
@ -23,7 +23,11 @@ const sidebars = {
|
||||||
},
|
},
|
||||||
items: ["introduction/development", "introduction/contributing"],
|
items: ["introduction/development", "introduction/contributing"],
|
||||||
},
|
},
|
||||||
{ type: "category", label: "Codebase", items: ["codebase/json-schema"] },
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Codebase",
|
||||||
|
items: ["codebase/json-schema", "codebase/frames"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@excalidraw/excalidraw",
|
label: "@excalidraw/excalidraw",
|
||||||
|
@ -49,7 +53,7 @@ const sidebars = {
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
"@excalidraw/excalidraw/api/props/initialdata",
|
"@excalidraw/excalidraw/api/props/initialdata",
|
||||||
"@excalidraw/excalidraw/api/props/ref",
|
"@excalidraw/excalidraw/api/props/excalidraw-api",
|
||||||
"@excalidraw/excalidraw/api/props/render-props",
|
"@excalidraw/excalidraw/api/props/render-props",
|
||||||
"@excalidraw/excalidraw/api/props/ui-options",
|
"@excalidraw/excalidraw/api/props/ui-options",
|
||||||
],
|
],
|
||||||
|
|
|
@ -15,7 +15,7 @@ const FeatureList = [
|
||||||
Svg: require("@site/static/img/undraw_blank_canvas.svg").default,
|
Svg: require("@site/static/img/undraw_blank_canvas.svg").default,
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Want to build your own app powered by Excalidraw by don't know where to
|
Want to build your own app powered by Excalidraw but don't know where to
|
||||||
start?
|
start?
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|
4
dev-docs/vercel.json
Normal file
4
dev-docs/vercel.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"outputDirectory": "build",
|
||||||
|
"installCommand": "yarn install"
|
||||||
|
}
|
|
@ -145,6 +145,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/highlight" "^7.18.6"
|
"@babel/highlight" "^7.18.6"
|
||||||
|
|
||||||
|
"@babel/code-frame@^7.22.13":
|
||||||
|
version "7.22.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
|
||||||
|
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/highlight" "^7.22.13"
|
||||||
|
chalk "^2.4.2"
|
||||||
|
|
||||||
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
|
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
|
||||||
version "7.18.8"
|
version "7.18.8"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
|
||||||
|
@ -202,6 +210,16 @@
|
||||||
"@jridgewell/gen-mapping" "^0.3.2"
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
|
|
||||||
|
"@babel/generator@^7.23.0":
|
||||||
|
version "7.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
|
||||||
|
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.23.0"
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.17"
|
||||||
|
jsesc "^2.5.1"
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure@^7.18.6":
|
"@babel/helper-annotate-as-pure@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
|
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
|
||||||
|
@ -265,6 +283,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
|
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
|
||||||
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
|
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
|
||||||
|
|
||||||
|
"@babel/helper-environment-visitor@^7.22.20":
|
||||||
|
version "7.22.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
|
||||||
|
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
|
||||||
|
|
||||||
"@babel/helper-explode-assignable-expression@^7.18.6":
|
"@babel/helper-explode-assignable-expression@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
|
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
|
||||||
|
@ -280,6 +303,14 @@
|
||||||
"@babel/template" "^7.18.6"
|
"@babel/template" "^7.18.6"
|
||||||
"@babel/types" "^7.18.9"
|
"@babel/types" "^7.18.9"
|
||||||
|
|
||||||
|
"@babel/helper-function-name@^7.23.0":
|
||||||
|
version "7.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
|
||||||
|
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/template" "^7.22.15"
|
||||||
|
"@babel/types" "^7.23.0"
|
||||||
|
|
||||||
"@babel/helper-hoist-variables@^7.18.6":
|
"@babel/helper-hoist-variables@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
|
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
|
||||||
|
@ -287,6 +318,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.18.6"
|
"@babel/types" "^7.18.6"
|
||||||
|
|
||||||
|
"@babel/helper-hoist-variables@^7.22.5":
|
||||||
|
version "7.22.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
|
||||||
|
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.22.5"
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions@^7.18.9":
|
"@babel/helper-member-expression-to-functions@^7.18.9":
|
||||||
version "7.18.9"
|
version "7.18.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
|
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
|
||||||
|
@ -374,11 +412,28 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.18.6"
|
"@babel/types" "^7.18.6"
|
||||||
|
|
||||||
|
"@babel/helper-split-export-declaration@^7.22.6":
|
||||||
|
version "7.22.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
|
||||||
|
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.22.5"
|
||||||
|
|
||||||
|
"@babel/helper-string-parser@^7.22.5":
|
||||||
|
version "7.22.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
|
||||||
|
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.18.6":
|
"@babel/helper-validator-identifier@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
||||||
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
|
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier@^7.22.20":
|
||||||
|
version "7.22.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||||
|
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
|
||||||
|
|
||||||
"@babel/helper-validator-option@^7.18.6":
|
"@babel/helper-validator-option@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
|
||||||
|
@ -412,11 +467,25 @@
|
||||||
chalk "^2.0.0"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
|
"@babel/highlight@^7.22.13":
|
||||||
|
version "7.22.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
|
||||||
|
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-validator-identifier" "^7.22.20"
|
||||||
|
chalk "^2.4.2"
|
||||||
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.12.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8", "@babel/parser@^7.18.9":
|
"@babel/parser@^7.12.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.8", "@babel/parser@^7.18.9":
|
||||||
version "7.18.9"
|
version "7.18.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
|
||||||
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
|
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
|
||||||
|
|
||||||
|
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
|
||||||
|
version "7.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||||
|
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||||
|
|
||||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
|
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
|
||||||
|
@ -1147,19 +1216,28 @@
|
||||||
"@babel/parser" "^7.18.6"
|
"@babel/parser" "^7.18.6"
|
||||||
"@babel/types" "^7.18.6"
|
"@babel/types" "^7.18.6"
|
||||||
|
|
||||||
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
|
"@babel/template@^7.22.15":
|
||||||
version "7.18.9"
|
version "7.22.15"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||||
integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==
|
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.18.6"
|
"@babel/code-frame" "^7.22.13"
|
||||||
"@babel/generator" "^7.18.9"
|
"@babel/parser" "^7.22.15"
|
||||||
"@babel/helper-environment-visitor" "^7.18.9"
|
"@babel/types" "^7.22.15"
|
||||||
"@babel/helper-function-name" "^7.18.9"
|
|
||||||
"@babel/helper-hoist-variables" "^7.18.6"
|
"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9":
|
||||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
version "7.23.2"
|
||||||
"@babel/parser" "^7.18.9"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||||
"@babel/types" "^7.18.9"
|
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.22.13"
|
||||||
|
"@babel/generator" "^7.23.0"
|
||||||
|
"@babel/helper-environment-visitor" "^7.22.20"
|
||||||
|
"@babel/helper-function-name" "^7.23.0"
|
||||||
|
"@babel/helper-hoist-variables" "^7.22.5"
|
||||||
|
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||||
|
"@babel/parser" "^7.23.0"
|
||||||
|
"@babel/types" "^7.23.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
|
|
||||||
|
@ -1171,6 +1249,15 @@
|
||||||
"@babel/helper-validator-identifier" "^7.18.6"
|
"@babel/helper-validator-identifier" "^7.18.6"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
|
||||||
|
version "7.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
|
||||||
|
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.22.5"
|
||||||
|
"@babel/helper-validator-identifier" "^7.22.20"
|
||||||
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@colors/colors@1.5.0":
|
"@colors/colors@1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
|
@ -1631,10 +1718,10 @@
|
||||||
url-loader "^4.1.1"
|
url-loader "^4.1.1"
|
||||||
webpack "^5.73.0"
|
webpack "^5.73.0"
|
||||||
|
|
||||||
"@excalidraw/excalidraw@0.15.2-eb020d0":
|
"@excalidraw/excalidraw@0.17.0":
|
||||||
version "0.15.2-eb020d0"
|
version "0.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2-eb020d0.tgz#25bd61e6f23da7c084fb16a3e0fe0dd9ad8e6533"
|
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.0.tgz#3c64aa8e36406ac171b008cfecbdce5bb0755725"
|
||||||
integrity sha512-TKGLzpOVqFQcwK1GFKTDXgg1s2U6tc5KE3qXuv87osbzOtftQn3x4+VH61vwdj11l00nEN80SMdXUC43T9uJqQ==
|
integrity sha512-NzP22v5xMqxYW27ZtTHhiGFe7kE8NeBk45aoeM/mDSkXiOXPDH+PcvwzHRN/Ei+Vj/0sTPHxejn8bZyRWKGjXg==
|
||||||
|
|
||||||
"@hapi/hoek@^9.0.0":
|
"@hapi/hoek@^9.0.0":
|
||||||
version "9.3.0"
|
version "9.3.0"
|
||||||
|
@ -1670,6 +1757,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri@^3.1.0":
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
|
||||||
|
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
|
||||||
|
|
||||||
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
|
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||||
|
@ -1688,6 +1780,19 @@
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec@^1.4.14":
|
||||||
|
version "1.4.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping@^0.3.17":
|
||||||
|
version "0.3.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
|
||||||
|
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
|
"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
|
||||||
version "0.3.14"
|
version "0.3.14"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
|
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
|
||||||
|
@ -3462,6 +3567,13 @@ docusaurus-plugin-sass@0.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
sass-loader "^10.1.1"
|
sass-loader "^10.1.1"
|
||||||
|
|
||||||
|
docusaurus2-dotenv@1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/docusaurus2-dotenv/-/docusaurus2-dotenv-1.4.0.tgz#9ab900e29de9081f9f1f28f7224ff63760385641"
|
||||||
|
integrity sha512-iWqem5fnBAyeBBtX75Fxp71uUAnwFaXzOmade8zAhN4vL3RG9m27sLSRwjJGVVgIkEo3esjGyCcTGTiCjfi+sg==
|
||||||
|
dependencies:
|
||||||
|
dotenv-webpack "1.7.0"
|
||||||
|
|
||||||
dom-converter@^0.2.0:
|
dom-converter@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
||||||
|
@ -3547,6 +3659,25 @@ dot-prop@^5.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-obj "^2.0.0"
|
is-obj "^2.0.0"
|
||||||
|
|
||||||
|
dotenv-defaults@^1.0.2:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz#032c024f4b5906d9990eb06d722dc74cc60ec1bd"
|
||||||
|
integrity sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==
|
||||||
|
dependencies:
|
||||||
|
dotenv "^6.2.0"
|
||||||
|
|
||||||
|
dotenv-webpack@1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-1.7.0.tgz#4384d8c57ee6f405c296278c14a9f9167856d3a1"
|
||||||
|
integrity sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==
|
||||||
|
dependencies:
|
||||||
|
dotenv-defaults "^1.0.2"
|
||||||
|
|
||||||
|
dotenv@^6.2.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
|
||||||
|
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
|
||||||
|
|
||||||
duplexer3@^0.1.4:
|
duplexer3@^0.1.4:
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
|
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
import polyfill from "../polyfill";
|
import polyfill from "../packages/excalidraw/polyfill";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../packages/excalidraw/analytics";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../packages/excalidraw/appState";
|
||||||
import { ErrorDialog } from "../components/ErrorDialog";
|
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
EVENT,
|
EVENT,
|
||||||
THEME,
|
THEME,
|
||||||
TITLE_TIMEOUT,
|
TITLE_TIMEOUT,
|
||||||
VERSION_TIMEOUT,
|
VERSION_TIMEOUT,
|
||||||
} from "../constants";
|
} from "../packages/excalidraw/constants";
|
||||||
import { loadFromBlob } from "../data/blob";
|
import { loadFromBlob } from "../packages/excalidraw/data/blob";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FileId,
|
FileId,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
Theme,
|
Theme,
|
||||||
} from "../element/types";
|
} from "../packages/excalidraw/element/types";
|
||||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
|
||||||
import { t } from "../i18n";
|
import { t } from "../packages/excalidraw/i18n";
|
||||||
import {
|
import {
|
||||||
Excalidraw,
|
Excalidraw,
|
||||||
defaultLang,
|
defaultLang,
|
||||||
LiveCollaborationTrigger,
|
LiveCollaborationTrigger,
|
||||||
|
TTDDialog,
|
||||||
|
TTDDialogTrigger,
|
||||||
} from "../packages/excalidraw/index";
|
} from "../packages/excalidraw/index";
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -33,7 +35,7 @@ import {
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
ExcalidrawInitialDataState,
|
ExcalidrawInitialDataState,
|
||||||
UIAppState,
|
UIAppState,
|
||||||
} from "../types";
|
} from "../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
getVersion,
|
getVersion,
|
||||||
|
@ -43,7 +45,7 @@ import {
|
||||||
ResolvablePromise,
|
ResolvablePromise,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
isRunningInIframe,
|
isRunningInIframe,
|
||||||
} from "../utils";
|
} from "../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
FIREBASE_STORAGE_PREFIXES,
|
FIREBASE_STORAGE_PREFIXES,
|
||||||
STORAGE_KEYS,
|
STORAGE_KEYS,
|
||||||
|
@ -68,33 +70,40 @@ import {
|
||||||
importUsernameFromLocalStorage,
|
importUsernameFromLocalStorage,
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
import CustomStats from "./CustomStats";
|
import CustomStats from "./CustomStats";
|
||||||
import { restore, restoreAppState, RestoredDataState } from "../data/restore";
|
import {
|
||||||
|
restore,
|
||||||
|
restoreAppState,
|
||||||
|
RestoredDataState,
|
||||||
|
} from "../packages/excalidraw/data/restore";
|
||||||
import {
|
import {
|
||||||
ExportToExcalidrawPlus,
|
ExportToExcalidrawPlus,
|
||||||
exportToExcalidrawPlus,
|
exportToExcalidrawPlus,
|
||||||
} from "./components/ExportToExcalidrawPlus";
|
} from "./components/ExportToExcalidrawPlus";
|
||||||
import { updateStaleImageStatuses } from "./data/FileManager";
|
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../packages/excalidraw/element/mutateElement";
|
||||||
import { isInitializedImageElement } from "../element/typeChecks";
|
import { isInitializedImageElement } from "../packages/excalidraw/element/typeChecks";
|
||||||
import { loadFilesFromFirebase } from "./data/firebase";
|
import { loadFilesFromFirebase } from "./data/firebase";
|
||||||
import { LocalData } from "./data/LocalData";
|
import { LocalData } from "./data/LocalData";
|
||||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { reconcileElements } from "./collab/reconciliation";
|
import { reconcileElements } from "./collab/reconciliation";
|
||||||
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
|
import {
|
||||||
|
parseLibraryTokensFromUrl,
|
||||||
|
useHandleLibrary,
|
||||||
|
} from "../packages/excalidraw/data/library";
|
||||||
import { AppMainMenu } from "./components/AppMainMenu";
|
import { AppMainMenu } from "./components/AppMainMenu";
|
||||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||||
import { AppFooter } from "./components/AppFooter";
|
import { AppFooter } from "./components/AppFooter";
|
||||||
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
||||||
import { useAtomWithInitialValue } from "../jotai";
|
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
||||||
import { appJotaiStore } from "./app-jotai";
|
import { appJotaiStore } from "./app-jotai";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { ResolutionType } from "../utility-types";
|
import { ResolutionType } from "../packages/excalidraw/utility-types";
|
||||||
import { ShareableLinkDialog } from "../components/ShareableLinkDialog";
|
import { ShareableLinkDialog } from "../packages/excalidraw/components/ShareableLinkDialog";
|
||||||
import { openConfirmModal } from "../components/OverwriteConfirm/OverwriteConfirmState";
|
import { openConfirmModal } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
|
||||||
import { OverwriteConfirmDialog } from "../components/OverwriteConfirm/OverwriteConfirm";
|
import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
|
||||||
import Trans from "../components/Trans";
|
import Trans from "../packages/excalidraw/components/Trans";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
|
@ -601,7 +610,7 @@ const ExcalidrawWrapper = () => {
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
) => {
|
) => {
|
||||||
if (exportedElements.length === 0) {
|
if (exportedElements.length === 0) {
|
||||||
return window.alert(t("alerts.cannotExportEmptyCanvas"));
|
throw new Error(t("alerts.cannotExportEmptyCanvas"));
|
||||||
}
|
}
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
try {
|
try {
|
||||||
|
@ -617,7 +626,7 @@ const ExcalidrawWrapper = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
setErrorMessage(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
|
@ -627,7 +636,7 @@ const ExcalidrawWrapper = () => {
|
||||||
if (error.name !== "AbortError") {
|
if (error.name !== "AbortError") {
|
||||||
const { width, height } = canvas;
|
const { width, height } = canvas;
|
||||||
console.error(error, { width, height });
|
console.error(error, { width, height });
|
||||||
setErrorMessage(error.message);
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,7 +693,7 @@ const ExcalidrawWrapper = () => {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
ref={excalidrawRefCallback}
|
excalidrawAPI={excalidrawRefCallback}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
initialData={initialStatePromiseRef.current.promise}
|
initialData={initialStatePromiseRef.current.promise}
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
|
@ -707,6 +716,11 @@ const ExcalidrawWrapper = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
onSuccess={() => {
|
||||||
|
excalidrawAPI?.updateScene({
|
||||||
|
appState: { openDialog: null },
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -768,6 +782,64 @@ const ExcalidrawWrapper = () => {
|
||||||
)}
|
)}
|
||||||
</OverwriteConfirmDialog>
|
</OverwriteConfirmDialog>
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
|
<TTDDialog
|
||||||
|
onTextSubmit={async (input) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${
|
||||||
|
import.meta.env.VITE_APP_AI_BACKEND
|
||||||
|
}/v1/ai/text-to-diagram/generate`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ prompt: input }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const rateLimit = response.headers.has("X-Ratelimit-Limit")
|
||||||
|
? parseInt(response.headers.get("X-Ratelimit-Limit") || "0", 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const rateLimitRemaining = response.headers.has(
|
||||||
|
"X-Ratelimit-Remaining",
|
||||||
|
)
|
||||||
|
? parseInt(
|
||||||
|
response.headers.get("X-Ratelimit-Remaining") || "0",
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 429) {
|
||||||
|
return {
|
||||||
|
rateLimit,
|
||||||
|
rateLimitRemaining,
|
||||||
|
error: new Error(
|
||||||
|
"Too many requests today, please try again tomorrow!",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(json.message || "Generation failed...");
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatedResponse = json.generatedResponse;
|
||||||
|
if (!generatedResponse) {
|
||||||
|
throw new Error("Generation failed...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { generatedResponse, rateLimit, rateLimitRemaining };
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error("Request failed");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TTDDialogTrigger />
|
||||||
{isCollaborating && isOffline && (
|
{isCollaborating && isOffline && (
|
||||||
<div className="collab-offline-warning">
|
<div className="collab-offline-warning">
|
||||||
{t("alerts.collabOfflineWarning")}
|
{t("alerts.collabOfflineWarning")}
|
|
@ -1,14 +1,14 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { debounce, getVersion, nFormatter } from "../utils";
|
import { debounce, getVersion, nFormatter } from "../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
getElementsStorageSize,
|
getElementsStorageSize,
|
||||||
getTotalStorageSize,
|
getTotalStorageSize,
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
import { DEFAULT_VERSION } from "../constants";
|
import { DEFAULT_VERSION } from "../packages/excalidraw/constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../packages/excalidraw/i18n";
|
||||||
import { copyTextToSystemClipboard } from "../clipboard";
|
import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
|
||||||
import { UIAppState } from "../types";
|
import { UIAppState } from "../packages/excalidraw/types";
|
||||||
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
|
@ -15,11 +15,17 @@ export const FILE_CACHE_MAX_AGE_SEC = 31536000;
|
||||||
export const WS_EVENTS = {
|
export const WS_EVENTS = {
|
||||||
SERVER_VOLATILE: "server-volatile-broadcast",
|
SERVER_VOLATILE: "server-volatile-broadcast",
|
||||||
SERVER: "server-broadcast",
|
SERVER: "server-broadcast",
|
||||||
};
|
USER_FOLLOW_CHANGE: "user-follow",
|
||||||
|
USER_FOLLOW_ROOM_CHANGE: "user-follow-room-change",
|
||||||
|
} as const;
|
||||||
|
|
||||||
export enum WS_SCENE_EVENT_TYPES {
|
export enum WS_SUBTYPES {
|
||||||
|
INVALID_RESPONSE = "INVALID_RESPONSE",
|
||||||
INIT = "SCENE_INIT",
|
INIT = "SCENE_INIT",
|
||||||
UPDATE = "SCENE_UPDATE",
|
UPDATE = "SCENE_UPDATE",
|
||||||
|
MOUSE_LOCATION = "MOUSE_LOCATION",
|
||||||
|
IDLE_STATUS = "IDLE_STATUS",
|
||||||
|
USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIREBASE_STORAGE_PREFIXES = {
|
export const FIREBASE_STORAGE_PREFIXES = {
|
|
@ -1,31 +1,37 @@
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import { PureComponent } from "react";
|
import { PureComponent } from "react";
|
||||||
import { ExcalidrawImperativeAPI } from "../../types";
|
import {
|
||||||
import { ErrorDialog } from "../../components/ErrorDialog";
|
ExcalidrawImperativeAPI,
|
||||||
import { APP_NAME, ENV, EVENT } from "../../constants";
|
SocketId,
|
||||||
import { ImportedDataState } from "../../data/types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
|
||||||
|
import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
|
||||||
|
import { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
} from "../../element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
import {
|
import {
|
||||||
getSceneVersion,
|
getSceneVersion,
|
||||||
restoreElements,
|
restoreElements,
|
||||||
|
zoomToFitBounds,
|
||||||
} from "../../packages/excalidraw/index";
|
} from "../../packages/excalidraw/index";
|
||||||
import { Collaborator, Gesture } from "../../types";
|
import { Collaborator, Gesture } from "../../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
|
assertNever,
|
||||||
preventUnload,
|
preventUnload,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
withBatchedUpdates,
|
throttleRAF,
|
||||||
} from "../../utils";
|
} from "../../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
CURSOR_SYNC_TIMEOUT,
|
CURSOR_SYNC_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
FIREBASE_STORAGE_PREFIXES,
|
FIREBASE_STORAGE_PREFIXES,
|
||||||
INITIAL_SCENE_UPDATE_TIMEOUT,
|
INITIAL_SCENE_UPDATE_TIMEOUT,
|
||||||
LOAD_IMAGES_TIMEOUT,
|
LOAD_IMAGES_TIMEOUT,
|
||||||
WS_SCENE_EVENT_TYPES,
|
WS_SUBTYPES,
|
||||||
SYNC_FULL_SCENE_INTERVAL_MS,
|
SYNC_FULL_SCENE_INTERVAL_MS,
|
||||||
|
WS_EVENTS,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import {
|
import {
|
||||||
generateCollaborationLinkData,
|
generateCollaborationLinkData,
|
||||||
|
@ -48,29 +54,35 @@ import {
|
||||||
} from "../data/localStorage";
|
} from "../data/localStorage";
|
||||||
import Portal from "./Portal";
|
import Portal from "./Portal";
|
||||||
import RoomDialog from "./RoomDialog";
|
import RoomDialog from "./RoomDialog";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import { UserIdleState } from "../../types";
|
import { UserIdleState } from "../../packages/excalidraw/types";
|
||||||
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
import {
|
||||||
|
IDLE_THRESHOLD,
|
||||||
|
ACTIVE_THRESHOLD,
|
||||||
|
} from "../../packages/excalidraw/constants";
|
||||||
import {
|
import {
|
||||||
encodeFilesForUpload,
|
encodeFilesForUpload,
|
||||||
FileManager,
|
FileManager,
|
||||||
updateStaleImageStatuses,
|
updateStaleImageStatuses,
|
||||||
} from "../data/FileManager";
|
} from "../data/FileManager";
|
||||||
import { AbortError } from "../../errors";
|
import { AbortError } from "../../packages/excalidraw/errors";
|
||||||
import {
|
import {
|
||||||
isImageElement,
|
isImageElement,
|
||||||
isInitializedImageElement,
|
isInitializedImageElement,
|
||||||
} from "../../element/typeChecks";
|
} from "../../packages/excalidraw/element/typeChecks";
|
||||||
import { newElementWith } from "../../element/mutateElement";
|
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
||||||
import {
|
import {
|
||||||
ReconciledElements,
|
ReconciledElements,
|
||||||
reconcileElements as _reconcileElements,
|
reconcileElements as _reconcileElements,
|
||||||
} from "./reconciliation";
|
} from "./reconciliation";
|
||||||
import { decryptData } from "../../data/encryption";
|
import { decryptData } from "../../packages/excalidraw/data/encryption";
|
||||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||||
import { LocalData } from "../data/LocalData";
|
import { LocalData } from "../data/LocalData";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { appJotaiStore } from "../app-jotai";
|
import { appJotaiStore } from "../app-jotai";
|
||||||
|
import { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||||
|
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
|
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const collabDialogShownAtom = atom(false);
|
export const collabDialogShownAtom = atom(false);
|
||||||
|
@ -111,7 +123,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
|
|
||||||
private socketInitializationTimer?: number;
|
private socketInitializationTimer?: number;
|
||||||
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||||
private collaborators = new Map<string, Collaborator>();
|
private collaborators = new Map<SocketId, Collaborator>();
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -151,12 +163,28 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
this.idleTimeoutId = null;
|
this.idleTimeoutId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onUmmount: (() => void) | null = null;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
||||||
window.addEventListener("online", this.onOfflineStatusToggle);
|
window.addEventListener("online", this.onOfflineStatusToggle);
|
||||||
window.addEventListener("offline", this.onOfflineStatusToggle);
|
window.addEventListener("offline", this.onOfflineStatusToggle);
|
||||||
window.addEventListener(EVENT.UNLOAD, this.onUnload);
|
window.addEventListener(EVENT.UNLOAD, this.onUnload);
|
||||||
|
|
||||||
|
const unsubOnUserFollow = this.excalidrawAPI.onUserFollow((payload) => {
|
||||||
|
this.portal.socket && this.portal.broadcastUserFollowed(payload);
|
||||||
|
});
|
||||||
|
const throttledRelayUserViewportBounds = throttleRAF(
|
||||||
|
this.relayVisibleSceneBounds,
|
||||||
|
);
|
||||||
|
const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() =>
|
||||||
|
throttledRelayUserViewportBounds(),
|
||||||
|
);
|
||||||
|
this.onUmmount = () => {
|
||||||
|
unsubOnUserFollow();
|
||||||
|
unsubOnScrollChange();
|
||||||
|
};
|
||||||
|
|
||||||
this.onOfflineStatusToggle();
|
this.onOfflineStatusToggle();
|
||||||
|
|
||||||
const collabAPI: CollabAPI = {
|
const collabAPI: CollabAPI = {
|
||||||
|
@ -204,6 +232,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
window.clearTimeout(this.idleTimeoutId);
|
window.clearTimeout(this.idleTimeoutId);
|
||||||
this.idleTimeoutId = null;
|
this.idleTimeoutId = null;
|
||||||
}
|
}
|
||||||
|
this.onUmmount?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
|
isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
|
||||||
|
@ -356,7 +385,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
iv: Uint8Array,
|
iv: Uint8Array,
|
||||||
encryptedData: ArrayBuffer,
|
encryptedData: ArrayBuffer,
|
||||||
decryptionKey: string,
|
decryptionKey: string,
|
||||||
) => {
|
): Promise<ValueOf<SocketUpdateDataSource>> => {
|
||||||
try {
|
try {
|
||||||
const decrypted = await decryptData(iv, encryptedData, decryptionKey);
|
const decrypted = await decryptData(iv, encryptedData, decryptionKey);
|
||||||
|
|
||||||
|
@ -368,7 +397,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
window.alert(t("alerts.decryptFailed"));
|
window.alert(t("alerts.decryptFailed"));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return {
|
return {
|
||||||
type: "INVALID_RESPONSE",
|
type: WS_SUBTYPES.INVALID_RESPONSE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -484,9 +513,9 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (decryptedData.type) {
|
switch (decryptedData.type) {
|
||||||
case "INVALID_RESPONSE":
|
case WS_SUBTYPES.INVALID_RESPONSE:
|
||||||
return;
|
return;
|
||||||
case WS_SCENE_EVENT_TYPES.INIT: {
|
case WS_SUBTYPES.INIT: {
|
||||||
if (!this.portal.socketInitialized) {
|
if (!this.portal.socketInitialized) {
|
||||||
this.initializeRoom({ fetchScene: false });
|
this.initializeRoom({ fetchScene: false });
|
||||||
const remoteElements = decryptedData.payload.elements;
|
const remoteElements = decryptedData.payload.elements;
|
||||||
|
@ -502,41 +531,75 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WS_SCENE_EVENT_TYPES.UPDATE:
|
case WS_SUBTYPES.UPDATE:
|
||||||
this.handleRemoteSceneUpdate(
|
this.handleRemoteSceneUpdate(
|
||||||
this.reconcileElements(decryptedData.payload.elements),
|
this.reconcileElements(decryptedData.payload.elements),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "MOUSE_LOCATION": {
|
case WS_SUBTYPES.MOUSE_LOCATION: {
|
||||||
const { pointer, button, username, selectedElementIds } =
|
const { pointer, button, username, selectedElementIds } =
|
||||||
decryptedData.payload;
|
decryptedData.payload;
|
||||||
|
|
||||||
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
|
const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
|
||||||
decryptedData.payload.socketId ||
|
decryptedData.payload.socketId ||
|
||||||
// @ts-ignore legacy, see #2094 (#2097)
|
// @ts-ignore legacy, see #2094 (#2097)
|
||||||
decryptedData.payload.socketID;
|
decryptedData.payload.socketID;
|
||||||
|
|
||||||
const collaborators = new Map(this.collaborators);
|
this.updateCollaborator(socketId, {
|
||||||
const user = collaborators.get(socketId) || {}!;
|
pointer,
|
||||||
user.pointer = pointer;
|
button,
|
||||||
user.button = button;
|
selectedElementIds,
|
||||||
user.selectedElementIds = selectedElementIds;
|
username,
|
||||||
user.username = username;
|
});
|
||||||
collaborators.set(socketId, user);
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS: {
|
||||||
|
const { sceneBounds, socketId } = decryptedData.payload;
|
||||||
|
|
||||||
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
|
|
||||||
|
// we're not following the user
|
||||||
|
// (shouldn't happen, but could be late message or bug upstream)
|
||||||
|
if (appState.userToFollow?.socketId !== socketId) {
|
||||||
|
console.warn(
|
||||||
|
`receiving remote client's (from ${socketId}) viewport bounds even though we're not subscribed to it!`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cross-follow case, ignore updates in this case
|
||||||
|
if (
|
||||||
|
appState.userToFollow &&
|
||||||
|
appState.followedBy.has(appState.userToFollow.socketId)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
collaborators,
|
appState: zoomToFitBounds({
|
||||||
|
appState,
|
||||||
|
bounds: sceneBounds,
|
||||||
|
fitToViewport: true,
|
||||||
|
viewportZoomFactor: 1,
|
||||||
|
}).appState,
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_SUBTYPES.IDLE_STATUS: {
|
||||||
|
const { userState, socketId, username } = decryptedData.payload;
|
||||||
|
this.updateCollaborator(socketId, {
|
||||||
|
userState,
|
||||||
|
username,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "IDLE_STATUS": {
|
|
||||||
const { userState, socketId, username } = decryptedData.payload;
|
default: {
|
||||||
const collaborators = new Map(this.collaborators);
|
assertNever(decryptedData, null);
|
||||||
const user = collaborators.get(socketId) || {}!;
|
|
||||||
user.userState = userState;
|
|
||||||
user.username = username;
|
|
||||||
this.excalidrawAPI.updateScene({
|
|
||||||
collaborators,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -553,6 +616,17 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
scenePromise.resolve(sceneData);
|
scenePromise.resolve(sceneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.portal.socket.on(
|
||||||
|
WS_EVENTS.USER_FOLLOW_ROOM_CHANGE,
|
||||||
|
(followedBy: SocketId[]) => {
|
||||||
|
this.excalidrawAPI.updateScene({
|
||||||
|
appState: { followedBy: new Set(followedBy) },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.relayVisibleSceneBounds({ force: true });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.initializeIdleDetector();
|
this.initializeIdleDetector();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -721,20 +795,39 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
setCollaborators(sockets: string[]) {
|
setCollaborators(sockets: SocketId[]) {
|
||||||
const collaborators: InstanceType<typeof Collab>["collaborators"] =
|
const collaborators: InstanceType<typeof Collab>["collaborators"] =
|
||||||
new Map();
|
new Map();
|
||||||
for (const socketId of sockets) {
|
for (const socketId of sockets) {
|
||||||
if (this.collaborators.has(socketId)) {
|
collaborators.set(
|
||||||
collaborators.set(socketId, this.collaborators.get(socketId)!);
|
socketId,
|
||||||
} else {
|
Object.assign({}, this.collaborators.get(socketId), {
|
||||||
collaborators.set(socketId, {});
|
isCurrentUser: socketId === this.portal.socket?.id,
|
||||||
}
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.collaborators = collaborators;
|
this.collaborators = collaborators;
|
||||||
this.excalidrawAPI.updateScene({ collaborators });
|
this.excalidrawAPI.updateScene({ collaborators });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCollaborator = (socketId: SocketId, updates: Partial<Collaborator>) => {
|
||||||
|
const collaborators = new Map(this.collaborators);
|
||||||
|
const user: Mutable<Collaborator> = Object.assign(
|
||||||
|
{},
|
||||||
|
collaborators.get(socketId),
|
||||||
|
updates,
|
||||||
|
{
|
||||||
|
isCurrentUser: socketId === this.portal.socket?.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
collaborators.set(socketId, user);
|
||||||
|
this.collaborators = collaborators;
|
||||||
|
|
||||||
|
this.excalidrawAPI.updateScene({
|
||||||
|
collaborators,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
|
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
|
||||||
this.lastBroadcastedOrReceivedSceneVersion = version;
|
this.lastBroadcastedOrReceivedSceneVersion = version;
|
||||||
};
|
};
|
||||||
|
@ -760,6 +853,19 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
CURSOR_SYNC_TIMEOUT,
|
CURSOR_SYNC_TIMEOUT,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
relayVisibleSceneBounds = (props?: { force: boolean }) => {
|
||||||
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
|
|
||||||
|
if (this.portal.socket && (appState.followedBy.size > 0 || props?.force)) {
|
||||||
|
this.portal.broadcastVisibleSceneBounds(
|
||||||
|
{
|
||||||
|
sceneBounds: getVisibleSceneBounds(appState),
|
||||||
|
},
|
||||||
|
`follow@${this.portal.socket.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onIdleStateChange = (userState: UserIdleState) => {
|
onIdleStateChange = (userState: UserIdleState) => {
|
||||||
this.portal.broadcastIdleChange(userState);
|
this.portal.broadcastIdleChange(userState);
|
||||||
};
|
};
|
||||||
|
@ -769,7 +875,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
getSceneVersion(elements) >
|
getSceneVersion(elements) >
|
||||||
this.getLastBroadcastedOrReceivedSceneVersion()
|
this.getLastBroadcastedOrReceivedSceneVersion()
|
||||||
) {
|
) {
|
||||||
this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements, false);
|
this.portal.broadcastScene(WS_SUBTYPES.UPDATE, elements, false);
|
||||||
this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements);
|
this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements);
|
||||||
this.queueBroadcastAllElements();
|
this.queueBroadcastAllElements();
|
||||||
}
|
}
|
||||||
|
@ -782,7 +888,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
||||||
|
|
||||||
queueBroadcastAllElements = throttle(() => {
|
queueBroadcastAllElements = throttle(() => {
|
||||||
this.portal.broadcastScene(
|
this.portal.broadcastScene(
|
||||||
WS_SCENE_EVENT_TYPES.UPDATE,
|
WS_SUBTYPES.UPDATE,
|
||||||
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
|
@ -6,23 +6,24 @@ import {
|
||||||
|
|
||||||
import { TCollabClass } from "./Collab";
|
import { TCollabClass } from "./Collab";
|
||||||
|
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
||||||
|
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
||||||
import {
|
import {
|
||||||
WS_EVENTS,
|
OnUserFollowedPayload,
|
||||||
FILE_UPLOAD_TIMEOUT,
|
SocketId,
|
||||||
WS_SCENE_EVENT_TYPES,
|
UserIdleState,
|
||||||
} from "../app_constants";
|
} from "../../packages/excalidraw/types";
|
||||||
import { UserIdleState } from "../../types";
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
import { trackEvent } from "../../analytics";
|
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import { newElementWith } from "../../element/mutateElement";
|
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
||||||
import { BroadcastedExcalidrawElement } from "./reconciliation";
|
import { BroadcastedExcalidrawElement } from "./reconciliation";
|
||||||
import { encryptData } from "../../data/encryption";
|
import { encryptData } from "../../packages/excalidraw/data/encryption";
|
||||||
import { PRECEDING_ELEMENT_KEY } from "../../constants";
|
import { PRECEDING_ELEMENT_KEY } from "../../packages/excalidraw/constants";
|
||||||
|
import type { Socket } from "socket.io-client";
|
||||||
|
|
||||||
class Portal {
|
class Portal {
|
||||||
collab: TCollabClass;
|
collab: TCollabClass;
|
||||||
socket: SocketIOClient.Socket | null = null;
|
socket: Socket | null = null;
|
||||||
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
||||||
roomId: string | null = null;
|
roomId: string | null = null;
|
||||||
roomKey: string | null = null;
|
roomKey: string | null = null;
|
||||||
|
@ -32,7 +33,7 @@ class Portal {
|
||||||
this.collab = collab;
|
this.collab = collab;
|
||||||
}
|
}
|
||||||
|
|
||||||
open(socket: SocketIOClient.Socket, id: string, key: string) {
|
open(socket: Socket, id: string, key: string) {
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.roomId = id;
|
this.roomId = id;
|
||||||
this.roomKey = key;
|
this.roomKey = key;
|
||||||
|
@ -46,12 +47,12 @@ class Portal {
|
||||||
});
|
});
|
||||||
this.socket.on("new-user", async (_socketId: string) => {
|
this.socket.on("new-user", async (_socketId: string) => {
|
||||||
this.broadcastScene(
|
this.broadcastScene(
|
||||||
WS_SCENE_EVENT_TYPES.INIT,
|
WS_SUBTYPES.INIT,
|
||||||
this.collab.getSceneElementsIncludingDeleted(),
|
this.collab.getSceneElementsIncludingDeleted(),
|
||||||
/* syncAll */ true,
|
/* syncAll */ true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.socket.on("room-user-change", (clients: string[]) => {
|
this.socket.on("room-user-change", (clients: SocketId[]) => {
|
||||||
this.collab.setCollaborators(clients);
|
this.collab.setCollaborators(clients);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ class Portal {
|
||||||
async _broadcastSocketData(
|
async _broadcastSocketData(
|
||||||
data: SocketUpdateData,
|
data: SocketUpdateData,
|
||||||
volatile: boolean = false,
|
volatile: boolean = false,
|
||||||
|
roomId?: string,
|
||||||
) {
|
) {
|
||||||
if (this.isOpen()) {
|
if (this.isOpen()) {
|
||||||
const json = JSON.stringify(data);
|
const json = JSON.stringify(data);
|
||||||
|
@ -91,7 +93,7 @@ class Portal {
|
||||||
|
|
||||||
this.socket?.emit(
|
this.socket?.emit(
|
||||||
volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER,
|
volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER,
|
||||||
this.roomId,
|
roomId ?? this.roomId,
|
||||||
encryptedBuffer,
|
encryptedBuffer,
|
||||||
iv,
|
iv,
|
||||||
);
|
);
|
||||||
|
@ -130,11 +132,11 @@ class Portal {
|
||||||
}, FILE_UPLOAD_TIMEOUT);
|
}, FILE_UPLOAD_TIMEOUT);
|
||||||
|
|
||||||
broadcastScene = async (
|
broadcastScene = async (
|
||||||
updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE,
|
updateType: WS_SUBTYPES.INIT | WS_SUBTYPES.UPDATE,
|
||||||
allElements: readonly ExcalidrawElement[],
|
allElements: readonly ExcalidrawElement[],
|
||||||
syncAll: boolean,
|
syncAll: boolean,
|
||||||
) => {
|
) => {
|
||||||
if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) {
|
if (updateType === WS_SUBTYPES.INIT && !syncAll) {
|
||||||
throw new Error("syncAll must be true when sending SCENE.INIT");
|
throw new Error("syncAll must be true when sending SCENE.INIT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,9 +185,9 @@ class Portal {
|
||||||
broadcastIdleChange = (userState: UserIdleState) => {
|
broadcastIdleChange = (userState: UserIdleState) => {
|
||||||
if (this.socket?.id) {
|
if (this.socket?.id) {
|
||||||
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
||||||
type: "IDLE_STATUS",
|
type: WS_SUBTYPES.IDLE_STATUS,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id as SocketId,
|
||||||
userState,
|
userState,
|
||||||
username: this.collab.state.username,
|
username: this.collab.state.username,
|
||||||
},
|
},
|
||||||
|
@ -203,9 +205,9 @@ class Portal {
|
||||||
}) => {
|
}) => {
|
||||||
if (this.socket?.id) {
|
if (this.socket?.id) {
|
||||||
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
||||||
type: "MOUSE_LOCATION",
|
type: WS_SUBTYPES.MOUSE_LOCATION,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id as SocketId,
|
||||||
pointer: payload.pointer,
|
pointer: payload.pointer,
|
||||||
button: payload.button || "up",
|
button: payload.button || "up",
|
||||||
selectedElementIds:
|
selectedElementIds:
|
||||||
|
@ -213,12 +215,43 @@ class Portal {
|
||||||
username: this.collab.state.username,
|
username: this.collab.state.username,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._broadcastSocketData(
|
return this._broadcastSocketData(
|
||||||
data as SocketUpdateData,
|
data as SocketUpdateData,
|
||||||
true, // volatile
|
true, // volatile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
broadcastVisibleSceneBounds = (
|
||||||
|
payload: {
|
||||||
|
sceneBounds: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"]["payload"]["sceneBounds"];
|
||||||
|
},
|
||||||
|
roomId: string,
|
||||||
|
) => {
|
||||||
|
if (this.socket?.id) {
|
||||||
|
const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
|
||||||
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
|
||||||
|
payload: {
|
||||||
|
socketId: this.socket.id as SocketId,
|
||||||
|
username: this.collab.state.username,
|
||||||
|
sceneBounds: payload.sceneBounds,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._broadcastSocketData(
|
||||||
|
data as SocketUpdateData,
|
||||||
|
true, // volatile
|
||||||
|
roomId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
broadcastUserFollowed = (payload: OnUserFollowedPayload) => {
|
||||||
|
if (this.socket?.id) {
|
||||||
|
this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Portal;
|
export default Portal;
|
|
@ -1,4 +1,4 @@
|
||||||
@import "../../css/variables.module";
|
@import "../../packages/excalidraw/css/variables.module.scss";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.RoomDialog {
|
.RoomDialog {
|
|
@ -1,13 +1,13 @@
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import * as Popover from "@radix-ui/react-popover";
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
|
|
||||||
import { copyTextToSystemClipboard } from "../../clipboard";
|
import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard";
|
||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
import { getFrame } from "../../utils";
|
import { getFrame } from "../../packages/excalidraw/utils";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
import { KEYS } from "../../keys";
|
import { KEYS } from "../../packages/excalidraw/keys";
|
||||||
|
|
||||||
import { Dialog } from "../../components/Dialog";
|
import { Dialog } from "../../packages/excalidraw/components/Dialog";
|
||||||
import {
|
import {
|
||||||
copyIcon,
|
copyIcon,
|
||||||
playerPlayIcon,
|
playerPlayIcon,
|
||||||
|
@ -16,11 +16,11 @@ import {
|
||||||
shareIOS,
|
shareIOS,
|
||||||
shareWindows,
|
shareWindows,
|
||||||
tablerCheckIcon,
|
tablerCheckIcon,
|
||||||
} from "../../components/icons";
|
} from "../../packages/excalidraw/components/icons";
|
||||||
import { TextField } from "../../components/TextField";
|
import { TextField } from "../../packages/excalidraw/components/TextField";
|
||||||
import { FilledButton } from "../../components/FilledButton";
|
import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
|
||||||
|
|
||||||
import { ReactComponent as CollabImage } from "../../assets/lock.svg";
|
import { ReactComponent as CollabImage } from "../../packages/excalidraw/assets/lock.svg";
|
||||||
import "./RoomDialog.scss";
|
import "./RoomDialog.scss";
|
||||||
|
|
||||||
const getShareIcon = () => {
|
const getShareIcon = () => {
|
|
@ -1,7 +1,7 @@
|
||||||
import { PRECEDING_ELEMENT_KEY } from "../../constants";
|
import { PRECEDING_ELEMENT_KEY } from "../../packages/excalidraw/constants";
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
||||||
import { AppState } from "../../types";
|
import { AppState } from "../../packages/excalidraw/types";
|
||||||
import { arrayToMapWithIndex } from "../../utils";
|
import { arrayToMapWithIndex } from "../../packages/excalidraw/utils";
|
||||||
|
|
||||||
export type ReconciledElements = readonly ExcalidrawElement[] & {
|
export type ReconciledElements = readonly ExcalidrawElement[] & {
|
||||||
_brand: "reconciledElements";
|
_brand: "reconciledElements";
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { PlusPromoIcon } from "../../components/icons";
|
import { PlusPromoIcon } from "../../packages/excalidraw/components/icons";
|
||||||
import { MainMenu } from "../../packages/excalidraw/index";
|
import { MainMenu } from "../../packages/excalidraw/index";
|
||||||
import { LanguageList } from "./LanguageList";
|
import { LanguageList } from "./LanguageList";
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { PlusPromoIcon } from "../../components/icons";
|
import { PlusPromoIcon } from "../../packages/excalidraw/components/icons";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
import { POINTER_EVENTS } from "../../constants";
|
import { POINTER_EVENTS } from "../../packages/excalidraw/constants";
|
||||||
|
|
||||||
export const AppWelcomeScreen: React.FC<{
|
export const AppWelcomeScreen: React.FC<{
|
||||||
setCollabDialogShown: (toggle: boolean) => any;
|
setCollabDialogShown: (toggle: boolean) => any;
|
|
@ -1,6 +1,6 @@
|
||||||
import { shield } from "../../components/icons";
|
import { shield } from "../../packages/excalidraw/components/icons";
|
||||||
import { Tooltip } from "../../components/Tooltip";
|
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
|
|
||||||
export const EncryptedIcon = () => {
|
export const EncryptedIcon = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
|
@ -1,20 +1,30 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card } from "../../components/Card";
|
import { Card } from "../../packages/excalidraw/components/Card";
|
||||||
import { ToolButton } from "../../components/ToolButton";
|
import { ToolButton } from "../../packages/excalidraw/components/ToolButton";
|
||||||
import { serializeAsJSON } from "../../data/json";
|
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
||||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||||
import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
|
import {
|
||||||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
FileId,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
BinaryFileData,
|
||||||
|
BinaryFiles,
|
||||||
|
} from "../../packages/excalidraw/types";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
import { encryptData, generateEncryptionKey } from "../../data/encryption";
|
import {
|
||||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
encryptData,
|
||||||
|
generateEncryptionKey,
|
||||||
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
||||||
import { encodeFilesForUpload } from "../data/FileManager";
|
import { encodeFilesForUpload } from "../data/FileManager";
|
||||||
import { MIME_TYPES } from "../../constants";
|
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
import { getFrame } from "../../utils";
|
import { getFrame } from "../../packages/excalidraw/utils";
|
||||||
import { ExcalidrawLogo } from "../../components/ExcalidrawLogo";
|
import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
|
||||||
|
|
||||||
export const exportToExcalidrawPlus = async (
|
export const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
@ -80,7 +90,8 @@ export const ExportToExcalidrawPlus: React.FC<{
|
||||||
appState: Partial<AppState>;
|
appState: Partial<AppState>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
}> = ({ elements, appState, files, onError }) => {
|
onSuccess: () => void;
|
||||||
|
}> = ({ elements, appState, files, onError, onSuccess }) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<Card color="primary">
|
<Card color="primary">
|
||||||
|
@ -107,6 +118,7 @@ export const ExportToExcalidrawPlus: React.FC<{
|
||||||
try {
|
try {
|
||||||
trackEvent("export", "eplus", `ui (${getFrame()})`);
|
trackEvent("export", "eplus", `ui (${getFrame()})`);
|
||||||
await exportToExcalidrawPlus(elements, appState, files);
|
await exportToExcalidrawPlus(elements, appState, files);
|
||||||
|
onSuccess();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error.name !== "AbortError") {
|
if (error.name !== "AbortError") {
|
|
@ -1,7 +1,7 @@
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { THEME } from "../../constants";
|
import { THEME } from "../../packages/excalidraw/constants";
|
||||||
import { Theme } from "../../element/types";
|
import { Theme } from "../../packages/excalidraw/element/types";
|
||||||
|
|
||||||
// https://github.com/tholman/github-corners
|
// https://github.com/tholman/github-corners
|
||||||
export const GitHubCorner = React.memo(
|
export const GitHubCorner = React.memo(
|
|
@ -1,8 +1,8 @@
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { appLangCodeAtom } from "..";
|
import { appLangCodeAtom } from "../App";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../packages/excalidraw/i18n";
|
||||||
import { languages } from "../../i18n";
|
import { languages } from "../../packages/excalidraw/i18n";
|
||||||
|
|
||||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||||
const { t, langCode } = useI18n();
|
const { t, langCode } = useI18n();
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { t } from "../i18n";
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import Trans from "./Trans";
|
import Trans from "../../packages/excalidraw/components/Trans";
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
|
@ -1,19 +1,19 @@
|
||||||
import { compressData } from "../../data/encode";
|
import { compressData } from "../../packages/excalidraw/data/encode";
|
||||||
import { newElementWith } from "../../element/mutateElement";
|
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
||||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
FileId,
|
FileId,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
} from "../../element/types";
|
} from "../../packages/excalidraw/element/types";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import {
|
import {
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFileMetadata,
|
BinaryFileMetadata,
|
||||||
ExcalidrawImperativeAPI,
|
ExcalidrawImperativeAPI,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../../types";
|
} from "../../packages/excalidraw/types";
|
||||||
|
|
||||||
export class FileManager {
|
export class FileManager {
|
||||||
/** files being fetched */
|
/** files being fetched */
|
|
@ -6,16 +6,23 @@
|
||||||
*
|
*
|
||||||
* - DataState refers to full state of the app: appState, elements, images,
|
* - DataState refers to full state of the app: appState, elements, images,
|
||||||
* though some state is saved separately (collab username, library) for one
|
* though some state is saved separately (collab username, library) for one
|
||||||
* reason or another. We also save different data to different sotrage
|
* reason or another. We also save different data to different storage
|
||||||
* (localStorage, indexedDB).
|
* (localStorage, indexedDB).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
|
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
|
||||||
import { clearAppStateForLocalStorage } from "../../appState";
|
import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
|
||||||
import { clearElementsForLocalStorage } from "../../element";
|
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
|
||||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
import {
|
||||||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
ExcalidrawElement,
|
||||||
import { debounce } from "../../utils";
|
FileId,
|
||||||
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
BinaryFileData,
|
||||||
|
BinaryFiles,
|
||||||
|
} from "../../packages/excalidraw/types";
|
||||||
|
import { debounce } from "../../packages/excalidraw/utils";
|
||||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
||||||
import { FileManager } from "./FileManager";
|
import { FileManager } from "./FileManager";
|
||||||
import { Locker } from "./Locker";
|
import { Locker } from "./Locker";
|
|
@ -1,20 +1,27 @@
|
||||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
import {
|
||||||
import { getSceneVersion } from "../../element";
|
ExcalidrawElement,
|
||||||
|
FileId,
|
||||||
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import { getSceneVersion } from "../../packages/excalidraw/element";
|
||||||
import Portal from "../collab/Portal";
|
import Portal from "../collab/Portal";
|
||||||
import { restoreElements } from "../../data/restore";
|
import { restoreElements } from "../../packages/excalidraw/data/restore";
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFileMetadata,
|
BinaryFileMetadata,
|
||||||
DataURL,
|
DataURL,
|
||||||
} from "../../types";
|
} from "../../packages/excalidraw/types";
|
||||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||||
import { decompressData } from "../../data/encode";
|
import { decompressData } from "../../packages/excalidraw/data/encode";
|
||||||
import { encryptData, decryptData } from "../../data/encryption";
|
import {
|
||||||
import { MIME_TYPES } from "../../constants";
|
encryptData,
|
||||||
|
decryptData,
|
||||||
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
|
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
||||||
import { reconcileElements } from "../collab/reconciliation";
|
import { reconcileElements } from "../collab/reconciliation";
|
||||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
import { getSyncableElements, SyncableExcalidrawElement } from ".";
|
||||||
import { ResolutionType } from "../../utility-types";
|
import { ResolutionType } from "../../packages/excalidraw/utility-types";
|
||||||
|
import type { Socket } from "socket.io-client";
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -132,12 +139,12 @@ const decryptElements = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
class FirebaseSceneVersionCache {
|
class FirebaseSceneVersionCache {
|
||||||
private static cache = new WeakMap<SocketIOClient.Socket, number>();
|
private static cache = new WeakMap<Socket, number>();
|
||||||
static get = (socket: SocketIOClient.Socket) => {
|
static get = (socket: Socket) => {
|
||||||
return FirebaseSceneVersionCache.cache.get(socket);
|
return FirebaseSceneVersionCache.cache.get(socket);
|
||||||
};
|
};
|
||||||
static set = (
|
static set = (
|
||||||
socket: SocketIOClient.Socket,
|
socket: Socket,
|
||||||
elements: readonly SyncableExcalidrawElement[],
|
elements: readonly SyncableExcalidrawElement[],
|
||||||
) => {
|
) => {
|
||||||
FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
|
FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
|
||||||
|
@ -279,7 +286,7 @@ export const saveToFirebase = async (
|
||||||
export const loadFromFirebase = async (
|
export const loadFromFirebase = async (
|
||||||
roomId: string,
|
roomId: string,
|
||||||
roomKey: string,
|
roomKey: string,
|
||||||
socket: SocketIOClient.Socket | null,
|
socket: Socket | null,
|
||||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||||
const firebase = await loadFirestore();
|
const firebase = await loadFirestore();
|
||||||
const db = firebase.firestore();
|
const db = firebase.firestore();
|
|
@ -1,27 +1,36 @@
|
||||||
import { compressData, decompressData } from "../../data/encode";
|
import {
|
||||||
|
compressData,
|
||||||
|
decompressData,
|
||||||
|
} from "../../packages/excalidraw/data/encode";
|
||||||
import {
|
import {
|
||||||
decryptData,
|
decryptData,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
IV_LENGTH_BYTES,
|
IV_LENGTH_BYTES,
|
||||||
} from "../../data/encryption";
|
} from "../../packages/excalidraw/data/encryption";
|
||||||
import { serializeAsJSON } from "../../data/json";
|
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
||||||
import { restore } from "../../data/restore";
|
import { restore } from "../../packages/excalidraw/data/restore";
|
||||||
import { ImportedDataState } from "../../data/types";
|
import { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
import { isInvisiblySmallElement } from "../../element/sizeHelpers";
|
import { SceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
|
||||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
||||||
import { t } from "../../i18n";
|
import {
|
||||||
|
ExcalidrawElement,
|
||||||
|
FileId,
|
||||||
|
} from "../../packages/excalidraw/element/types";
|
||||||
|
import { t } from "../../packages/excalidraw/i18n";
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
|
SocketId,
|
||||||
UserIdleState,
|
UserIdleState,
|
||||||
} from "../../types";
|
} from "../../packages/excalidraw/types";
|
||||||
import { bytesToHexString } from "../../utils";
|
import { bytesToHexString } from "../../packages/excalidraw/utils";
|
||||||
import {
|
import {
|
||||||
DELETED_ELEMENT_TIMEOUT,
|
DELETED_ELEMENT_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
ROOM_ID_BYTES,
|
ROOM_ID_BYTES,
|
||||||
|
WS_SUBTYPES,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import { encodeFilesForUpload } from "./FileManager";
|
import { encodeFilesForUpload } from "./FileManager";
|
||||||
import { saveFilesToFirebase } from "./firebase";
|
import { saveFilesToFirebase } from "./firebase";
|
||||||
|
@ -91,32 +100,43 @@ export type EncryptedData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SocketUpdateDataSource = {
|
export type SocketUpdateDataSource = {
|
||||||
|
INVALID_RESPONSE: {
|
||||||
|
type: WS_SUBTYPES.INVALID_RESPONSE;
|
||||||
|
};
|
||||||
SCENE_INIT: {
|
SCENE_INIT: {
|
||||||
type: "SCENE_INIT";
|
type: WS_SUBTYPES.INIT;
|
||||||
payload: {
|
payload: {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
SCENE_UPDATE: {
|
SCENE_UPDATE: {
|
||||||
type: "SCENE_UPDATE";
|
type: WS_SUBTYPES.UPDATE;
|
||||||
payload: {
|
payload: {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
MOUSE_LOCATION: {
|
MOUSE_LOCATION: {
|
||||||
type: "MOUSE_LOCATION";
|
type: WS_SUBTYPES.MOUSE_LOCATION;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
pointer: { x: number; y: number };
|
pointer: { x: number; y: number; tool: "pointer" | "laser" };
|
||||||
button: "down" | "up";
|
button: "down" | "up";
|
||||||
selectedElementIds: AppState["selectedElementIds"];
|
selectedElementIds: AppState["selectedElementIds"];
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
IDLE_STATUS: {
|
USER_VISIBLE_SCENE_BOUNDS: {
|
||||||
type: "IDLE_STATUS";
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
|
username: string;
|
||||||
|
sceneBounds: SceneBounds;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
IDLE_STATUS: {
|
||||||
|
type: WS_SUBTYPES.IDLE_STATUS;
|
||||||
|
payload: {
|
||||||
|
socketId: SocketId;
|
||||||
userState: UserIdleState;
|
userState: UserIdleState;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
@ -124,10 +144,7 @@ export type SocketUpdateDataSource = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SocketUpdateDataIncoming =
|
export type SocketUpdateDataIncoming =
|
||||||
| SocketUpdateDataSource[keyof SocketUpdateDataSource]
|
SocketUpdateDataSource[keyof SocketUpdateDataSource];
|
||||||
| {
|
|
||||||
type: "INVALID_RESPONSE";
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SocketUpdateData =
|
export type SocketUpdateData =
|
||||||
SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
|
SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
|
|
@ -1,12 +1,12 @@
|
||||||
import { ExcalidrawElement } from "../../element/types";
|
import { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
||||||
import { AppState } from "../../types";
|
import { AppState } from "../../packages/excalidraw/types";
|
||||||
import {
|
import {
|
||||||
clearAppStateForLocalStorage,
|
clearAppStateForLocalStorage,
|
||||||
getDefaultAppState,
|
getDefaultAppState,
|
||||||
} from "../../appState";
|
} from "../../packages/excalidraw/appState";
|
||||||
import { clearElementsForLocalStorage } from "../../element";
|
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
import { ImportedDataState } from "../../data/types";
|
import { ImportedDataState } from "../../packages/excalidraw/data/types";
|
||||||
|
|
||||||
export const saveUsernameToLocalStorage = (username: string) => {
|
export const saveUsernameToLocalStorage = (username: string) => {
|
||||||
try {
|
try {
|
|
@ -131,5 +131,5 @@ export class Debug {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//@ts-ignore
|
||||||
window.debug = Debug;
|
window.debug = Debug;
|
3
excalidraw-app/global.d.ts
vendored
Normal file
3
excalidraw-app/global.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
interface Window {
|
||||||
|
__EXCALIDRAW_SHA__: string | undefined;
|
||||||
|
}
|
|
@ -121,7 +121,7 @@
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/fonts.css" type="text/css" />
|
<link rel="stylesheet" href="/fonts/fonts.css" type="text/css" />
|
||||||
<% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%"==="true" ) { %>
|
<% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%"==="true" ) { %>
|
||||||
<script>
|
<script>
|
||||||
{
|
{
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
<h1 class="visually-hidden">Excalidraw</h1>
|
<h1 class="visually-hidden">Excalidraw</h1>
|
||||||
</header>
|
</header>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="index.tsx"></script>
|
||||||
<% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%" !== 'true') { %>
|
<% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%" !== 'true') { %>
|
||||||
<!-- 100% privacy friendly analytics -->
|
<!-- 100% privacy friendly analytics -->
|
||||||
<script>
|
<script>
|
|
@ -1,14 +1,13 @@
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import ExcalidrawApp from "./excalidraw-app";
|
import ExcalidrawApp from "./App";
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
import "./excalidraw-app/sentry";
|
import "../excalidraw-app/sentry";
|
||||||
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
||||||
const rootElement = document.getElementById("root")!;
|
const rootElement = document.getElementById("root")!;
|
||||||
const root = createRoot(rootElement);
|
const root = createRoot(rootElement);
|
||||||
registerSW();
|
registerSW();
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ExcalidrawApp />
|
<ExcalidrawApp />
|
40
excalidraw-app/package.json
Normal file
40
excalidraw-app/package.json
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "excalidraw-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"homepage": ".",
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all",
|
||||||
|
"not safari < 12",
|
||||||
|
"not kaios <= 2.5",
|
||||||
|
"not edge < 79",
|
||||||
|
"not chrome < 70",
|
||||||
|
"not and_uc < 13",
|
||||||
|
"not samsung < 10"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"prettier": "@excalidraw/prettier-config",
|
||||||
|
"scripts": {
|
||||||
|
"build-node": "node ./scripts/build-node.js",
|
||||||
|
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build",
|
||||||
|
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
||||||
|
"build:version": "node ../scripts/build-version.js",
|
||||||
|
"build": "yarn build:app && yarn build:version",
|
||||||
|
"start": "yarn && vite",
|
||||||
|
"start:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
|
||||||
|
"build:preview": "yarn build && vite preview --port 5000"
|
||||||
|
}
|
||||||
|
}
|
34
excalidraw-app/tests/LanguageList.test.tsx
Normal file
34
excalidraw-app/tests/LanguageList.test.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { defaultLang } from "../../packages/excalidraw/i18n";
|
||||||
|
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
||||||
|
import {
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
render,
|
||||||
|
} from "../../packages/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
|
import ExcalidrawApp from "../App";
|
||||||
|
|
||||||
|
describe("Test LanguageList", () => {
|
||||||
|
it("rerenders UI on language change", async () => {
|
||||||
|
await render(<ExcalidrawApp />);
|
||||||
|
|
||||||
|
// select rectangle tool to show properties menu
|
||||||
|
UI.clickTool("rectangle");
|
||||||
|
// english lang should display `thin` label
|
||||||
|
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
|
||||||
|
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
|
||||||
|
|
||||||
|
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||||
|
target: { value: "de-DE" },
|
||||||
|
});
|
||||||
|
// switching to german, `thin` label should no longer exist
|
||||||
|
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
|
||||||
|
// reset language
|
||||||
|
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
|
||||||
|
target: { value: defaultLang.code },
|
||||||
|
});
|
||||||
|
// switching back to English
|
||||||
|
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,11 +1,11 @@
|
||||||
import ExcalidrawApp from "../excalidraw-app";
|
import ExcalidrawApp from "../App";
|
||||||
import {
|
import {
|
||||||
mockBoundingClientRect,
|
mockBoundingClientRect,
|
||||||
render,
|
render,
|
||||||
restoreOriginalGetBoundingClientRect,
|
restoreOriginalGetBoundingClientRect,
|
||||||
} from "./test-utils";
|
} from "../../packages/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import { UI } from "./helpers/ui";
|
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
||||||
|
|
||||||
describe("Test MobileMenu", () => {
|
describe("Test MobileMenu", () => {
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
@ -17,8 +17,10 @@ describe("Test MobileMenu", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await render(<ExcalidrawApp />);
|
await render(<ExcalidrawApp />);
|
||||||
//@ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
|
h.app.refreshViewportBreakpoints();
|
||||||
|
// @ts-ignore
|
||||||
|
h.app.refreshEditorBreakpoints();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
@ -28,11 +30,15 @@ describe("Test MobileMenu", () => {
|
||||||
it("should set device correctly", () => {
|
it("should set device correctly", () => {
|
||||||
expect(h.app.device).toMatchInlineSnapshot(`
|
expect(h.app.device).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"canDeviceFitSidebar": false,
|
"editor": {
|
||||||
"isLandscape": true,
|
"canFitSidebar": false,
|
||||||
"isMobile": true,
|
"isMobile": true,
|
||||||
"isSmScreen": false,
|
},
|
||||||
"isTouchScreen": false,
|
"isTouchScreen": false,
|
||||||
|
"viewport": {
|
||||||
|
"isLandscape": false,
|
||||||
|
"isMobile": true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
|
@ -1,8 +1,12 @@
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import { render, updateSceneData, waitFor } from "./test-utils";
|
import {
|
||||||
import ExcalidrawApp from "../excalidraw-app";
|
render,
|
||||||
import { API } from "./helpers/api";
|
updateSceneData,
|
||||||
import { createUndoAction } from "../actions/actionHistory";
|
waitFor,
|
||||||
|
} from "../../packages/excalidraw/tests/test-utils";
|
||||||
|
import ExcalidrawApp from "../App";
|
||||||
|
import { API } from "../../packages/excalidraw/tests/helpers/api";
|
||||||
|
import { createUndoAction } from "../../packages/excalidraw/actions/actionHistory";
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
Object.defineProperty(window, "crypto", {
|
Object.defineProperty(window, "crypto", {
|
||||||
|
@ -16,7 +20,7 @@ Object.defineProperty(window, "crypto", {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
|
vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => {
|
||||||
const module = (await importActual()) as any;
|
const module = (await importActual()) as any;
|
||||||
return {
|
return {
|
||||||
__esmodule: true,
|
__esmodule: true,
|
||||||
|
@ -27,7 +31,7 @@ vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("../excalidraw-app/data/firebase.ts", () => {
|
vi.mock("../../excalidraw-app/data/firebase.ts", () => {
|
||||||
const loadFromFirebase = async () => null;
|
const loadFromFirebase = async () => null;
|
||||||
const saveToFirebase = () => {};
|
const saveToFirebase = () => {};
|
||||||
const isSavedToFirebase = () => true;
|
const isSavedToFirebase = () => true;
|
|
@ -1,13 +1,14 @@
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { PRECEDING_ELEMENT_KEY } from "../constants";
|
import { PRECEDING_ELEMENT_KEY } from "../../packages/excalidraw/constants";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
||||||
import {
|
import {
|
||||||
BroadcastedExcalidrawElement,
|
BroadcastedExcalidrawElement,
|
||||||
ReconciledElements,
|
ReconciledElements,
|
||||||
reconcileElements,
|
reconcileElements,
|
||||||
} from "../excalidraw-app/collab/reconciliation";
|
} from "../../excalidraw-app/collab/reconciliation";
|
||||||
import { randomInteger } from "../random";
|
import { randomInteger } from "../../packages/excalidraw/random";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../../packages/excalidraw/types";
|
||||||
|
import { cloneJSON } from "../../packages/excalidraw/utils";
|
||||||
|
|
||||||
type Id = string;
|
type Id = string;
|
||||||
type ElementLike = {
|
type ElementLike = {
|
||||||
|
@ -93,8 +94,6 @@ const cleanElements = (elements: ReconciledElements) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloneDeep = (data: any) => JSON.parse(JSON.stringify(data));
|
|
||||||
|
|
||||||
const test = <U extends `${string}:${"L" | "R"}`>(
|
const test = <U extends `${string}:${"L" | "R"}`>(
|
||||||
local: (Id | ElementLike)[],
|
local: (Id | ElementLike)[],
|
||||||
remote: (Id | ElementLike)[],
|
remote: (Id | ElementLike)[],
|
||||||
|
@ -115,15 +114,15 @@ const test = <U extends `${string}:${"L" | "R"}`>(
|
||||||
"remote reconciliation",
|
"remote reconciliation",
|
||||||
);
|
);
|
||||||
|
|
||||||
const __local = cleanElements(cloneDeep(_remote));
|
const __local = cleanElements(cloneJSON(_remote) as ReconciledElements);
|
||||||
const __remote = addParents(cleanElements(cloneDeep(remoteReconciled)));
|
const __remote = addParents(cleanElements(cloneJSON(remoteReconciled)));
|
||||||
if (bidirectional) {
|
if (bidirectional) {
|
||||||
try {
|
try {
|
||||||
expect(
|
expect(
|
||||||
cleanElements(
|
cleanElements(
|
||||||
reconcileElements(
|
reconcileElements(
|
||||||
cloneDeep(__local),
|
cloneJSON(__local),
|
||||||
cloneDeep(__remote),
|
cloneJSON(__remote),
|
||||||
{} as AppState,
|
{} as AppState,
|
||||||
),
|
),
|
||||||
),
|
),
|
46
excalidraw-app/vite-env.d.ts
vendored
Normal file
46
excalidraw-app/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/// <reference types="vite-plugin-pwa/vanillajs" />
|
||||||
|
/// <reference types="vite-plugin-pwa/info" />
|
||||||
|
/// <reference types="vite-plugin-svgr/client" />
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
// The port to run the dev server
|
||||||
|
VITE_APP_PORT: string;
|
||||||
|
|
||||||
|
VITE_APP_BACKEND_V2_GET_URL: string;
|
||||||
|
VITE_APP_BACKEND_V2_POST_URL: string;
|
||||||
|
|
||||||
|
// collaboration WebSocket server (https: string
|
||||||
|
VITE_APP_WS_SERVER_URL: string;
|
||||||
|
|
||||||
|
// set this only if using the collaboration workflow we use on excalidraw.com
|
||||||
|
VITE_APP_PORTAL_URL: string;
|
||||||
|
VITE_APP_AI_BACKEND: string;
|
||||||
|
|
||||||
|
VITE_APP_FIREBASE_CONFIG: string;
|
||||||
|
|
||||||
|
// whether to disable live reload / HMR. Usuaully what you want to do when
|
||||||
|
// debugging Service Workers.
|
||||||
|
VITE_APP_DEV_DISABLE_LIVE_RELOAD: string;
|
||||||
|
|
||||||
|
VITE_APP_DISABLE_SENTRY: string;
|
||||||
|
|
||||||
|
// Set this flag to false if you want to open the overlay by default
|
||||||
|
VITE_APP_COLLAPSE_OVERLAY: string;
|
||||||
|
|
||||||
|
// Enable eslint in dev server
|
||||||
|
VITE_APP_ENABLE_ESLINT: string;
|
||||||
|
|
||||||
|
VITE_APP_PLUS_LP: string;
|
||||||
|
|
||||||
|
VITE_APP_PLUS_APP: string;
|
||||||
|
|
||||||
|
VITE_APP_GIT_SHA: string;
|
||||||
|
|
||||||
|
MODE: string;
|
||||||
|
|
||||||
|
DEV: string;
|
||||||
|
PROD: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
|
@ -6,8 +6,7 @@ import { VitePWA } from "vite-plugin-pwa";
|
||||||
import checker from "vite-plugin-checker";
|
import checker from "vite-plugin-checker";
|
||||||
|
|
||||||
// To load .env.local variables
|
// To load .env.local variables
|
||||||
const envVars = loadEnv("", process.cwd());
|
const envVars = loadEnv("", `../`);
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
|
@ -15,6 +14,9 @@ export default defineConfig({
|
||||||
// open the browser
|
// open the browser
|
||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
|
// We need to specify the envDir since now there are no
|
||||||
|
//more located in parallel with the vite.config.ts file but in parent dir
|
||||||
|
envDir: "../",
|
||||||
build: {
|
build: {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
@ -25,7 +27,7 @@ export default defineConfig({
|
||||||
// or fallback hence not clubbing with locales so first load followed by offline mode works fine. This is how CRA used to work too.
|
// or fallback hence not clubbing with locales so first load followed by offline mode works fine. This is how CRA used to work too.
|
||||||
manualChunks(id) {
|
manualChunks(id) {
|
||||||
if (
|
if (
|
||||||
id.includes("src/locales") &&
|
id.includes("packages/excalidraw/locales") &&
|
||||||
id.match(/en.json|percentages.json/) === null
|
id.match(/en.json|percentages.json/) === null
|
||||||
) {
|
) {
|
||||||
const index = id.indexOf("locales/");
|
const index = id.indexOf("locales/");
|
||||||
|
@ -44,7 +46,7 @@ export default defineConfig({
|
||||||
eslint:
|
eslint:
|
||||||
envVars.VITE_APP_ENABLE_ESLINT === "false"
|
envVars.VITE_APP_ENABLE_ESLINT === "false"
|
||||||
? undefined
|
? undefined
|
||||||
: { lintCommand: 'eslint "./src/**/*.{js,ts,tsx}"' },
|
: { lintCommand: 'eslint "./**/*.{js,ts,tsx}"' },
|
||||||
overlay: {
|
overlay: {
|
||||||
initialIsOpen: envVars.VITE_APP_COLLAPSE_OVERLAY === "false",
|
initialIsOpen: envVars.VITE_APP_COLLAPSE_OVERLAY === "false",
|
||||||
badgeStyle: "margin-bottom: 4rem; margin-left: 1rem",
|
badgeStyle: "margin-bottom: 4rem; margin-left: 1rem",
|
||||||
|
@ -111,7 +113,17 @@ export default defineConfig({
|
||||||
{
|
{
|
||||||
src: "apple-touch-icon.png",
|
src: "apple-touch-icon.png",
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
sizes: "256x256",
|
sizes: "180x180",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "favicon-32x32.png",
|
||||||
|
sizes: "32x32",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "favicon-16x16.png",
|
||||||
|
sizes: "16x16",
|
||||||
|
type: "image/png",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
start_url: "/",
|
start_url: "/",
|
||||||
|
@ -178,5 +190,5 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
publicDir: "./public",
|
publicDir: "../public",
|
||||||
});
|
});
|
86
package.json
86
package.json
|
@ -1,61 +1,22 @@
|
||||||
{
|
{
|
||||||
"browserslist": {
|
"private": true,
|
||||||
"production": [
|
"name": "excalidraw-monorepo",
|
||||||
">0.2%",
|
"workspaces": [
|
||||||
"not dead",
|
"excalidraw-app",
|
||||||
"not ie <= 11",
|
"packages/excalidraw",
|
||||||
"not op_mini all",
|
"packages/utils"
|
||||||
"not safari < 12",
|
],
|
||||||
"not kaios <= 2.5",
|
|
||||||
"not edge < 79",
|
|
||||||
"not chrome < 70",
|
|
||||||
"not and_uc < 13",
|
|
||||||
"not samsung < 10"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "6.0.2",
|
|
||||||
"@excalidraw/random-username": "1.0.0",
|
"@excalidraw/random-username": "1.0.0",
|
||||||
"@radix-ui/react-popover": "1.0.3",
|
|
||||||
"@radix-ui/react-tabs": "1.0.2",
|
|
||||||
"@sentry/browser": "6.2.5",
|
"@sentry/browser": "6.2.5",
|
||||||
"@sentry/integrations": "6.2.5",
|
"@sentry/integrations": "6.2.5",
|
||||||
"@testing-library/jest-dom": "5.16.2",
|
|
||||||
"@testing-library/react": "12.1.5",
|
|
||||||
"@tldraw/vec": "1.7.1",
|
|
||||||
"browser-fs-access": "0.29.1",
|
|
||||||
"canvas-roundrect-polyfill": "0.0.1",
|
|
||||||
"clsx": "1.1.1",
|
|
||||||
"cross-env": "7.0.3",
|
|
||||||
"eslint-plugin-react": "7.32.2",
|
|
||||||
"fake-indexeddb": "3.1.7",
|
|
||||||
"firebase": "8.3.3",
|
"firebase": "8.3.3",
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"image-blob-reduce": "3.0.1",
|
|
||||||
"jotai": "1.13.1",
|
"jotai": "1.13.1",
|
||||||
"lodash.throttle": "4.1.1",
|
|
||||||
"nanoid": "3.3.3",
|
|
||||||
"open-color": "1.9.1",
|
|
||||||
"pako": "1.0.11",
|
|
||||||
"perfect-freehand": "1.2.0",
|
|
||||||
"pica": "7.1.1",
|
|
||||||
"png-chunk-text": "1.0.0",
|
|
||||||
"png-chunks-encode": "1.0.0",
|
|
||||||
"png-chunks-extract": "1.0.0",
|
|
||||||
"points-on-curve": "0.2.0",
|
|
||||||
"pwacompat": "2.0.17",
|
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"roughjs": "4.5.2",
|
"socket.io-client": "4.7.2"
|
||||||
"sass": "1.51.0",
|
|
||||||
"socket.io-client": "2.3.1",
|
|
||||||
"tunnel-rat": "0.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@excalidraw/eslint-config": "1.0.3",
|
"@excalidraw/eslint-config": "1.0.3",
|
||||||
|
@ -63,12 +24,9 @@
|
||||||
"@types/chai": "4.3.0",
|
"@types/chai": "4.3.0",
|
||||||
"@types/jest": "27.4.0",
|
"@types/jest": "27.4.0",
|
||||||
"@types/lodash.throttle": "4.1.7",
|
"@types/lodash.throttle": "4.1.7",
|
||||||
"@types/pako": "1.0.3",
|
|
||||||
"@types/pica": "5.1.3",
|
|
||||||
"@types/react": "18.0.15",
|
"@types/react": "18.0.15",
|
||||||
"@types/react-dom": "18.0.6",
|
"@types/react-dom": "18.0.6",
|
||||||
"@types/resize-observer-browser": "0.1.7",
|
"@types/socket.io-client": "3.0.0",
|
||||||
"@types/socket.io-client": "1.4.36",
|
|
||||||
"@vitejs/plugin-react": "3.1.0",
|
"@vitejs/plugin-react": "3.1.0",
|
||||||
"@vitest/coverage-v8": "0.33.0",
|
"@vitest/coverage-v8": "0.33.0",
|
||||||
"@vitest/ui": "0.32.2",
|
"@vitest/ui": "0.32.2",
|
||||||
|
@ -85,27 +43,25 @@
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"rewire": "6.0.0",
|
"rewire": "6.0.0",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
"vite": "4.4.2",
|
"vite": "5.0.6",
|
||||||
"vite-plugin-checker": "0.6.1",
|
"vite-plugin-checker": "0.6.1",
|
||||||
"vite-plugin-ejs": "1.6.4",
|
"vite-plugin-ejs": "1.7.0",
|
||||||
"vite-plugin-pwa": "0.16.4",
|
"vite-plugin-pwa": "0.17.4",
|
||||||
"vite-plugin-svgr": "2.4.0",
|
"vite-plugin-svgr": "2.4.0",
|
||||||
"vitest": "0.34.1",
|
"vitest": "1.0.1",
|
||||||
"vitest-canvas-mock": "0.3.2"
|
"vitest-canvas-mock": "0.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": "18.0.0 - 20.x.x"
|
||||||
},
|
},
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"name": "excalidraw",
|
|
||||||
"prettier": "@excalidraw/prettier-config",
|
"prettier": "@excalidraw/prettier-config",
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build",
|
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true VITE_APP_DISABLE_TRACKING=true vite build",
|
||||||
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA vite build",
|
||||||
"build:version": "node ./scripts/build-version.js",
|
"build:version": "node ./scripts/build-version.js",
|
||||||
"build": "yarn build:app && yarn build:version",
|
"build": "yarn --cwd ./excalidraw-app build",
|
||||||
"fix:code": "yarn test:code --fix",
|
"fix:code": "yarn test:code --fix",
|
||||||
"fix:other": "yarn prettier --write",
|
"fix:other": "yarn prettier --write",
|
||||||
"fix": "yarn fix:other && yarn fix:code",
|
"fix": "yarn fix:other && yarn fix:code",
|
||||||
|
@ -113,10 +69,10 @@
|
||||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"start": "vite",
|
"start": "yarn --cwd ./excalidraw-app start",
|
||||||
"start:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
|
"start:app: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 --watch=false",
|
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
|
||||||
"test:app": "vitest --config vitest.config.ts",
|
"test:app": "vitest",
|
||||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
"test:other": "yarn prettier --list-different",
|
"test:other": "yarn prettier --list-different",
|
||||||
"test:typecheck": "tsc",
|
"test:typecheck": "tsc",
|
||||||
|
@ -124,10 +80,10 @@
|
||||||
"test": "yarn test:app",
|
"test": "yarn test:app",
|
||||||
"test:coverage": "vitest --coverage",
|
"test:coverage": "vitest --coverage",
|
||||||
"test:coverage:watch": "vitest --coverage --watch",
|
"test:coverage:watch": "vitest --coverage --watch",
|
||||||
"test:ui": "yarn test --ui",
|
"test:ui": "yarn test --ui --coverage.enabled=true",
|
||||||
"autorelease": "node scripts/autorelease.js",
|
"autorelease": "node scripts/autorelease.js",
|
||||||
"prerelease": "node scripts/prerelease.js",
|
"prerelease:excalidraw": "node scripts/prerelease.js",
|
||||||
"build:preview": "yarn build && vite preview --port 5000",
|
"build:preview": "yarn build && vite preview --port 5000",
|
||||||
"release": "node scripts/release.js"
|
"release:excalidraw": "node scripts/release.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
packages/excalidraw/.gitignore
vendored
Normal file
4
packages/excalidraw/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
types
|
||||||
|
bundle.js
|
||||||
|
bundle.css
|
|
@ -1,16 +1,16 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"path": "dist/excalidraw.production.min.js",
|
"path": "dist/excalidraw.production.min.js",
|
||||||
"limit": "305 kB"
|
"limit": "340 kB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "dist/excalidraw-assets/locales",
|
"path": "dist/excalidraw-assets/locales",
|
||||||
"name": "dist/excalidraw-assets/locales",
|
"name": "dist/excalidraw-assets/locales",
|
||||||
"limit": "270 kB"
|
"limit": "290 kB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "dist/excalidraw-assets/vendor-*.js",
|
"path": "dist/excalidraw-assets/vendor-*.js",
|
||||||
"name": "dist/excalidraw-assets/vendor*.js",
|
"name": "dist/excalidraw-assets/vendor*.js",
|
||||||
"limit": "30 kB"
|
"limit": "900 kB"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -11,12 +11,265 @@ The change should be grouped under one of the below section and must contain PR
|
||||||
Please add the latest change on the top under the correct section.
|
Please add the latest change on the top under the correct section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
|
||||||
|
- Remove `ExcalidrawEmbeddableElement.validated` attribute. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
|
||||||
|
|
||||||
|
- Create an `ESM` build for `@excalidraw/excalidraw`. The API is in progress and subject to change before stable release. There are some changes on how the package will be consumed
|
||||||
|
|
||||||
|
#### Bundler
|
||||||
|
|
||||||
|
- CSS needs to be imported so you will need to import the css along with the excalidraw component
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||||
|
import "@excalidraw/excalidraw/index.css";
|
||||||
|
```
|
||||||
|
|
||||||
|
- The `types` path is updated
|
||||||
|
|
||||||
|
Instead of importing from `@excalidraw/excalidraw/types/`, you will need to import from `@excalidraw/excalidraw/dist/excalidraw` or `@excalidraw/excalidraw/dist/utils` depending on the types you are using.
|
||||||
|
|
||||||
|
However this we will be fixing before stable release, so in case you want to try it out you will need to update the types for now.
|
||||||
|
|
||||||
|
#### Browser
|
||||||
|
|
||||||
|
- Since its `ESM` so now script type `module` can be used to load it and css needs to be loaded as well.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.css"
|
||||||
|
/>
|
||||||
|
<script type="module">
|
||||||
|
import * as ExcalidrawLib from "https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.js";
|
||||||
|
window.ExcalidrawLib = ExcalidrawLib;
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
||||||
|
|
||||||
|
## 0.17.1 (2023-11-28)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean.
|
||||||
|
|
||||||
|
```
|
||||||
|
define: {
|
||||||
|
"process.env.IS_PREACT": JSON.stringify("true"),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excalidraw Library
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Disable caching bounds for arrow labels [#7343](https://github.com/excalidraw/excalidraw/pull/7343)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0.17.0 (2023-11-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Added support for disabling `image` tool (also disabling image insertion in general, though keeps support for importing from `.excalidraw` files) [#6320](https://github.com/excalidraw/excalidraw/pull/6320).
|
||||||
|
|
||||||
|
For disabling `image` you need to set 👇
|
||||||
|
|
||||||
|
```
|
||||||
|
UIOptions.tools = {
|
||||||
|
image: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Support `excalidrawAPI` prop for accessing the Excalidraw API [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||||
|
|
||||||
|
- Export [`getCommonBounds`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#getcommonbounds) helper from the package [#7247](https://github.com/excalidraw/excalidraw/pull/7247).
|
||||||
|
|
||||||
|
- Support frames via programmatic API [#7205](https://github.com/excalidraw/excalidraw/pull/7205).
|
||||||
|
|
||||||
|
- Export `elementsOverlappingBBox`, `isElementInsideBBox`, `elementPartiallyOverlapsWithOrContainsBBox` helpers for filtering/checking if elements within bounds. [#6727](https://github.com/excalidraw/excalidraw/pull/6727)
|
||||||
|
|
||||||
|
- Regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping [#7195](https://github.com/excalidraw/excalidraw/pull/7195)
|
||||||
|
|
||||||
|
- Add onChange, onPointerDown, onPointerUp api subscribers [#7154](https://github.com/excalidraw/excalidraw/pull/7154).
|
||||||
|
|
||||||
|
- Support props.locked for setActiveTool [#7153](https://github.com/excalidraw/excalidraw/pull/7153).
|
||||||
|
|
||||||
|
- Add `selected` prop for `MainMenu.Item` and `MainMenu.ItemCustom` components to indicate active state. [#7078](https://github.com/excalidraw/excalidraw/pull/7078)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Double image dialog on image insertion [#7152](https://github.com/excalidraw/excalidraw/pull/7152).
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the [`excalidrawAPI`](http://localhost:3003/docs/@excalidraw/excalidraw/api/props/excalidraw-api) [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||||
|
|
||||||
|
- Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0 [#7251](https://github.com/excalidraw/excalidraw/pull/7251).
|
||||||
|
|
||||||
|
- [`useDevice`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils#usedevice) hook's return value was changed to differentiate between `editor` and `viewport` breakpoints. [#7243](https://github.com/excalidraw/excalidraw/pull/7243)
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
- Support Preact [#7255](https://github.com/excalidraw/excalidraw/pull/7255). The host needs to set `process.env.IS_PREACT` to `true`
|
||||||
|
|
||||||
|
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use the `preact` build.
|
||||||
|
|
||||||
|
Since `Vite` removes env variables by default, you can update the Vite config to ensure its available :point_down:
|
||||||
|
|
||||||
|
```
|
||||||
|
define: {
|
||||||
|
"process.env.IS_PREACT": process.env.IS_PREACT,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excalidraw Library
|
||||||
|
|
||||||
|
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Allow D&D dice app domain for embeds [#7263](https://github.com/excalidraw/excalidraw/pull/7263)
|
||||||
|
|
||||||
|
- Remove full screen shortcut [#7222](https://github.com/excalidraw/excalidraw/pull/7222)
|
||||||
|
|
||||||
|
- Make adaptive-roughness less aggressive [#7250](https://github.com/excalidraw/excalidraw/pull/7250)
|
||||||
|
|
||||||
|
- Render frames on export [#7210](https://github.com/excalidraw/excalidraw/pull/7210)
|
||||||
|
|
||||||
|
- Support mermaid flowchart and sequence diagrams to excalidraw diagrams 🥳 [#6920](https://github.com/excalidraw/excalidraw/pull/6920)
|
||||||
|
|
||||||
|
- Support frames via programmatic API [#7205](https://github.com/excalidraw/excalidraw/pull/7205)
|
||||||
|
|
||||||
|
- Make clipboard more robust and reintroduce contextmenu actions [#7198](https://github.com/excalidraw/excalidraw/pull/7198)
|
||||||
|
|
||||||
|
- Support giphy.com embed domain [#7192](https://github.com/excalidraw/excalidraw/pull/7192)
|
||||||
|
|
||||||
|
- Renderer tweaks [#6698](https://github.com/excalidraw/excalidraw/pull/6698)
|
||||||
|
|
||||||
|
- Closing of "Save to.." Dialog on Save To Disk [#7168](https://github.com/excalidraw/excalidraw/pull/7168)
|
||||||
|
|
||||||
|
- Added Copy/Paste from Google Docs [#7136](https://github.com/excalidraw/excalidraw/pull/7136)
|
||||||
|
|
||||||
|
- Remove bound-arrows from frames [#7157](https://github.com/excalidraw/excalidraw/pull/7157)
|
||||||
|
|
||||||
|
- New dark mode theme & light theme tweaks [#7104](https://github.com/excalidraw/excalidraw/pull/7104)
|
||||||
|
|
||||||
|
- Better laser cursor for dark mode [#7132](https://github.com/excalidraw/excalidraw/pull/7132)
|
||||||
|
|
||||||
|
- Laser pointer improvements [#7128](https://github.com/excalidraw/excalidraw/pull/7128)
|
||||||
|
|
||||||
|
- Initial Laser Pointer MVP [#6739](https://github.com/excalidraw/excalidraw/pull/6739)
|
||||||
|
|
||||||
|
- Export `iconFillColor()` [#6996](https://github.com/excalidraw/excalidraw/pull/6996)
|
||||||
|
|
||||||
|
- Element alignments - snapping [#6256](https://github.com/excalidraw/excalidraw/pull/6256)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Image insertion bugs [#7278](https://github.com/excalidraw/excalidraw/pull/7278)
|
||||||
|
|
||||||
|
- ExportToSvg to honor frameRendering also for name not only for frame itself [#7270](https://github.com/excalidraw/excalidraw/pull/7270)
|
||||||
|
|
||||||
|
- Can't toggle penMode off due to missing typecheck in togglePenMode [#7273](https://github.com/excalidraw/excalidraw/pull/7273)
|
||||||
|
|
||||||
|
- Replace hard coded font family with const value in addFrameLabelsAsTextElements [#7269](https://github.com/excalidraw/excalidraw/pull/7269)
|
||||||
|
|
||||||
|
- Perf issue when ungrouping elements within frame [#7265](https://github.com/excalidraw/excalidraw/pull/7265)
|
||||||
|
|
||||||
|
- Fixes the shortcut collision between "toggleHandTool" and "distributeHorizontally" [#7189](https://github.com/excalidraw/excalidraw/pull/7189)
|
||||||
|
|
||||||
|
- Allow pointer events when editing a linear element [#7238](https://github.com/excalidraw/excalidraw/pull/7238)
|
||||||
|
|
||||||
|
- Make modal use viewport breakpoints [#7246](https://github.com/excalidraw/excalidraw/pull/7246)
|
||||||
|
|
||||||
|
- Align input `:hover`/`:focus` with spec [#7225](https://github.com/excalidraw/excalidraw/pull/7225)
|
||||||
|
|
||||||
|
- Dialog remounting on className updates [#7224](https://github.com/excalidraw/excalidraw/pull/7224)
|
||||||
|
|
||||||
|
- Don't update label position when dragging labelled arrows [#6891](https://github.com/excalidraw/excalidraw/pull/6891)
|
||||||
|
|
||||||
|
- Frame add/remove/z-index ordering changes [#7194](https://github.com/excalidraw/excalidraw/pull/7194)
|
||||||
|
|
||||||
|
- Element relative position when dragging multiple elements on grid [#7107](https://github.com/excalidraw/excalidraw/pull/7107)
|
||||||
|
|
||||||
|
- Freedraw non-solid bg hitbox not working [#7193](https://github.com/excalidraw/excalidraw/pull/7193)
|
||||||
|
|
||||||
|
- Actions panel ux improvement [#6850](https://github.com/excalidraw/excalidraw/pull/6850)
|
||||||
|
|
||||||
|
- Better fill rendering with latest RoughJS [#7031](https://github.com/excalidraw/excalidraw/pull/7031)
|
||||||
|
|
||||||
|
- Fix for Strange Symbol Appearing on Canvas after Deleting Grouped Graphics (Issue #7116) [#7170](https://github.com/excalidraw/excalidraw/pull/7170)
|
||||||
|
|
||||||
|
- Attempt to fix flake in wysiwyg tests [#7173](https://github.com/excalidraw/excalidraw/pull/7173)
|
||||||
|
|
||||||
|
- Ensure `ClipboardItem` created in the same tick to fix safari [#7066](https://github.com/excalidraw/excalidraw/pull/7066)
|
||||||
|
|
||||||
|
- Wysiwyg left in undefined state on reload [#7123](https://github.com/excalidraw/excalidraw/pull/7123)
|
||||||
|
|
||||||
|
- Ensure relative z-index of elements added to frame is retained [#7134](https://github.com/excalidraw/excalidraw/pull/7134)
|
||||||
|
|
||||||
|
- Memoize static canvas on `props.renderConfig` [#7131](https://github.com/excalidraw/excalidraw/pull/7131)
|
||||||
|
|
||||||
|
- Regression from #6739 preventing redirect link in view mode [#7120](https://github.com/excalidraw/excalidraw/pull/7120)
|
||||||
|
|
||||||
|
- Update links to excalidraw-app [#7072](https://github.com/excalidraw/excalidraw/pull/7072)
|
||||||
|
|
||||||
|
- Ensure we do not stop laser update prematurely [#7100](https://github.com/excalidraw/excalidraw/pull/7100)
|
||||||
|
|
||||||
|
- Remove invisible elements safely [#7083](https://github.com/excalidraw/excalidraw/pull/7083)
|
||||||
|
|
||||||
|
- Icon size in manifest [#7073](https://github.com/excalidraw/excalidraw/pull/7073)
|
||||||
|
|
||||||
|
- Elements being dropped/duplicated when added to frame [#7057](https://github.com/excalidraw/excalidraw/pull/7057)
|
||||||
|
|
||||||
|
- Frame name not editable on dbl-click [#7037](https://github.com/excalidraw/excalidraw/pull/7037)
|
||||||
|
|
||||||
|
- Polyfill `Element.replaceChildren` [#7034](https://github.com/excalidraw/excalidraw/pull/7034)
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- DRY out tool typing [#7086](https://github.com/excalidraw/excalidraw/pull/7086)
|
||||||
|
|
||||||
|
- Refactor event globals to differentiate from `lastPointerUp` [#7084](https://github.com/excalidraw/excalidraw/pull/7084)
|
||||||
|
|
||||||
|
- DRY out and simplify setting active tool from toolbar [#7079](https://github.com/excalidraw/excalidraw/pull/7079)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Improve element in frame check [#7124](https://github.com/excalidraw/excalidraw/pull/7124)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0.16.1 (2023-09-21)
|
||||||
|
|
||||||
|
## Excalidraw Library
|
||||||
|
|
||||||
|
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- More eye-droper fixes [#7019](https://github.com/excalidraw/excalidraw/pull/7019)
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Move excalidraw-app outside src [#6987](https://github.com/excalidraw/excalidraw/pull/6987)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0.16.0 (2023-09-19)
|
## 0.16.0 (2023-09-19)
|
||||||
|
|
||||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||||
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||||
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateEmbeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||||
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
||||||
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
||||||
- Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
- Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||||
- Exposed `DefaultSidebar` component to allow modifying the default sidebar, such as adding custom tabs to it. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
- Exposed `DefaultSidebar` component to allow modifying the default sidebar, such as adding custom tabs to it. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||||
|
@ -295,7 +548,7 @@ Please add the latest change on the top under the correct section.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
- [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
||||||
|
|
||||||
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
@ -28,7 +29,7 @@ const alignActionsPredicate = (
|
||||||
return (
|
return (
|
||||||
selectedElements.length > 1 &&
|
selectedElements.length > 1 &&
|
||||||
// TODO enable aligning frames when implemented properly
|
// TODO enable aligning frames when implemented properly
|
||||||
!selectedElements.some((el) => el.type === "frame")
|
!selectedElements.some((el) => isFrameLikeElement(el))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getNormalizedZoom } from "../scene";
|
||||||
import { centerScrollOn } from "../scene/scroll";
|
import { centerScrollOn } from "../scene/scroll";
|
||||||
import { getStateForZoom } from "../scene/zoom";
|
import { getStateForZoom } from "../scene/zoom";
|
||||||
import { AppState, NormalizedZoomValue } from "../types";
|
import { AppState, NormalizedZoomValue } from "../types";
|
||||||
import { getShortcutKey, setCursor, updateActiveTool } from "../utils";
|
import { getShortcutKey, updateActiveTool } from "../utils";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { Tooltip } from "../components/Tooltip";
|
import { Tooltip } from "../components/Tooltip";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
|
@ -20,7 +20,8 @@ import {
|
||||||
isHandToolActive,
|
isHandToolActive,
|
||||||
} from "../appState";
|
} from "../appState";
|
||||||
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
||||||
import { Bounds } from "../element/bounds";
|
import { SceneBounds } from "../element/bounds";
|
||||||
|
import { setCursor } from "../cursor";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
|
@ -108,6 +109,7 @@ export const actionZoomIn = register({
|
||||||
},
|
},
|
||||||
appState,
|
appState,
|
||||||
),
|
),
|
||||||
|
userToFollow: null,
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
|
@ -145,6 +147,7 @@ export const actionZoomOut = register({
|
||||||
},
|
},
|
||||||
appState,
|
appState,
|
||||||
),
|
),
|
||||||
|
userToFollow: null,
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
|
@ -182,6 +185,7 @@ export const actionResetZoom = register({
|
||||||
},
|
},
|
||||||
appState,
|
appState,
|
||||||
),
|
),
|
||||||
|
userToFollow: null,
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
|
@ -207,7 +211,7 @@ export const actionResetZoom = register({
|
||||||
});
|
});
|
||||||
|
|
||||||
const zoomValueToFitBoundsOnViewport = (
|
const zoomValueToFitBoundsOnViewport = (
|
||||||
bounds: Bounds,
|
bounds: SceneBounds,
|
||||||
viewportDimensions: { width: number; height: number },
|
viewportDimensions: { width: number; height: number },
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = bounds;
|
const [x1, y1, x2, y2] = bounds;
|
||||||
|
@ -225,22 +229,20 @@ const zoomValueToFitBoundsOnViewport = (
|
||||||
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const zoomToFit = ({
|
export const zoomToFitBounds = ({
|
||||||
targetElements,
|
bounds,
|
||||||
appState,
|
appState,
|
||||||
fitToViewport = false,
|
fitToViewport = false,
|
||||||
viewportZoomFactor = 0.7,
|
viewportZoomFactor = 0.7,
|
||||||
}: {
|
}: {
|
||||||
targetElements: readonly ExcalidrawElement[];
|
bounds: SceneBounds;
|
||||||
appState: Readonly<AppState>;
|
appState: Readonly<AppState>;
|
||||||
/** whether to fit content to viewport (beyond >100%) */
|
/** whether to fit content to viewport (beyond >100%) */
|
||||||
fitToViewport: boolean;
|
fitToViewport: boolean;
|
||||||
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
||||||
viewportZoomFactor?: number;
|
viewportZoomFactor?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
|
const [x1, y1, x2, y2] = bounds;
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = commonBounds;
|
|
||||||
const centerX = (x1 + x2) / 2;
|
const centerX = (x1 + x2) / 2;
|
||||||
const centerY = (y1 + y2) / 2;
|
const centerY = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
@ -264,10 +266,24 @@ export const zoomToFit = ({
|
||||||
30.0,
|
30.0,
|
||||||
) as NormalizedZoomValue;
|
) as NormalizedZoomValue;
|
||||||
|
|
||||||
scrollX = (appState.width / 2) * (1 / newZoomValue) - centerX;
|
let appStateWidth = appState.width;
|
||||||
|
|
||||||
|
if (appState.openSidebar) {
|
||||||
|
const sidebarDOMElem = document.querySelector(
|
||||||
|
".sidebar",
|
||||||
|
) as HTMLElement | null;
|
||||||
|
const sidebarWidth = sidebarDOMElem?.offsetWidth ?? 0;
|
||||||
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||||
|
|
||||||
|
appStateWidth = !isRTL
|
||||||
|
? appState.width - sidebarWidth
|
||||||
|
: appState.width + sidebarWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollX = (appStateWidth / 2) * (1 / newZoomValue) - centerX;
|
||||||
scrollY = (appState.height / 2) * (1 / newZoomValue) - centerY;
|
scrollY = (appState.height / 2) * (1 / newZoomValue) - centerY;
|
||||||
} else {
|
} else {
|
||||||
newZoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
|
newZoomValue = zoomValueToFitBoundsOnViewport(bounds, {
|
||||||
width: appState.width,
|
width: appState.width,
|
||||||
height: appState.height,
|
height: appState.height,
|
||||||
});
|
});
|
||||||
|
@ -296,6 +312,29 @@ export const zoomToFit = ({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const zoomToFit = ({
|
||||||
|
targetElements,
|
||||||
|
appState,
|
||||||
|
fitToViewport,
|
||||||
|
viewportZoomFactor,
|
||||||
|
}: {
|
||||||
|
targetElements: readonly ExcalidrawElement[];
|
||||||
|
appState: Readonly<AppState>;
|
||||||
|
/** whether to fit content to viewport (beyond >100%) */
|
||||||
|
fitToViewport: boolean;
|
||||||
|
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
||||||
|
viewportZoomFactor?: number;
|
||||||
|
}) => {
|
||||||
|
const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
|
||||||
|
|
||||||
|
return zoomToFitBounds({
|
||||||
|
bounds: commonBounds,
|
||||||
|
appState,
|
||||||
|
fitToViewport,
|
||||||
|
viewportZoomFactor,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Note, this action differs from actionZoomToFitSelection in that it doesn't
|
// Note, this action differs from actionZoomToFitSelection in that it doesn't
|
||||||
// zoom beyond 100%. In other words, if the content is smaller than viewport
|
// zoom beyond 100%. In other words, if the content is smaller than viewport
|
||||||
// size, it won't be zoomed in.
|
// size, it won't be zoomed in.
|
||||||
|
@ -306,7 +345,10 @@ export const actionZoomToFitSelectionInViewport = register({
|
||||||
const selectedElements = app.scene.getSelectedElements(appState);
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
return zoomToFit({
|
return zoomToFit({
|
||||||
targetElements: selectedElements.length ? selectedElements : elements,
|
targetElements: selectedElements.length ? selectedElements : elements,
|
||||||
appState,
|
appState: {
|
||||||
|
...appState,
|
||||||
|
userToFollow: null,
|
||||||
|
},
|
||||||
fitToViewport: false,
|
fitToViewport: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -326,7 +368,10 @@ export const actionZoomToFitSelection = register({
|
||||||
const selectedElements = app.scene.getSelectedElements(appState);
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
return zoomToFit({
|
return zoomToFit({
|
||||||
targetElements: selectedElements.length ? selectedElements : elements,
|
targetElements: selectedElements.length ? selectedElements : elements,
|
||||||
appState,
|
appState: {
|
||||||
|
...appState,
|
||||||
|
userToFollow: null,
|
||||||
|
},
|
||||||
fitToViewport: true,
|
fitToViewport: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -343,7 +388,14 @@ export const actionZoomToFit = register({
|
||||||
viewMode: true,
|
viewMode: true,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
perform: (elements, appState) =>
|
perform: (elements, appState) =>
|
||||||
zoomToFit({ targetElements: elements, appState, fitToViewport: false }),
|
zoomToFit({
|
||||||
|
targetElements: elements,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
userToFollow: null,
|
||||||
|
},
|
||||||
|
fitToViewport: false,
|
||||||
|
}),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event.code === CODES.ONE &&
|
event.code === CODES.ONE &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
|
@ -437,5 +489,6 @@ export const actionToggleHandTool = register({
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
keyTest: (event) => event.key === KEYS.H,
|
keyTest: (event) =>
|
||||||
|
!event.altKey && !event[KEYS.CTRL_OR_CMD] && event.key === KEYS.H,
|
||||||
});
|
});
|
|
@ -3,33 +3,43 @@ import { register } from "./register";
|
||||||
import {
|
import {
|
||||||
copyTextToSystemClipboard,
|
copyTextToSystemClipboard,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
|
createPasteEvent,
|
||||||
probablySupportsClipboardBlob,
|
probablySupportsClipboardBlob,
|
||||||
probablySupportsClipboardWriteText,
|
probablySupportsClipboardWriteText,
|
||||||
|
readSystemClipboard,
|
||||||
} from "../clipboard";
|
} from "../clipboard";
|
||||||
import { actionDeleteSelected } from "./actionDeleteSelected";
|
import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||||
import { exportCanvas } from "../data/index";
|
import { exportCanvas, prepareElementsForExport } from "../data/index";
|
||||||
import { getNonDeletedElements, isTextElement } from "../element";
|
import { isTextElement } from "../element";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { isFirefox } from "../constants";
|
||||||
|
|
||||||
export const actionCopy = register({
|
export const actionCopy = register({
|
||||||
name: "copy",
|
name: "copy",
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState, _, app) => {
|
perform: async (elements, appState, event: ClipboardEvent | null, app) => {
|
||||||
const elementsToCopy = app.scene.getSelectedElements({
|
const elementsToCopy = app.scene.getSelectedElements({
|
||||||
selectedElementIds: appState.selectedElementIds,
|
selectedElementIds: appState.selectedElementIds,
|
||||||
includeBoundTextElement: true,
|
includeBoundTextElement: true,
|
||||||
includeElementsInFrames: true,
|
includeElementsInFrames: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
copyToClipboard(elementsToCopy, app.files);
|
try {
|
||||||
|
await copyToClipboard(elementsToCopy, app.files, event);
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
errorMessage: error.message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
predicate: (elements, appState, appProps, app) => {
|
|
||||||
return app.device.isMobile && !!navigator.clipboard;
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.copy",
|
contextItemLabel: "labels.copy",
|
||||||
// don't supply a shortcut since we handle this conditionally via onCopy event
|
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||||
keyTest: undefined,
|
keyTest: undefined,
|
||||||
|
@ -38,15 +48,55 @@ export const actionCopy = register({
|
||||||
export const actionPaste = register({
|
export const actionPaste = register({
|
||||||
name: "paste",
|
name: "paste",
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements: any, appStates: any, data, app) => {
|
perform: async (elements, appState, data, app) => {
|
||||||
app.pasteFromClipboard(null);
|
let types;
|
||||||
|
try {
|
||||||
|
types = await readSystemClipboard();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === "AbortError" || error.name === "NotAllowedError") {
|
||||||
|
// user probably aborted the action. Though not 100% sure, it's best
|
||||||
|
// to not annoy them with an error message.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`actionPaste ${error.name}: ${error.message}`);
|
||||||
|
|
||||||
|
if (isFirefox) {
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
errorMessage: t("hints.firefox_clipboard_write"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
errorMessage: t("errors.asyncPasteFailedOnRead"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.pasteFromClipboard(createPasteEvent({ types }));
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
errorMessage: t("errors.asyncPasteFailedOnParse"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
predicate: (elements, appState, appProps, app) => {
|
|
||||||
return app.device.isMobile && !!navigator.clipboard;
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.paste",
|
contextItemLabel: "labels.paste",
|
||||||
// don't supply a shortcut since we handle this conditionally via onCopy event
|
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||||
keyTest: undefined,
|
keyTest: undefined,
|
||||||
|
@ -55,13 +105,10 @@ export const actionPaste = register({
|
||||||
export const actionCut = register({
|
export const actionCut = register({
|
||||||
name: "cut",
|
name: "cut",
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState, data, app) => {
|
perform: (elements, appState, event: ClipboardEvent | null, app) => {
|
||||||
actionCopy.perform(elements, appState, data, app);
|
actionCopy.perform(elements, appState, event, app);
|
||||||
return actionDeleteSelected.perform(elements, appState);
|
return actionDeleteSelected.perform(elements, appState);
|
||||||
},
|
},
|
||||||
predicate: (elements, appState, appProps, app) => {
|
|
||||||
return app.device.isMobile && !!navigator.clipboard;
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.cut",
|
contextItemLabel: "labels.cut",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
||||||
});
|
});
|
||||||
|
@ -75,20 +122,23 @@ export const actionCopyAsSvg = register({
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const selectedElements = app.scene.getSelectedElements({
|
|
||||||
selectedElementIds: appState.selectedElementIds,
|
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
||||||
includeBoundTextElement: true,
|
elements,
|
||||||
includeElementsInFrames: true,
|
appState,
|
||||||
});
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await exportCanvas(
|
await exportCanvas(
|
||||||
"clipboard-svg",
|
"clipboard-svg",
|
||||||
selectedElements.length
|
exportedElements,
|
||||||
? selectedElements
|
|
||||||
: getNonDeletedElements(elements),
|
|
||||||
appState,
|
appState,
|
||||||
app.files,
|
app.files,
|
||||||
appState,
|
{
|
||||||
|
...appState,
|
||||||
|
exportingFrame,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
|
@ -124,16 +174,17 @@ export const actionCopyAsPng = register({
|
||||||
includeBoundTextElement: true,
|
includeBoundTextElement: true,
|
||||||
includeElementsInFrames: true,
|
includeElementsInFrames: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
true,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await exportCanvas(
|
await exportCanvas("clipboard", exportedElements, appState, app.files, {
|
||||||
"clipboard",
|
...appState,
|
||||||
selectedElements.length
|
exportingFrame,
|
||||||
? selectedElements
|
});
|
||||||
: getNonDeletedElements(elements),
|
|
||||||
appState,
|
|
||||||
app.files,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
|
@ -10,7 +10,7 @@ import { newElementWith } from "../element/mutateElement";
|
||||||
import { getElementsInGroup } from "../groups";
|
import { getElementsInGroup } from "../groups";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { fixBindingsAfterDeletion } from "../element/binding";
|
import { fixBindingsAfterDeletion } from "../element/binding";
|
||||||
import { isBoundToContainer } from "../element/typeChecks";
|
import { isBoundToContainer, isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { updateActiveTool } from "../utils";
|
import { updateActiveTool } from "../utils";
|
||||||
import { TrashIcon } from "../components/icons";
|
import { TrashIcon } from "../components/icons";
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const deleteSelectedElements = (
|
||||||
) => {
|
) => {
|
||||||
const framesToBeDeleted = new Set(
|
const framesToBeDeleted = new Set(
|
||||||
getSelectedElements(
|
getSelectedElements(
|
||||||
elements.filter((el) => el.type === "frame"),
|
elements.filter((el) => isFrameLikeElement(el)),
|
||||||
appState,
|
appState,
|
||||||
).map((el) => el.id),
|
).map((el) => el.id),
|
||||||
);
|
);
|
||||||
|
@ -46,6 +46,7 @@ const deleteSelectedElements = (
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
|
selectedGroupIds: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -5,6 +5,7 @@ import {
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { distributeElements, Distribution } from "../distribute";
|
import { distributeElements, Distribution } from "../distribute";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
@ -19,7 +20,7 @@ const enableActionGroup = (appState: AppState, app: AppClassProperties) => {
|
||||||
return (
|
return (
|
||||||
selectedElements.length > 1 &&
|
selectedElements.length > 1 &&
|
||||||
// TODO enable distributing frames when implemented properly
|
// TODO enable distributing frames when implemented properly
|
||||||
!selectedElements.some((el) => el.type === "frame")
|
!selectedElements.some((el) => isFrameLikeElement(el))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,12 +20,12 @@ import {
|
||||||
bindTextToShapeAfterDuplication,
|
bindTextToShapeAfterDuplication,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { isBoundToContainer, isFrameElement } from "../element/typeChecks";
|
import { isBoundToContainer, isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { normalizeElementOrder } from "../element/sortElements";
|
import { normalizeElementOrder } from "../element/sortElements";
|
||||||
import { DuplicateIcon } from "../components/icons";
|
import { DuplicateIcon } from "../components/icons";
|
||||||
import {
|
import {
|
||||||
bindElementsToFramesAfterDuplication,
|
bindElementsToFramesAfterDuplication,
|
||||||
getFrameElements,
|
getFrameChildren,
|
||||||
} from "../frame";
|
} from "../frame";
|
||||||
import {
|
import {
|
||||||
excludeElementsInFramesFromSelection,
|
excludeElementsInFramesFromSelection,
|
||||||
|
@ -140,11 +140,11 @@ const duplicateElements = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundTextElement = getBoundTextElement(element);
|
const boundTextElement = getBoundTextElement(element);
|
||||||
const isElementAFrame = isFrameElement(element);
|
const isElementAFrameLike = isFrameLikeElement(element);
|
||||||
|
|
||||||
if (idsOfElementsToDuplicate.get(element.id)) {
|
if (idsOfElementsToDuplicate.get(element.id)) {
|
||||||
// if a group or a container/bound-text or frame, duplicate atomically
|
// if a group or a container/bound-text or frame, duplicate atomically
|
||||||
if (element.groupIds.length || boundTextElement || isElementAFrame) {
|
if (element.groupIds.length || boundTextElement || isElementAFrameLike) {
|
||||||
const groupId = getSelectedGroupForElement(appState, element);
|
const groupId = getSelectedGroupForElement(appState, element);
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -154,8 +154,8 @@ const duplicateElements = (
|
||||||
sortedElements,
|
sortedElements,
|
||||||
groupId,
|
groupId,
|
||||||
).flatMap((element) =>
|
).flatMap((element) =>
|
||||||
isFrameElement(element)
|
isFrameLikeElement(element)
|
||||||
? [...getFrameElements(elements, element.id), element]
|
? [...getFrameChildren(elements, element.id), element]
|
||||||
: [element],
|
: [element],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -180,8 +180,8 @@ const duplicateElements = (
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isElementAFrame) {
|
if (isElementAFrameLike) {
|
||||||
const elementsInFrame = getFrameElements(sortedElements, element.id);
|
const elementsInFrame = getFrameChildren(sortedElements, element.id);
|
||||||
|
|
||||||
elementsWithClones.push(
|
elementsWithClones.push(
|
||||||
...markAsProcessed([
|
...markAsProcessed([
|
|
@ -1,4 +1,4 @@
|
||||||
import { Excalidraw } from "../packages/excalidraw/index";
|
import { Excalidraw } from "../index";
|
||||||
import { queryByTestId, fireEvent } from "@testing-library/react";
|
import { queryByTestId, fireEvent } from "@testing-library/react";
|
||||||
import { render } from "../tests/test-utils";
|
import { render } from "../tests/test-utils";
|
||||||
import { Pointer, UI } from "../tests/helpers/ui";
|
import { Pointer, UI } from "../tests/helpers/ui";
|
|
@ -1,4 +1,5 @@
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
|
import { isFrameLikeElement } from "../element/typeChecks";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
|
@ -51,7 +52,7 @@ export const actionToggleElementLock = register({
|
||||||
selectedElementIds: appState.selectedElementIds,
|
selectedElementIds: appState.selectedElementIds,
|
||||||
includeBoundTextElement: false,
|
includeBoundTextElement: false,
|
||||||
});
|
});
|
||||||
if (selected.length === 1 && selected[0].type !== "frame") {
|
if (selected.length === 1 && !isFrameLikeElement(selected[0])) {
|
||||||
return selected[0].locked
|
return selected[0].locked
|
||||||
? "labels.elementLock.unlock"
|
? "labels.elementLock.unlock"
|
||||||
: "labels.elementLock.lock";
|
: "labels.elementLock.lock";
|
|
@ -191,7 +191,15 @@ export const actionSaveFileToDisk = register({
|
||||||
},
|
},
|
||||||
app.files,
|
app.files,
|
||||||
);
|
);
|
||||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
openDialog: null,
|
||||||
|
fileHandle,
|
||||||
|
toast: { message: t("toast.fileSaved") },
|
||||||
|
},
|
||||||
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.name !== "AbortError") {
|
if (error?.name !== "AbortError") {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -209,7 +217,7 @@ export const actionSaveFileToDisk = register({
|
||||||
icon={saveAs}
|
icon={saveAs}
|
||||||
title={t("buttons.saveAs")}
|
title={t("buttons.saveAs")}
|
||||||
aria-label={t("buttons.saveAs")}
|
aria-label={t("buttons.saveAs")}
|
||||||
showAriaLabel={useDevice().isMobile}
|
showAriaLabel={useDevice().editor.isMobile}
|
||||||
hidden={!nativeFileSystemSupported}
|
hidden={!nativeFileSystemSupported}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-as-button"
|
data-testid="save-as-button"
|
|
@ -1,6 +1,6 @@
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isInvisiblySmallElement } from "../element";
|
import { isInvisiblySmallElement } from "../element";
|
||||||
import { updateActiveTool, resetCursor } from "../utils";
|
import { updateActiveTool } from "../utils";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { done } from "../components/icons";
|
import { done } from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "../element/binding";
|
} from "../element/binding";
|
||||||
import { isBindingElement, isLinearElement } from "../element/typeChecks";
|
import { isBindingElement, isLinearElement } from "../element/typeChecks";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { resetCursor } from "../cursor";
|
||||||
|
|
||||||
export const actionFinalize = register({
|
export const actionFinalize = register({
|
||||||
name: "finalize",
|
name: "finalize",
|
||||||
|
@ -90,7 +91,9 @@ export const actionFinalize = register({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvisiblySmallElement(multiPointElement)) {
|
if (isInvisiblySmallElement(multiPointElement)) {
|
||||||
newElements = newElements.slice(0, -1);
|
newElements = newElements.filter(
|
||||||
|
(el) => el.id !== multiPointElement.id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the multi point line closes the loop,
|
// If the multi point line closes the loop,
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue