Compare commits

..

2 Commits

Author SHA1 Message Date
d308451584 fix offsets 2024-10-25 11:28:01 +08:00
f444383b46 fix: image cropping svg + compat mode 2024-10-25 00:04:04 +02:00
405 changed files with 15885 additions and 23872 deletions

View File

@ -8,7 +8,7 @@ VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfu
VITE_APP_WS_SERVER_URL=http://localhost:3002
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=http://localhost:3000
VITE_APP_PLUS_APP=https://app.excalidraw.com
VITE_APP_AI_BACKEND=http://localhost:3015
@ -37,14 +37,3 @@ VITE_APP_COLLAPSE_OVERLAY=true
# Set this flag to false to disable eslint
VITE_APP_ENABLE_ESLINT=true
# Enable PWA in dev server
VITE_APP_ENABLE_PWA=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2g5T+Rub6Kbf1Mf57t0
7r2zeHuVg4dla3r5ryXMswtzz6x767octl6oLThn33mQsPSy3GKglFZoCTXJR4ij
ba8SxB04sL/N8eRrKja7TFWjCVtRwTTfyy771NYYNFVJclkxHyE5qw4m27crHF1y
UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
HQIDAQAB'

View File

@ -15,18 +15,3 @@ VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
VITE_APP_ENABLE_TRACKING=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApQ0jM9Qz8TdFLzcuAZZX
/WvuKSOJxiw6AR/ZcE3eFQWM/mbFdhQgyK8eHGkKQifKzH1xUZjCxyXcxW6ZO02t
kPOPxhz+nxUrIoWCD/V4NGmUA1lxwHuO21HN1gzKrN3xHg5EGjyouR9vibT9VDGF
gq6+4Ic/kJX+AD2MM7Yre2+FsOdysrmuW2Fu3ahuC1uQE7pOe1j0k7auNP0y1q53
PrB8Ts2LUpepWC1l7zIXFm4ViDULuyWXTEpUcHSsEH8vpd1tckjypxCwkipfZsXx
iPszy0o0Dx2iArPfWMXlFAI9mvyFCyFC3+nSvfyAUb2C4uZgCwAuyFh/ydPF4DEE
PQIDAQAB'
# Set the below flags explicitly to false in production mode since vite loads and merges .env.local vars when running the build command
VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=false
VITE_APP_COLLAPSE_OVERLAY=false
# Enable eslint in dev server
VITE_APP_ENABLE_ESLINT=false

View File

@ -3,20 +3,6 @@
"rules": {
"import/no-anonymous-default-export": "off",
"no-restricted-globals": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"disallowTypeAnnotations": false,
"fixStyle": "separate-type-imports"
}
],
"no-restricted-imports": [
"error",
{
"name": "jotai",
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
}
]
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }]
}
}

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}

View File

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

1
.gitignore vendored
View File

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

View File

