Compare commits
84 Commits
dependabot
...
master
Author | SHA1 | Date | |
---|---|---|---|
5639bb8e87 | |||
![]() |
712f267519 | ||
![]() |
41a7613dff | ||
![]() |
95d89a751a | ||
![]() |
6b5fb30d69 | ||
![]() |
d92a849038 | ||
![]() |
0a534f1bc6 | ||
![]() |
4ca5f53b1f | ||
![]() |
f7dcc893ea | ||
![]() |
4dfb8a3f8e | ||
![]() |
298812e1d0 | ||
![]() |
35bb449a4b | ||
![]() |
c4c064982f | ||
![]() |
51dbd4831b | ||
![]() |
7e41026812 | ||
![]() |
a8ebe514da | ||
![]() |
a30e1b25c6 | ||
![]() |
ff2ed5d26a | ||
![]() |
e058a08b33 | ||
![]() |
a306a909a0 | ||
![]() |
3dc54a724a | ||
![]() |
a7c61319dd | ||
![]() |
cec5232a7a | ||
![]() |
d4f70e9f31 | ||
![]() |
e19fd1332a | ||
![]() |
6e655cdb24 | ||
![]() |
192c4e7658 | ||
![]() |
195a743874 | ||
![]() |
4a60fe3d22 | ||
![]() |
2a0d15799c | ||
![]() |
a18b139a60 | ||
![]() |
1913599594 | ||
![]() |
debf2ad608 | ||
![]() |
8fb2f70414 | ||
![]() |
5fc13e4309 | ||
![]() |
b5d60973b7 | ||
![]() |
a5d6939826 | ||
![]() |
0cf36d6b30 | ||
![]() |
58f7d33d80 | ||
![]() |
6fe7de8020 | ||
![]() |
01304aac49 | ||
![]() |
dff69e9191 | ||
![]() |
6fc85022ae | ||
![]() |
e48b63a0ae | ||
![]() |
c2caf78e95 | ||
![]() |
ce267aa0d3 | ||
![]() |
6e47fadb59 | ||
![]() |
b3d5ba0567 | ||
![]() |
c79e892e55 | ||
![]() |
57a9e301d4 | ||
![]() |
7c58477382 | ||
![]() |
83fac6d0db | ||
![]() |
f2e8404c7b | ||
![]() |
d797c2e210 | ||
![]() |
0cd5a259ae | ||
![]() |
432a46ef9e | ||
![]() |
a18f059188 | ||
![]() |
ab89d4c16f | ||
![]() |
6c3a434f2a | ||
![]() |
e1bb59fb8f | ||
![]() |
77aca48c84 | ||
![]() |
58990b41ae | ||
![]() |
99d8bff175 | ||
![]() |
30983d801a | ||
![]() |
21ffaf4d76 | ||
![]() |
82b9a6b464 | ||
![]() |
817d8c553c | ||
![]() |
69bc5bdaab | ||
![]() |
d587b8a3de | ||
![]() |
4ec812bc18 | ||
![]() |
a9e2d2348b | ||
![]() |
70c3e921bb | ||
![]() |
d92384b77d | ||
![]() |
c5d3bb0b6a | ||
![]() |
d21c6a1bc6 | ||
![]() |
d1112bbf4f | ||
![]() |
2523624f15 | ||
![]() |
68578556ff | ||
![]() |
ecef5d12f4 | ||
![]() |
392118bf26 | ||
![]() |
0ffeaeaecf | ||
![]() |
31e8476c78 | ||
![]() |
9ee0b8ffcb | ||
![]() |
16b86d7d16 |
@ -48,3 +48,6 @@ UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
|
|||||||
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
|
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
|
||||||
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
|
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
|
||||||
HQIDAQAB'
|
HQIDAQAB'
|
||||||
|
|
||||||
|
# set to true in .env.development.local to disable the prevent unload dialog
|
||||||
|
VITE_APP_DISABLE_PREVENT_UNLOAD=
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
|
VITE_APP_BACKEND_V2_GET_URL=https://ex.dylanbanta.com/api/v2/scenes/
|
||||||
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
|
VITE_APP_BACKEND_V2_POST_URL=https://ex.dylanbanta.com/api/v2/scenes/
|
||||||
|
|
||||||
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
|
||||||
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
|
||||||
|
@ -1,6 +1,21 @@
|
|||||||
{
|
{
|
||||||
"extends": ["@excalidraw/eslint-config", "react-app"],
|
"extends": ["@excalidraw/eslint-config", "react-app"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"import/order": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
|
||||||
|
"pathGroups": [
|
||||||
|
{
|
||||||
|
"pattern": "@excalidraw/**",
|
||||||
|
"group": "external",
|
||||||
|
"position": "after"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"newlines-between": "always-and-inside-groups",
|
||||||
|
"warnOnUnassignedImports": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"import/no-anonymous-default-export": "off",
|
"import/no-anonymous-default-export": "off",
|
||||||
"no-restricted-globals": "off",
|
"no-restricted-globals": "off",
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
@ -17,6 +32,12 @@
|
|||||||
"name": "jotai",
|
"name": "jotai",
|
||||||
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
|
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"react/jsx-no-target-blank": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowReferrer": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.gitignore
vendored
@ -25,5 +25,4 @@ packages/excalidraw/types
|
|||||||
coverage
|
coverage
|
||||||
dev-dist
|
dev-dist
|
||||||
html
|
html
|
||||||
examples/**/bundle.*
|
|
||||||
meta*.json
|
meta*.json
|
12
README.md
@ -63,7 +63,7 @@ The Excalidraw editor (npm package) supports:
|
|||||||
- 🏗️ Customizable.
|
- 🏗️ Customizable.
|
||||||
- 📷 Image support.
|
- 📷 Image support.
|
||||||
- 😀 Shape libraries support.
|
- 😀 Shape libraries support.
|
||||||
- 👅 Localization (i18n) support.
|
- 🌐 Localization (i18n) support.
|
||||||
- 🖼️ Export to PNG, SVG & clipboard.
|
- 🖼️ Export to PNG, SVG & clipboard.
|
||||||
- 💾 Open format - export drawings as an `.excalidraw` json file.
|
- 💾 Open format - export drawings as an `.excalidraw` json file.
|
||||||
- ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
|
- ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
|
||||||
@ -87,13 +87,11 @@ 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).
|
**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
|
npm install react react-dom @excalidraw/excalidraw
|
||||||
```
|
# or
|
||||||
|
|
||||||
or via yarn
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn add react react-dom @excalidraw/excalidraw
|
yarn add react react-dom @excalidraw/excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
|
Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
|
||||||
|
|
||||||
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node.
|
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should be a valid React Node.
|
||||||
|
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will only for `Desktop` devices.
|
This will only work for `Desktop` devices.
|
||||||
|
|
||||||
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
|
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
|
||||||
|
|
||||||
@ -65,4 +65,4 @@ const App = () => (
|
|||||||
// Need to render when code is span across multiple components
|
// Need to render when code is span across multiple components
|
||||||
// in Live Code blocks editor
|
// in Live Code blocks editor
|
||||||
render(<App />);
|
render(<App />);
|
||||||
```
|
```
|
||||||
|
@ -8,15 +8,15 @@
|
|||||||
import { FONT_FAMILY } from "@excalidraw/excalidraw";
|
import { FONT_FAMILY } from "@excalidraw/excalidraw";
|
||||||
```
|
```
|
||||||
|
|
||||||
`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
|
`FONT_FAMILY` contains all the font families used in `Excalidraw`. The default families are the following:
|
||||||
|
|
||||||
| Font Family | Description |
|
| Font Family | Description |
|
||||||
| ----------- | ---------------------- |
|
| ----------- | ---------------------- |
|
||||||
| `Virgil` | The `Hand-drawn` font |
|
| `Excalifont` | The `Hand-drawn` font |
|
||||||
| `Helvetica` | The `Normal` Font |
|
| `Nunito` | The `Normal` Font |
|
||||||
| `Cascadia` | The `Code` Font |
|
| `Comic Shanns` | The `Code` Font |
|
||||||
|
|
||||||
Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
|
Pre-selected family is `FONT_FAMILY.Excalifont`, unless it's overriden with `initialData.appState.currentItemFontFamily`.
|
||||||
|
|
||||||
### THEME
|
### THEME
|
||||||
|
|
||||||
|
@ -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 |
|
| `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. |
|
| `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. |
|
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
|
||||||
| `commitToStore` | `boolean` | Implies if the change should be captured and commited to the `store`. Commited changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `false`. |
|
| `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. |
|
||||||
|
|
||||||
```jsx live
|
```jsx live
|
||||||
function App() {
|
function App() {
|
||||||
@ -105,6 +105,7 @@ function App() {
|
|||||||
appState: {
|
appState: {
|
||||||
viewBackgroundColor: "#edf2ff",
|
viewBackgroundColor: "#edf2ff",
|
||||||
},
|
},
|
||||||
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
excalidrawAPI.updateScene(sceneData);
|
excalidrawAPI.updateScene(sceneData);
|
||||||
};
|
};
|
||||||
@ -121,6 +122,18 @@ 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
|
### updateLibrary
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
All `props` are _optional_.
|
All `props` are _optional_.
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | null></code> | `null` | The initial data with which app loads. |
|
||||||
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | \_ | Callback triggered with the excalidraw api once rendered |
|
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | \_ | Callback triggered with the excalidraw api once rendered |
|
||||||
| [`isCollaborating`](#iscollaborating) | `boolean` | \_ | This indicates if the app is in `collaboration` mode |
|
| [`isCollaborating`](#iscollaborating) | `boolean` | \_ | This indicates if the app is in `collaboration` mode |
|
||||||
@ -13,7 +13,7 @@ All `props` are _optional_.
|
|||||||
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
|
| [`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 |
|
| [`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. |
|
| [`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. |
|
| [`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. |
|
| [`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 |
|
| [`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 |
|
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | \_ | Render function that renders custom UI in top right corner |
|
||||||
@ -29,8 +29,9 @@ All `props` are _optional_.
|
|||||||
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
|
||||||
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
|
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
|
||||||
| [`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 |
|
| [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| <code>((link: string) => boolean | undefined)</code> | \_ | 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>` |
|
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
|
||||||
|
| [`renderScrollbars`] | `boolean`| | `false` | Indicates whether scrollbars will be shown
|
||||||
|
|
||||||
### Storing custom data on Excalidraw elements
|
### Storing custom data on Excalidraw elements
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ You can pass `null` / `undefined` if not applicable.
|
|||||||
restoreElements(
|
restoreElements(
|
||||||
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>
|
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/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/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
||||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
|
||||||
)
|
)
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -51,8 +51,9 @@ The extra optional parameter to configure restored elements. It has the followin
|
|||||||
|
|
||||||
| Prop | Type | Description|
|
| 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. |
|
| `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. |
|
| `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. |
|
||||||
|
|
||||||
**_How to use_**
|
**_How to use_**
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ restore(
|
|||||||
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/>
|
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/>
|
||||||
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/>
|
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/>
|
||||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/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/>
|
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L4">DataState</a><br/>
|
||||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
opts: { refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
|
||||||
|
|
||||||
)
|
)
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -13,18 +13,18 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
|
|||||||
1. Install the dependencies
|
1. Install the dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd packages/excalidraw && yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start the example app
|
2. Start the example app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn start
|
yarn start:example
|
||||||
```
|
```
|
||||||
|
|
||||||
[http://localhost:3001](http://localhost:3001) will open in your default browser.
|
[http://localhost:3001](http://localhost:3001) will open in your default browser.
|
||||||
|
|
||||||
The example is same as the [codesandbox example](https://ehlz3.csb.app/)
|
This is the same example as the [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
|
@ -17,11 +17,9 @@ We strongly recommend turning it off. You can follow the steps below on how to d
|
|||||||
<div style={{width:'30rem'}}>
|
<div style={{width:'30rem'}}>
|
||||||
|
|
||||||
2. Once opened, look for **Aggressively Block Fingerprinting**
|
2. Once opened, look for **Aggressively Block Fingerprinting**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3. Switch to **Block Fingerprinting**
|
3. Switch to **Block Fingerprinting**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
4. Thats all. All text elements should be fixed now 🎉
|
4. Thats all. All text elements should be fixed now 🎉
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
**Excalidraw** is published to npm as a component you can directly embed in your projects.
|
**Excalidraw** is exported as a component to be directly embedded in your project.
|
||||||
|
|
||||||
Using `npm`:
|
Use `npm` or `yarn` to install the package.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install react react-dom @excalidraw/excalidraw
|
npm install react react-dom @excalidraw/excalidraw
|
||||||
```
|
# or
|
||||||
|
|
||||||
or `yarn`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn add react react-dom @excalidraw/excalidraw
|
yarn add react react-dom @excalidraw/excalidraw
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -20,24 +16,40 @@ yarn add react react-dom @excalidraw/excalidraw
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Static assets
|
### Self-hosting fonts
|
||||||
|
|
||||||
Excalidraw depends on assets such as localization files (if you opt to use them), fonts, and others.
|
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
|
||||||
|
|
||||||
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.
|
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:
|
||||||
|
|
||||||
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
|
```js
|
||||||
window.EXCALIDRAW_ASSET_PATH = "/";
|
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
|
### 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.
|
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
|
||||||
|
|
||||||
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
|
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
|
||||||
|
@ -128,11 +128,10 @@ If you are using `pages router` then importing the wrapper dynamically would wor
|
|||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</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/).
|
||||||
|
|
||||||
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`, check [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example for details.
|
||||||
|
|
||||||
|
|
||||||
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
|
|
||||||
|
|
||||||
### Preact
|
### Preact
|
||||||
|
|
||||||
@ -157,27 +156,9 @@ Since Vite removes env variables by default, you can update the vite config to e
|
|||||||
|
|
||||||
## Browser
|
## Browser
|
||||||
|
|
||||||
To use it in a browser directly:
|
To use it Excalidraw in a browser directly, use the following setup :point_down:
|
||||||
|
|
||||||
For development use :point_down:
|
> **Note**: We rely on import maps to de-duplicate `react`, `react-dom` and `react/jsx-runtime` versions.
|
||||||
|
|
||||||
```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 Tabs from "@theme/Tabs";
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
@ -191,13 +172,23 @@ import TabItem from "@theme/TabItem";
|
|||||||
<head>
|
<head>
|
||||||
<title>Excalidraw in browser</title>
|
<title>Excalidraw in browser</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
|
<link
|
||||||
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
|
rel="stylesheet"
|
||||||
|
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
|
||||||
<script
|
/>
|
||||||
type="text/javascript"
|
<link rel="stylesheet" href="./index.css" />
|
||||||
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
|
<script>
|
||||||
></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>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -214,6 +205,14 @@ import TabItem from "@theme/TabItem";
|
|||||||
<TabItem value="js" label="Javascript">
|
<TabItem value="js" label="Javascript">
|
||||||
|
|
||||||
```js showLineNumbers
|
```js showLineNumbers
|
||||||
|
// See https://www.npmjs.com/package/@excalidraw/excalidraw documentation.
|
||||||
|
import * as ExcalidrawLib from 'https://esm.sh/@excalidraw/excalidraw@0.18.0/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 = () => {
|
const App = () => {
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Fragment,
|
React.Fragment,
|
||||||
@ -236,4 +235,4 @@ root.render(React.createElement(App));
|
|||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
You can try it out [here](https://codesandbox.io/p/sandbox/excalidraw-in-browser-tlqom?file=%2Findex.html%3A1%2C10).
|
You can try it out [here](https://jsfiddle.net/vfn6dm14/3/).
|
||||||
|
@ -149,6 +149,29 @@ const config = {
|
|||||||
systemvars: true,
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"@docusaurus/core": "2.2.0",
|
"@docusaurus/core": "2.2.0",
|
||||||
"@docusaurus/preset-classic": "2.2.0",
|
"@docusaurus/preset-classic": "2.2.0",
|
||||||
"@docusaurus/theme-live-codeblock": "2.2.0",
|
"@docusaurus/theme-live-codeblock": "2.2.0",
|
||||||
"@excalidraw/excalidraw": "0.17.6",
|
"@excalidraw/excalidraw": "0.18.0",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"docusaurus-plugin-sass": "0.2.3",
|
"docusaurus-plugin-sass": "0.2.3",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
const FeatureList = [
|
const FeatureList = [
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
type FeatureItem = {
|
type FeatureItem = {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React from "react";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import Layout from "@theme/Layout";
|
|
||||||
import Link from "@docusaurus/Link";
|
import Link from "@docusaurus/Link";
|
||||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||||
import styles from "./index.module.css";
|
|
||||||
import HomepageFeatures from "@site/src/components/Homepage";
|
import HomepageFeatures from "@site/src/components/Homepage";
|
||||||
|
import Layout from "@theme/Layout";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
function HomepageHeader() {
|
function HomepageHeader() {
|
||||||
const { siteConfig } = useDocusaurusContext();
|
const { siteConfig } = useDocusaurusContext();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Import the original mapper
|
// Import the original mapper
|
||||||
import MDXComponents from "@theme-original/MDXComponents";
|
|
||||||
import Highlight from "@site/src/components/Highlight";
|
import Highlight from "@site/src/components/Highlight";
|
||||||
|
import MDXComponents from "@theme-original/MDXComponents";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Re-use the default mapping
|
// Re-use the default mapping
|
||||||
|
@ -3,11 +3,18 @@ import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
|
|||||||
import initialData from "@site/src/initialData";
|
import initialData from "@site/src/initialData";
|
||||||
import { useColorMode } from "@docusaurus/theme-common";
|
import { useColorMode } from "@docusaurus/theme-common";
|
||||||
|
|
||||||
|
import "@excalidraw/excalidraw/index.css";
|
||||||
|
|
||||||
let ExcalidrawComp = {};
|
let ExcalidrawComp = {};
|
||||||
if (ExecutionEnvironment.canUseDOM) {
|
if (ExecutionEnvironment.canUseDOM) {
|
||||||
ExcalidrawComp = require("@excalidraw/excalidraw");
|
ExcalidrawComp = require("@excalidraw/excalidraw");
|
||||||
}
|
}
|
||||||
const Excalidraw = React.forwardRef((props, ref) => {
|
const Excalidraw = React.forwardRef((props, ref) => {
|
||||||
|
if (!window.EXCALIDRAW_ASSET_PATH) {
|
||||||
|
window.EXCALIDRAW_ASSET_PATH =
|
||||||
|
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
|
||||||
|
}
|
||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
return <ExcalidrawComp.Excalidraw theme={colorMode} {...props} ref={ref} />;
|
return <ExcalidrawComp.Excalidraw theme={colorMode} {...props} ref={ref} />;
|
||||||
});
|
});
|
||||||
|
1436
dev-docs/yarn.lock
@ -19,7 +19,7 @@ services:
|
|||||||
- ./:/opt/node_app/app:delegated
|
- ./:/opt/node_app/app:delegated
|
||||||
- ./package.json:/opt/node_app/package.json
|
- ./package.json:/opt/node_app/package.json
|
||||||
- ./yarn.lock:/opt/node_app/yarn.lock
|
- ./yarn.lock:/opt/node_app/yarn.lock
|
||||||
- notused:/opt/node_app/app/node_modules
|
# - notused:/opt/node_app/app/node_modules
|
||||||
|
|
||||||
volumes:
|
# volumes:
|
||||||
notused:
|
# notused:
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "examples",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"@excalidraw/excalidraw": "*"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig"
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"outputDirectory": "dist",
|
|
||||||
"installCommand": "yarn install"
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
server: {
|
|
||||||
port: 3001,
|
|
||||||
// open the browser
|
|
||||||
open: true,
|
|
||||||
},
|
|
||||||
publicDir: "public",
|
|
||||||
});
|
|
@ -1,313 +0,0 @@
|
|||||||
# 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"
|
|
@ -4,22 +4,21 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
|
"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",
|
"dev": "yarn build:workspace && next dev -p 3005",
|
||||||
"build": "yarn build:workspace && next build",
|
"build": "yarn build:workspace && next build",
|
||||||
"start": "next start -p 3006",
|
"start": "next start -p 3006",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@excalidraw/excalidraw": "*",
|
|
||||||
"next": "14.1",
|
"next": "14.1",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "18.2.0",
|
"@types/react": "19.0.10",
|
||||||
"@types/react-dom": "18.2.0",
|
"@types/react-dom": "19.0.4",
|
||||||
"path2d-polyfill": "2.0.1",
|
"path2d-polyfill": "2.0.1",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -1,5 +1,6 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
|
||||||
import "../common.scss";
|
import "../common.scss";
|
||||||
|
|
||||||
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import * as excalidrawLib from "@excalidraw/excalidraw";
|
import * as excalidrawLib from "@excalidraw/excalidraw";
|
||||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||||
import App from "../../components/ExampleApp";
|
|
||||||
|
|
||||||
import "@excalidraw/excalidraw/index.css";
|
import "@excalidraw/excalidraw/index.css";
|
||||||
|
|
||||||
|
import App from "../../with-script-in-browser/components/ExampleApp";
|
||||||
|
|
||||||
const ExcalidrawWrapper: React.FC = () => {
|
const ExcalidrawWrapper: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
@ -1,4 +1,5 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
import "../common.scss";
|
import "../common.scss";
|
||||||
|
|
||||||
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
|
5
examples/with-script-in-browser/.codesandbox/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
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
|
35
examples/with-script-in-browser/.codesandbox/tasks.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
|
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
const COMMENT_SVG = (
|
const COMMENT_SVG = (
|
||||||
<svg
|
<svg
|
@ -52,7 +52,7 @@
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw .panelColumn {
|
.excalidraw .selected-shape-actions {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@ -6,13 +7,24 @@ import React, {
|
|||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
} from "react";
|
} from "react";
|
||||||
import ExampleSidebar from "./sidebar/ExampleSidebar";
|
|
||||||
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
|
import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
|
||||||
|
import type {
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
Theme,
|
||||||
|
} from "@excalidraw/excalidraw/element/types";
|
||||||
|
import type {
|
||||||
|
AppState,
|
||||||
|
BinaryFileData,
|
||||||
|
ExcalidrawImperativeAPI,
|
||||||
|
ExcalidrawInitialDataState,
|
||||||
|
Gesture,
|
||||||
|
LibraryItems,
|
||||||
|
PointerDownState as ExcalidrawPointerDownState,
|
||||||
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { nanoid } from "nanoid";
|
import initialData from "../initialData";
|
||||||
|
|
||||||
import type { ResolvablePromise } from "../utils";
|
|
||||||
import {
|
import {
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
distance2d,
|
distance2d,
|
||||||
@ -23,25 +35,12 @@ import {
|
|||||||
|
|
||||||
import CustomFooter from "./CustomFooter";
|
import CustomFooter from "./CustomFooter";
|
||||||
import MobileFooter from "./MobileFooter";
|
import MobileFooter from "./MobileFooter";
|
||||||
import initialData from "../initialData";
|
import ExampleSidebar from "./sidebar/ExampleSidebar";
|
||||||
|
|
||||||
import type {
|
|
||||||
AppState,
|
|
||||||
BinaryFileData,
|
|
||||||
ExcalidrawImperativeAPI,
|
|
||||||
ExcalidrawInitialDataState,
|
|
||||||
Gesture,
|
|
||||||
LibraryItems,
|
|
||||||
PointerDownState as ExcalidrawPointerDownState,
|
|
||||||
} from "@excalidraw/excalidraw/dist/excalidraw/types";
|
|
||||||
import type {
|
|
||||||
NonDeletedExcalidrawElement,
|
|
||||||
Theme,
|
|
||||||
} from "@excalidraw/excalidraw/dist/excalidraw/element/types";
|
|
||||||
import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types";
|
|
||||||
|
|
||||||
import "./ExampleApp.scss";
|
import "./ExampleApp.scss";
|
||||||
|
|
||||||
|
import type { ResolvablePromise } from "../utils";
|
||||||
|
|
||||||
type Comment = {
|
type Comment = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@ -105,6 +104,7 @@ export default function ExampleApp({
|
|||||||
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||||
const [zenModeEnabled, setZenModeEnabled] = useState(false);
|
const [zenModeEnabled, setZenModeEnabled] = useState(false);
|
||||||
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
||||||
|
const [renderScrollbars, setRenderScrollbars] = useState(false);
|
||||||
const [blobUrl, setBlobUrl] = useState<string>("");
|
const [blobUrl, setBlobUrl] = useState<string>("");
|
||||||
const [canvasUrl, setCanvasUrl] = useState<string>("");
|
const [canvasUrl, setCanvasUrl] = useState<string>("");
|
||||||
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
|
||||||
@ -193,6 +193,7 @@ export default function ExampleApp({
|
|||||||
}) => setPointerData(payload),
|
}) => setPointerData(payload),
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
zenModeEnabled,
|
zenModeEnabled,
|
||||||
|
renderScrollbars,
|
||||||
gridModeEnabled,
|
gridModeEnabled,
|
||||||
theme,
|
theme,
|
||||||
name: "Custom name of drawing",
|
name: "Custom name of drawing",
|
||||||
@ -711,6 +712,14 @@ export default function ExampleApp({
|
|||||||
/>
|
/>
|
||||||
Grid mode
|
Grid mode
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={renderScrollbars}
|
||||||
|
onChange={() => setRenderScrollbars(!renderScrollbars)}
|
||||||
|
/>
|
||||||
|
Render scrollbars
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
@ -1,6 +1,9 @@
|
|||||||
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
|
import React from "react";
|
||||||
import CustomFooter from "./CustomFooter";
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
|
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
|
import CustomFooter from "./CustomFooter";
|
||||||
|
|
||||||
const MobileFooter = ({
|
const MobileFooter = ({
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import "./ExampleSidebar.scss";
|
import "./ExampleSidebar.scss";
|
||||||
|
|
||||||
export default function Sidebar({ children }: { children: React.ReactNode }) {
|
export default function Sidebar({ children }: { children: React.ReactNode }) {
|
@ -11,17 +11,15 @@
|
|||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
<script>
|
<script>
|
||||||
window.name = "codesandbox";
|
window.name = "codesandbox";
|
||||||
window.EXCALIDRAW_ASSET_PATH = window.origin;
|
window.EXCALIDRAW_ASSET_PATH =
|
||||||
|
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/dist/browser/dev/index.css" />
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||||
<div id="root"></div>
|
<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">
|
<script type="module">
|
||||||
import * as ExcalidrawLib from "@excalidraw/excalidraw";
|
import * as ExcalidrawLib from "@excalidraw/excalidraw";
|
||||||
|
|
@ -1,10 +1,11 @@
|
|||||||
import App from "../components/ExampleApp";
|
|
||||||
import React, { StrictMode } from "react";
|
import React, { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
import "@excalidraw/excalidraw/index.css";
|
||||||
|
|
||||||
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
import type * as TExcalidraw from "@excalidraw/excalidraw";
|
||||||
|
|
||||||
import "@excalidraw/excalidraw/index.css";
|
import App from "./components/ExampleApp";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
22
examples/with-script-in-browser/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "with-script-in-browser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "19.0.0",
|
||||||
|
"react-dom": "19.0.0",
|
||||||
|
"@excalidraw/excalidraw": "*",
|
||||||
|
"browser-fs-access": "0.29.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "5.0.12",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview --port 5002",
|
||||||
|
"build:preview": "yarn build && yarn preview",
|
||||||
|
"build:package": "yarn workspace @excalidraw/excalidraw run build:esm"
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
9
examples/with-script-in-browser/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { unstable_batchedUpdates } from "react-dom";
|
|
||||||
import { fileOpen as _fileOpen } from "browser-fs-access";
|
|
||||||
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
import { MIME_TYPES } from "@excalidraw/excalidraw";
|
||||||
import { AbortError } from "../../packages/excalidraw/errors";
|
import { fileOpen as _fileOpen } from "browser-fs-access";
|
||||||
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
|
|
||||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||||
|
|
||||||
@ -85,7 +84,7 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
|||||||
if (rejectPromise) {
|
if (rejectPromise) {
|
||||||
// so that something is shown in console if we need to debug this
|
// so that something is shown in console if we need to debug this
|
||||||
console.warn("Opening the file was canceled (legacy-fs).");
|
console.warn("Opening the file was canceled (legacy-fs).");
|
||||||
rejectPromise(new AbortError());
|
rejectPromise(new Error("Request Aborted"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
5
examples/with-script-in-browser/vercel.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"outputDirectory": "dist",
|
||||||
|
"installCommand": "yarn install",
|
||||||
|
"buildCommand": "yarn build:package && yarn build"
|
||||||
|
}
|
19
examples/with-script-in-browser/vite.config.mts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -1,40 +1,27 @@
|
|||||||
import polyfill from "../packages/excalidraw/polyfill";
|
import {
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
Excalidraw,
|
||||||
import { trackEvent } from "../packages/excalidraw/analytics";
|
LiveCollaborationTrigger,
|
||||||
import { getDefaultAppState } from "../packages/excalidraw/appState";
|
TTDDialogTrigger,
|
||||||
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
|
CaptureUpdateAction,
|
||||||
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
reconcileElements,
|
||||||
|
} from "@excalidraw/excalidraw";
|
||||||
|
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||||
|
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
|
||||||
|
import {
|
||||||
|
CommandPalette,
|
||||||
|
DEFAULT_CATEGORIES,
|
||||||
|
} from "@excalidraw/excalidraw/components/CommandPalette/CommandPalette";
|
||||||
|
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
|
||||||
|
import { OverwriteConfirmDialog } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
|
||||||
|
import { openConfirmModal } from "@excalidraw/excalidraw/components/OverwriteConfirm/OverwriteConfirmState";
|
||||||
|
import { ShareableLinkDialog } from "@excalidraw/excalidraw/components/ShareableLinkDialog";
|
||||||
|
import Trans from "@excalidraw/excalidraw/components/Trans";
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
EVENT,
|
EVENT,
|
||||||
THEME,
|
THEME,
|
||||||
TITLE_TIMEOUT,
|
TITLE_TIMEOUT,
|
||||||
VERSION_TIMEOUT,
|
VERSION_TIMEOUT,
|
||||||
} from "../packages/excalidraw/constants";
|
|
||||||
import { loadFromBlob } from "../packages/excalidraw/data/blob";
|
|
||||||
import type {
|
|
||||||
FileId,
|
|
||||||
NonDeletedExcalidrawElement,
|
|
||||||
OrderedExcalidrawElement,
|
|
||||||
} from "../packages/excalidraw/element/types";
|
|
||||||
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
|
|
||||||
import { t } from "../packages/excalidraw/i18n";
|
|
||||||
import {
|
|
||||||
Excalidraw,
|
|
||||||
LiveCollaborationTrigger,
|
|
||||||
TTDDialogTrigger,
|
|
||||||
StoreAction,
|
|
||||||
reconcileElements,
|
|
||||||
} from "../packages/excalidraw";
|
|
||||||
import type {
|
|
||||||
AppState,
|
|
||||||
ExcalidrawImperativeAPI,
|
|
||||||
BinaryFiles,
|
|
||||||
ExcalidrawInitialDataState,
|
|
||||||
UIAppState,
|
|
||||||
} from "../packages/excalidraw/types";
|
|
||||||
import type { ResolvablePromise } from "../packages/excalidraw/utils";
|
|
||||||
import {
|
|
||||||
debounce,
|
debounce,
|
||||||
getVersion,
|
getVersion,
|
||||||
getFrame,
|
getFrame,
|
||||||
@ -42,75 +29,14 @@ import {
|
|||||||
preventUnload,
|
preventUnload,
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
isRunningInIframe,
|
isRunningInIframe,
|
||||||
} from "../packages/excalidraw/utils";
|
isDevEnv,
|
||||||
import {
|
} from "@excalidraw/common";
|
||||||
FIREBASE_STORAGE_PREFIXES,
|
import polyfill from "@excalidraw/excalidraw/polyfill";
|
||||||
isExcalidrawPlusSignedUser,
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
STORAGE_KEYS,
|
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
|
||||||
SYNC_BROWSER_TABS_TIMEOUT,
|
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
|
||||||
} from "./app_constants";
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
import type { CollabAPI } from "./collab/Collab";
|
|
||||||
import Collab, {
|
|
||||||
collabAPIAtom,
|
|
||||||
isCollaboratingAtom,
|
|
||||||
isOfflineAtom,
|
|
||||||
} from "./collab/Collab";
|
|
||||||
import {
|
|
||||||
exportToBackend,
|
|
||||||
getCollaborationLinkData,
|
|
||||||
isCollaborationLink,
|
|
||||||
loadScene,
|
|
||||||
} from "./data";
|
|
||||||
import {
|
|
||||||
importFromLocalStorage,
|
|
||||||
importUsernameFromLocalStorage,
|
|
||||||
} from "./data/localStorage";
|
|
||||||
import CustomStats from "./CustomStats";
|
|
||||||
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 "../packages/excalidraw/element/mutateElement";
|
|
||||||
import { isInitializedImageElement } from "../packages/excalidraw/element/typeChecks";
|
|
||||||
import { loadFilesFromFirebase } from "./data/firebase";
|
|
||||||
import {
|
|
||||||
LibraryIndexedDBAdapter,
|
|
||||||
LibraryLocalStorageMigrationAdapter,
|
|
||||||
LocalData,
|
|
||||||
} from "./data/LocalData";
|
|
||||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import {
|
|
||||||
parseLibraryTokensFromUrl,
|
|
||||||
useHandleLibrary,
|
|
||||||
} 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 "./index.scss";
|
|
||||||
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 "../packages/excalidraw/data/reconcile";
|
|
||||||
import {
|
|
||||||
CommandPalette,
|
|
||||||
DEFAULT_CATEGORIES,
|
|
||||||
} from "../packages/excalidraw/components/CommandPalette/CommandPalette";
|
|
||||||
import {
|
import {
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
XBrandIcon,
|
XBrandIcon,
|
||||||
@ -120,7 +46,84 @@ import {
|
|||||||
exportToPlus,
|
exportToPlus,
|
||||||
share,
|
share,
|
||||||
youtubeIcon,
|
youtubeIcon,
|
||||||
} from "../packages/excalidraw/components/icons";
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
|
import { isElementLink } from "@excalidraw/element";
|
||||||
|
import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
|
||||||
|
import { newElementWith } from "@excalidraw/element";
|
||||||
|
import { isInitializedImageElement } from "@excalidraw/element";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import {
|
||||||
|
parseLibraryTokensFromUrl,
|
||||||
|
useHandleLibrary,
|
||||||
|
} from "@excalidraw/excalidraw/data/library";
|
||||||
|
|
||||||
|
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||||
|
import type { RestoredDataState } from "@excalidraw/excalidraw/data/restore";
|
||||||
|
import type {
|
||||||
|
FileId,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
OrderedExcalidrawElement,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
import type {
|
||||||
|
AppState,
|
||||||
|
ExcalidrawImperativeAPI,
|
||||||
|
BinaryFiles,
|
||||||
|
ExcalidrawInitialDataState,
|
||||||
|
UIAppState,
|
||||||
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
import type { ResolutionType } from "@excalidraw/common/utility-types";
|
||||||
|
import type { ResolvablePromise } from "@excalidraw/common/utils";
|
||||||
|
|
||||||
|
import CustomStats from "./CustomStats";
|
||||||
|
import {
|
||||||
|
Provider,
|
||||||
|
useAtom,
|
||||||
|
useAtomValue,
|
||||||
|
useAtomWithInitialValue,
|
||||||
|
appJotaiStore,
|
||||||
|
} from "./app-jotai";
|
||||||
|
import {
|
||||||
|
FIREBASE_STORAGE_PREFIXES,
|
||||||
|
isExcalidrawPlusSignedUser,
|
||||||
|
STORAGE_KEYS,
|
||||||
|
SYNC_BROWSER_TABS_TIMEOUT,
|
||||||
|
} from "./app_constants";
|
||||||
|
import Collab, {
|
||||||
|
collabAPIAtom,
|
||||||
|
isCollaboratingAtom,
|
||||||
|
isOfflineAtom,
|
||||||
|
} from "./collab/Collab";
|
||||||
|
import { AppFooter } from "./components/AppFooter";
|
||||||
|
import { AppMainMenu } from "./components/AppMainMenu";
|
||||||
|
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||||
|
import {
|
||||||
|
ExportToExcalidrawPlus,
|
||||||
|
exportToExcalidrawPlus,
|
||||||
|
} from "./components/ExportToExcalidrawPlus";
|
||||||
|
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||||
|
|
||||||
|
import {
|
||||||
|
exportToBackend,
|
||||||
|
getCollaborationLinkData,
|
||||||
|
isCollaborationLink,
|
||||||
|
loadScene,
|
||||||
|
} from "./data";
|
||||||
|
|
||||||
|
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||||
|
import {
|
||||||
|
importFromLocalStorage,
|
||||||
|
importUsernameFromLocalStorage,
|
||||||
|
} from "./data/localStorage";
|
||||||
|
|
||||||
|
import { loadFilesFromFirebase } from "./data/firebase";
|
||||||
|
import {
|
||||||
|
LibraryIndexedDBAdapter,
|
||||||
|
LibraryLocalStorageMigrationAdapter,
|
||||||
|
LocalData,
|
||||||
|
} from "./data/LocalData";
|
||||||
|
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
||||||
|
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
|
||||||
|
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
|
||||||
import { useHandleAppTheme } from "./useHandleAppTheme";
|
import { useHandleAppTheme } from "./useHandleAppTheme";
|
||||||
import { getPreferredLanguage } from "./app-language/language-detector";
|
import { getPreferredLanguage } from "./app-language/language-detector";
|
||||||
import { useAppLangCode } from "./app-language/language-state";
|
import { useAppLangCode } from "./app-language/language-state";
|
||||||
@ -131,7 +134,10 @@ import DebugCanvas, {
|
|||||||
} from "./components/DebugCanvas";
|
} from "./components/DebugCanvas";
|
||||||
import { AIComponents } from "./components/AI";
|
import { AIComponents } from "./components/AI";
|
||||||
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
||||||
import { isElementLink } from "../packages/excalidraw/element/elementLink";
|
|
||||||
|
import "./index.scss";
|
||||||
|
|
||||||
|
import type { CollabAPI } from "./collab/Collab";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@ -377,7 +383,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
const [, forceRefresh] = useState(false);
|
const [, forceRefresh] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (import.meta.env.DEV) {
|
if (isDevEnv()) {
|
||||||
const debugState = loadSavedDebugState();
|
const debugState = loadSavedDebugState();
|
||||||
|
|
||||||
if (debugState.enabled && !window.visualDebug) {
|
if (debugState.enabled && !window.visualDebug) {
|
||||||
@ -486,7 +492,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
...data.scene,
|
...data.scene,
|
||||||
...restore(data.scene, null, null, { repairBindings: true }),
|
...restore(data.scene, null, null, { repairBindings: true }),
|
||||||
storeAction: StoreAction.CAPTURE,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -513,7 +519,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
setLangCode(getPreferredLanguage());
|
setLangCode(getPreferredLanguage());
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
...localDataState,
|
...localDataState,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
LibraryIndexedDBAdapter.load().then((data) => {
|
LibraryIndexedDBAdapter.load().then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -602,7 +608,13 @@ const ExcalidrawWrapper = () => {
|
|||||||
excalidrawAPI.getSceneElements(),
|
excalidrawAPI.getSceneElements(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
preventUnload(event);
|
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
||||||
|
preventUnload(event);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener(EVENT.BEFORE_UNLOAD, unloadHandler);
|
window.addEventListener(EVENT.BEFORE_UNLOAD, unloadHandler);
|
||||||
@ -645,7 +657,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
if (didChange) {
|
if (didChange) {
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -914,16 +926,21 @@ const ExcalidrawWrapper = () => {
|
|||||||
<ShareDialog
|
<ShareDialog
|
||||||
collabAPI={collabAPI}
|
collabAPI={collabAPI}
|
||||||
onExportToBackend={async () => {
|
onExportToBackend={async () => {
|
||||||
if (excalidrawAPI) {
|
if (!excalidrawAPI) {
|
||||||
try {
|
return;
|
||||||
await onExportToBackend(
|
}
|
||||||
excalidrawAPI.getSceneElements(),
|
try {
|
||||||
excalidrawAPI.getAppState(),
|
const { url, errorMessage } = await exportToBackend(
|
||||||
excalidrawAPI.getFiles(),
|
excalidrawAPI.getSceneElements(),
|
||||||
);
|
excalidrawAPI.getAppState(),
|
||||||
} catch (error: any) {
|
excalidrawAPI.getFiles(),
|
||||||
setErrorMessage(error.message);
|
);
|
||||||
|
if (errorMessage) {
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
setLatestShareableLink(url);
|
||||||
|
} catch (error: any) {
|
||||||
|
setErrorMessage(error.message);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
|
import { Stats } from "@excalidraw/excalidraw";
|
||||||
|
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
|
||||||
|
import {
|
||||||
|
DEFAULT_VERSION,
|
||||||
|
debounce,
|
||||||
|
getVersion,
|
||||||
|
nFormatter,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { debounce, getVersion, nFormatter } from "../packages/excalidraw/utils";
|
|
||||||
|
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
import type { UIAppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getElementsStorageSize,
|
getElementsStorageSize,
|
||||||
getTotalStorageSize,
|
getTotalStorageSize,
|
||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
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 };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
import { base64urlToString } from "@excalidraw/excalidraw/data/encode";
|
||||||
|
import { ExcalidrawError } from "@excalidraw/excalidraw/errors";
|
||||||
import { useLayoutEffect, useRef } from "react";
|
import { useLayoutEffect, useRef } from "react";
|
||||||
import { STORAGE_KEYS } from "./app_constants";
|
|
||||||
import { LocalData } from "./data/LocalData";
|
|
||||||
import type {
|
import type {
|
||||||
FileId,
|
FileId,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
} from "../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import type { AppState, BinaryFileData } from "../packages/excalidraw/types";
|
import type { AppState, BinaryFileData } from "@excalidraw/excalidraw/types";
|
||||||
import { ExcalidrawError } from "../packages/excalidraw/errors";
|
|
||||||
import { base64urlToString } from "../packages/excalidraw/data/encode";
|
import { STORAGE_KEYS } from "./app_constants";
|
||||||
|
import { LocalData } from "./data/LocalData";
|
||||||
|
|
||||||
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
|
const EVENT_REQUEST_SCENE = "REQUEST_SCENE";
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { useI18n, languages } from "@excalidraw/excalidraw/i18n";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useI18n, languages } from "../../packages/excalidraw/i18n";
|
|
||||||
import { useSetAtom } from "../app-jotai";
|
import { useSetAtom } from "../app-jotai";
|
||||||
|
|
||||||
import { appLangCodeAtom } from "./language-state";
|
import { appLangCodeAtom } from "./language-state";
|
||||||
|
|
||||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { defaultLang, languages } from "@excalidraw/excalidraw";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import { defaultLang, languages } from "../../packages/excalidraw";
|
|
||||||
|
|
||||||
export const languageDetector = new LanguageDetector();
|
export const languageDetector = new LanguageDetector();
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { atom, useAtom } from "../app-jotai";
|
import { atom, useAtom } from "../app-jotai";
|
||||||
|
|
||||||
import { getPreferredLanguage, languageDetector } from "./language-detector";
|
import { getPreferredLanguage, languageDetector } from "./language-detector";
|
||||||
|
|
||||||
export const appLangCodeAtom = atom(getPreferredLanguage());
|
export const appLangCodeAtom = atom(getPreferredLanguage());
|
||||||
|
@ -1,33 +1,55 @@
|
|||||||
|
import {
|
||||||
|
CaptureUpdateAction,
|
||||||
|
getSceneVersion,
|
||||||
|
restoreElements,
|
||||||
|
zoomToFitBounds,
|
||||||
|
reconcileElements,
|
||||||
|
} from "@excalidraw/excalidraw";
|
||||||
|
import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
|
||||||
|
import { APP_NAME, EVENT } from "@excalidraw/common";
|
||||||
|
import {
|
||||||
|
IDLE_THRESHOLD,
|
||||||
|
ACTIVE_THRESHOLD,
|
||||||
|
UserIdleState,
|
||||||
|
assertNever,
|
||||||
|
isDevEnv,
|
||||||
|
isTestEnv,
|
||||||
|
preventUnload,
|
||||||
|
resolvablePromise,
|
||||||
|
throttleRAF,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
import { decryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||||
|
import { getVisibleSceneBounds } from "@excalidraw/element";
|
||||||
|
import { newElementWith } from "@excalidraw/element";
|
||||||
|
import { isImageElement, isInitializedImageElement } from "@excalidraw/element";
|
||||||
|
import { AbortError } from "@excalidraw/excalidraw/errors";
|
||||||
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
|
import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
|
||||||
|
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import { PureComponent } from "react";
|
import { PureComponent } from "react";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BinaryFileData,
|
ReconciledExcalidrawElement,
|
||||||
ExcalidrawImperativeAPI,
|
RemoteExcalidrawElement,
|
||||||
SocketId,
|
} from "@excalidraw/excalidraw/data/reconcile";
|
||||||
} from "../../packages/excalidraw/types";
|
import type { ImportedDataState } from "@excalidraw/excalidraw/data/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 {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FileId,
|
FileId,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
} from "../../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import {
|
import type {
|
||||||
StoreAction,
|
BinaryFileData,
|
||||||
getSceneVersion,
|
ExcalidrawImperativeAPI,
|
||||||
restoreElements,
|
SocketId,
|
||||||
zoomToFitBounds,
|
Collaborator,
|
||||||
reconcileElements,
|
Gesture,
|
||||||
} from "../../packages/excalidraw";
|
} from "@excalidraw/excalidraw/types";
|
||||||
import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
|
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
|
||||||
import {
|
|
||||||
assertNever,
|
import { appJotaiStore, atom } from "../app-jotai";
|
||||||
preventUnload,
|
|
||||||
resolvablePromise,
|
|
||||||
throttleRAF,
|
|
||||||
} from "../../packages/excalidraw/utils";
|
|
||||||
import {
|
import {
|
||||||
CURSOR_SYNC_TIMEOUT,
|
CURSOR_SYNC_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
@ -38,15 +60,17 @@ import {
|
|||||||
SYNC_FULL_SCENE_INTERVAL_MS,
|
SYNC_FULL_SCENE_INTERVAL_MS,
|
||||||
WS_EVENTS,
|
WS_EVENTS,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
import type {
|
|
||||||
SocketUpdateDataSource,
|
|
||||||
SyncableExcalidrawElement,
|
|
||||||
} from "../data";
|
|
||||||
import {
|
import {
|
||||||
generateCollaborationLinkData,
|
generateCollaborationLinkData,
|
||||||
getCollaborationLink,
|
getCollaborationLink,
|
||||||
getSyncableElements,
|
getSyncableElements,
|
||||||
} from "../data";
|
} from "../data";
|
||||||
|
import {
|
||||||
|
encodeFilesForUpload,
|
||||||
|
FileManager,
|
||||||
|
updateStaleImageStatuses,
|
||||||
|
} from "../data/FileManager";
|
||||||
|
import { LocalData } from "../data/LocalData";
|
||||||
import {
|
import {
|
||||||
isSavedToFirebase,
|
isSavedToFirebase,
|
||||||
loadFilesFromFirebase,
|
loadFilesFromFirebase,
|
||||||
@ -58,36 +82,15 @@ import {
|
|||||||
importUsernameFromLocalStorage,
|
importUsernameFromLocalStorage,
|
||||||
saveUsernameToLocalStorage,
|
saveUsernameToLocalStorage,
|
||||||
} from "../data/localStorage";
|
} from "../data/localStorage";
|
||||||
import Portal from "./Portal";
|
|
||||||
import { t } from "../../packages/excalidraw/i18n";
|
|
||||||
import { UserIdleState } from "../../packages/excalidraw/types";
|
|
||||||
import {
|
|
||||||
IDLE_THRESHOLD,
|
|
||||||
ACTIVE_THRESHOLD,
|
|
||||||
} from "../../packages/excalidraw/constants";
|
|
||||||
import {
|
|
||||||
encodeFilesForUpload,
|
|
||||||
FileManager,
|
|
||||||
updateStaleImageStatuses,
|
|
||||||
} from "../data/FileManager";
|
|
||||||
import { AbortError } from "../../packages/excalidraw/errors";
|
|
||||||
import {
|
|
||||||
isImageElement,
|
|
||||||
isInitializedImageElement,
|
|
||||||
} 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 { resetBrowserStateVersions } from "../data/tabSync";
|
||||||
import { LocalData } from "../data/LocalData";
|
|
||||||
import { appJotaiStore, atom } 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 { collabErrorIndicatorAtom } from "./CollabError";
|
||||||
|
import Portal from "./Portal";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ReconciledExcalidrawElement,
|
SocketUpdateDataSource,
|
||||||
RemoteExcalidrawElement,
|
SyncableExcalidrawElement,
|
||||||
} from "../../packages/excalidraw/data/reconcile";
|
} from "../data";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const isCollaboratingAtom = atom(false);
|
export const isCollaboratingAtom = atom(false);
|
||||||
@ -235,7 +238,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
|
|
||||||
appJotaiStore.set(collabAPIAtom, collabAPI);
|
appJotaiStore.set(collabAPIAtom, collabAPI);
|
||||||
|
|
||||||
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
if (isTestEnv() || isDevEnv()) {
|
||||||
window.collab = window.collab || ({} as Window["collab"]);
|
window.collab = window.collab || ({} as Window["collab"]);
|
||||||
Object.defineProperties(window, {
|
Object.defineProperties(window, {
|
||||||
collab: {
|
collab: {
|
||||||
@ -295,7 +298,13 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
// the purpose is to run in immediately after user decides to stay
|
// the purpose is to run in immediately after user decides to stay
|
||||||
this.saveCollabRoomToFirebase(syncableElements);
|
this.saveCollabRoomToFirebase(syncableElements);
|
||||||
|
|
||||||
preventUnload(event);
|
if (import.meta.env.VITE_APP_DISABLE_PREVENT_UNLOAD !== "true") {
|
||||||
|
preventUnload(event);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"preventing unload disabled (VITE_APP_DISABLE_PREVENT_UNLOAD)",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -383,7 +392,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
|
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -534,7 +543,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
// to database even if deleted before creating the room.
|
// to database even if deleted before creating the room.
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.saveCollabRoomToFirebase(getSyncableElements(elements));
|
this.saveCollabRoomToFirebase(getSyncableElements(elements));
|
||||||
@ -772,7 +781,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
) => {
|
) => {
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
elements,
|
elements,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadImageFiles();
|
this.loadImageFiles();
|
||||||
@ -1008,7 +1017,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
if (isTestEnv() || isDevEnv()) {
|
||||||
window.collab = window.collab || ({} as Window["collab"]);
|
window.collab = window.collab || ({} as Window["collab"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
||||||
import { warning } from "../../packages/excalidraw/components/icons";
|
import { warning } from "@excalidraw/excalidraw/components/icons";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { atom } from "../app-jotai";
|
import { atom } from "../app-jotai";
|
||||||
|
|
||||||
import "./CollabError.scss";
|
import "./CollabError.scss";
|
||||||
@ -19,16 +20,16 @@ export const collabErrorIndicatorAtom = atom<ErrorIndicator>({
|
|||||||
|
|
||||||
const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
|
const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
|
||||||
const [isAnimating, setIsAnimating] = useState(false);
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
|
const clearAnimationRef = useRef<string | number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
clearAnimationRef.current = setTimeout(() => {
|
clearAnimationRef.current = window.setTimeout(() => {
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(clearAnimationRef.current);
|
window.clearTimeout(clearAnimationRef.current);
|
||||||
};
|
};
|
||||||
}, [collabError.message, collabError.nonce]);
|
}, [collabError.message, collabError.nonce]);
|
||||||
|
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
|
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
||||||
|
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||||
|
import { encryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||||
|
import { newElementWith } from "@excalidraw/element";
|
||||||
|
import throttle from "lodash.throttle";
|
||||||
|
|
||||||
|
import type { UserIdleState } from "@excalidraw/common";
|
||||||
|
import type { OrderedExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
import type {
|
||||||
|
OnUserFollowedPayload,
|
||||||
|
SocketId,
|
||||||
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
|
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
||||||
|
import { isSyncableElement } from "../data";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
SocketUpdateData,
|
SocketUpdateData,
|
||||||
SocketUpdateDataSource,
|
SocketUpdateDataSource,
|
||||||
SyncableExcalidrawElement,
|
SyncableExcalidrawElement,
|
||||||
} from "../data";
|
} from "../data";
|
||||||
import { isSyncableElement } from "../data";
|
|
||||||
|
|
||||||
import type { TCollabClass } from "./Collab";
|
import type { TCollabClass } from "./Collab";
|
||||||
|
|
||||||
import type { OrderedExcalidrawElement } from "../../packages/excalidraw/element/types";
|
|
||||||
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
|
||||||
import type {
|
|
||||||
OnUserFollowedPayload,
|
|
||||||
SocketId,
|
|
||||||
UserIdleState,
|
|
||||||
} from "../../packages/excalidraw/types";
|
|
||||||
import { trackEvent } from "../../packages/excalidraw/analytics";
|
|
||||||
import throttle from "lodash.throttle";
|
|
||||||
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
|
||||||
import { encryptData } from "../../packages/excalidraw/data/encryption";
|
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import { StoreAction } from "../../packages/excalidraw";
|
|
||||||
|
|
||||||
class Portal {
|
class Portal {
|
||||||
collab: TCollabClass;
|
collab: TCollabClass;
|
||||||
@ -133,7 +134,7 @@ class Portal {
|
|||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
this.collab.excalidrawAPI.updateScene({
|
this.collab.excalidrawAPI.updateScene({
|
||||||
elements: newElements,
|
elements: newElements,
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, FILE_UPLOAD_TIMEOUT);
|
}, FILE_UPLOAD_TIMEOUT);
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import type { ExcalidrawImperativeAPI } from "../../packages/excalidraw/types";
|
|
||||||
import {
|
import {
|
||||||
DiagramToCodePlugin,
|
DiagramToCodePlugin,
|
||||||
exportToBlob,
|
exportToBlob,
|
||||||
getTextFromElements,
|
getTextFromElements,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
TTDDialog,
|
TTDDialog,
|
||||||
} from "../../packages/excalidraw";
|
} from "@excalidraw/excalidraw";
|
||||||
import { getDataURL } from "../../packages/excalidraw/data/blob";
|
import { getDataURL } from "@excalidraw/excalidraw/data/blob";
|
||||||
import { safelyParseJSON } from "../../packages/excalidraw/utils";
|
import { safelyParseJSON } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
export const AIComponents = ({
|
export const AIComponents = ({
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
||||||
@ -72,7 +73,7 @@ export const AIComponents = ({
|
|||||||
</br>
|
</br>
|
||||||
<div>You can also try <a href="${
|
<div>You can also try <a href="${
|
||||||
import.meta.env.VITE_APP_PLUS_LP
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noreferrer noopener">Excalidraw+</a> to get more requests.</div>
|
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noopener">Excalidraw+</a> to get more requests.</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`,
|
</html>`,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { Footer } from "@excalidraw/excalidraw/index";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Footer } from "../../packages/excalidraw/index";
|
|
||||||
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
|
||||||
|
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
||||||
import { EncryptedIcon } from "./EncryptedIcon";
|
import { EncryptedIcon } from "./EncryptedIcon";
|
||||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
|
||||||
import { DebugFooter, isVisualDebuggerEnabled } from "./DebugCanvas";
|
|
||||||
|
|
||||||
export const AppFooter = React.memo(
|
export const AppFooter = React.memo(
|
||||||
({ onChange }: { onChange: () => void }) => {
|
({ onChange }: { onChange: () => void }) => {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
loginIcon,
|
loginIcon,
|
||||||
ExcalLogo,
|
ExcalLogo,
|
||||||
eyeIcon,
|
eyeIcon,
|
||||||
} from "../../packages/excalidraw/components/icons";
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
import type { Theme } from "../../packages/excalidraw/element/types";
|
import { MainMenu } from "@excalidraw/excalidraw/index";
|
||||||
import { MainMenu } from "../../packages/excalidraw/index";
|
import React from "react";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
|
||||||
|
import { isDevEnv } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import type { Theme } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { LanguageList } from "../app-language/LanguageList";
|
import { LanguageList } from "../app-language/LanguageList";
|
||||||
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
|
||||||
import { saveDebugState } from "./DebugCanvas";
|
import { saveDebugState } from "./DebugCanvas";
|
||||||
|
|
||||||
export const AppMainMenu: React.FC<{
|
export const AppMainMenu: React.FC<{
|
||||||
@ -54,7 +59,7 @@ export const AppMainMenu: React.FC<{
|
|||||||
>
|
>
|
||||||
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
||||||
</MainMenu.ItemLink>
|
</MainMenu.ItemLink>
|
||||||
{import.meta.env.DEV && (
|
{isDevEnv() && (
|
||||||
<MainMenu.Item
|
<MainMenu.Item
|
||||||
icon={eyeIcon}
|
icon={eyeIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import { loginIcon } from "@excalidraw/excalidraw/components/icons";
|
||||||
|
import { POINTER_EVENTS } from "@excalidraw/common";
|
||||||
|
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||||
|
import { WelcomeScreen } from "@excalidraw/excalidraw/index";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
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 { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
import { POINTER_EVENTS } from "../../packages/excalidraw/constants";
|
|
||||||
|
|
||||||
export const AppWelcomeScreen: React.FC<{
|
export const AppWelcomeScreen: React.FC<{
|
||||||
onCollabDialogOpen: () => any;
|
onCollabDialogOpen: () => any;
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
|
|
||||||
import { type AppState } from "../../packages/excalidraw/types";
|
|
||||||
import { throttleRAF } from "../../packages/excalidraw/utils";
|
|
||||||
import {
|
|
||||||
bootstrapCanvas,
|
|
||||||
getNormalizedCanvasDimensions,
|
|
||||||
} from "../../packages/excalidraw/renderer/helpers";
|
|
||||||
import type { DebugElement } from "../../packages/excalidraw/visualdebug";
|
|
||||||
import {
|
import {
|
||||||
ArrowheadArrowIcon,
|
ArrowheadArrowIcon,
|
||||||
CloseIcon,
|
CloseIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "../../packages/excalidraw/components/icons";
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import {
|
||||||
|
bootstrapCanvas,
|
||||||
|
getNormalizedCanvasDimensions,
|
||||||
|
} from "@excalidraw/excalidraw/renderer/helpers";
|
||||||
|
import { type AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
import { throttleRAF } from "@excalidraw/common";
|
||||||
|
import { useCallback, useImperativeHandle, useRef } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isLineSegment,
|
isLineSegment,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LineSegment,
|
type LineSegment,
|
||||||
} from "../../packages/math";
|
} from "@excalidraw/math";
|
||||||
|
import { isCurve } from "@excalidraw/math/curve";
|
||||||
|
|
||||||
|
import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
|
||||||
|
|
||||||
|
import type { Curve } from "@excalidraw/math";
|
||||||
|
|
||||||
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
const renderLine = (
|
const renderLine = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@ -33,6 +39,28 @@ const renderLine = (
|
|||||||
context.restore();
|
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) => {
|
const renderOrigin = (context: CanvasRenderingContext2D, zoom: number) => {
|
||||||
context.strokeStyle = "#888";
|
context.strokeStyle = "#888";
|
||||||
context.save();
|
context.save();
|
||||||
@ -60,6 +88,16 @@ const render = (
|
|||||||
el.color,
|
el.color,
|
||||||
);
|
);
|
||||||
break;
|
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)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -276,36 +314,35 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
|||||||
interface DebugCanvasProps {
|
interface DebugCanvasProps {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
scale: number;
|
scale: number;
|
||||||
|
ref?: React.Ref<HTMLCanvasElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebugCanvas = forwardRef<HTMLCanvasElement, DebugCanvasProps>(
|
const DebugCanvas = ({ appState, scale, ref }: DebugCanvasProps) => {
|
||||||
({ appState, scale }, ref) => {
|
const { width, height } = appState;
|
||||||
const { width, height } = appState;
|
|
||||||
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
|
useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
|
||||||
ref,
|
ref,
|
||||||
() => canvasRef.current,
|
() => canvasRef.current,
|
||||||
[canvasRef],
|
[canvasRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
style={{
|
style={{
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
width={width * scale}
|
width={width * scale}
|
||||||
height={height * scale}
|
height={height * scale}
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
>
|
>
|
||||||
Debug Canvas
|
Debug Canvas
|
||||||
</canvas>
|
</canvas>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
|
||||||
export default DebugCanvas;
|
export default DebugCanvas;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { shield } from "../../packages/excalidraw/components/icons";
|
import { Tooltip } from "@excalidraw/excalidraw/components/Tooltip";
|
||||||
import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
import { shield } from "@excalidraw/excalidraw/components/icons";
|
||||||
import { useI18n } from "../../packages/excalidraw/i18n";
|
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||||
|
|
||||||
export const EncryptedIcon = () => {
|
export const EncryptedIcon = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -10,7 +10,7 @@ export const EncryptedIcon = () => {
|
|||||||
className="encrypted-icon tooltip"
|
className="encrypted-icon tooltip"
|
||||||
href="https://plus.excalidraw.com/blog/end-to-end-encryption"
|
href="https://plus.excalidraw.com/blog/end-to-end-encryption"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener"
|
||||||
aria-label={t("encrypted.link")}
|
aria-label={t("encrypted.link")}
|
||||||
>
|
>
|
||||||
<Tooltip label={t("encrypted.tooltip")} long={true}>
|
<Tooltip label={t("encrypted.tooltip")} long={true}>
|
||||||
|
@ -10,7 +10,7 @@ export const ExcalidrawPlusAppLink = () => {
|
|||||||
import.meta.env.VITE_APP_PLUS_APP
|
import.meta.env.VITE_APP_PLUS_APP
|
||||||
}?utm_source=excalidraw&utm_medium=app&utm_content=signedInUserRedirectButton#excalidraw-redirect`}
|
}?utm_source=excalidraw&utm_medium=app&utm_content=signedInUserRedirectButton#excalidraw-redirect`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noopener"
|
||||||
className="plus-button"
|
className="plus-button"
|
||||||
>
|
>
|
||||||
Go to Excalidraw+
|
Go to Excalidraw+
|
||||||
|
@ -1,30 +1,33 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card } from "../../packages/excalidraw/components/Card";
|
import { uploadBytes, ref } from "firebase/storage";
|
||||||
import { ToolButton } from "../../packages/excalidraw/components/ToolButton";
|
import { nanoid } from "nanoid";
|
||||||
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
|
||||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||||
|
import { Card } from "@excalidraw/excalidraw/components/Card";
|
||||||
|
import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";
|
||||||
|
import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton";
|
||||||
|
import { MIME_TYPES, getFrame } from "@excalidraw/common";
|
||||||
|
import {
|
||||||
|
encryptData,
|
||||||
|
generateEncryptionKey,
|
||||||
|
} from "@excalidraw/excalidraw/data/encryption";
|
||||||
|
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
||||||
|
import { isInitializedImageElement } from "@excalidraw/element";
|
||||||
|
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
FileId,
|
FileId,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "../../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { useI18n } from "../../packages/excalidraw/i18n";
|
|
||||||
import {
|
|
||||||
encryptData,
|
|
||||||
generateEncryptionKey,
|
|
||||||
} from "../../packages/excalidraw/data/encryption";
|
|
||||||
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
|
||||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
|
||||||
import { encodeFilesForUpload } from "../data/FileManager";
|
import { encodeFilesForUpload } from "../data/FileManager";
|
||||||
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
||||||
import { trackEvent } from "../../packages/excalidraw/analytics";
|
|
||||||
import { getFrame } from "../../packages/excalidraw/utils";
|
|
||||||
import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
|
|
||||||
|
|
||||||
export const exportToExcalidrawPlus = async (
|
export const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
@ -32,7 +35,7 @@ export const exportToExcalidrawPlus = async (
|
|||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
name: string,
|
name: string,
|
||||||
) => {
|
) => {
|
||||||
const firebase = await loadFirebaseStorage();
|
const storage = await loadFirebaseStorage();
|
||||||
|
|
||||||
const id = `${nanoid(12)}`;
|
const id = `${nanoid(12)}`;
|
||||||
|
|
||||||
@ -49,15 +52,13 @@ export const exportToExcalidrawPlus = async (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await firebase
|
const storageRef = ref(storage, `/migrations/scenes/${id}`);
|
||||||
.storage()
|
await uploadBytes(storageRef, blob, {
|
||||||
.ref(`/migrations/scenes/${id}`)
|
customMetadata: {
|
||||||
.put(blob, {
|
data: JSON.stringify({ version: 2, name }),
|
||||||
customMetadata: {
|
created: Date.now().toString(),
|
||||||
data: JSON.stringify({ version: 2, name }),
|
},
|
||||||
created: Date.now().toString(),
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const filesMap = new Map<FileId, BinaryFileData>();
|
const filesMap = new Map<FileId, BinaryFileData>();
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { THEME } from "@excalidraw/common";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { THEME } from "../../packages/excalidraw/constants";
|
|
||||||
import type { Theme } from "../../packages/excalidraw/element/types";
|
import type { Theme } from "@excalidraw/element/types";
|
||||||
|
|
||||||
// https://github.com/tholman/github-corners
|
// https://github.com/tholman/github-corners
|
||||||
export const GitHubCorner = React.memo(
|
export const GitHubCorner = React.memo(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import Trans from "@excalidraw/excalidraw/components/Trans";
|
||||||
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { t } from "../../packages/excalidraw/i18n";
|
import React from "react";
|
||||||
import Trans from "../../packages/excalidraw/components/Trans";
|
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import { StoreAction } from "../../packages/excalidraw";
|
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
||||||
import { compressData } from "../../packages/excalidraw/data/encode";
|
import { compressData } from "@excalidraw/excalidraw/data/encode";
|
||||||
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
|
import { newElementWith } from "@excalidraw/element";
|
||||||
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
import { isInitializedImageElement } from "@excalidraw/element";
|
||||||
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
FileId,
|
FileId,
|
||||||
InitializedExcalidrawImageElement,
|
InitializedExcalidrawImageElement,
|
||||||
} from "../../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import { t } from "../../packages/excalidraw/i18n";
|
|
||||||
import type {
|
import type {
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFileMetadata,
|
BinaryFileMetadata,
|
||||||
ExcalidrawImperativeAPI,
|
ExcalidrawImperativeAPI,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
type FileVersion = Required<BinaryFileData>["version"];
|
type FileVersion = Required<BinaryFileData>["version"];
|
||||||
|
|
||||||
@ -268,6 +269,6 @@ export const updateStaleImageStatuses = (params: {
|
|||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
}),
|
}),
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
* (localStorage, indexedDB).
|
* (localStorage, indexedDB).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
|
||||||
|
import {
|
||||||
|
CANVAS_SEARCH_TAB,
|
||||||
|
DEFAULT_SIDEBAR,
|
||||||
|
debounce,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
import { clearElementsForLocalStorage } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
createStore,
|
createStore,
|
||||||
entries,
|
entries,
|
||||||
@ -19,26 +26,19 @@ import {
|
|||||||
setMany,
|
setMany,
|
||||||
get,
|
get,
|
||||||
} from "idb-keyval";
|
} from "idb-keyval";
|
||||||
import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
|
|
||||||
import {
|
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
|
||||||
CANVAS_SEARCH_TAB,
|
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||||
DEFAULT_SIDEBAR,
|
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
|
||||||
} 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 "../../packages/excalidraw/element/types";
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
import type { MaybePromise } from "../../packages/excalidraw/utility-types";
|
import type { MaybePromise } from "@excalidraw/common/utility-types";
|
||||||
import { debounce } from "../../packages/excalidraw/utils";
|
|
||||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
import { FileManager } from "./FileManager";
|
import { FileManager } from "./FileManager";
|
||||||
import { Locker } from "./Locker";
|
import { Locker } from "./Locker";
|
||||||
import { updateBrowserStateVersion } from "./tabSync";
|
import { updateBrowserStateVersion } from "./tabSync";
|
||||||
|
@ -1,30 +1,42 @@
|
|||||||
import { reconcileElements } from "../../packages/excalidraw";
|
import { reconcileElements } from "@excalidraw/excalidraw";
|
||||||
|
import { MIME_TYPES } from "@excalidraw/common";
|
||||||
|
import { decompressData } from "@excalidraw/excalidraw/data/encode";
|
||||||
|
import {
|
||||||
|
encryptData,
|
||||||
|
decryptData,
|
||||||
|
} from "@excalidraw/excalidraw/data/encryption";
|
||||||
|
import { restoreElements } from "@excalidraw/excalidraw/data/restore";
|
||||||
|
import { getSceneVersion } from "@excalidraw/element";
|
||||||
|
import { initializeApp } from "firebase/app";
|
||||||
|
import {
|
||||||
|
getFirestore,
|
||||||
|
doc,
|
||||||
|
getDoc,
|
||||||
|
runTransaction,
|
||||||
|
Bytes,
|
||||||
|
} from "firebase/firestore";
|
||||||
|
import { getStorage, ref, uploadBytes } from "firebase/storage";
|
||||||
|
|
||||||
|
import type { RemoteExcalidrawElement } from "@excalidraw/excalidraw/data/reconcile";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FileId,
|
FileId,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
} from "../../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import { getSceneVersion } from "../../packages/excalidraw/element";
|
|
||||||
import type Portal from "../collab/Portal";
|
|
||||||
import { restoreElements } from "../../packages/excalidraw/data/restore";
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFileMetadata,
|
BinaryFileMetadata,
|
||||||
DataURL,
|
DataURL,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
|
||||||
import { decompressData } from "../../packages/excalidraw/data/encode";
|
|
||||||
import {
|
|
||||||
encryptData,
|
|
||||||
decryptData,
|
|
||||||
} from "../../packages/excalidraw/data/encryption";
|
|
||||||
import { MIME_TYPES } from "../../packages/excalidraw/constants";
|
|
||||||
import type { SyncableExcalidrawElement } from ".";
|
|
||||||
import { getSyncableElements } from ".";
|
import { getSyncableElements } from ".";
|
||||||
import type { ResolutionType } from "../../packages/excalidraw/utility-types";
|
|
||||||
|
import type { SyncableExcalidrawElement } from ".";
|
||||||
|
import type Portal from "../collab/Portal";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import type { RemoteExcalidrawElement } from "../../packages/excalidraw/data/reconcile";
|
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -41,80 +53,42 @@ try {
|
|||||||
FIREBASE_CONFIG = {};
|
FIREBASE_CONFIG = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let firebasePromise: Promise<typeof import("firebase/app").default> | null =
|
let firebaseApp: ReturnType<typeof initializeApp> | null = null;
|
||||||
null;
|
let firestore: ReturnType<typeof getFirestore> | null = null;
|
||||||
let firestorePromise: Promise<any> | null | true = null;
|
let firebaseStorage: ReturnType<typeof getStorage> | null = null;
|
||||||
let firebaseStoragePromise: Promise<any> | null | true = null;
|
|
||||||
|
|
||||||
let isFirebaseInitialized = false;
|
const _initializeFirebase = () => {
|
||||||
|
if (!firebaseApp) {
|
||||||
const _loadFirebase = async () => {
|
firebaseApp = initializeApp(FIREBASE_CONFIG);
|
||||||
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 _getFirebase = async (): Promise<
|
const _getFirestore = () => {
|
||||||
typeof import("firebase/app").default
|
if (!firestore) {
|
||||||
> => {
|
firestore = getFirestore(_initializeFirebase());
|
||||||
if (!firebasePromise) {
|
|
||||||
firebasePromise = _loadFirebase();
|
|
||||||
}
|
}
|
||||||
return firebasePromise;
|
return firestore;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getStorage = () => {
|
||||||
|
if (!firebaseStorage) {
|
||||||
|
firebaseStorage = getStorage(_initializeFirebase());
|
||||||
|
}
|
||||||
|
return firebaseStorage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
const loadFirestore = async () => {
|
|
||||||
const firebase = await _getFirebase();
|
|
||||||
if (!firestorePromise) {
|
|
||||||
firestorePromise = import(
|
|
||||||
/* webpackChunkName: "firestore" */ "firebase/firestore"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (firestorePromise !== true) {
|
|
||||||
await firestorePromise;
|
|
||||||
firestorePromise = true;
|
|
||||||
}
|
|
||||||
return firebase;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loadFirebaseStorage = async () => {
|
export const loadFirebaseStorage = async () => {
|
||||||
const firebase = await _getFirebase();
|
return _getStorage();
|
||||||
if (!firebaseStoragePromise) {
|
|
||||||
firebaseStoragePromise = import(
|
|
||||||
/* webpackChunkName: "storage" */ "firebase/storage"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (firebaseStoragePromise !== true) {
|
|
||||||
await firebaseStoragePromise;
|
|
||||||
firebaseStoragePromise = true;
|
|
||||||
}
|
|
||||||
return firebase;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FirebaseStoredScene {
|
type FirebaseStoredScene = {
|
||||||
sceneVersion: number;
|
sceneVersion: number;
|
||||||
iv: firebase.default.firestore.Blob;
|
iv: Bytes;
|
||||||
ciphertext: firebase.default.firestore.Blob;
|
ciphertext: Bytes;
|
||||||
}
|
};
|
||||||
|
|
||||||
const encryptElements = async (
|
const encryptElements = async (
|
||||||
key: string,
|
key: string,
|
||||||
@ -175,7 +149,7 @@ export const saveFilesToFirebase = async ({
|
|||||||
prefix: string;
|
prefix: string;
|
||||||
files: { id: FileId; buffer: Uint8Array }[];
|
files: { id: FileId; buffer: Uint8Array }[];
|
||||||
}) => {
|
}) => {
|
||||||
const firebase = await loadFirebaseStorage();
|
const storage = await loadFirebaseStorage();
|
||||||
|
|
||||||
const erroredFiles: FileId[] = [];
|
const erroredFiles: FileId[] = [];
|
||||||
const savedFiles: FileId[] = [];
|
const savedFiles: FileId[] = [];
|
||||||
@ -183,17 +157,10 @@ export const saveFilesToFirebase = async ({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async ({ id, buffer }) => {
|
files.map(async ({ id, buffer }) => {
|
||||||
try {
|
try {
|
||||||
await firebase
|
const storageRef = ref(storage, `${prefix}/${id}`);
|
||||||
.storage()
|
await uploadBytes(storageRef, buffer, {
|
||||||
.ref(`${prefix}/${id}`)
|
cacheControl: `public, max-age=${FILE_CACHE_MAX_AGE_SEC}`,
|
||||||
.put(
|
});
|
||||||
new Blob([buffer], {
|
|
||||||
type: MIME_TYPES.binary,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
cacheControl: `public, max-age=${FILE_CACHE_MAX_AGE_SEC}`,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
savedFiles.push(id);
|
savedFiles.push(id);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
erroredFiles.push(id);
|
erroredFiles.push(id);
|
||||||
@ -205,7 +172,6 @@ export const saveFilesToFirebase = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createFirebaseSceneDocument = async (
|
const createFirebaseSceneDocument = async (
|
||||||
firebase: ResolutionType<typeof loadFirestore>,
|
|
||||||
elements: readonly SyncableExcalidrawElement[],
|
elements: readonly SyncableExcalidrawElement[],
|
||||||
roomKey: string,
|
roomKey: string,
|
||||||
) => {
|
) => {
|
||||||
@ -213,10 +179,8 @@ const createFirebaseSceneDocument = async (
|
|||||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||||
return {
|
return {
|
||||||
sceneVersion,
|
sceneVersion,
|
||||||
ciphertext: firebase.firestore.Blob.fromUint8Array(
|
ciphertext: Bytes.fromUint8Array(new Uint8Array(ciphertext)),
|
||||||
new Uint8Array(ciphertext),
|
iv: Bytes.fromUint8Array(iv),
|
||||||
),
|
|
||||||
iv: firebase.firestore.Blob.fromUint8Array(iv),
|
|
||||||
} as FirebaseStoredScene;
|
} as FirebaseStoredScene;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -236,20 +200,14 @@ export const saveToFirebase = async (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firebase = await loadFirestore();
|
const firestore = _getFirestore();
|
||||||
const firestore = firebase.firestore();
|
const docRef = doc(firestore, "scenes", roomId);
|
||||||
|
|
||||||
const docRef = firestore.collection("scenes").doc(roomId);
|
const storedScene = await runTransaction(firestore, async (transaction) => {
|
||||||
|
|
||||||
const storedScene = await firestore.runTransaction(async (transaction) => {
|
|
||||||
const snapshot = await transaction.get(docRef);
|
const snapshot = await transaction.get(docRef);
|
||||||
|
|
||||||
if (!snapshot.exists) {
|
if (!snapshot.exists()) {
|
||||||
const storedScene = await createFirebaseSceneDocument(
|
const storedScene = await createFirebaseSceneDocument(elements, roomKey);
|
||||||
firebase,
|
|
||||||
elements,
|
|
||||||
roomKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
transaction.set(docRef, storedScene);
|
transaction.set(docRef, storedScene);
|
||||||
|
|
||||||
@ -269,7 +227,6 @@ export const saveToFirebase = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const storedScene = await createFirebaseSceneDocument(
|
const storedScene = await createFirebaseSceneDocument(
|
||||||
firebase,
|
|
||||||
reconciledElements,
|
reconciledElements,
|
||||||
roomKey,
|
roomKey,
|
||||||
);
|
);
|
||||||
@ -294,15 +251,13 @@ export const loadFromFirebase = async (
|
|||||||
roomKey: string,
|
roomKey: string,
|
||||||
socket: Socket | null,
|
socket: Socket | null,
|
||||||
): Promise<readonly SyncableExcalidrawElement[] | null> => {
|
): Promise<readonly SyncableExcalidrawElement[] | null> => {
|
||||||
const firebase = await loadFirestore();
|
const firestore = _getFirestore();
|
||||||
const db = firebase.firestore();
|
const docRef = doc(firestore, "scenes", roomId);
|
||||||
|
const docSnap = await getDoc(docRef);
|
||||||
const docRef = db.collection("scenes").doc(roomId);
|
if (!docSnap.exists()) {
|
||||||
const doc = await docRef.get();
|
|
||||||
if (!doc.exists) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const storedScene = doc.data() as FirebaseStoredScene;
|
const storedScene = docSnap.data() as FirebaseStoredScene;
|
||||||
const elements = getSyncableElements(
|
const elements = getSyncableElements(
|
||||||
restoreElements(await decryptElements(storedScene, roomKey), null),
|
restoreElements(await decryptElements(storedScene, roomKey), null),
|
||||||
);
|
);
|
||||||
|
@ -1,42 +1,46 @@
|
|||||||
import {
|
import {
|
||||||
compressData,
|
compressData,
|
||||||
decompressData,
|
decompressData,
|
||||||
} from "../../packages/excalidraw/data/encode";
|
} from "@excalidraw/excalidraw/data/encode";
|
||||||
import {
|
import {
|
||||||
decryptData,
|
decryptData,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
IV_LENGTH_BYTES,
|
IV_LENGTH_BYTES,
|
||||||
} from "../../packages/excalidraw/data/encryption";
|
} from "@excalidraw/excalidraw/data/encryption";
|
||||||
import { serializeAsJSON } from "../../packages/excalidraw/data/json";
|
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
||||||
import { restore } from "../../packages/excalidraw/data/restore";
|
import { restore } from "@excalidraw/excalidraw/data/restore";
|
||||||
import type { ImportedDataState } from "../../packages/excalidraw/data/types";
|
import { isInvisiblySmallElement } from "@excalidraw/element";
|
||||||
import type { SceneBounds } from "../../packages/excalidraw/element/bounds";
|
import { isInitializedImageElement } from "@excalidraw/element";
|
||||||
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
|
import { t } from "@excalidraw/excalidraw/i18n";
|
||||||
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
|
import { bytesToHexString } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import type { UserIdleState } from "@excalidraw/common";
|
||||||
|
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||||
|
import type { SceneBounds } from "@excalidraw/element";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FileId,
|
FileId,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
} from "../../packages/excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import { t } from "../../packages/excalidraw/i18n";
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
SocketId,
|
SocketId,
|
||||||
UserIdleState,
|
} from "@excalidraw/excalidraw/types";
|
||||||
} from "../../packages/excalidraw/types";
|
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
||||||
import type { MakeBrand } from "../../packages/excalidraw/utility-types";
|
|
||||||
import { bytesToHexString } from "../../packages/excalidraw/utils";
|
|
||||||
import type { WS_SUBTYPES } from "../app_constants";
|
|
||||||
import {
|
import {
|
||||||
DELETED_ELEMENT_TIMEOUT,
|
DELETED_ELEMENT_TIMEOUT,
|
||||||
FILE_UPLOAD_MAX_BYTES,
|
FILE_UPLOAD_MAX_BYTES,
|
||||||
ROOM_ID_BYTES,
|
ROOM_ID_BYTES,
|
||||||
} from "../app_constants";
|
} from "../app_constants";
|
||||||
|
|
||||||
import { encodeFilesForUpload } from "./FileManager";
|
import { encodeFilesForUpload } from "./FileManager";
|
||||||
import { saveFilesToFirebase } from "./firebase";
|
import { saveFilesToFirebase } from "./firebase";
|
||||||
|
|
||||||
|
import type { WS_SUBTYPES } from "../app_constants";
|
||||||
|
|
||||||
export type SyncableExcalidrawElement = OrderedExcalidrawElement &
|
export type SyncableExcalidrawElement = OrderedExcalidrawElement &
|
||||||
MakeBrand<"SyncableExcalidrawElement">;
|
MakeBrand<"SyncableExcalidrawElement">;
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import type { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
|
||||||
import type { AppState } from "../../packages/excalidraw/types";
|
|
||||||
import {
|
import {
|
||||||
clearAppStateForLocalStorage,
|
clearAppStateForLocalStorage,
|
||||||
getDefaultAppState,
|
getDefaultAppState,
|
||||||
} from "../../packages/excalidraw/appState";
|
} from "@excalidraw/excalidraw/appState";
|
||||||
import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
|
import { clearElementsForLocalStorage } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
export const saveUsernameToLocalStorage = (username: string) => {
|
export const saveUsernameToLocalStorage = (username: string) => {
|
||||||
|
3
excalidraw-app/global.d.ts
vendored
@ -1,3 +1,6 @@
|
|||||||
|
import "@excalidraw/excalidraw/global";
|
||||||
|
import "@excalidraw/excalidraw/css";
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
__EXCALIDRAW_SHA__: string | undefined;
|
__EXCALIDRAW_SHA__: string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import ExcalidrawApp from "./App";
|
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
import "../excalidraw-app/sentry";
|
import "../excalidraw-app/sentry";
|
||||||
|
|
||||||
|
import ExcalidrawApp from "./App";
|
||||||
|
|
||||||
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
|
||||||
const rootElement = document.getElementById("root")!;
|
const rootElement = document.getElementById("root")!;
|
||||||
const root = createRoot(rootElement);
|
const root = createRoot(rootElement);
|
||||||
|
@ -29,20 +29,20 @@
|
|||||||
"@excalidraw/random-username": "1.0.0",
|
"@excalidraw/random-username": "1.0.0",
|
||||||
"@sentry/browser": "9.0.1",
|
"@sentry/browser": "9.0.1",
|
||||||
"callsites": "4.2.0",
|
"callsites": "4.2.0",
|
||||||
"firebase": "8.3.3",
|
"firebase": "11.3.1",
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"jotai": "2.11.0",
|
"jotai": "2.11.0",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "19.0.0",
|
||||||
"socket.io-client": "4.7.2",
|
"socket.io-client": "4.7.2",
|
||||||
"vite-plugin-html": "3.2.2"
|
"vite-plugin-html": "3.2.2"
|
||||||
},
|
},
|
||||||
"prettier": "@excalidraw/prettier-config",
|
"prettier": "@excalidraw/prettier-config",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "cross-env VITE_APP_DISABLE_SENTRY=true vite build",
|
"build:app:docker": "vite build",
|
||||||
"build:app": "cross-env VITE_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA cross-env VITE_APP_ENABLE_TRACKING=true vite build",
|
"build:app": "vite build",
|
||||||
"build:version": "node ../scripts/build-version.js",
|
"build:version": "node ../scripts/build-version.js",
|
||||||
"build": "yarn build:app && yarn build:version",
|
"build": "yarn build:app && yarn build:version",
|
||||||
"start": "yarn && vite",
|
"start": "yarn && vite",
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||||
import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard";
|
import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
|
||||||
import { trackEvent } from "../../packages/excalidraw/analytics";
|
import { Dialog } from "@excalidraw/excalidraw/components/Dialog";
|
||||||
import { getFrame } from "../../packages/excalidraw/utils";
|
import { FilledButton } from "@excalidraw/excalidraw/components/FilledButton";
|
||||||
import { useI18n } from "../../packages/excalidraw/i18n";
|
import { TextField } from "@excalidraw/excalidraw/components/TextField";
|
||||||
import { KEYS } from "../../packages/excalidraw/keys";
|
|
||||||
import { Dialog } from "../../packages/excalidraw/components/Dialog";
|
|
||||||
import {
|
import {
|
||||||
copyIcon,
|
copyIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
@ -13,17 +11,20 @@ import {
|
|||||||
share,
|
share,
|
||||||
shareIOS,
|
shareIOS,
|
||||||
shareWindows,
|
shareWindows,
|
||||||
} from "../../packages/excalidraw/components/icons";
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
import { TextField } from "../../packages/excalidraw/components/TextField";
|
import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
||||||
import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
|
import { useCopyStatus } from "@excalidraw/excalidraw/hooks/useCopiedIndicator";
|
||||||
import type { CollabAPI } from "../collab/Collab";
|
import { useI18n } from "@excalidraw/excalidraw/i18n";
|
||||||
import { activeRoomLinkAtom } from "../collab/Collab";
|
import { KEYS, getFrame } from "@excalidraw/common";
|
||||||
import { useUIAppState } from "../../packages/excalidraw/context/ui-appState";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator";
|
|
||||||
import { atom, useAtom, useAtomValue } from "../app-jotai";
|
import { atom, useAtom, useAtomValue } from "../app-jotai";
|
||||||
|
import { activeRoomLinkAtom } from "../collab/Collab";
|
||||||
|
|
||||||
import "./ShareDialog.scss";
|
import "./ShareDialog.scss";
|
||||||
|
|
||||||
|
import type { CollabAPI } from "../collab/Collab";
|
||||||
|
|
||||||
type OnExportToBackend = () => void;
|
type OnExportToBackend = () => void;
|
||||||
type ShareDialogType = "share" | "collaborationOnly";
|
type ShareDialogType = "share" | "collaborationOnly";
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { defaultLang } from "../../packages/excalidraw/i18n";
|
import { defaultLang } from "@excalidraw/excalidraw/i18n";
|
||||||
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||||
import {
|
import {
|
||||||
screen,
|
screen,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
waitFor,
|
waitFor,
|
||||||
render,
|
render,
|
||||||
} from "../../packages/excalidraw/tests/test-utils";
|
} from "@excalidraw/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import ExcalidrawApp from "../App";
|
import ExcalidrawApp from "../App";
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import ExcalidrawApp from "../App";
|
import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||||
import {
|
import {
|
||||||
mockBoundingClientRect,
|
mockBoundingClientRect,
|
||||||
render,
|
render,
|
||||||
restoreOriginalGetBoundingClientRect,
|
restoreOriginalGetBoundingClientRect,
|
||||||
} from "../../packages/excalidraw/tests/test-utils";
|
} from "@excalidraw/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import { UI } from "../../packages/excalidraw/tests/helpers/ui";
|
import ExcalidrawApp from "../App";
|
||||||
|
|
||||||
describe("Test MobileMenu", () => {
|
describe("Test MobileMenu", () => {
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
@ -198,7 +198,7 @@ exports[`Test MobileMenu > should initialize with welcome screen and hide once u
|
|||||||
<a
|
<a
|
||||||
class="welcome-screen-menu-item "
|
class="welcome-screen-menu-item "
|
||||||
href="undefined/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
|
href="undefined/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
|
||||||
rel="noreferrer"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { vi } from "vitest";
|
import { CaptureUpdateAction, newElementWith } from "@excalidraw/excalidraw";
|
||||||
import {
|
|
||||||
act,
|
|
||||||
render,
|
|
||||||
waitFor,
|
|
||||||
} from "../../packages/excalidraw/tests/test-utils";
|
|
||||||
import ExcalidrawApp from "../App";
|
|
||||||
import { API } from "../../packages/excalidraw/tests/helpers/api";
|
|
||||||
import { syncInvalidIndices } from "../../packages/excalidraw/fractionalIndex";
|
|
||||||
import {
|
import {
|
||||||
createRedoAction,
|
createRedoAction,
|
||||||
createUndoAction,
|
createUndoAction,
|
||||||
} from "../../packages/excalidraw/actions/actionHistory";
|
} from "@excalidraw/excalidraw/actions/actionHistory";
|
||||||
import { StoreAction, newElementWith } from "../../packages/excalidraw";
|
import { syncInvalidIndices } from "@excalidraw/element";
|
||||||
|
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||||
|
import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
import { StoreIncrement } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import type { DurableIncrement, EphemeralIncrement } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import ExcalidrawApp from "../App";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@ -68,6 +69,79 @@ vi.mock("socket.io-client", () => {
|
|||||||
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
|
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
|
||||||
*/
|
*/
|
||||||
describe("collaboration", () => {
|
describe("collaboration", () => {
|
||||||
|
it("should emit two ephemeral increments even though updates get batched", async () => {
|
||||||
|
const durableIncrements: DurableIncrement[] = [];
|
||||||
|
const ephemeralIncrements: EphemeralIncrement[] = [];
|
||||||
|
|
||||||
|
await render(<ExcalidrawApp />);
|
||||||
|
|
||||||
|
h.store.onStoreIncrementEmitter.on((increment) => {
|
||||||
|
if (StoreIncrement.isDurable(increment)) {
|
||||||
|
durableIncrements.push(increment);
|
||||||
|
} else {
|
||||||
|
ephemeralIncrements.push(increment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line dot-notation
|
||||||
|
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||||
|
expect(durableIncrements.length).toBe(0);
|
||||||
|
expect(ephemeralIncrements.length).toBe(0);
|
||||||
|
|
||||||
|
const rectProps = {
|
||||||
|
type: "rectangle",
|
||||||
|
id: "A",
|
||||||
|
height: 200,
|
||||||
|
width: 100,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const rect = API.createElement({ ...rectProps });
|
||||||
|
|
||||||
|
API.updateScene({
|
||||||
|
elements: [rect],
|
||||||
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// expect(commitSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(durableIncrements.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// simulate two batched remote updates
|
||||||
|
act(() => {
|
||||||
|
h.app.updateScene({
|
||||||
|
elements: [newElementWith(h.elements[0], { x: 100 })],
|
||||||
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
|
});
|
||||||
|
h.app.updateScene({
|
||||||
|
elements: [newElementWith(h.elements[0], { x: 200 })],
|
||||||
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
|
});
|
||||||
|
|
||||||
|
// we scheduled two micro actions,
|
||||||
|
// which confirms they are going to be executed as part of one batched component update
|
||||||
|
// eslint-disable-next-line dot-notation
|
||||||
|
expect(h.store["scheduledMicroActions"].length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// altough the updates get batched,
|
||||||
|
// we expect two ephemeral increments for each update,
|
||||||
|
// and each such update should have the expected change
|
||||||
|
expect(ephemeralIncrements.length).toBe(2);
|
||||||
|
expect(ephemeralIncrements[0].change.elements.A).toEqual(
|
||||||
|
expect.objectContaining({ x: 100 }),
|
||||||
|
);
|
||||||
|
expect(ephemeralIncrements[1].change.elements.A).toEqual(
|
||||||
|
expect.objectContaining({ x: 200 }),
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line dot-notation
|
||||||
|
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should allow to undo / redo even on force-deleted elements", async () => {
|
it("should allow to undo / redo even on force-deleted elements", async () => {
|
||||||
await render(<ExcalidrawApp />);
|
await render(<ExcalidrawApp />);
|
||||||
const rect1Props = {
|
const rect1Props = {
|
||||||
@ -89,7 +163,7 @@ describe("collaboration", () => {
|
|||||||
|
|
||||||
API.updateScene({
|
API.updateScene({
|
||||||
elements: syncInvalidIndices([rect1, rect2]),
|
elements: syncInvalidIndices([rect1, rect2]),
|
||||||
storeAction: StoreAction.CAPTURE,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
});
|
});
|
||||||
|
|
||||||
API.updateScene({
|
API.updateScene({
|
||||||
@ -97,7 +171,7 @@ describe("collaboration", () => {
|
|||||||
rect1,
|
rect1,
|
||||||
newElementWith(h.elements[1], { isDeleted: true }),
|
newElementWith(h.elements[1], { isDeleted: true }),
|
||||||
]),
|
]),
|
||||||
storeAction: StoreAction.CAPTURE,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -125,7 +199,7 @@ describe("collaboration", () => {
|
|||||||
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const undoAction = createUndoAction(h.history, h.store);
|
const undoAction = createUndoAction(h.history);
|
||||||
act(() => h.app.actionManager.executeAction(undoAction));
|
act(() => h.app.actionManager.executeAction(undoAction));
|
||||||
|
|
||||||
// with explicit undo (as addition) we expect our item to be restored from the snapshot!
|
// with explicit undo (as addition) we expect our item to be restored from the snapshot!
|
||||||
@ -144,7 +218,7 @@ describe("collaboration", () => {
|
|||||||
// simulate force deleting the element remotely
|
// simulate force deleting the element remotely
|
||||||
API.updateScene({
|
API.updateScene({
|
||||||
elements: syncInvalidIndices([rect1]),
|
elements: syncInvalidIndices([rect1]),
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -157,7 +231,7 @@ describe("collaboration", () => {
|
|||||||
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
expect(h.elements).toEqual([expect.objectContaining(rect1Props)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const redoAction = createRedoAction(h.history, h.store);
|
const redoAction = createRedoAction(h.history);
|
||||||
act(() => h.app.actionManager.executeAction(redoAction));
|
act(() => h.app.actionManager.executeAction(redoAction));
|
||||||
|
|
||||||
// with explicit redo (as removal) we again restore the element from the snapshot!
|
// with explicit redo (as removal) we again restore the element from the snapshot!
|
||||||
@ -182,7 +256,7 @@ describe("collaboration", () => {
|
|||||||
h.elements[0],
|
h.elements[0],
|
||||||
newElementWith(h.elements[1], { x: 100 }),
|
newElementWith(h.elements[1], { x: 100 }),
|
||||||
]),
|
]),
|
||||||
storeAction: StoreAction.CAPTURE,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -217,7 +291,7 @@ describe("collaboration", () => {
|
|||||||
// simulate force deleting the element remotely
|
// simulate force deleting the element remotely
|
||||||
API.updateScene({
|
API.updateScene({
|
||||||
elements: syncInvalidIndices([rect1]),
|
elements: syncInvalidIndices([rect1]),
|
||||||
storeAction: StoreAction.UPDATE,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
|
|
||||||
// snapshot was correctly updated and marked the element as deleted
|
// snapshot was correctly updated and marked the element as deleted
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { THEME } from "@excalidraw/excalidraw";
|
||||||
|
import { EVENT, CODES, KEYS } from "@excalidraw/common";
|
||||||
import { useEffect, useLayoutEffect, useState } from "react";
|
import { useEffect, useLayoutEffect, useState } from "react";
|
||||||
import { THEME } from "../packages/excalidraw";
|
|
||||||
import { EVENT } from "../packages/excalidraw/constants";
|
import type { Theme } from "@excalidraw/element/types";
|
||||||
import type { Theme } from "../packages/excalidraw/element/types";
|
|
||||||
import { CODES, KEYS } from "../packages/excalidraw/keys";
|
|
||||||
import { STORAGE_KEYS } from "./app_constants";
|
import { STORAGE_KEYS } from "./app_constants";
|
||||||
|
|
||||||
const getDarkThemeMediaQuery = (): MediaQueryList | undefined =>
|
const getDarkThemeMediaQuery = (): MediaQueryList | undefined =>
|
||||||
|