@ -7,7 +7,7 @@
<h4 align="center">
<a href="https://excalidraw.com">Excalidraw Editor</a> |
<a href="https://plus.excalidraw.com/blog">Blog</a> |
<a href="https://blog.excalidraw.com">Blog</a> |
<a href="https://docs.excalidraw.com">Documentation</a> |
<a href="https://plus.excalidraw.com">Excalidraw+</a>
</h4>
@ -87,11 +87,13 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
Use `npm` or `yarn` to install the package.
```bash
```
npm install react react-dom @excalidraw/excalidraw
# or
```
or via yarn
```
yarn add react react-dom @excalidraw/excalidraw
```

View File

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

View File

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

View File

@ -3,32 +3,31 @@
All `props` are _optional_.
| Name | Type | Default | Description |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- |
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` &#124; `null` &#124; <code>Promise<object &#124; null></code> | `null` | The initial data with which app loads. |
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | \_ | Callback triggered with the excalidraw api once rendered |
| [`isCollaborating`](#iscollaborating) | `boolean` | \_ | This indicates if the app is in `collaboration` mode |
| [`onChange`](#onchange) | `function` | \_ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
| [`onPointerUpdate`](#onpointerupdate) | `function` | \_ | Callback triggered when mouse pointer is updated. |
| [`onPointerDown`](#onpointerdown) | `function` | \_ | This prop if passed gets triggered on pointer down events |
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | \_ | Callback to be triggered if passed when something is pasted into the scene |
| [`onLibraryChange`](#onlibrarychange) | `function` | \_ | The callback if supplied is triggered when the library is updated and receives the library items. |
| [`generateLinkForSelection`](#generateLinkForSelection) | `function` | \_ | Allows you to override `url` generation when linking to Excalidraw elements. |
| [`onLinkOpen`](#onlinkopen) | `function` | \_ | The callback if supplied is triggered when any link is opened. |
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | _ | Callback triggered with the excalidraw api once rendered |
| [`isCollaborating`](#iscollaborating) | `boolean` | _ | This indicates if the app is in `collaboration` mode |
| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down events |
| [`onScrollChange`](#onscrollchange) | `function` | _ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | _ | Callback to be triggered if passed when something is pasted into the scene |
| [`onLibraryChange`](#onlibrarychange) | `function` | _ | The callback if supplied is triggered when the library is updated and receives the library items. |
| [`onLinkOpen`](#onlinkopen) | `function` | _ | The callback if supplied is triggered when any link is opened. |
| [`langCode`](#langcode) | `string` | `en` | Language code string to be used in Excalidraw |
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | \_ | Render function that renders custom UI in top right corner |
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | \_ | Render function that can be used to render custom stats on the stats dialog. |
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | \_ | This indicates if the app is in `view` mode. |
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | \_ | This indicates if the `zen` mode is enabled |
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | \_ | This indicates if the `grid` mode is enabled |
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | \_ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | _ | Render function that renders custom UI in top right corner |
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | _ | Render function that can be used to render custom stats on the stats dialog. |
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | _ | This indicates if the app is in `view` mode. |
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | _ | This indicates if the `zen` mode is enabled |
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | _ | This indicates if the `grid` mode is enabled |
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`theme`](#theme) | `"light"` &#124; `"dark"` | `"light"` | The theme of the Excalidraw component |
| [`name`](#name) | `string` | | Name of the drawing |
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
| [`generateIdForFile`](#generateidforfile) | `function` | _ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateEmbeddable) | string[] | `boolean | RegExp | RegExp[] | ((link: string) => boolean | undefined)` | \_ | use for custom src url validation |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
@ -94,8 +93,9 @@ This callback is triggered when mouse pointer is updated.
This prop if passed will be triggered on pointer down events and has the below signature.
<pre>
(activeTool:{" "}
(activeTool:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
{" "}
AppState["activeTool"]
@ -143,14 +143,6 @@ This callback if supplied will get triggered when the library is updated and has
It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
### generateLinkForSelection
This prop if passed will be used to replace the default link generation function. The idea is that the host app can take over the creation of element links, which can be used to navigate to a particular element or a group. If the host app chooses a different key for element link id, then the host app should also take care of the handling and the navigation in `onLinkOpen`.
```tsx
(id: string, type: "element" | "group") => string;
```
### onLinkOpen
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
@ -215,7 +207,8 @@ This prop indicates whether the shows the grid. When supplied, the value takes p
### libraryReturnUrl
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
### theme
@ -227,6 +220,7 @@ You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify t
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
### detectScroll
Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method).

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import { Excalidraw } from "@excalidraw/excalidraw";
Throughout the documentation we use live, editable Excalidraw examples like the one shown below.
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
For example, we're passing a `theme` prop to it based on the current color theme of the docs you're just reading.
:::
@ -70,9 +70,9 @@ If you are using `pages router` then importing the wrapper dynamically would wor
height: 141.9765625,
},]));
return (
<div style={{height:"500px", width:"500px"}}>
<div style={{height:"500px", width:"500px"}}>
<Excalidraw />
</div>
</div>
);
};
export default ExcalidrawWrapper;
@ -84,8 +84,8 @@ If you are using `pages router` then importing the wrapper dynamically would wor
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
@ -97,7 +97,7 @@ If you are using `pages router` then importing the wrapper dynamically would wor
export default function Page() {
return (
<ExcalidrawWrapper />
<ExcalidrawWrapper />
);
}
```
@ -108,7 +108,7 @@ If you are using `pages router` then importing the wrapper dynamically would wor
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
@ -128,10 +128,11 @@ If you are using `pages router` then importing the wrapper dynamically would wor
</TabItem>
</Tabs>
{/* Link should be updated to point to the latest! */}
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs.vercel.app/).
The `types` are available at `@excalidraw/excalidraw/types`, check [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example for details.
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/).
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
### Preact
@ -152,13 +153,31 @@ Since Vite removes env variables by default, you can update the vite config to e
"process.env.IS_PREACT": JSON.stringify("true"),
},
```
:::
:::
## Browser
To use it Excalidraw in a browser directly, use the following setup :point_down:
To use it in a browser directly:
> **Note**: We rely on import maps to de-duplicate `react`, `react-dom` and `react/jsx-runtime` versions.
For development use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
```
For production use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"
></script>
```
You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
@ -172,23 +191,13 @@ import TabItem from "@theme/TabItem";
<head>
<title>Excalidraw in browser</title>
<meta charset="UTF-8" />
<link
rel="stylesheet"
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
/>
<link rel="stylesheet" href="./index.css" />
<script>
window.EXCALIDRAW_ASSET_PATH = "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
</script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.0.0",
"react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.0.0"
}
}
</script>
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
</head>
<body>
@ -205,14 +214,6 @@ import TabItem from "@theme/TabItem";
<TabItem value="js" label="Javascript">
```js showLineNumbers
// See https://www.npmjs.com/package/@excalidraw/excalidraw documentation.
import * as ExcalidrawLib from 'https://esm.sh/@excalidraw/excalidraw@0.18.0-rc.1/dist/dev/index.js?external=react,react-dom';
import React from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0"
window.ExcalidrawLib = ExcalidrawLib;
console.log("Excalidraw library", ExcalidrawLib);
const App = () => {
return React.createElement(
React.Fragment,
@ -234,5 +235,3 @@ root.render(React.createElement(App));
</TabItem>
</Tabs>
You can try it out [here](https://jsfiddle.net/64y130L8/1/).

View File

@ -66,7 +66,7 @@ const config = {
label: "Docs",
},
{
to: "https://plus.excalidraw.com/blog",
to: "https://blog.excalidraw.com",
label: "Blog",
position: "left",
},
@ -111,7 +111,7 @@ const config = {
items: [
{
label: "Blog",
to: "https://plus.excalidraw.com/blog",
to: "https://blog.excalidraw.com",
},
{
label: "GitHub",
@ -149,29 +149,6 @@ const config = {
systemvars: true,
},
],
function () {
return {
name: "disable-fully-specified-error",
configureWebpack() {
return {
module: {
rules: [
{
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
},
],
},
optimization: {
// disable terser minification
minimize: false,
},
};
},
};
},
],
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -33,12 +33,12 @@ import type {
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "@excalidraw/excalidraw/types";
} from "@excalidraw/excalidraw/dist/excalidraw/types";
import type {
NonDeletedExcalidrawElement,
Theme,
} from "@excalidraw/excalidraw/element/types";
import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
} from "@excalidraw/excalidraw/dist/excalidraw/element/types";
import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types";
import "./ExampleApp.scss";

View File

@ -1,5 +1,4 @@
import React from "react";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
import CustomFooter from "./CustomFooter";
import type * as TExcalidraw from "@excalidraw/excalidraw";

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { unstable_batchedUpdates } from "react-dom";
import { fileOpen as _fileOpen } from "browser-fs-access";
import { MIME_TYPES } from "@excalidraw/excalidraw";
import { AbortError } from "../../packages/excalidraw/errors";
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
@ -84,7 +85,7 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
if (rejectPromise) {
// so that something is shown in console if we need to debug this
console.warn("Opening the file was canceled (legacy-fs).");
rejectPromise(new Error("Request Aborted"));
rejectPromise(new AbortError());
}
};
},

View File

@ -4,21 +4,22 @@
"private": true,
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"dev": "yarn build:workspace && next dev -p 3005",
"build": "yarn build:workspace && next build",
"start": "next start -p 3006",
"lint": "next lint"
},
"dependencies": {
"@excalidraw/excalidraw": "*",
"next": "14.1",
"react": "19.0.0",
"react-dom": "19.0.0"
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"path2d-polyfill": "2.0.1",
"typescript": "^5"
}

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,7 +1,7 @@
"use client";
import * as excalidrawLib from "@excalidraw/excalidraw";
import { Excalidraw } from "@excalidraw/excalidraw";
import App from "../../with-script-in-browser/components/ExampleApp";
import App from "../../components/ExampleApp";
import "@excalidraw/excalidraw/index.css";

View File

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

View File

@ -1,4 +1,4 @@
import App from "./components/ExampleApp";
import App from "../components/ExampleApp";
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";

View File

@ -0,0 +1,21 @@
{
"name": "with-script-in-browser",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"@excalidraw/excalidraw": "*"
},
"devDependencies": {
"vite": "5.0.12",
"typescript": "^5"
},
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"start": "yarn build:workspace && vite",
"build": "yarn build:workspace && vite build",
"build:preview": "yarn build && vite preview --port 5002"
}
}

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import polyfill from "@excalidraw/excalidraw/polyfill";
import polyfill from "../packages/excalidraw/polyfill";
import { useCallback, useEffect, useRef, useState } from "react";
import { trackEvent } from "@excalidraw/excalidraw/analytics";
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
import { trackEvent } from "../packages/excalidraw/analytics";
import { getDefaultAppState } from "../packages/excalidraw/appState";
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
import { TopErrorBoundary } from "./components/TopErrorBoundary";
import {
APP_NAME,
@ -10,30 +10,30 @@ import {
THEME,
TITLE_TIMEOUT,
VERSION_TIMEOUT,
} from "@excalidraw/excalidraw/constants";
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
} from "../packages/excalidraw/constants";
import { loadFromBlob } from "../packages/excalidraw/data/blob";
import type {
FileId,
NonDeletedExcalidrawElement,
OrderedExcalidrawElement,
} from "@excalidraw/excalidraw/element/types";
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
import { t } from "@excalidraw/excalidraw/i18n";
} from "../packages/excalidraw/element/types";
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
import { t } from "../packages/excalidraw/i18n";
import {
Excalidraw,
LiveCollaborationTrigger,
TTDDialogTrigger,
CaptureUpdateAction,
StoreAction,
reconcileElements,
} from "@excalidraw/excalidraw";
} from "../packages/excalidraw";
import type {
AppState,
ExcalidrawImperativeAPI,
BinaryFiles,
ExcalidrawInitialDataState,
UIAppState,
} from "@excalidraw/excalidraw/types";
import type { ResolvablePromise } from "@excalidraw/excalidraw/utils";
} from "../packages/excalidraw/types";
import type { ResolvablePromise } from "../packages/excalidraw/utils";
import {
debounce,
getVersion,
@ -42,7 +42,7 @@ import {
preventUnload,
resolvablePromise,
isRunningInIframe,
} from "@excalidraw/excalidraw/utils";
} from "../packages/excalidraw/utils";
import {
FIREBASE_STORAGE_PREFIXES,
isExcalidrawPlusSignedUser,
@ -66,15 +66,15 @@ import {
importUsernameFromLocalStorage,
} from "./data/localStorage";
import CustomStats from "./CustomStats";
import type { RestoredDataState } from "@excalidraw/excalidraw/data/restore";
import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
import type { RestoredDataState } from "../packages/excalidraw/data/restore";
import { restore, restoreAppState } from "../packages/excalidraw/data/restore";
import {
ExportToExcalidrawPlus,
exportToExcalidrawPlus,
} from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
import { newElementWith } from "../packages/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "../packages/excalidraw/element/typeChecks";
import { loadFilesFromFirebase } from "./data/firebase";
import {
LibraryIndexedDBAdapter,
@ -86,31 +86,27 @@ import clsx from "clsx";
import {
parseLibraryTokensFromUrl,
useHandleLibrary,
} from "@excalidraw/excalidraw/data/library";
} from "../packages/excalidraw/data/library";
import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter";
import {
Provider,
useAtom,
useAtomValue,
useAtomWithInitialValue,
appJotaiStore,
} from "./app-jotai";
import { Provider, useAtom, useAtomValue } from "jotai";
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
import { appJotaiStore } from "./app-jotai";
import "./index.scss";
import type { ResolutionType } from "@excalidraw/excalidraw/utility-types";
import { ShareableLinkDialog } from "@excalidraw/excalidraw/components/ShareableLinkDialog";
import { openConfirmModal } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
import { OverwriteConfirmDialog } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
import Trans from "@excalidraw/excalidraw/components/Trans";
import type { ResolutionType } from "../packages/excalidraw/utility-types";
import { ShareableLinkDialog } from "../packages/excalidraw/components/ShareableLinkDialog";
import { openConfirmModal } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
import Trans from "../packages/excalidraw/components/Trans";
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
import type { RemoteExcalidrawElement } from "../packages/excalidraw/data/reconcile";
import {
CommandPalette,
DEFAULT_CATEGORIES,
} from "@excalidraw/excalidraw/components/CommandPalette/CommandPalette";
} from "../packages/excalidraw/components/CommandPalette/CommandPalette";
import {
GithubIcon,
XBrandIcon,
@ -120,8 +116,8 @@ import {
exportToPlus,
share,
youtubeIcon,
} from "@excalidraw/excalidraw/components/icons";
import { useHandleAppTheme } from "./useHandleAppTheme";
} from "../packages/excalidraw/components/icons";
import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme";
import { getPreferredLanguage } from "./app-language/language-detector";
import { useAppLangCode } from "./app-language/language-state";
import DebugCanvas, {
@ -130,8 +126,6 @@ import DebugCanvas, {
loadSavedDebugState,
} from "./components/DebugCanvas";
import { AIComponents } from "./components/AI";
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
import { isElementLink } from "@excalidraw/excalidraw/element/elementLink";
polyfill();
@ -332,7 +326,8 @@ const ExcalidrawWrapper = () => {
const [errorMessage, setErrorMessage] = useState("");
const isCollabDisabled = isRunningInIframe();
const { editorTheme, appTheme, setAppTheme } = useHandleAppTheme();
const [appTheme, setAppTheme] = useAtom(appThemeAtom);
const { editorTheme } = useHandleAppTheme();
const [langCode, setLangCode] = useAppLangCode();
@ -486,7 +481,7 @@ const ExcalidrawWrapper = () => {
excalidrawAPI.updateScene({
...data.scene,
...restore(data.scene, null, null, { repairBindings: true }),
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
storeAction: StoreAction.CAPTURE,
});
}
});
@ -513,7 +508,7 @@ const ExcalidrawWrapper = () => {
setLangCode(getPreferredLanguage());
excalidrawAPI.updateScene({
...localDataState,
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
LibraryIndexedDBAdapter.load().then((data) => {
if (data) {
@ -645,7 +640,7 @@ const ExcalidrawWrapper = () => {
if (didChange) {
excalidrawAPI.updateScene({
elements,
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
}
}
@ -852,12 +847,6 @@ const ExcalidrawWrapper = () => {
</div>
);
}}
onLinkOpen={(element, event) => {
if (element.link && isElementLink(element.link)) {
event.preventDefault();
excalidrawAPI?.scrollToContent(element.link, { animate: true });
}
}}
>
<AppMainMenu
onCollabDialogOpen={onCollabDialogOpen}
@ -1136,15 +1125,9 @@ const ExcalidrawWrapper = () => {
};
const ExcalidrawApp = () => {
const isCloudExportWindow =
window.location.pathname === "/excalidraw-plus-export";
if (isCloudExportWindow) {
return <ExcalidrawPlusIframeExport />;
}
return (
<TopErrorBoundary>
<Provider store={appJotaiStore}>
<Provider unstable_createStore={() => appJotaiStore}>
<ExcalidrawWrapper />
</Provider>
</TopErrorBoundary>

View File

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

View File

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

View File

@ -1,37 +1,3 @@
// eslint-disable-next-line no-restricted-imports
import {
atom,
Provider,
useAtom,
useAtomValue,
useSetAtom,
createStore,
type PrimitiveAtom,
} from "jotai";
import { useLayoutEffect } from "react";
import { unstable_createStore } from "jotai";
export const appJotaiStore = createStore();
export { atom, Provider, useAtom, useAtomValue, useSetAtom };
export const useAtomWithInitialValue = <
T extends unknown,
A extends PrimitiveAtom<T>,
>(
atom: A,
initialValue: T | (() => T),
) => {
const [value, setValue] = useAtom(atom);
useLayoutEffect(() => {
if (typeof initialValue === "function") {
// @ts-ignore
setValue(initialValue());
} else {
setValue(initialValue);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [value, setValue] as const;
};
export const appJotaiStore = unstable_createStore();

View File

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

View File

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

View File

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

View File

@ -1,34 +1,31 @@
import throttle from "lodash.throttle";
import { PureComponent } from "react";
import type {
BinaryFileData,
ExcalidrawImperativeAPI,
SocketId,
Collaborator,
Gesture,
} from "@excalidraw/excalidraw/types";
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "@excalidraw/excalidraw/constants";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
} from "../../packages/excalidraw/types";
import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import type {
ExcalidrawElement,
FileId,
InitializedExcalidrawImageElement,
OrderedExcalidrawElement,
} from "@excalidraw/excalidraw/element/types";
} from "../../packages/excalidraw/element/types";
import {
CaptureUpdateAction,
StoreAction,
getSceneVersion,
restoreElements,
zoomToFitBounds,
reconcileElements,
} from "@excalidraw/excalidraw";
} from "../../packages/excalidraw";
import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
import {
assertNever,
preventUnload,
resolvablePromise,
throttleRAF,
} from "@excalidraw/excalidraw/utils";
} from "../../packages/excalidraw/utils";
import {
CURSOR_SYNC_TIMEOUT,
FILE_UPLOAD_MAX_BYTES,
@ -60,35 +57,36 @@ import {
saveUsernameToLocalStorage,
} from "../data/localStorage";
import Portal from "./Portal";
import { t } from "@excalidraw/excalidraw/i18n";
import { t } from "../../packages/excalidraw/i18n";
import { UserIdleState } from "../../packages/excalidraw/types";
import {
IDLE_THRESHOLD,
ACTIVE_THRESHOLD,
UserIdleState,
} from "@excalidraw/excalidraw/constants";
} from "../../packages/excalidraw/constants";
import {
encodeFilesForUpload,
FileManager,
updateStaleImageStatuses,
} from "../data/FileManager";
import { AbortError } from "@excalidraw/excalidraw/errors";
import { AbortError } from "../../packages/excalidraw/errors";
import {
isImageElement,
isInitializedImageElement,
} from "@excalidraw/excalidraw/element/typeChecks";
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
import { decryptData } from "@excalidraw/excalidraw/data/encryption";
} from "../../packages/excalidraw/element/typeChecks";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { decryptData } from "../../packages/excalidraw/data/encryption";
import { resetBrowserStateVersions } from "../data/tabSync";
import { LocalData } from "../data/LocalData";
import { appJotaiStore, atom } from "../app-jotai";
import type { Mutable, ValueOf } from "@excalidraw/excalidraw/utility-types";
import { getVisibleSceneBounds } from "@excalidraw/excalidraw/element/bounds";
import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
import { atom } from "jotai";
import { appJotaiStore } from "../app-jotai";
import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
import { collabErrorIndicatorAtom } from "./CollabError";
import type {
ReconciledExcalidrawElement,
RemoteExcalidrawElement,
} from "@excalidraw/excalidraw/data/reconcile";
} from "../../packages/excalidraw/data/reconcile";
export const collabAPIAtom = atom<CollabAPI | null>(null);
export const isCollaboratingAtom = atom(false);
@ -159,7 +157,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
throw new AbortError();
}
const { savedFiles, erroredFiles } = await saveFilesToFirebase({
return saveFilesToFirebase({
prefix: `${FIREBASE_STORAGE_PREFIXES.collabFiles}/${roomId}`,
files: await encodeFilesForUpload({
files: addedFiles,
@ -167,29 +165,6 @@ class Collab extends PureComponent<CollabProps, CollabState> {
maxBytes: FILE_UPLOAD_MAX_BYTES,
}),
});
return {
savedFiles: savedFiles.reduce(
(acc: Map<FileId, BinaryFileData>, id) => {
const fileData = addedFiles.get(id);
if (fileData) {
acc.set(id, fileData);
}
return acc;
},
new Map(),
),
erroredFiles: erroredFiles.reduce(
(acc: Map<FileId, BinaryFileData>, id) => {
const fileData = addedFiles.get(id);
if (fileData) {
acc.set(id, fileData);
}
return acc;
},
new Map(),
),
};
},
});
this.excalidrawAPI = props.excalidrawAPI;
@ -384,7 +359,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
this.excalidrawAPI.updateScene({
elements,
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
}
};
@ -419,7 +394,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
.filter((element) => {
return (
isInitializedImageElement(element) &&
!this.fileManager.isFileTracked(element.fileId) &&
!this.fileManager.isFileHandled(element.fileId) &&
!element.isDeleted &&
(opts.forceFetchFiles
? element.status !== "pending" ||
@ -535,7 +510,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
// to database even if deleted before creating the room.
this.excalidrawAPI.updateScene({
elements,
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
this.saveCollabRoomToFirebase(getSyncableElements(elements));
@ -773,7 +748,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
) => {
this.excalidrawAPI.updateScene({
elements,
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
this.loadImageFiles();

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import type { ExcalidrawImperativeAPI } from "../../packages/excalidraw/types";
import {
DiagramToCodePlugin,
exportToBlob,
getTextFromElements,
MIME_TYPES,
TTDDialog,
} from "@excalidraw/excalidraw";
import { getDataURL } from "@excalidraw/excalidraw/data/blob";
import { safelyParseJSON } from "@excalidraw/excalidraw/utils";
} from "../../packages/excalidraw";
import { getDataURL } from "../../packages/excalidraw/data/blob";
import { safelyParseJSON } from "../../packages/excalidraw/utils";
export const AIComponents = ({
excalidrawAPI,

View File

@ -1,5 +1,5 @@
import React from "react";
import { Footer } from "@excalidraw/excalidraw/index";
import { Footer } from "../../packages/excalidraw/index";
import { EncryptedIcon } from "./EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
import { isExcalidrawPlusSignedUser } from "../app_constants";

View File

@ -3,9 +3,9 @@ import {
loginIcon,
ExcalLogo,
eyeIcon,
} from "@excalidraw/excalidraw/components/icons";
import type { Theme } from "@excalidraw/excalidraw/element/types";
import { MainMenu } from "@excalidraw/excalidraw/index";
} from "../../packages/excalidraw/components/icons";
import type { Theme } from "../../packages/excalidraw/element/types";
import { MainMenu } from "../../packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants";
import { LanguageList } from "../app-language/LanguageList";
import { saveDebugState } from "./DebugCanvas";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,41 +1,29 @@
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
import { compressData } from "@excalidraw/excalidraw/data/encode";
import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
import { StoreAction } from "../../packages/excalidraw";
import { compressData } from "../../packages/excalidraw/data/encode";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
import type {
ExcalidrawElement,
ExcalidrawImageElement,
FileId,
InitializedExcalidrawImageElement,
} from "@excalidraw/excalidraw/element/types";
import { t } from "@excalidraw/excalidraw/i18n";
} from "../../packages/excalidraw/element/types";
import { t } from "../../packages/excalidraw/i18n";
import type {
BinaryFileData,
BinaryFileMetadata,
ExcalidrawImperativeAPI,
BinaryFiles,
} from "@excalidraw/excalidraw/types";
type FileVersion = Required<BinaryFileData>["version"];
} from "../../packages/excalidraw/types";
export class FileManager {
/** files being fetched */
private fetchingFiles = new Map<ExcalidrawImageElement["fileId"], true>();
private erroredFiles_fetch = new Map<
ExcalidrawImageElement["fileId"],
true
>();
/** files being saved */
private savingFiles = new Map<
ExcalidrawImageElement["fileId"],
FileVersion
>();
private savingFiles = new Map<ExcalidrawImageElement["fileId"], true>();
/* files already saved to persistent storage */
private savedFiles = new Map<ExcalidrawImageElement["fileId"], FileVersion>();
private erroredFiles_save = new Map<
ExcalidrawImageElement["fileId"],
FileVersion
>();
private savedFiles = new Map<ExcalidrawImageElement["fileId"], true>();
private erroredFiles = new Map<ExcalidrawImageElement["fileId"], true>();
private _getFiles;
private _saveFiles;
@ -49,8 +37,8 @@ export class FileManager {
erroredFiles: Map<FileId, true>;
}>;
saveFiles: (data: { addedFiles: Map<FileId, BinaryFileData> }) => Promise<{
savedFiles: Map<FileId, BinaryFileData>;
erroredFiles: Map<FileId, BinaryFileData>;
savedFiles: Map<FileId, true>;
erroredFiles: Map<FileId, true>;
}>;
}) {
this._getFiles = getFiles;
@ -58,28 +46,19 @@ export class FileManager {
}
/**
* returns whether file is saved/errored, or being processed
* returns whether file is already saved or being processed
*/
isFileTracked = (id: FileId) => {
isFileHandled = (id: FileId) => {
return (
this.savedFiles.has(id) ||
this.savingFiles.has(id) ||
this.fetchingFiles.has(id) ||
this.erroredFiles_fetch.has(id) ||
this.erroredFiles_save.has(id)
this.savingFiles.has(id) ||
this.erroredFiles.has(id)
);
};
isFileSavedOrBeingSaved = (file: BinaryFileData) => {
const fileVersion = this.getFileVersion(file);
return (
this.savedFiles.get(file.id) === fileVersion ||
this.savingFiles.get(file.id) === fileVersion
);
};
getFileVersion = (file: BinaryFileData) => {
return file.version ?? 1;
isFileSaved = (id: FileId) => {
return this.savedFiles.has(id);
};
saveFiles = async ({
@ -92,16 +71,13 @@ export class FileManager {
const addedFiles: Map<FileId, BinaryFileData> = new Map();
for (const element of elements) {
const fileData =
isInitializedImageElement(element) && files[element.fileId];
if (
fileData &&
// NOTE if errored during save, won't retry due to this check
!this.isFileSavedOrBeingSaved(fileData)
isInitializedImageElement(element) &&
files[element.fileId] &&
!this.isFileHandled(element.fileId)
) {
addedFiles.set(element.fileId, files[element.fileId]);
this.savingFiles.set(element.fileId, this.getFileVersion(fileData));
this.savingFiles.set(element.fileId, true);
}
}
@ -110,12 +86,8 @@ export class FileManager {
addedFiles,
});
for (const [fileId, fileData] of savedFiles) {
this.savedFiles.set(fileId, this.getFileVersion(fileData));
}
for (const [fileId, fileData] of erroredFiles) {
this.erroredFiles_save.set(fileId, this.getFileVersion(fileData));
for (const [fileId] of savedFiles) {
this.savedFiles.set(fileId, true);
}
return {
@ -149,10 +121,10 @@ export class FileManager {
const { loadedFiles, erroredFiles } = await this._getFiles(ids);
for (const file of loadedFiles) {
this.savedFiles.set(file.id, this.getFileVersion(file));
this.savedFiles.set(file.id, true);
}
for (const [fileId] of erroredFiles) {
this.erroredFiles_fetch.set(fileId, true);
this.erroredFiles.set(fileId, true);
}
return { loadedFiles, erroredFiles };
@ -188,7 +160,7 @@ export class FileManager {
): element is InitializedExcalidrawImageElement => {
return (
isInitializedImageElement(element) &&
this.savedFiles.has(element.fileId) &&
this.isFileSaved(element.fileId) &&
element.status === "pending"
);
};
@ -197,8 +169,7 @@ export class FileManager {
this.fetchingFiles.clear();
this.savingFiles.clear();
this.savedFiles.clear();
this.erroredFiles_fetch.clear();
this.erroredFiles_save.clear();
this.erroredFiles.clear();
}
}
@ -268,6 +239,6 @@ export const updateStaleImageStatuses = (params: {
}
return element;
}),
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
};

View File

@ -19,25 +19,25 @@ import {
setMany,
get,
} from "idb-keyval";
import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
import {
CANVAS_SEARCH_TAB,
DEFAULT_SIDEBAR,
} from "@excalidraw/excalidraw/constants";
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
import { clearElementsForLocalStorage } from "@excalidraw/excalidraw/element";
} from "../../packages/excalidraw/constants";
import type { LibraryPersistedData } from "../../packages/excalidraw/data/library";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
import type {
ExcalidrawElement,
FileId,
} from "@excalidraw/excalidraw/element/types";
} from "../../packages/excalidraw/element/types";
import type {
AppState,
BinaryFileData,
BinaryFiles,
} from "@excalidraw/excalidraw/types";
import type { MaybePromise } from "@excalidraw/excalidraw/utility-types";
import { debounce } from "@excalidraw/excalidraw/utils";
} from "../../packages/excalidraw/types";
import type { MaybePromise } from "../../packages/excalidraw/utility-types";
import { debounce } from "../../packages/excalidraw/utils";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
import { FileManager } from "./FileManager";
import { Locker } from "./Locker";
@ -183,8 +183,8 @@ export class LocalData {
);
},
async saveFiles({ addedFiles }) {
const savedFiles = new Map<FileId, BinaryFileData>();
const erroredFiles = new Map<FileId, BinaryFileData>();
const savedFiles = new Map<FileId, true>();
const erroredFiles = new Map<FileId, true>();
// before we use `storage` event synchronization, let's update the flag
// optimistically. Hopefully nothing fails, and an IDB read executed
@ -195,10 +195,10 @@ export class LocalData {
[...addedFiles].map(async ([id, fileData]) => {
try {
await set(id, fileData, filesStore);
savedFiles.set(id, fileData);
savedFiles.set(id, true);
} catch (error: any) {
console.error(error);
erroredFiles.set(id, fileData);
erroredFiles.set(id, true);
}
}),
);

View File

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

View File

@ -1,33 +1,33 @@
import {
compressData,
decompressData,
} from "@excalidraw/excalidraw/data/encode";
} from "../../packages/excalidraw/data/encode";
import {
decryptData,
generateEncryptionKey,
IV_LENGTH_BYTES,
} from "@excalidraw/excalidraw/data/encryption";
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
import { restore } from "@excalidraw/excalidraw/data/restore";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
import type { SceneBounds } from "@excalidraw/excalidraw/element/bounds";
import { isInvisiblySmallElement } from "@excalidraw/excalidraw/element/sizeHelpers";
import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
} from "../../packages/excalidraw/data/encryption";
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
import { restore } from "../../packages/excalidraw/data/restore";
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import type { SceneBounds } from "../../packages/excalidraw/element/bounds";
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
import type {
ExcalidrawElement,
FileId,
OrderedExcalidrawElement,
} from "@excalidraw/excalidraw/element/types";
import { t } from "@excalidraw/excalidraw/i18n";
} from "../../packages/excalidraw/element/types";
import { t } from "../../packages/excalidraw/i18n";
import type {
AppState,
BinaryFileData,
BinaryFiles,
SocketId,
} from "@excalidraw/excalidraw/types";
import type { UserIdleState } from "@excalidraw/excalidraw/constants";
import type { MakeBrand } from "@excalidraw/excalidraw/utility-types";
import { bytesToHexString } from "@excalidraw/excalidraw/utils";
UserIdleState,
} from "../../packages/excalidraw/types";
import type { MakeBrand } from "../../packages/excalidraw/utility-types";
import { bytesToHexString } from "../../packages/excalidraw/utils";
import type { WS_SUBTYPES } from "../app_constants";
import {
DELETED_ELEMENT_TIMEOUT,

View File

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

View File

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

View File

@ -54,7 +54,11 @@
content="https://excalidraw.com/og-image-3.png"
/>
<link rel="canonical" href="https://excalidraw.com" />
<!-- General tags -->
<meta
name="description"
content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
/>
<!------------------------------------------------------------------------->
<!-- to minimize white flash on load when user has dark mode enabled -->
@ -120,6 +124,12 @@
<!-- Following placeholder is replaced during the build step -->
<!-- PLACEHOLDER:EXCALIDRAW_APP_FONTS -->
<% } else { %>
<script>
window.EXCALIDRAW_ASSET_PATH = window.origin;
</script>
<% } %>
<!-- Register Assistant as the UI font, before the scene inits -->
<link
rel="stylesheet"
@ -127,12 +137,6 @@
type="text/css"
/>
<% } else { %>
<script>
window.EXCALIDRAW_ASSET_PATH = window.origin;
</script>
<% } %>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
@ -214,7 +218,6 @@
</header>
<div id="root"></div>
<script type="module" src="index.tsx"></script>
<% if (typeof PROD != 'undefined' && PROD == true) { %>
<!-- 100% privacy friendly analytics -->
<script>
// need to load this script dynamically bcs. of iframe embed tracking
@ -247,6 +250,5 @@
}
</script>
<!-- end LEGACY GOOGLE ANALYTICS -->
<% } %>
</body>
</html>

View File

@ -23,18 +23,18 @@
]
},
"engines": {
"node": "18.0.0 - 22.x.x"
"node": ">=18.0.0"
},
"dependencies": {
"@excalidraw/random-username": "1.0.0",
"@sentry/browser": "9.0.1",
"callsites": "4.2.0",
"firebase": "11.3.1",
"@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5",
"firebase": "8.3.3",
"i18next-browser-languagedetector": "6.1.4",
"idb-keyval": "6.0.3",
"jotai": "2.11.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"jotai": "1.13.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"socket.io-client": "4.7.2",
"vite-plugin-html": "3.2.2"
},

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,17 @@
import { vi } from "vitest";
import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
import {
act,
render,
waitFor,
} from "../../packages/excalidraw/tests/test-utils";
import ExcalidrawApp from "../App";
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
import { syncInvalidIndices } from "@excalidraw/excalidraw/fractionalIndex";
import { API } from "../../packages/excalidraw/tests/helpers/api";
import { syncInvalidIndices } from "../../packages/excalidraw/fractionalIndex";
import {
createRedoAction,
createUndoAction,
} from "@excalidraw/excalidraw/actions/actionHistory";
import { CaptureUpdateAction, newElementWith } from "@excalidraw/excalidraw";
} from "../../packages/excalidraw/actions/actionHistory";
import { StoreAction, newElementWith } from "../../packages/excalidraw";
const { h } = window;
@ -85,7 +89,7 @@ describe("collaboration", () => {
API.updateScene({
elements: syncInvalidIndices([rect1, rect2]),
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
storeAction: StoreAction.CAPTURE,
});
API.updateScene({
@ -93,7 +97,7 @@ describe("collaboration", () => {
rect1,
newElementWith(h.elements[1], { isDeleted: true }),
]),
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
storeAction: StoreAction.CAPTURE,
});
await waitFor(() => {
@ -140,7 +144,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
await waitFor(() => {
@ -178,7 +182,7 @@ describe("collaboration", () => {
h.elements[0],
newElementWith(h.elements[1], { x: 100 }),
]),
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
storeAction: StoreAction.CAPTURE,
});
await waitFor(() => {
@ -213,7 +217,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
captureUpdate: CaptureUpdateAction.NEVER,
storeAction: StoreAction.UPDATE,
});
// snapshot was correctly updated and marked the element as deleted

View File

@ -1,22 +1,23 @@
import { atom, useAtom } from "jotai";
import { useEffect, useLayoutEffect, useState } from "react";
import { THEME } from "@excalidraw/excalidraw";
import { EVENT } from "@excalidraw/excalidraw/constants";
import type { Theme } from "@excalidraw/excalidraw/element/types";
import { CODES, KEYS } from "@excalidraw/excalidraw/keys";
import { THEME } from "../packages/excalidraw";
import { EVENT } from "../packages/excalidraw/constants";
import type { Theme } from "../packages/excalidraw/element/types";
import { CODES, KEYS } from "../packages/excalidraw/keys";
import { STORAGE_KEYS } from "./app_constants";
export const appThemeAtom = atom<Theme | "system">(
(localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as
| Theme
| "system"
| null) || THEME.LIGHT,
);
const getDarkThemeMediaQuery = (): MediaQueryList | undefined =>
window.matchMedia?.("(prefers-color-scheme: dark)");
export const useHandleAppTheme = () => {
const [appTheme, setAppTheme] = useState<Theme | "system">(() => {
return (
(localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as
| Theme
| "system"
| null) || THEME.LIGHT
);
});
const [appTheme, setAppTheme] = useAtom(appThemeAtom);
const [editorTheme, setEditorTheme] = useState<Theme>(THEME.LIGHT);
useEffect(() => {
@ -65,5 +66,5 @@ export const useHandleAppTheme = () => {
}
}, [appTheme]);
return { editorTheme, appTheme, setAppTheme };
return { editorTheme };
};

View File

@ -29,9 +29,6 @@ interface ImportMetaEnv {
// Enable eslint in dev server
VITE_APP_ENABLE_ESLINT: string;
// Enable PWA in dev server
VITE_APP_ENABLE_PWA: string;
VITE_APP_PLUS_LP: string;
VITE_APP_PLUS_APP: string;

View File

@ -1,4 +1,3 @@
import path from "path";
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import svgrPlugin from "vite-plugin-svgr";
@ -8,262 +7,232 @@ import checker from "vite-plugin-checker";
import { createHtmlPlugin } from "vite-plugin-html";
import Sitemap from "vite-plugin-sitemap";
import { woff2BrowserPlugin } from "../scripts/woff2/woff2-vite-plugins";
export default defineConfig(({ mode }) => {
// To load .env variables
const envVars = loadEnv(mode, `../`);
// https://vitejs.dev/config/
return {
server: {
port: Number(envVars.VITE_APP_PORT || 3000),
// open the browser
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: "../",
resolve: {
alias: [
{
find: /^@excalidraw\/excalidraw$/,
replacement: path.resolve(__dirname, "../packages/excalidraw/index.tsx"),
},
{
find: /^@excalidraw\/excalidraw\/(.*?)/,
replacement: path.resolve(__dirname, "../packages/excalidraw/$1"),
},
{
find: /^@excalidraw\/utils$/,
replacement: path.resolve(__dirname, "../packages/utils/index.ts"),
},
{
find: /^@excalidraw\/utils\/(.*?)/,
replacement: path.resolve(__dirname, "../packages/utils/$1"),
},
{
find: /^@excalidraw\/math$/,
replacement: path.resolve(__dirname, "../packages/math/index.ts"),
},
{
find: /^@excalidraw\/math\/(.*?)/,
replacement: path.resolve(__dirname, "../packages/math/$1"),
},
],
},
build: {
outDir: "build",
rollupOptions: {
output: {
assetFileNames(chunkInfo) {
if (chunkInfo?.name?.endsWith(".woff2")) {
const family = chunkInfo.name.split("-")[0];
return `fonts/${family}/[name][extname]`;
}
return "assets/[name]-[hash][extname]";
},
// Creating separate chunk for locales except for en and percentages.json so they
// can be cached at runtime and not merged with
// app precache. en.json and percentages.json are needed for first load
// 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) {
if (
id.includes("packages/excalidraw/locales") &&
id.match(/en.json|percentages.json/) === null
) {
const index = id.indexOf("locales/");
// Taking the substring after "locales/"
return `locales/${id.substring(index + 8)}`;
}
},
// To load .env.local variables
const envVars = loadEnv("", `../`);
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: Number(envVars.VITE_APP_PORT || 3000),
// open the browser
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: {
outDir: "build",
rollupOptions: {
output: {
assetFileNames(chunkInfo) {
if (chunkInfo?.name?.endsWith(".woff2")) {
const family = chunkInfo.name.split("-")[0];
return `fonts/${family}/[name][extname]`;
}
return "assets/[name]-[hash][extname]";
},
// Creating separate chunk for locales except for en and percentages.json so they
// can be cached at runtime and not merged with
// app precache. en.json and percentages.json are needed for first load
// 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) {
if (
id.includes("packages/excalidraw/locales") &&
id.match(/en.json|percentages.json/) === null
) {
const index = id.indexOf("locales/");
// Taking the substring after "locales/"
return `locales/${id.substring(index + 8)}`;
}
},
},
sourcemap: true,
// don't auto-inline small assets (i.e. fonts hosted on CDN)
assetsInlineLimit: 0,
},
plugins: [
Sitemap({
hostname: "https://excalidraw.com",
outDir: "build",
changefreq: "monthly",
// its static in public folder
generateRobotsTxt: false,
}),
woff2BrowserPlugin(),
react(),
checker({
typescript: true,
eslint:
envVars.VITE_APP_ENABLE_ESLINT === "false"
? undefined
: { lintCommand: 'eslint "./**/*.{js,ts,tsx}"' },
overlay: {
initialIsOpen: envVars.VITE_APP_COLLAPSE_OVERLAY === "false",
badgeStyle: "margin-bottom: 4rem; margin-left: 1rem",
},
}),
svgrPlugin(),
ViteEjsPlugin(),
VitePWA({
registerType: "autoUpdate",
devOptions: {
/* set this flag to true to enable in Development mode */
enabled: envVars.VITE_APP_ENABLE_PWA === "true",
},
sourcemap: true,
// don't auto-inline small assets (i.e. fonts hosted on CDN)
assetsInlineLimit: 0,
},
plugins: [
Sitemap({
hostname: "https://excalidraw.com",
outDir: "build",
changefreq: "monthly",
// its static in public folder
generateRobotsTxt: false,
}),
woff2BrowserPlugin(),
react(),
checker({
typescript: true,
eslint:
envVars.VITE_APP_ENABLE_ESLINT === "false"
? undefined
: { lintCommand: 'eslint "./**/*.{js,ts,tsx}"' },
overlay: {
initialIsOpen: envVars.VITE_APP_COLLAPSE_OVERLAY === "false",
badgeStyle: "margin-bottom: 4rem; margin-left: 1rem",
},
}),
svgrPlugin(),
ViteEjsPlugin(),
VitePWA({
registerType: "autoUpdate",
devOptions: {
/* set this flag to true to enable in Development mode */
enabled: false,
},
workbox: {
// don't precache fonts, locales and separate chunks
globIgnores: [
"fonts.css",
"**/locales/**",
"service-worker.js",
"**/*.chunk-*.js",
],
runtimeCaching: [
{
urlPattern: new RegExp(".+.woff2"),
handler: "CacheFirst",
options: {
cacheName: "fonts",
expiration: {
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 90, // 90 days
},
cacheableResponse: {
// 0 to cache "opaque" responses from cross-origin requests (i.e. CDN)
statuses: [0, 200],
},
workbox: {
// don't precache fonts, locales and separate chunks
globIgnores: [
"fonts.css",
"**/locales/**",
"service-worker.js",
"**/*.chunk-*.js",
],
runtimeCaching: [
{
urlPattern: new RegExp(".+.woff2"),
handler: "CacheFirst",
options: {
cacheName: "fonts",
expiration: {
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 90, // 90 days
},
},
{
urlPattern: new RegExp("fonts.css"),
handler: "StaleWhileRevalidate",
options: {
cacheName: "fonts",
expiration: {
maxEntries: 50,
},
cacheableResponse: {
// 0 to cache "opaque" responses from cross-origin requests (i.e. CDN)
statuses: [0, 200],
},
},
{
urlPattern: new RegExp("locales/[^/]+.js"),
handler: "CacheFirst",
options: {
cacheName: "locales",
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 30 days
},
},
},
{
urlPattern: new RegExp(".chunk-.+.js"),
handler: "CacheFirst",
options: {
cacheName: "chunk",
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 90, // <== 90 days
},
},
},
],
},
manifest: {
short_name: "Excalidraw",
name: "Excalidraw",
description:
"Excalidraw is a whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them.",
icons: [
{
src: "android-chrome-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "apple-touch-icon.png",
type: "image/png",
sizes: "180x180",
},
{
src: "favicon-32x32.png",
sizes: "32x32",
type: "image/png",
},
{
src: "favicon-16x16.png",
sizes: "16x16",
type: "image/png",
},
],
start_url: "/",
id:"excalidraw",
display: "standalone",
theme_color: "#121212",
background_color: "#ffffff",
file_handlers: [
{
action: "/",
accept: {
"application/vnd.excalidraw+json": [".excalidraw"],
},
},
],
share_target: {
action: "/web-share-target",
method: "POST",
enctype: "multipart/form-data",
params: {
files: [
{
name: "file",
accept: [
"application/vnd.excalidraw+json",
"application/json",
".excalidraw",
],
},
],
},
},
screenshots: [
{
src: "/screenshots/virtual-whiteboard.png",
type: "image/png",
sizes: "462x945",
{
urlPattern: new RegExp("fonts.css"),
handler: "StaleWhileRevalidate",
options: {
cacheName: "fonts",
expiration: {
maxEntries: 50,
},
},
{
src: "/screenshots/wireframe.png",
type: "image/png",
sizes: "462x945",
},
{
urlPattern: new RegExp("locales/[^/]+.js"),
handler: "CacheFirst",
options: {
cacheName: "locales",
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 30 days
},
},
{
src: "/screenshots/illustration.png",
type: "image/png",
sizes: "462x945",
},
{
urlPattern: new RegExp(".chunk-.+.js"),
handler: "CacheFirst",
options: {
cacheName: "chunk",
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 90, // <== 90 days
},
},
{
src: "/screenshots/shapes.png",
type: "image/png",
sizes: "462x945",
},
],
},
manifest: {
short_name: "Excalidraw",
name: "Excalidraw",
description:
"Excalidraw is a whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them.",
icons: [
{
src: "android-chrome-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "apple-touch-icon.png",
type: "image/png",
sizes: "180x180",
},
{
src: "favicon-32x32.png",
sizes: "32x32",
type: "image/png",
},
{
src: "favicon-16x16.png",
sizes: "16x16",
type: "image/png",
},
],
start_url: "/",
display: "standalone",
theme_color: "#121212",
background_color: "#ffffff",
file_handlers: [
{
action: "/",
accept: {
"application/vnd.excalidraw+json": [".excalidraw"],
},
{
src: "/screenshots/collaboration.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/export.png",
type: "image/png",
sizes: "462x945",
},
],
},
],
share_target: {
action: "/web-share-target",
method: "POST",
enctype: "multipart/form-data",
params: {
files: [
{
name: "file",
accept: [
"application/vnd.excalidraw+json",
"application/json",
".excalidraw",
],
},
],
},
},
}),
createHtmlPlugin({
minify: true,
}),
],
publicDir: "../public",
};
screenshots: [
{
src: "/screenshots/virtual-whiteboard.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/wireframe.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/illustration.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/shapes.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/collaboration.png",
type: "image/png",
sizes: "462x945",
},
{
src: "/screenshots/export.png",
type: "image/png",
sizes: "462x945",
},
],
},
}),
createHtmlPlugin({
minify: true,
}),
],
publicDir: "../public",
});

View File

@ -7,20 +7,21 @@
"packages/excalidraw",
"packages/utils",
"packages/math",
"examples/*"
"examples/excalidraw",
"examples/excalidraw/*"
],
"devDependencies": {
"@babel/preset-env": "7.26.9",
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"@excalidraw/eslint-config": "1.0.3",
"@excalidraw/prettier-config": "1.0.2",
"@types/chai": "4.3.0",
"@types/jest": "27.4.0",
"@types/lodash.throttle": "4.1.7",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"@types/socket.io-client": "3.0.0",
"@vitejs/plugin-react": "3.1.0",
"@vitest/coverage-v8": "3.0.7",
"@vitest/coverage-v8": "2.0.5",
"@vitest/ui": "2.0.5",
"chai": "4.3.6",
"dotenv": "16.0.1",
@ -38,13 +39,13 @@
"vite": "5.0.12",
"vite-plugin-checker": "0.7.2",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-pwa": "0.21.1",
"vite-plugin-pwa": "0.17.4",
"vite-plugin-svgr": "4.2.0",
"vitest": "3.0.6",
"vitest": "2.0.5",
"vitest-canvas-mock": "0.3.3"
},
"engines": {
"node": "18.0.0 - 22.x.x"
"node": "18.0.0 - 20.x.x"
},
"homepage": ".",
"prettier": "@excalidraw/prettier-config",
@ -52,13 +53,17 @@
"build-node": "node ./scripts/build-node.js",
"build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
"build:app": "yarn --cwd ./excalidraw-app build:app",
"build:package": "yarn --cwd ./packages/excalidraw build:esm",
"build:version": "yarn --cwd ./excalidraw-app build:version",
"build": "yarn --cwd ./excalidraw-app build",
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
"fix:code": "yarn test:code --fix",
"fix:other": "yarn prettier --write",
"fix": "yarn fix:other && yarn fix:code",
"locales-coverage": "node scripts/build-locales-coverage.js",
"locales-coverage:description": "node scripts/locales-coverage-description.js",
"prepare": "husky install",
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"start": "yarn --cwd ./excalidraw-app start",
"start:production": "yarn --cwd ./excalidraw-app start:production",
"start:example": "yarn build:package && yarn --cwd ./examples/with-script-in-browser start",
"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:app": "vitest",
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
@ -69,21 +74,16 @@
"test:coverage": "vitest --coverage",
"test:coverage:watch": "vitest --coverage --watch",
"test:ui": "yarn test --ui --coverage.enabled=true",
"fix:code": "yarn test:code --fix",
"fix:other": "yarn prettier --write",
"fix": "yarn fix:other && yarn fix:code",
"locales-coverage": "node scripts/build-locales-coverage.js",
"locales-coverage:description": "node scripts/locales-coverage-description.js",
"prepare": "husky install",
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"autorelease": "node scripts/autorelease.js",
"prerelease:excalidraw": "node scripts/prerelease.js",
"build:preview": "yarn build && vite preview --port 5000",
"release:excalidraw": "node scripts/release.js",
"rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/{build,dist}",
"rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/*/{build,dist}",
"rm:node_modules": "rm -rf node_modules && rm -rf excalidraw-app/node_modules && rm -rf packages/*/node_modules",
"clean-install": "yarn rm:node_modules && yarn install"
},
"resolutions": {
"@types/react": "18.2.0",
"strip-ansi": "6.0.1"
}
}

View File

@ -11,715 +11,85 @@ 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.
-->
## Excalidraw Library
## 18.0.0 (2025-02-28)
### Highlights
- Command palette [#7804](https://github.com/excalidraw/excalidraw/pull/7804)
- Multiplayer undo / redo [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
- Editable element stats [#6382](https://github.com/excalidraw/excalidraw/pull/6382)
- Text element wrapping [#7999](https://github.com/excalidraw/excalidraw/pull/7999)
- Font picker with more fonts [#8012](https://github.com/excalidraw/excalidraw/pull/8012)
- Font for Chinese, Japanese and Korean [#8530](https://github.com/excalidraw/excalidraw/pull/8530)
- Font subsetting for SVG export [#8384](https://github.com/excalidraw/excalidraw/pull/8384)
- Elbow arrows [#8299](https://github.com/excalidraw/excalidraw/pull/8299), [#8952](https://github.com/excalidraw/excalidraw/pull/8952)
- Flowcharts [#8329](https://github.com/excalidraw/excalidraw/pull/8329)
- Scene search [#8438](https://github.com/excalidraw/excalidraw/pull/8438)
- Image cropping [#8613](https://github.com/excalidraw/excalidraw/pull/8613)
- Element linking [#8812](https://github.com/excalidraw/excalidraw/pull/8812)
### Breaking changes
#### Deprecated UMD bundle in favor of ES modules [#7441](https://github.com/excalidraw/excalidraw/pull/7441), [#9127](https://github.com/excalidraw/excalidraw/pull/9127)
We've transitioned from `UMD` to `ESM` bundle format. Our new `dist` bundles inside `@excalidraw/excalidraw` package now contain only bundled source files, making any dependencies tree-shakable. The npm package comes with the following structure:
> **Note**: The structure is simplified for the sake of brevity, omitting lazy-loadable modules, including locales (previously treated as json assets) and source maps in the development bundle.
```
@excalidraw/excalidraw/
├── dist/
│ ├── dev/
│ │ ├── fonts/
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── index.js.map
│ ├── prod/
│ │ ├── fonts/
│ │ ├── index.css
│ │ ├── index.js
│ └── types/
```
##### JavaScript: required `"type": "module"` in package.json
Make sure that your JavaScript environment supports ES modules, as it might be required to define `"type": "module"` in your `package.json` file or as part of the `<script type="module" />` attribute.
##### Typescript: deprecated "moduleResolution": `"node"` or `"node10"`
Since `"node"` and `"node10"` do not support `package.json` `"exports"` fields, having these values in your `tsconfig.json` will not work. Instead, use `"bundler"`, `"node16"` or `"nodenext"` values. For more information, see [Typescript's documentation](https://www.typescriptlang.org/tsconfig/#moduleResolution).
##### New structure of the imports
Dependening on the environment, this is how imports should look like with the `ESM`:
**With bundler (Vite, Next.js, etc.)**
```ts
// excalidraw library with public API
import * as excalidrawLib from "@excalidraw/excalidraw";
// excalidraw react component
import { Excalidraw } from "@excalidraw/excalidraw";
// excalidraw styles, usually auto-processed by the build tool (i.e. vite, next, etc.)
import "@excalidraw/excalidraw/index.css";
// excalidraw types (optional)
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
```
**Without bundler (Browser)**
```html
<!-- Environment: browser with a script tag and no bundler -->
<!-- excalidraw styles -->
<link
rel="stylesheet"
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
/>
<!-- import maps used for deduplicating react & react-dom versions -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.0.0",
"react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.0.0"
}
}
</script>
<script type="module">
import React from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0";
import * as ExcalidrawLib from "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.js?external=react,react-dom";
</script>
```
#### Deprecated `excalidraw-assets` and `excalidraw-assets-dev` folders [#8012](https://github.com/excalidraw/excalidraw/pull/8012), [#9127](https://github.com/excalidraw/excalidraw/pull/9127)
The `excalidraw-assets` and `excalidraw-assets-dev` folders, which contained locales and fonts, are no longer used and have been deprecated.
##### Locales
Locales are no longer treated as static `.json` assets, but are transpiled with `esbuild` dirrectly to the `.js` as ES modules. Note that some build tools (i.e. Vite) may require setting `es2022` as a build target, in order to support "Arbitrary module namespace identifier names", e.g. `export { english as "en-us" } )`.
```js
// vite.config.js
optimizeDeps: {
esbuildOptions: {
// Bumping to 2022 due to "Arbitrary module namespace identifier names" not being
// supported in Vite's default browser target https://github.com/vitejs/vite/issues/13556
target: "es2022",
// Tree shaking is optional, but recommended
treeShaking: true,
},
}
```
##### Fonts
New fonts, which we've added, are automatically loaded from the CDN. For self-hosting purposes, you'll have to copy the content of the folder `node_modules/@excalidraw/excalidraw/dist/prod/fonts` to the path where your assets should be served from (i.e. `public/` directory in your project). In that case, you should also set `window.EXCALIDRAW_ASSET_PATH` to the very same path, i.e. `/` in case it's in the root:
```js
<script>window.EXCALIDRAW_ASSET_PATH = "/";</script>
```
or, if you serve your assets from the root of your CDN, you would do:
```js
<script>
window.EXCALIDRAW_ASSET_PATH = "https://cdn.domain.com/subpath/";
</script>
```
or, if you prefer the path to be dynamicly set based on the `location.origin`, you could do the following:
```jsx
// Next.js
<Script id="load-env-variables" strategy="beforeInteractive">
{`window["EXCALIDRAW_ASSET_PATH"] = location.origin;`} // or use just "/"!
</Script>
```
#### Deprecated `commitToHistory` in favor of `captureUpdate` in `updateScene` API [#7348](https://github.com/excalidraw/excalidraw/pull/7348), [#7898](https://github.com/excalidraw/excalidraw/pull//7898)
```js
// before
updateScene({ elements, appState, commitToHistory: true }); // A
updateScene({ elements, appState, commitToHistory: false }); // B
// after
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
updateScene({
elements,
appState,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}); // A
updateScene({
elements,
appState,
captureUpdate: CaptureUpdateAction.NEVER,
}); // B
```
The `updateScene` API has changed due to the added `Store` component, as part of multiplayer undo / redo initiative. Specifically, optional `sceneData` parameter `commitToHistory: boolean` was replaced with optional `captureUpdate: CaptureUpdateActionType` parameter. Therefore, make sure to update all instances of `updateScene`, which use `commitToHistory` parameter according to the _before / after_ table below.
> **Note**: Some updates are not observed by the store / history - i.e. updates to `collaborators` object or parts of `AppState` which are not observed (not `ObservedAppState`). Such updates will never make it to the undo / redo stacks, regardless of the passed `captureUpdate` value.
| Undo behaviour | `commitToHistory` (before) | `captureUpdate` (after) | Notes |
| --- | --- | --- | --- |
| _Immediately undoable_ | `true` | `CaptureUpdateAction.IMMEDIATELY` | Use for updates which should be captured. Should be used for most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
| _Eventually undoable_ | `false` (default) | `CaptureUpdateAction.EVENTUALLY` (default) | Use for updates which should not be captured immediately - likely exceptions which are part of some async multi-step process. Otherwise, all such updates would end up being captured with the next `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` or internally by the editor. These updates will _eventually_ make it to the local undo / redo stacks. |
| _Never undoable_ | n/a | `CaptureUpdateAction.NEVER` | **NEW**: Previously there was no equivalent for this value. Now, it's recommended to use `CaptureUpdateAction.NEVER` for updates which should never be recorded, such as remote updates or scene initialization. These updates will _never_ make it to the local undo / redo stacks. |
#### Other
- `ExcalidrawTextElement.baseline` was removed and replaced with a vertical offset computation based on font metrics, performed on each text element re-render. In case of custom font usage, extend the `FONT_METRICS` object with the related properties. [#7693](https://github.com/excalidraw/excalidraw/pull/7693)
- `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)
- Stats container CSS has changed, so if you're using `renderCustomStats`, you may need to adjust your styles to retain the same layout. [#8361](https://github.com/excalidraw/excalidraw/pull/8361)
- `<DefaultSidebar />` triggers are now always merged with host app triggers, rendered through `<DefaultSidebar.Triggers/>`. `<DefaultSidebar.Triggers/>` no longer accepts any props other than children. [#8498](https://github.com/excalidraw/excalidraw/pull/8498)
## Unreleased
### Features
- Added hand-drawn font for Chinese, Japanese and Korean (CJK) as a fallback for Excalifont. Improved overal text wrapping algorithm, not only accounting for CJK, but covering various edge cases with white spaces and text-align center/right. Added support for multi-codepoint emojis wrapping. Offloaded SVG export to Web Workers, with an automatic fallback to the main thread if not supported or not desired.[#8530](https://github.com/excalidraw/excalidraw/pull/8530)
- Prefer user defined coordinates and dimensions when creating a frame using [`convertToExcalidrawElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements) [#8517](https://github.com/excalidraw/excalidraw/pull/8517)
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>` [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>`. [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
- `MainMenu.DefaultItems.ToggleTheme` now supports `onSelect(theme: string)` callback, and optionally `allowSystemTheme: boolean` alongside `theme: string` to indicate you want to allow users to set to system theme (you need to handle this yourself) [#7853](https://github.com/excalidraw/excalidraw/pull/7853)
- Added support for multiplayer undo/redo, by calculating invertible increments and storing them inside the local-only undo/redo stacks. [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
- Add `useHandleLibrary`'s `opts.adapter` as the new recommended pattern to handle library initialization and persistence on library updates [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Added font picker component to have the ability to choose from a range of different fonts. Also, changed the default fonts to `Excalifont`, `Nunito` and `Comic Shanns` and deprecated `Virgil`, `Helvetica` and `Cascadia`.
- Add `useHandleLibrary`'s `opts.migrationAdapter` adapter to handle library migration during init, when migrating from one data store to another (e.g. from LocalStorage to IndexedDB) [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- `MainMenu.DefaultItems.ToggleTheme` now supports `onSelect(theme: string)` callback, and optionally `allowSystemTheme: boolean` alongside `theme: string` to indicate you want to allow users to set to system theme (you need to handle this yourself). [#7853](https://github.com/excalidraw/excalidraw/pull/7853)
- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638)
- Add `useHandleLibrary`'s `opts.adapter` as the new recommended pattern to handle library initialization and persistence on library updates. [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
- Add `useHandleLibrary`'s `opts.migrationAdapter` adapter to handle library migration during init, when migrating from one data store to another (e.g. from LocalStorage to IndexedDB). [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Soft-deprecate `useHandleLibrary`'s `opts.getInitialLibraryItems` in favor of `opts.adapter`. [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Extended `window.EXCALIDRAW_ASSET_PATH` to accept array of paths `string[]` as a value, allowing to specify multiple base `URL` fallbacks. [#8286](https://github.com/excalidraw/excalidraw/pull/8286)
- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638).
- Custom text metrics provider [#9121](https://github.com/excalidraw/excalidraw/pull/9121)
- Add `props.onDuplicate` [#9117](https://github.com/excalidraw/excalidraw/pull/9117)
- Change empty arrowhead icon [#9100](https://github.com/excalidraw/excalidraw/pull/9100)
- Tweak slider colors to be more muted [#9076](https://github.com/excalidraw/excalidraw/pull/9076)
- Improve library sidebar performance [#9060](https://github.com/excalidraw/excalidraw/pull/9060)
- Implement custom Range component for opacity control [#9009](https://github.com/excalidraw/excalidraw/pull/9009)
- Box select frame & children to allow resizing at the same time [#9031](https://github.com/excalidraw/excalidraw/pull/9031)
- Allow installing libs from excal github [#9041](https://github.com/excalidraw/excalidraw/pull/9041)
- Update jotai [#9015](https://github.com/excalidraw/excalidraw/pull/9015)
- Do not delete frame children on frame delete [#9011](https://github.com/excalidraw/excalidraw/pull/9011)
- Add action to wrap selected items in a frame [#9005](https://github.com/excalidraw/excalidraw/pull/9005)
- Reintroduce `.excalidraw.png` default when embedding scene [#8979](https://github.com/excalidraw/excalidraw/pull/8979)
- Add mimeTypes on file save [#8946](https://github.com/excalidraw/excalidraw/pull/8946)
- Add crowfoot to arrowheads [#8942](https://github.com/excalidraw/excalidraw/pull/8942)
- Make HTML attribute sanitization stricter [#8977](https://github.com/excalidraw/excalidraw/pull/8977)
- Validate library install urls [#8976](https://github.com/excalidraw/excalidraw/pull/8976)
- Cleanup svg export and move payload to `<metadata>` [#8975](https://github.com/excalidraw/excalidraw/pull/8975)
- Use stats panel to crop [#8848](https://github.com/excalidraw/excalidraw/pull/8848)
- Snap when cropping as well [#8831](https://github.com/excalidraw/excalidraw/pull/8831)
- Update blog url [#8767](https://github.com/excalidraw/excalidraw/pull/8767)
- Export scene to e+ on workspace creation/redemption [#8514](https://github.com/excalidraw/excalidraw/pull/8514)
- Added sitemap & fixed robot txt [#8699](https://github.com/excalidraw/excalidraw/pull/8699)
- Do not strip unknown element properties on restore [#8682](https://github.com/excalidraw/excalidraw/pull/8682)
- Added reddit links as embeddable [#8099](https://github.com/excalidraw/excalidraw/pull/8099)
- Self-hosting existing google fonts [#8540](https://github.com/excalidraw/excalidraw/pull/8540)
- Flip arrowheads if only arrow(s) selected [#8525](https://github.com/excalidraw/excalidraw/pull/8525)
- Common elbow mid segments [#8440](https://github.com/excalidraw/excalidraw/pull/8440)
- Merge search sidebar back to default sidebar [#8497](https://github.com/excalidraw/excalidraw/pull/8497)
- Smarter zooming when scrolling to match & only match on search/switch [#8488](https://github.com/excalidraw/excalidraw/pull/8488)
- Reset copyStatus on export dialog settings change [#8443](https://github.com/excalidraw/excalidraw/pull/8443)
- Tweak copy button success animation [#8441](https://github.com/excalidraw/excalidraw/pull/8441)
- Enable panning/zoom while in wysiwyg [#8437](https://github.com/excalidraw/excalidraw/pull/8437)
- Visual debugger [#8344](https://github.com/excalidraw/excalidraw/pull/8344)
- Improve elbow arrow keyboard move [#8392](https://github.com/excalidraw/excalidraw/pull/8392)
- Rewrite d2c to not require token [#8269](https://github.com/excalidraw/excalidraw/pull/8269)
- Split `gridSize` from enabled state & support custom `gridStep` [#8364](https://github.com/excalidraw/excalidraw/pull/8364)
- Improve zoom-to-content when creating flowchart [#8368](https://github.com/excalidraw/excalidraw/pull/8368)
- Stats popup style tweaks [#8361](https://github.com/excalidraw/excalidraw/pull/8361)
- Remove automatic frame naming [#8302](https://github.com/excalidraw/excalidraw/pull/8302)
- Ability to debug the state of fractional indices [#8235](https://github.com/excalidraw/excalidraw/pull/8235)
- Improve mermaid detection on paste [#8287](https://github.com/excalidraw/excalidraw/pull/8287)
- Upgrade mermaid-to-excalidraw to v1.1.0 [#8226](https://github.com/excalidraw/excalidraw/pull/8226)
- Bump max file size [#8220](https://github.com/excalidraw/excalidraw/pull/8220)
- Smarter preferred lang detection [#8205](https://github.com/excalidraw/excalidraw/pull/8205)
- Support Stats bound text `fontSize` editing [#8187](https://github.com/excalidraw/excalidraw/pull/8187)
- Paste as mermaid if applicable [#8116](https://github.com/excalidraw/excalidraw/pull/8116)
- Stop autoselecting text on text edit on mobile [#8076](https://github.com/excalidraw/excalidraw/pull/8076)
- Create new text with width [#8038](https://github.com/excalidraw/excalidraw/pull/8038)
- Wrap long text when pasting [#8026](https://github.com/excalidraw/excalidraw/pull/8026)
- Upgrade to mermaid-to-excalidraw v1 🚀 [#8022](https://github.com/excalidraw/excalidraw/pull/8022)
- Rerender canvas on focus [#8035](https://github.com/excalidraw/excalidraw/pull/8035)
- Add missing `type="button"` [#8030](https://github.com/excalidraw/excalidraw/pull/8030)
- Add install-PWA to command palette [#7935](https://github.com/excalidraw/excalidraw/pull/7935)
- Tweak a few icons & add line editor button to side panel [#7990](https://github.com/excalidraw/excalidraw/pull/7990)
- Allow binding only via linear element ends [#7946](https://github.com/excalidraw/excalidraw/pull/7946)
- Resize elements from the sides [#7855](https://github.com/excalidraw/excalidraw/pull/7855)
- Record freedraw tool selection to history [#7949](https://github.com/excalidraw/excalidraw/pull/7949)
- Export reconciliation [#7917](https://github.com/excalidraw/excalidraw/pull/7917)
- Add "toggle grid" to command palette [#7887](https://github.com/excalidraw/excalidraw/pull/7887)
- Fractional indexing [#7359](https://github.com/excalidraw/excalidraw/pull/7359)
- Show firefox-compatible command palette shortcut alias [#7825](https://github.com/excalidraw/excalidraw/pull/7825)
- Upgrade mermaid-to-excalidraw to 0.3.0 [#7819](https://github.com/excalidraw/excalidraw/pull/7819)
- Support to not render remote cursor & username [#7130](https://github.com/excalidraw/excalidraw/pull/7130)
- Expose more collaborator status icons [#7777](https://github.com/excalidraw/excalidraw/pull/7777)
- Close dropdown on escape [#7750](https://github.com/excalidraw/excalidraw/pull/7750)
- Text measurements based on font metrics [#7693](https://github.com/excalidraw/excalidraw/pull/7693)
- Improve collab error notification [#7741](https://github.com/excalidraw/excalidraw/pull/7741)
- Grouped together Undo and Redo buttons on mobile [#9109](https://github.com/excalidraw/excalidraw/pull/9109)
- Load old library if migration fails
- Change LibraryPersistenceAdapter `load()` `source` -> `priority`
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
### Fixes
- Fix inconsistency in resizing while maintaining aspect ratio [#9116](https://github.com/excalidraw/excalidraw/pull/9116)
- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656)
- IFrame and elbow arrow interaction fix [#9101](https://github.com/excalidraw/excalidraw/pull/9101)
### Breaking Changes
- Duplicating/removing frame while children selected [#9079](https://github.com/excalidraw/excalidraw/pull/9079)
- Stats container CSS changed, so if you're using `renderCustomStats`, you may need to adjust your styles to retain the same layout.
- Elbow arrow z-index binding [#9067](https://github.com/excalidraw/excalidraw/pull/9067)
- `updateScene` API has changed due to the added `Store` component as part of the multiplayer undo / redo initiative. Specifically, `sceneData` property `commitToHistory: boolean` was replaced with `storeAction: StoreActionType`. Make sure to update all instances of `updateScene` according to the _before / after_ table below. [#7898](https://github.com/excalidraw/excalidraw/pull/7898)
- Library item checkbox style regression [#9080](https://github.com/excalidraw/excalidraw/pull/9080)
| | Before `commitToHistory` | After `storeAction` | Notes |
| --- | --- | --- | --- |
| _Immediately undoable_ | `true` | `"capture"` | As before, use for all updates which should be recorded by the store & history. Should be used for the most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
| _Eventually undoable_ | `false` | `"none"` | Similar to before, use for all updates which should not be recorded immediately (likely exceptions which are part of some async multi-step process) or those not meant to be recorded at all (i.e. updates to `collaborators` object, parts of `AppState` which are not observed by the store & history - not `ObservedAppState`).<br/><br/>**IMPORTANT** It's likely you should switch to `"update"` in all the other cases. Otherwise, all such updates would end up being recorded with the next `"capture"` - triggered either by the next `updateScene` or internally by the editor. These updates will _eventually_ make it to the local undo / redo stacks. |
| _Never undoable_ | n/a | `"update"` | **NEW**: previously there was no equivalent for this value. Now, it's recommended to use `"update"` for all remote updates (from the other clients), scene initialization, or those updates, which should not be locally "undoable". These updates will _never_ make it to the local undo / redo stacks. |
- Elbow arrow orthogonality [#9073](https://github.com/excalidraw/excalidraw/pull/9073)
- `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)
- Button bg CSS variable leaking into other styles [#9075](https://github.com/excalidraw/excalidraw/pull/9075)
- `ExcalidrawTextElement.baseline` was removed and replaced with a vertical offset computation based on font metrics, performed on each text element re-render. In case of custom font usage, extend the `FONT_METRICS` object with the related properties.
- Fonts not loading on export (again) [#9064](https://github.com/excalidraw/excalidraw/pull/9064)
- 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
- Merge server-side fonts with liberation sans [#9052](https://github.com/excalidraw/excalidraw/pull/9052)
#### Bundler
- Hyperlinks html entities [#9063](https://github.com/excalidraw/excalidraw/pull/9063)
- CSS needs to be imported so you will need to import the css along with the excalidraw component
- Remove flushSync to fix flickering [#9057](https://github.com/excalidraw/excalidraw/pull/9057)
```js
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
```
- Excalidraw issue #9045 flowcharts: align attributes of new node [#9047](https://github.com/excalidraw/excalidraw/pull/9047)
- The `types` path is updated
- Align arrows bound to elements excalidraw#8833 [#8998](https://github.com/excalidraw/excalidraw/pull/8998)
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.
- Update elbow arrow on font size change #8798 [#9002](https://github.com/excalidraw/excalidraw/pull/9002)
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.
- Undo for elbow arrows create incorrect routing [#9046](https://github.com/excalidraw/excalidraw/pull/9046)
#### Browser
- Flowchart clones the current arrowhead [#8581](https://github.com/excalidraw/excalidraw/pull/8581)
- Since its `ESM` so now script type `module` can be used to load it and css needs to be loaded as well.
- Adding partial group to frame [#9014](https://github.com/excalidraw/excalidraw/pull/9014)
```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>
```
- Do not refocus element link input on unrelated updates [#9037](https://github.com/excalidraw/excalidraw/pull/9037)
- Arrow binding behaving unexpectedly on pointerup [#9010](https://github.com/excalidraw/excalidraw/pull/9010)
- Change cursor by tool change immediately [#8212](https://github.com/excalidraw/excalidraw/pull/8212)
- Package build fails on worker chunks [#8990](https://github.com/excalidraw/excalidraw/pull/8990)
- Z-index clash in mobile UI [#8985](https://github.com/excalidraw/excalidraw/pull/8985)
- Elbow arrows do not work within frames (issue: #8964) [#8969](https://github.com/excalidraw/excalidraw/pull/8969)
- NormalizeSVG width and height from viewbox when size includes decimal points [#8939](https://github.com/excalidraw/excalidraw/pull/8939)
- Make arrow binding area adapt to zoom levels [#8927](https://github.com/excalidraw/excalidraw/pull/8927)
- Robust `state.editingFrame` teardown [#8941](https://github.com/excalidraw/excalidraw/pull/8941)
- Regression on dragging a selected frame by its name [#8924](https://github.com/excalidraw/excalidraw/pull/8924)
- Right-click paste for images in clipboard (Issue #8826) [#8845](https://github.com/excalidraw/excalidraw/pull/8845)
- Fixed image transparency by adding alpha option to preserve image alpha channel [#8895](https://github.com/excalidraw/excalidraw/pull/8895)
- Flush pending DOM updates before .focus() [#8901](https://github.com/excalidraw/excalidraw/pull/8901)
- Normalize svg using only absolute sizing [#8854](https://github.com/excalidraw/excalidraw/pull/8854)
- Element link selector dialog z-index & positioning [#8853](https://github.com/excalidraw/excalidraw/pull/8853)
- Update old blog links & add canonical url [#8846](https://github.com/excalidraw/excalidraw/pull/8846)
- Optimize frameToHighlight state change and snapLines state change [#8763](https://github.com/excalidraw/excalidraw/pull/8763)
- Make some events expllicitly active to avoid console warnings [#8757](https://github.com/excalidraw/excalidraw/pull/8757)
- Unify binding update options for `updateBoundElements()` [#8832](https://github.com/excalidraw/excalidraw/pull/8832)
- Cleanup scripts and support upto node 22 [#8794](https://github.com/excalidraw/excalidraw/pull/8794)
- Usage of `node12 which is deprecated` [#8791](https://github.com/excalidraw/excalidraw/pull/8791)
- Remove manifest.json [#8783](https://github.com/excalidraw/excalidraw/pull/8783)
- Load env vars correctly and set debug and linter flags to false explicitly in prod mode [#8770](https://github.com/excalidraw/excalidraw/pull/8770)
- Console error in dev mode due to missing font path in non-prod [#8756](https://github.com/excalidraw/excalidraw/pull/8756)
- Text pushes UI due to padding [#8745](https://github.com/excalidraw/excalidraw/pull/8745)
- Fix trailing line whitespaces layout shift [#8714](https://github.com/excalidraw/excalidraw/pull/8714)
- Load font faces in Safari manually [#8693](https://github.com/excalidraw/excalidraw/pull/8693)
- Restore svg image DataURL dimensions [#8730](https://github.com/excalidraw/excalidraw/pull/8730)
- Image cropping svg + compat mode [#8710](https://github.com/excalidraw/excalidraw/pull/8710)
- Usage of `node12 which is deprecated` [#8709](https://github.com/excalidraw/excalidraw/pull/8709)
- Image render perf [#8697](https://github.com/excalidraw/excalidraw/pull/8697)
- Undo/redo action for international keyboard layouts [#8649](https://github.com/excalidraw/excalidraw/pull/8649)
- Comic Shanns issues, new fonts structure [#8641](https://github.com/excalidraw/excalidraw/pull/8641)
- Remove export-to-clip-as-svg shortcut for now [#8660](https://github.com/excalidraw/excalidraw/pull/8660)
- Text disappearing on edit [#8558](https://github.com/excalidraw/excalidraw/pull/8558) (#8624)
- Elbow arrow fixedpoint flipping now properly flips on inverted resize and flip action [#8324](https://github.com/excalidraw/excalidraw/pull/8324)
- Svg and png frame clipping cases [#8515](https://github.com/excalidraw/excalidraw/pull/8515)
- Re-route elbow arrows when pasted [#8448](https://github.com/excalidraw/excalidraw/pull/8448)
- Buffer dependency [#8474](https://github.com/excalidraw/excalidraw/pull/8474)
- Linear element complete button disabled [#8492](https://github.com/excalidraw/excalidraw/pull/8492)
- Aspect ratio of distorted images are not preserved in SVG exports [#8061](https://github.com/excalidraw/excalidraw/pull/8061)
- WYSIWYG editor padding is not normalized with zoom.value [#8481](https://github.com/excalidraw/excalidraw/pull/8481)
- Improve canvas search scroll behavior further [#8491](https://github.com/excalidraw/excalidraw/pull/8491)
- AddFiles clears the whole image cache when each file is added - regression from #8471 [#8490](https://github.com/excalidraw/excalidraw/pull/8490)
- `select` instead of `focus` search input [#8483](https://github.com/excalidraw/excalidraw/pull/8483)
- Image rendering issue when passed in `initialData` [#8471](https://github.com/excalidraw/excalidraw/pull/8471)
- Add partial mocking [#8473](https://github.com/excalidraw/excalidraw/pull/8473)
- PropertiesPopover maxWidth changing fixed units to relative units [#8456](https://github.com/excalidraw/excalidraw/pull/8456)
- View mode wheel zooming does not work [#8452](https://github.com/excalidraw/excalidraw/pull/8452)
- Fixed copy to clipboard button [#8426](https://github.com/excalidraw/excalidraw/pull/8426)
- Context menu does not work after after dragging on StatsDragInput [#8386](https://github.com/excalidraw/excalidraw/pull/8386)
- Perf regression in `getCommonBounds` [#8429](https://github.com/excalidraw/excalidraw/pull/8429)
- Object snapping not working [#8381](https://github.com/excalidraw/excalidraw/pull/8381)
- Reimplement rectangle intersection [#8367](https://github.com/excalidraw/excalidraw/pull/8367)
- Round coordinates and sizes for rectangle intersection [#8366](https://github.com/excalidraw/excalidraw/pull/8366)
- Text content with tab characters act different in view/edit [#8336](https://github.com/excalidraw/excalidraw/pull/8336)
- Drawing from 0-dimension canvas [#8356](https://github.com/excalidraw/excalidraw/pull/8356)
- Disable flowchart keybindings inside inputs [#8353](https://github.com/excalidraw/excalidraw/pull/8353)
- Yet more patching of intersect code [#8352](https://github.com/excalidraw/excalidraw/pull/8352)
- Missing `act()` in flowchart tests [#8354](https://github.com/excalidraw/excalidraw/pull/8354)
- Z-index change by one causes app to freeze [#8314](https://github.com/excalidraw/excalidraw/pull/8314)
- Patch over intersection calculation issue [#8350](https://github.com/excalidraw/excalidraw/pull/8350)
- Point duplication in LEE on ALT+click [#8347](https://github.com/excalidraw/excalidraw/pull/8347)
- Do not allow resizing unbound elbow arrows either [#8333](https://github.com/excalidraw/excalidraw/pull/8333)
- Docker build in CI [#8312](https://github.com/excalidraw/excalidraw/pull/8312)
- Duplicating arrow without bound elements throws error [#8316](https://github.com/excalidraw/excalidraw/pull/8316)
- CVE-2023-45133 [#7988](https://github.com/excalidraw/excalidraw/pull/7988)
- Throttle fractional indices validation [#8306](https://github.com/excalidraw/excalidraw/pull/8306)
- Allow binding elbow arrows to frame children [#8309](https://github.com/excalidraw/excalidraw/pull/8309)
- Skip registering font faces for local fonts [#8303](https://github.com/excalidraw/excalidraw/pull/8303)
- Load fonts for `exportToCanvas` [#8298](https://github.com/excalidraw/excalidraw/pull/8298)
- Re-add Cascadia Code with ligatures [#8291](https://github.com/excalidraw/excalidraw/pull/8291)
- Linear elements not selected on pointer up from hitting its bound text [#8285](https://github.com/excalidraw/excalidraw/pull/8285)
- Revert default element canvas padding change [#8266](https://github.com/excalidraw/excalidraw/pull/8266)
- Freedraw jittering [#8238](https://github.com/excalidraw/excalidraw/pull/8238)
- Messed up env variable [#8231](https://github.com/excalidraw/excalidraw/pull/8231)
- Log allowed events [#8224](https://github.com/excalidraw/excalidraw/pull/8224)
- Memory leak - scene.destroy() and window.launchQueue [#8198](https://github.com/excalidraw/excalidraw/pull/8198)
- Stop updating text versions on init [#8191](https://github.com/excalidraw/excalidraw/pull/8191)
- Add binding update to manual stat changes [#8183](https://github.com/excalidraw/excalidraw/pull/8183)
- Binding after duplicating is now applied for both the old and duplicate shapes [#8185](https://github.com/excalidraw/excalidraw/pull/8185)
- Incorrect point offsetting in LinearElementEditor.movePoints() [#8145](https://github.com/excalidraw/excalidraw/pull/8145)
- Stats state leaking & race conds [#8177](https://github.com/excalidraw/excalidraw/pull/8177)
- Only bind arrow [#8152](https://github.com/excalidraw/excalidraw/pull/8152)
- Repair invalid binding on restore & fix type check [#8133](https://github.com/excalidraw/excalidraw/pull/8133)
- Wysiwyg blur-submit on mobile [#8075](https://github.com/excalidraw/excalidraw/pull/8075)
- Restore linear dimensions from points [#8062](https://github.com/excalidraw/excalidraw/pull/8062)
- Lp plus url [#8056](https://github.com/excalidraw/excalidraw/pull/8056)
- Fix twitter og image [#8050](https://github.com/excalidraw/excalidraw/pull/8050)
- Flaky snapshot tests with floating point precision issues [#8049](https://github.com/excalidraw/excalidraw/pull/8049)
- Always re-generate index of defined moved elements [#8040](https://github.com/excalidraw/excalidraw/pull/8040)
- Undo/redo when exiting view mode [#8024](https://github.com/excalidraw/excalidraw/pull/8024)
- Two finger panning is slow [#7849](https://github.com/excalidraw/excalidraw/pull/7849)
- Compatible safari layers button svg [#8020](https://github.com/excalidraw/excalidraw/pull/8020)
- Correctly resolve the package version [#8016](https://github.com/excalidraw/excalidraw/pull/8016)
- Re-introduce wysiwyg width offset [#8014](https://github.com/excalidraw/excalidraw/pull/8014)
- Font not rendered correctly on init [#8002](https://github.com/excalidraw/excalidraw/pull/8002)
- Command palette filter [#7981](https://github.com/excalidraw/excalidraw/pull/7981)
- Remove unused param from drawImagePlaceholder [#7991](https://github.com/excalidraw/excalidraw/pull/7991)
- Docker build of Excalidraw app [#7430](https://github.com/excalidraw/excalidraw/pull/7430)
- Typo in doc api [#7466](https://github.com/excalidraw/excalidraw/pull/7466)
- Use Reflect API instead of Object.hasOwn [#7958](https://github.com/excalidraw/excalidraw/pull/7958)
- CTRL/CMD & arrow point drag unbinds both sides [#6459](https://github.com/excalidraw/excalidraw/pull/6459) (#7877)
- Z-index for laser pointer to be able to draw on embeds and such [#7918](https://github.com/excalidraw/excalidraw/pull/7918)
- Double text rendering on edit [#7904](https://github.com/excalidraw/excalidraw/pull/7904)
- Collision regressions from vector geometry rewrite [#7902](https://github.com/excalidraw/excalidraw/pull/7902)
- Correct unit from 'eg' to 'deg' [#7891](https://github.com/excalidraw/excalidraw/pull/7891)
- Allow same origin for all necessary domains [#7889](https://github.com/excalidraw/excalidraw/pull/7889)
- Always make sure we render bound text above containers [#7880](https://github.com/excalidraw/excalidraw/pull/7880)
- Parse embeddable srcdoc urls strictly [#7884](https://github.com/excalidraw/excalidraw/pull/7884)
- Hit test for closed sharp curves [#7881](https://github.com/excalidraw/excalidraw/pull/7881)
- Gist embed allowing unsafe html [#7883](https://github.com/excalidraw/excalidraw/pull/7883)
- Command palette tweaks and fixes [#7876](https://github.com/excalidraw/excalidraw/pull/7876)
- Include borders when testing insides of a shape [#7865](https://github.com/excalidraw/excalidraw/pull/7865)
- External link not opening [#7859](https://github.com/excalidraw/excalidraw/pull/7859)
- Add safe check for arrow points length in tranformToExcalidrawElements [#7863](https://github.com/excalidraw/excalidraw/pull/7863)
- Import [#7869](https://github.com/excalidraw/excalidraw/pull/7869)
- Theme toggle shortcut `event.code` [#7868](https://github.com/excalidraw/excalidraw/pull/7868)
- Remove incorrect check from index.html [#7867](https://github.com/excalidraw/excalidraw/pull/7867)
- Stop using lookbehind for backwards compat [#7824](https://github.com/excalidraw/excalidraw/pull/7824)
- Ejs support in html files [#7822](https://github.com/excalidraw/excalidraw/pull/7822)
- `excalidrawAPI.toggleSidebar` not switching between tabs correctly [#7821](https://github.com/excalidraw/excalidraw/pull/7821)
- Correcting Assistant metrics [#7758](https://github.com/excalidraw/excalidraw/pull/7758)
- Add missing font metrics for Assistant [#7752](https://github.com/excalidraw/excalidraw/pull/7752)
- Export utils from excalidraw package in excalidraw library [#7731](https://github.com/excalidraw/excalidraw/pull/7731)
- Split renderScene so that locales aren't imported unnecessarily [#7718](https://github.com/excalidraw/excalidraw/pull/7718)
- Remove dependency of t in blob.ts [#7717](https://github.com/excalidraw/excalidraw/pull/7717)
- Remove dependency of t from clipboard and image [#7712](https://github.com/excalidraw/excalidraw/pull/7712)
- Remove scene hack from export.ts & remove pass elementsMap to getContainingFrame [#7713](https://github.com/excalidraw/excalidraw/pull/7713)
- Decouple pure functions from hyperlink to prevent mermaid bundling [#7710](https://github.com/excalidraw/excalidraw/pull/7710)
- Make bounds independent of scene [#7679](https://github.com/excalidraw/excalidraw/pull/7679)
- Make LinearElementEditor independent of scene [#7670](https://github.com/excalidraw/excalidraw/pull/7670)
- Remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap [#7663](https://github.com/excalidraw/excalidraw/pull/7663)
- Remove t from getDefaultAppState and allow name to be nullable [#7666](https://github.com/excalidraw/excalidraw/pull/7666)
- Stop using structuredClone [#9128](https://github.com/excalidraw/excalidraw/pull/9128)
### Refactor
- Remove `defaultProps` [#9035](https://github.com/excalidraw/excalidraw/pull/9035)
- Separate resizing logic from pointer [#8155](https://github.com/excalidraw/excalidraw/pull/8155)
- `point()` -> `pointFrom()` to fix compiler issue [#8578](https://github.com/excalidraw/excalidraw/pull/8578)
- Rename example `App.tsx` -> `ExampleApp.tsx` [#8501](https://github.com/excalidraw/excalidraw/pull/8501)
- Remove unused env variable [#8457](https://github.com/excalidraw/excalidraw/pull/8457)
- Rename `draggingElement` -> `newElement` [#8294](https://github.com/excalidraw/excalidraw/pull/8294)
- Update collision from ga to vector geometry [#7636](https://github.com/excalidraw/excalidraw/pull/7636)
### Performance
- Improved pointer events related performance when the sidebar is docked with a large library open [#9086](https://github.com/excalidraw/excalidraw/pull/9086)
- Reduce unnecessary frame clippings [#8980](https://github.com/excalidraw/excalidraw/pull/8980)
- Improve new element drawing [#8340](https://github.com/excalidraw/excalidraw/pull/8340)
- Cache the temp canvas created for labeled arrows [#8267](https://github.com/excalidraw/excalidraw/pull/8267)
### Build
- Set PWA flag in dev to false [#8788](https://github.com/excalidraw/excalidraw/pull/8788)
- Add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment [#8784](https://github.com/excalidraw/excalidraw/pull/8784)
- Upgrade vite to 5.4.x, vitest to 2.x and related vite packages [#8459](https://github.com/excalidraw/excalidraw/pull/8459)
- Add example apps `public` and vite `dev-dist` to eslintignore [#8326](https://github.com/excalidraw/excalidraw/pull/8326)
- Add `rm:build`, `rm:node_modules` & `clean-install` scripts [#8323](https://github.com/excalidraw/excalidraw/pull/8323)
- Update release script to build esm [#8308](https://github.com/excalidraw/excalidraw/pull/8308)
- Run tests on master branch [#8072](https://github.com/excalidraw/excalidraw/pull/8072)
- Specify `packageManager` field [#8010](https://github.com/excalidraw/excalidraw/pull/8010)
- Enable consistent type imports eslint rule [#7992](https://github.com/excalidraw/excalidraw/pull/7992)
- Export types for @excalidraw/utils [#7736](https://github.com/excalidraw/excalidraw/pull/7736)
- Create ESM build for utils package 🥳 [#7500](https://github.com/excalidraw/excalidraw/pull/7500)
- Upgrade to react@19 [#9182](https://github.com/excalidraw/excalidraw/pull/9182)
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
## 0.17.3 (2024-02-09)
@ -845,8 +215,6 @@ define: {
### Fixes
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
- 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)

View File

@ -1,45 +1,49 @@
# Excalidraw
**Excalidraw** is exported as a component to be directly embedded in your project.
**Excalidraw** is exported as a component to directly embed in your projects.
## Installation
Use `npm` or `yarn` to install the package.
You can use `npm`
```bash
npm install react react-dom @excalidraw/excalidraw
# or
```
or via `yarn`
```bash
yarn add react react-dom @excalidraw/excalidraw
```
> **Note**: If you don't want to wait for the next stable release and try out the unreleased changes, use `@excalidraw/excalidraw@next`.
After installation you will see a folder `excalidraw-assets` and `excalidraw-assets-dev` in `dist` directory which contains the assets needed for this app in prod and dev mode respectively.
#### Self-hosting fonts
Move the folder `excalidraw-assets` and `excalidraw-assets-dev` to the path where your assets are served.
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
By default it will try to load the files from [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist)
For self-hosting purposes, you'll have to copy the content of the folder `node_modules/@excalidraw/excalidraw/dist/prod/fonts` to the path where your assets should be served from (i.e. `public/` directory in your project). In that case, you should also set `window.EXCALIDRAW_ASSET_PATH` to the very same path, i.e. `/` in case it's in the root:
If you want to load assets from a different path you can set a variable `window.EXCALIDRAW_ASSET_PATH` depending on environment (for example if you have different URL's for dev and prod) to the url from where you want to load the assets.
```js
<script>window.EXCALIDRAW_ASSET_PATH = "/";</script>
```
#### Note
### Dimensions of Excalidraw
**If you don't want to wait for the next stable release and try out the unreleased changes you can use `@excalidraw/excalidraw@next`.**
## Dimensions of Excalidraw
Excalidraw takes _100%_ of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
## Demo
### Demo
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example.
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
## Integration
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration).
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration)
## API
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api).
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api)
## Contributing
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/contributing).
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/contributing)

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